From a8a69565c481692e1cef90b93ebf897e7bbd31a2 Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Thu, 17 Jul 2025 09:59:52 +0200 Subject: [PATCH 1/6] static and crosscompile --- CMakeLists.txt | 6 ++++++ exe/luajit-rocks | 2 +- extra/nn | 2 +- install.sh | 9 ++++++++- pkg/torch | 2 +- riscv64-toolchain.cmake | 5 +++++ 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 riscv64-toolchain.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 31884a9fe..9017018a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,10 @@ cmake_minimum_required(VERSION 2.8) + +if(CMAKE_CROSSCOMPILING) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ldl") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -ldl") +endif() + ADD_SUBDIRECTORY(exe) # If this variable is defined, a pure cmake build is done end-to-end with no luarocks diff --git a/exe/luajit-rocks b/exe/luajit-rocks index 411f4b9d9..20184a238 160000 --- a/exe/luajit-rocks +++ b/exe/luajit-rocks @@ -1 +1 @@ -Subproject commit 411f4b9d9c4be176d4aab965ebfce50911583e14 +Subproject commit 20184a238783b5f6ed909f4dd40c443d42eaa606 diff --git a/extra/nn b/extra/nn index 200ae7d55..1e668eabf 160000 --- a/extra/nn +++ b/extra/nn @@ -1 +1 @@ -Subproject commit 200ae7d55a3381a232256223c0694498f8f51df0 +Subproject commit 1e668eabf80c8214a58f7a5b5c318b338c2eefee diff --git a/install.sh b/install.sh index ad65434f0..b2b7b5691 100755 --- a/install.sh +++ b/install.sh @@ -70,7 +70,14 @@ echo "Installing Lua version: ${TORCH_LUA_VERSION}" mkdir -p install mkdir -p build cd build -cmake .. -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 + + +export CC="/usr/bin/riscv64-linux-gnu-gcc" +export CXX="riscv64-linux-gnu-g++" + +TOOLCHAIN_FILE="${THIS_DIR}/riscv64-toolchain.cmake" + +cmake .. -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" -DHAVE_DLOPEN=1 -DCMAKE_HAVE_DLOPEN=1 -DLUA_USE_DLOPEN=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 (make 2>&1 >>$PREFIX/install.log || exit 1) && (make install 2>&1 >>$PREFIX/install.log || exit 1) cd .. diff --git a/pkg/torch b/pkg/torch index 3e9e141ce..1600de6d4 160000 --- a/pkg/torch +++ b/pkg/torch @@ -1 +1 @@ -Subproject commit 3e9e141ced1afd0cad451e69f90e6e53503647ca +Subproject commit 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe diff --git a/riscv64-toolchain.cmake b/riscv64-toolchain.cmake new file mode 100644 index 000000000..e2b4c25aa --- /dev/null +++ b/riscv64-toolchain.cmake @@ -0,0 +1,5 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR riscv64) + +set(CMAKE_C_COMPILER /usr/bin/riscv64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER /usr/bin/riscv64-linux-gnu-g++) From db93cc118f94c20505b1a976a758a5172a4ffc43 Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Thu, 17 Jul 2025 10:45:16 +0200 Subject: [PATCH 2/6] info about crosscompilation patch --- README.md | 2 + changes-submodules.patch | 93070 +++++++++++++++++++++++++++++++++++++ 2 files changed, 93072 insertions(+) create mode 100644 changes-submodules.patch diff --git a/README.md b/README.md index f7ff56274..99f6cebf2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Build Status](https://travis-ci.org/torch/distro.svg?branch=master)](https://travis-ci.org/torch/distro) +> For the cross-compilation to work with submodules, clone this repo using the `--recursive` option and then apply the patches in the file `toolchain-changes.patch` + #### NOTE: Torch is not actively developed anymore and is in maintenance mode. Self-contained Torch installation diff --git a/changes-submodules.patch b/changes-submodules.patch new file mode 100644 index 000000000..2ed3153cb --- /dev/null +++ b/changes-submodules.patch @@ -0,0 +1,93070 @@ +Submodule exe/qtlua 8b80419..b268c3b: +diff --git a/exe/qtlua/doc/qtgui.md b/exe/qtlua/doc/qtgui.md +index 9985c94..025006b 100644 +--- a/exe/qtlua/doc/qtgui.md ++++ b/exe/qtlua/doc/qtgui.md +@@ -345,7 +345,7 @@ for selecting an existing file. + * Argument `opt` are the [file dialog options](http://doc.trolltech.com/4.4/qfiledialog.html#Option-enum). + + The function returns a `qt.QString` containing the selected file name +-and a `qt.QString` contaning the selected filter. ++and a `qt.QString` containing the selected filter. + + + ### qt.QFileDialog.getOpenFileNames([p,[c,[d,[f,[s,[opt]]]]]]) ### +diff --git a/exe/qtlua/doc/qtide.md b/exe/qtlua/doc/qtide.md +index b19b5b9..de71a2b 100644 +--- a/exe/qtlua/doc/qtide.md ++++ b/exe/qtlua/doc/qtide.md +@@ -67,7 +67,7 @@ Not yet implemented + + Starts the QLua Integrated Development Environment (IDE) + and ensure that the main window is visible. +-This function is called implicitely when program `qlua` ++This function is called implicitly when program `qlua` + is executed with option `-ide`. + + The optional argument `style` is a string +@@ -98,7 +98,7 @@ a graphic console. + + ### qt.QLuaIde ### + +-Object `qt.qLuaIde` represetns the global state of the IDE. ++Object `qt.qLuaIde` represents the global state of the IDE. + This is the unique instance of class `qt.QLuaIde` + which inherits [qt.QObject](qt.md#qobject). + Most of its capabilities are conveniently +diff --git a/exe/qtlua/packages/qtwidget/qtlualistener.cpp b/exe/qtlua/packages/qtwidget/qtlualistener.cpp +index 6f07e00..3c2097d 100644 +--- a/exe/qtlua/packages/qtwidget/qtlualistener.cpp ++++ b/exe/qtlua/packages/qtwidget/qtlualistener.cpp +@@ -21,8 +21,8 @@ f_enumerator(const char *s) + static const QMetaObject* qt() { return &staticQtMetaObject; } + }; + const QMetaObject *mo = QFakeObject::qt(); +- int index = mo->indexOfEnumerator(s); +- if (mo >= 0) ++ int index = (mo) ? mo->indexOfEnumerator(s) : -1; ++ if (index >= 0) + return mo->enumerator(index); + return QMetaEnum(); + } +diff --git a/exe/qtlua/packages/qtwidget/qtwidget.cpp b/exe/qtlua/packages/qtwidget/qtwidget.cpp +index 8567c69..29e3402 100644 +--- a/exe/qtlua/packages/qtwidget/qtwidget.cpp ++++ b/exe/qtlua/packages/qtwidget/qtwidget.cpp +@@ -38,8 +38,8 @@ + static QMetaEnum + f_enumerator(const char *s, const QMetaObject *mo) + { +- int index = mo->indexOfEnumerator(s); +- if (mo >= 0) ++ int index = (mo) ? mo->indexOfEnumerator(s) : -1; ++ if (index >= 0) + return mo->enumerator(index); + return QMetaEnum(); + } +Submodule extra/cunn 27d79db..1ae6aa0: +diff --git a/extra/cunn/README.md b/extra/cunn/README.md +index 22f490f..6d8609c 100644 +--- a/extra/cunn/README.md ++++ b/extra/cunn/README.md +@@ -87,7 +87,7 @@ local a = torch.CudaTensor(1000,1000):uniform() + a:add(1) + ``` + ... the GPU kernel to add 1 will only be scheduled for launch by `a:add(1)`. It might not have completed yet, or +-even have reached the GPU, at the time that the `a:add(1)` instructions has completed ++even have reached the GPU, at the time that the `a:add(1)` returns + * therefore for running wall-clock timings, you should call `cutorch.synchronize()` before each timecheck + point: + ```lua +diff --git a/extra/cunn/lib/THCUNN/BatchNormalization.cu b/extra/cunn/lib/THCUNN/BatchNormalization.cu +index 125e3ff..e6717c7 100644 +--- a/extra/cunn/lib/THCUNN/BatchNormalization.cu ++++ b/extra/cunn/lib/THCUNN/BatchNormalization.cu +@@ -5,7 +5,7 @@ + + #include "THCDeviceTensor.cuh" + #include "THCDeviceTensorUtils.cuh" +- ++#include "THCDeviceUtils.cuh" + const int WARP_SIZE = 32; + + // The maximum number of threads in a block +@@ -80,7 +80,7 @@ template + static __device__ __forceinline__ T warpSum(T val) { + #if __CUDA_ARCH__ >= 300 + for (int i = 0; i < getMSB(WARP_SIZE); ++i) { +- val += __shfl_xor(val, 1 << i, WARP_SIZE); ++ val += WARP_SHFL_XOR(val, 1 << i, WARP_SIZE); + } + #else + __shared__ T values[MAX_BLOCK_SIZE]; +diff --git a/extra/cunn/lib/THCUNN/CMakeLists.txt b/extra/cunn/lib/THCUNN/CMakeLists.txt +index d4777bf..6bc5813 100644 +--- a/extra/cunn/lib/THCUNN/CMakeLists.txt ++++ b/extra/cunn/lib/THCUNN/CMakeLists.txt +@@ -29,6 +29,15 @@ IF(NOT CUDA_FOUND) + FIND_PACKAGE(CUDA 6.5 REQUIRED) + ENDIF() + ++IF ($ENV{TH_BINARY_BUILD}) ++ MESSAGE(STATUS "TH_BINARY_BUILD detected. Statically linking libstdc++") ++ SET(CMAKE_CXX_FLAGS "-static-libstdc++ ${CMAKE_CXX_FLAGS}") ++ IF (UNIX AND NOT APPLE) ++ # hiding statically linked library symbols, this flag is not available for the linker under MACOSX ++ SET(CMAKE_CXX_FLAGS "-Wl,--exclude-libs,libstdc++.a ${CMAKE_CXX_FLAGS}") ++ ENDIF(UNIX AND NOT APPLE) ++ENDIF() ++ + # Detect CUDA architecture and get best NVCC flags + IF(NOT COMMAND CUDA_SELECT_NVCC_ARCH_FLAGS OR MSVC) + INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/select_compute_arch.cmake) +@@ -46,6 +55,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.9.3") + endif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + ++if(CUDA_VERSION VERSION_GREATER "8.0") ++ LIST(APPEND CUDA_NVCC_FLAGS "-D__CUDA_NO_HALF_OPERATORS__") ++endif(CUDA_VERSION VERSION_GREATER "8.0") ++ + IF(MSVC) + LIST(APPEND CUDA_NVCC_FLAGS "-Xcompiler /wd4819") + ADD_DEFINITIONS(-DTH_EXPORTS) +diff --git a/extra/cunn/lib/THCUNN/FeatureLPPooling.cu b/extra/cunn/lib/THCUNN/FeatureLPPooling.cu +new file mode 100644 +index 0000000..4ad190f +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/FeatureLPPooling.cu +@@ -0,0 +1,653 @@ ++#include "THCUNN.h" ++#include "THCAtomics.cuh" ++#include "THCDeviceTensor.cuh" ++#include "THCDeviceTensorUtils.cuh" ++#include "THCDeviceUtils.cuh" ++#include "THCNumerics.cuh" ++#include "THCTensorTypeUtils.cuh" ++ ++#define OUTPUT_FEATURES_PER_THREAD 32 ++#define MAX_WARPS_PER_RUN 4 ++ ++namespace detail { ++ ++/// Various utilities for dealing with arrays of values which are ++/// maintained in thread-local registers. All accesses are done in such ++/// a way such that the index is statically known, which preserves the ++/// compiler's ability to allocate the values to registers, as opposed ++/// to local memory. ++template ++struct RegisterUtils { ++ /// Register shifting: move elements towards the beginning of the ++ /// array (towards 0) by `Shift` places: ++ /// arr[i] = arr[i + Shift] ++ /// The `Shift` elements at the end are left unchanged. ++ template ++ __device__ __forceinline__ static void shiftLeft(T arr[N]) { ++ // e.g., N = 5, Shift = 2: ++ // 0 1 2 3 4 becomes => ++ // 2 3 4 3 4 (last are unchanged) ++#pragma unroll ++ for (int i = 0; i < N - Shift; ++i) { ++ arr[i] = arr[i + Shift]; ++ } ++ } ++}; ++ ++template ++__device__ __forceinline__ ++int getDim1Point(const THCDeviceTensor& input) { ++ int threadPoint = blockIdx.x * blockDim.x + threadIdx.x; ++ return threadPoint / input.getSize(3); ++} ++ ++template ++__device__ __forceinline__ ++int getDim2Point(const THCDeviceTensor& input) { ++ int threadPoint = blockIdx.x * blockDim.x + threadIdx.x; ++ return threadPoint % input.getSize(3); ++} ++ ++__device__ __forceinline__ ++int getStartOutputFeature() { ++ return blockIdx.y * OUTPUT_FEATURES_PER_THREAD; ++} ++ ++template ++__device__ __forceinline__ ++int getEndOutputFeature(const THCDeviceTensor& output) { ++ return min((blockIdx.y + 1) * OUTPUT_FEATURES_PER_THREAD, output.getSize(1)); ++} ++ ++__device__ __forceinline__ ++int getBatch() { ++ return blockIdx.z; ++} ++ ++// All of these functions that follow are MathOps; they are template ++// parameters so L2 can be more efficiently implemented ++// template ++// typedef T (*MathOp)(const T in, const T arg); ++ ++template ++__device__ __forceinline__ T power2(const T in, const T power) { ++ return THCNumerics::mul(in, in); ++} ++ ++template ++__device__ __forceinline__ T root2(const T in, const T power) { ++ return THCNumerics::sqrt(in); ++} ++ ++template ++__device__ __forceinline__ T powerGrad2(const T in, const T power) { ++ return in; ++} ++ ++template ++__device__ __forceinline__ T powerN(const T in, const T power) { ++ return THCNumerics::pow(in, power); ++} ++ ++template ++__device__ __forceinline__ T rootN(const T in, const T power) { ++ const T invPower = THCNumerics::cinv(power); ++ return THCNumerics::pow(in, invPower); ++} ++ ++template ++__device__ __forceinline__ T powerGradN(const T in, const T power) { ++ return THCNumerics::pow(in, ++ THCNumerics::sub(power, ++ ScalarConvert::to(1))); ++} ++ ++// Input is of the form: ++// [batch][feature dim][optional dim 1][optional dim 2] ++template ++__global__ void ++featureLPPoolingUpdateOutput(const THCDeviceTensor input, ++ THCDeviceTensor output, ++ T power) { ++ // What non-feature points is this thread handling? ++ int dim1Point = getDim1Point(input); ++ int dim2Point = getDim2Point(input); ++ ++ if (dim1Point >= input.getSize(2) || dim2Point >= input.getSize(3)) { ++ // This thread in the warp is out of bounds ++ return; ++ } ++ ++ // What feature points is this thread handling? ++ int startOutputFeature = getStartOutputFeature(); ++ int endOutputFeature = getEndOutputFeature(output); ++ int startInputFeature = startOutputFeature * Stride; ++ ++ // What batch points is this thread handling? ++ int batch = getBatch(); ++ ++ // If stride >= width, then there is no loaded data reuse. ++ // If stride > 1 and stride < width, then shift by stride, since we ++ // can reuse Width - Stride elements from the previous round. ++ // e.g., width = 5, stride = 2, ++ // output 0 uses input 0 1 2 3 4 ++ // output 1 uses input 2 3 4 5 6 (inputs 2 - 4 are reused, i.e., 5 - ++ // 2 elements are reused, and we have to shift the array by 2) ++ // ++ // e.g., width = 5, stride = 3, ++ // output 0 uses input 0 1 2 3 4 ++ // output 1 uses input 3 4 5 6 7 (inputs 3 - 4 are reused, i.e., 5 - 3 ++ // elements are reused, and we have to shift the array by 3) ++ ++ // Valid only pooling: load Width elements from input (Width - ++ // Stride is handled here, at the top of the loop we handle the ++ // remaining Stride elements). We already verified that the input is ++ // larger than the width. ++ // `in` will contain the input values ^ power. ++ T in[Width]; ++ ++#pragma unroll ++ for (int i = 0; i < Width - Stride; ++i) { ++ const T data = ++ input[batch][startInputFeature + i][dim1Point][dim2Point]; ++ in[i] = PowerFunc(data, power); ++ } ++ ++ for (int outputFeature = startOutputFeature; ++ outputFeature < endOutputFeature; ++ ++outputFeature) { ++ // If Stride < Width, we're loading Stride new values starting at ++ // Width - Stride ++ // If Stride >= Width, we're loading Width new values starting at 0 ++ if (Stride < Width) { ++ int nextInputFeature = outputFeature * Stride + Width - Stride; ++ ++#pragma unroll ++ for (int i = 0; i < Stride; ++i) { ++ const T data = ++ input[batch][nextInputFeature + i][dim1Point][dim2Point]; ++ in[Width - Stride + i] = PowerFunc(data, power); ++ } ++ } else { ++ int nextInputFeature = outputFeature * Stride; ++ ++#pragma unroll ++ for (int i = 0; i < Width; ++i) { ++ T data = input[batch][nextInputFeature + i][dim1Point][dim2Point]; ++ in[i] = PowerFunc(data, power); ++ } ++ } ++ ++ // Calculate the new output feature ++ T val = ScalarConvert::to(0); ++ for (int i = 0; i < Width; ++i) { ++ val = THCNumerics::add(val, in[i]); ++ } ++ ++ val = RootFunc(val, power); ++ output[batch][outputFeature][dim1Point][dim2Point] = val; ++ ++ if (Stride < Width) { ++ // Shift registers for calculating the next point ++ RegisterUtils::shiftLeft(in); ++ } ++ } ++} ++ ++// forward pass: f(a, ..., z) = (a^p + ... + z^p)^(1 / p) ++// for bprop: ++// partial df(a, ... z)/da = a^(p - 1) * (a^p + ... + z^p)^((1 / p) - 1) = ++// a^(p - 1) * 1/(f(a, ..., z)^(p - 1)) = (a / f(a, ..., z))^(p - 1) ++// ++// example: for p = 2, df(a, ..., z)/da = a / f(a, ..., z) ++// example: for p = 3, df(a, ..., z)/da = (a / f(a, ..., z))^2 ++// ++// PowerGradFunc implements x^(p - 1) ++template ++__global__ void ++featureLPPoolingUpdateGradInput(const THCDeviceTensor gradOutput, ++ const THCDeviceTensor input, ++ const THCDeviceTensor output, ++ THCDeviceTensor gradInput, ++ T power) { ++ // What non-feature points is this thread handling? ++ int dim1Point = getDim1Point(input); ++ int dim2Point = getDim2Point(input); ++ ++ if (dim1Point >= input.getSize(2) || dim2Point >= input.getSize(3)) { ++ // This thread in the warp is out of bounds ++ return; ++ } ++ ++ // What feature points is this thread handling? [start, end) ++ int startOutputFeature = getStartOutputFeature(); ++ int endOutputFeature = getEndOutputFeature(output); ++ ++ // What is the first input point that the output features depend ++ // upon? [start, end) ++ int startInputFeature = startOutputFeature * Stride; ++ int endInputFeature = endOutputFeature * Stride; ++ ++ // What batch points is this thread handling? ++ int batch = getBatch(); ++ ++ // atomicAdd into gradInput is slow, avoid it where possible. ++ // We can do this because there is a range of gradInput elements ++ // that we are updating exclusively. This is how we find it ++ // ++ // width = 3 stride = 1 example: ++ // ------------------------------ ++ // startOutputFeature for this thread ++ // | ++ // | ++ // previous thread's output feature ++ // | | ++ // | | gradOutput ++ // __v____v___________________ ++ // | | | | | | ++ // --------------------------- ++ // |\ \_____ ++ // | \__ \ gradInput ++ // __v____v____v_____________ ++ // | | | | | | ++ // --------------------------- ++ // A A ++ // | | ++ // startInputFeature ++ // | ++ // exclusiveStartInputFeature ++ // ++ // exclusiveStartInputFeature is the first input feature that we can ++ // write into exclusively; the one right before it overlaps with ++ // updates from a previous thread and thus has to use atomicAdd. ++ int exclusiveStartInputFeature = ++ startInputFeature == 0 ? ++ // no thread is before ourselves ++ 0 : ++ // there is a thread before ourselves ++ startInputFeature + (Width - 1) * Stride; ++ ++ // Similarly, exclusiveEndInputFeature is the last input feature ++ // that we can write into exclusively, since we might be overlapping ++ // with the following thread ++ int exclusiveEndInputFeature = ++ endOutputFeature == output.getSize(1) ? ++ // no thread is after ourselves ++ endInputFeature + (Width - 1) * Stride : ++ // there is a thread after ourselves ++ endInputFeature; ++ ++ // As with updateOutput preload input elements, except no need to ++ // transform them ++ T in[Width]; ++#pragma unroll ++ for (int i = 0; i < Width - Stride; ++i) { ++ in[i] = input[batch][startInputFeature + i][dim1Point][dim2Point]; ++ } ++ ++ for (int outputFeature = startOutputFeature; ++ outputFeature < endOutputFeature; ++ ++outputFeature) { ++ // As with updateOutput load the subsequent input elements that we ++ // need, except no need to transform them ++ // ++ // If Stride < Width, we're loading Stride new values starting at ++ // Width - Stride ++ // If Stride >= Width, we're loading Width new values starting at 0 ++ if (Stride < Width) { ++ int nextInputFeature = outputFeature * Stride + Width - Stride; ++ ++#pragma unroll ++ for (int i = 0; i < Stride; ++i) { ++ in[Width - Stride + i] = ++ input[batch][nextInputFeature + i][dim1Point][dim2Point]; ++ } ++ } else { ++ int nextInputFeature = outputFeature * Stride; ++ ++#pragma unroll ++ for (int i = 0; i < Width; ++i) { ++ in[i] = input[batch][nextInputFeature + i][dim1Point][dim2Point]; ++ } ++ } ++ ++ // A given output feature gradient contributes to `Width` input ++ // gradients ++ const T gradOut = ++ gradOutput[batch][outputFeature][dim1Point][dim2Point]; ++ ++ // Load output (f(x_is)). It is possible that this is zero, in ++ // which case we'll ignore this point. ++ T out = output[batch][outputFeature][dim1Point][dim2Point]; ++ if (THCNumerics::eq(out, ScalarConvert::to(0))) { ++ continue; ++ } ++ ++ int curStartInputFeature = outputFeature * Stride; ++ int curEndInputFeature = outputFeature * Stride + Width - 1; ++ ++ if (curStartInputFeature >= exclusiveStartInputFeature && ++ curEndInputFeature < exclusiveEndInputFeature) { ++ // This thread is exclusively responsible for updating these ++ // input points, so we need not make the addition atomic ++ for (int i = 0; i < Width; ++i) { ++ int inputFeature = outputFeature * Stride + i; ++ ++ // Calculate grad * (x_i / f(x_is))^(p - 1) ++ const T val = THCNumerics::mul( ++ gradOut, ++ PowerGradFunc(THCNumerics::div(in[i], out), power)); ++ ++ gradInput[batch][inputFeature][dim1Point][dim2Point] = ++ THCNumerics::add( ++ gradInput[batch][inputFeature][dim1Point][dim2Point], val); ++ } ++ } else { ++ // Handle start and end boundary cases: potential overlap with ++ // other threads ++ for (int i = 0; i < Width; ++i) { ++ int inputFeature = outputFeature * Stride + i; ++ ++ // Calculate grad * (x_i / f(x_is))^(p - 1) ++ T val = THCNumerics::mul( ++ gradOut, ++ PowerGradFunc(THCNumerics::div(in[i], out), power)); ++ ++ // We don't overlap other threads for this range ++ if (inputFeature >= exclusiveStartInputFeature && ++ inputFeature < exclusiveEndInputFeature) { ++ gradInput[batch][inputFeature][dim1Point][dim2Point] ++ = THCNumerics::add( ++ gradInput[batch][inputFeature][dim1Point][dim2Point], val); ++ } else { ++ // We are potentially overlapping with threads handling ++ // features before ourselves, so these need to be added atomically ++ atomicAdd(&gradInput[batch][inputFeature][dim1Point][dim2Point], ++ val); ++ } ++ } ++ } ++ ++ if (Stride < Width) { ++ // Shift registers for calculating the next point ++ RegisterUtils::shiftLeft(in); ++ } ++ } ++} ++ ++} // namespace detail ++ ++inline int lpPoolingOutputSize(int inputSize, int width, int stride) { ++ return ((inputSize - width) / stride) + 1; ++} ++ ++template ++bool ++runFeatureLPPoolingUpdateOutput(THCState* state, ++ const THCDeviceTensor& input, ++ THCDeviceTensor& output, ++ float power, int width, int stride) { ++ cudaStream_t stream = ++ THCState_getCurrentStream(state); ++ const cudaDeviceProp* deviceProperties = ++ THCState_getCurrentDeviceProperties(state); ++ ++ int outputFeatures = ((input.getSize(1) - width) / stride) + 1; ++ ++ THAssert(input.getSize(0) == output.getSize(0)); ++ THAssert(outputFeatures == output.getSize(1)); ++ THAssert(input.getSize(1) >= width); ++ ++ THAssert(input.getSize(2) == output.getSize(2)); ++ THAssert(input.getSize(3) == output.getSize(3)); ++ THAssert(power > 0.0f); ++ THAssert(width >= 1); ++ THAssert(stride >= 1); ++ ++ // Split non-features among threads and grid x ++ int totalNonFeatureSize = input.getSize(2) * input.getSize(3); ++ int numWarps = ++ min(THCCeilDiv(totalNonFeatureSize, deviceProperties->warpSize), ++ MAX_WARPS_PER_RUN); ++ int blockSize = deviceProperties->warpSize * numWarps; ++ ++ // Split non-features among grid x ++ int nonFeatureSizeBlocks = THCCeilDiv(totalNonFeatureSize, blockSize); ++ ++ // Split features among grid y, up to a maximum number of features per thread ++ int featureBlocks = THCCeilDiv(outputFeatures, OUTPUT_FEATURES_PER_THREAD); ++ ++ // Split batch among grid z. ++ dim3 grid(nonFeatureSizeBlocks, featureBlocks, input.getSize(0)); ++ dim3 block(blockSize); ++ ++#define L2_STRIDE_CASE(STRIDE, WIDTH) \ ++ case STRIDE: \ ++ detail:: \ ++ featureLPPoolingUpdateOutput<<>>( \ ++ input, output, \ ++ ScalarConvert::to(power)); \ ++ return true; ++ ++#define L2_WIDTH_CASE(WIDTH) \ ++ case WIDTH: \ ++ switch (stride) { \ ++ L2_STRIDE_CASE(1, WIDTH); \ ++ L2_STRIDE_CASE(2, WIDTH); \ ++ L2_STRIDE_CASE(3, WIDTH); \ ++ L2_STRIDE_CASE(4, WIDTH); \ ++ } ++ ++#define LP_STRIDE_CASE(STRIDE, WIDTH) \ ++ case STRIDE: \ ++ detail:: \ ++ featureLPPoolingUpdateOutput<<>>( \ ++ input, output, \ ++ ScalarConvert::to(power)); \ ++ return true; ++ ++#define LP_WIDTH_CASE(WIDTH) \ ++ case WIDTH: \ ++ switch (stride) { \ ++ LP_STRIDE_CASE(1, WIDTH); \ ++ LP_STRIDE_CASE(2, WIDTH); \ ++ LP_STRIDE_CASE(3, WIDTH); \ ++ LP_STRIDE_CASE(4, WIDTH); \ ++ } ++ ++ if (power == 2.0f) { ++ switch (width) { ++ L2_WIDTH_CASE(2); ++ L2_WIDTH_CASE(3); ++ L2_WIDTH_CASE(4); ++ L2_WIDTH_CASE(5); ++ L2_WIDTH_CASE(6); ++ L2_WIDTH_CASE(7); ++ L2_WIDTH_CASE(8); ++ L2_WIDTH_CASE(9); ++ L2_WIDTH_CASE(10); ++ L2_WIDTH_CASE(11); ++ L2_WIDTH_CASE(12); ++ L2_WIDTH_CASE(13); ++ L2_WIDTH_CASE(14); ++ L2_WIDTH_CASE(15); ++ L2_WIDTH_CASE(16); ++ } ++ } else { ++ switch (width) { ++ LP_WIDTH_CASE(2); ++ LP_WIDTH_CASE(3); ++ LP_WIDTH_CASE(4); ++ LP_WIDTH_CASE(5); ++ LP_WIDTH_CASE(6); ++ LP_WIDTH_CASE(7); ++ LP_WIDTH_CASE(8); ++ LP_WIDTH_CASE(9); ++ LP_WIDTH_CASE(10); ++ LP_WIDTH_CASE(11); ++ LP_WIDTH_CASE(12); ++ LP_WIDTH_CASE(13); ++ LP_WIDTH_CASE(14); ++ LP_WIDTH_CASE(15); ++ LP_WIDTH_CASE(16); ++ } ++ } ++ ++ // Otherwise, we have an unhandled width and/or stride. ++ return false; ++ ++#undef L2_STRIDE_CASE ++#undef L2_WIDTH_CASE ++#undef LP_STRIDE_CASE ++#undef LP_WIDTH_CASE ++} ++ ++template ++bool ++runFeatureLPPoolingUpdateGradInput(THCState* state, ++ const THCDeviceTensor& gradOutput, ++ const THCDeviceTensor& input, ++ const THCDeviceTensor& output, ++ THCDeviceTensor& gradInput, ++ float power, int width, int stride) { ++ cudaStream_t stream = ++ THCState_getCurrentStream(state); ++ const cudaDeviceProp* deviceProperties = ++ THCState_getCurrentDeviceProperties(state); ++ ++ for (int i = 0; i < 4; ++i) { ++ THAssert(gradOutput.getSize(i) == output.getSize(i)); ++ THAssert(gradInput.getSize(i) == input.getSize(i)); ++ } ++ ++ int outputFeatures = ((input.getSize(1) - width) / stride) + 1; ++ ++ THAssert(gradInput.getSize(0) == gradOutput.getSize(0)); ++ THAssert(outputFeatures == gradOutput.getSize(1)); ++ THAssert(gradInput.getSize(1) >= width); ++ ++ THAssert(gradInput.getSize(2) == gradOutput.getSize(2)); ++ THAssert(gradInput.getSize(3) == gradOutput.getSize(3)); ++ THAssert(power > 0.0f); ++ THAssert(width >= 1); ++ THAssert(stride >= 1); ++ ++ // Different threads are potentially adding into overlapping input ++ // points, so we must clear out gradInput before continuing. ++ gradInput.zero(stream); ++ ++ // Split non-features among threads and grid x ++ int totalNonFeatureSize = input.getSize(2) * input.getSize(3); ++ int numWarps = ++ min(THCCeilDiv(totalNonFeatureSize, deviceProperties->warpSize), ++ MAX_WARPS_PER_RUN); ++ int blockSize = deviceProperties->warpSize * numWarps; ++ ++ // Split non-features among grid x ++ int nonFeatureSizeBlocks = THCCeilDiv(totalNonFeatureSize, blockSize); ++ ++ // Split features among grid y, up to a maximum number of features per thread ++ int featureBlocks = THCCeilDiv(outputFeatures, OUTPUT_FEATURES_PER_THREAD); ++ ++ // Split batch among grid z. ++ dim3 grid(nonFeatureSizeBlocks, featureBlocks, input.getSize(0)); ++ dim3 block(blockSize); ++ ++#define L2_STRIDE_CASE(STRIDE, WIDTH) \ ++ case STRIDE: \ ++ detail:: \ ++ featureLPPoolingUpdateGradInput< \ ++ T, WIDTH, STRIDE, detail::powerGrad2><<>>( \ ++ gradOutput, input, output, gradInput, \ ++ ScalarConvert::to(power)); \ ++ return true; ++ ++#define L2_WIDTH_CASE(WIDTH) \ ++ case WIDTH: \ ++ switch (stride) { \ ++ L2_STRIDE_CASE(1, WIDTH); \ ++ L2_STRIDE_CASE(2, WIDTH); \ ++ L2_STRIDE_CASE(3, WIDTH); \ ++ L2_STRIDE_CASE(4, WIDTH); \ ++ } ++ ++#define LP_STRIDE_CASE(STRIDE, WIDTH) \ ++ case STRIDE: \ ++ detail:: \ ++ featureLPPoolingUpdateGradInput< \ ++ T, WIDTH, STRIDE, detail::powerGradN><<>>( \ ++ gradOutput, input, output, gradInput, \ ++ ScalarConvert::to(power)); \ ++ return true; ++ ++#define LP_WIDTH_CASE(WIDTH) \ ++ case WIDTH: \ ++ switch (stride) { \ ++ LP_STRIDE_CASE(1, WIDTH); \ ++ LP_STRIDE_CASE(2, WIDTH); \ ++ LP_STRIDE_CASE(3, WIDTH); \ ++ LP_STRIDE_CASE(4, WIDTH); \ ++ } ++ ++ if (power == 2.0f) { ++ switch (width) { ++ L2_WIDTH_CASE(2); ++ L2_WIDTH_CASE(3); ++ L2_WIDTH_CASE(4); ++ L2_WIDTH_CASE(5); ++ L2_WIDTH_CASE(6); ++ L2_WIDTH_CASE(7); ++ L2_WIDTH_CASE(8); ++ L2_WIDTH_CASE(9); ++ L2_WIDTH_CASE(10); ++ L2_WIDTH_CASE(11); ++ L2_WIDTH_CASE(12); ++ L2_WIDTH_CASE(13); ++ L2_WIDTH_CASE(14); ++ L2_WIDTH_CASE(15); ++ L2_WIDTH_CASE(16); ++ } ++ } else { ++ switch (width) { ++ LP_WIDTH_CASE(2); ++ LP_WIDTH_CASE(3); ++ LP_WIDTH_CASE(4); ++ LP_WIDTH_CASE(5); ++ LP_WIDTH_CASE(6); ++ LP_WIDTH_CASE(7); ++ LP_WIDTH_CASE(8); ++ LP_WIDTH_CASE(9); ++ LP_WIDTH_CASE(10); ++ LP_WIDTH_CASE(11); ++ LP_WIDTH_CASE(12); ++ LP_WIDTH_CASE(13); ++ LP_WIDTH_CASE(14); ++ LP_WIDTH_CASE(15); ++ LP_WIDTH_CASE(16); ++ } ++ } ++ ++ // Otherwise, we have an unhandled width and/or stride. ++ return false; ++ ++#undef L2_STRIDE_CASE ++#undef L2_WIDTH_CASE ++#undef LP_STRIDE_CASE ++#undef LP_WIDTH_CASE ++} ++ ++#include "generic/FeatureLPPooling.cu" ++#include "THCGenerateFloatTypes.h" +diff --git a/extra/cunn/lib/THCUNN/LogSigmoid.cu b/extra/cunn/lib/THCUNN/LogSigmoid.cu +index bb9753a..dbdfea5 100644 +--- a/extra/cunn/lib/THCUNN/LogSigmoid.cu ++++ b/extra/cunn/lib/THCUNN/LogSigmoid.cu +@@ -7,7 +7,9 @@ template + struct logSigmoid_updateOutput_functor + { + __device__ void operator()(T *output, const T *input) const { +- *output = -THCNumerics::log(1.f + THCNumerics::exp(- *input)); ++ const T max = fmaxType(0.f, - *input); ++ const T z = THCNumerics::exp(-max) + THCNumerics::exp(-*input -max); ++ *output = -(max + THCNumerics::log(z)); + } + }; + +@@ -15,8 +17,15 @@ template + struct logSigmoid_updateGradInput_functor + { + __device__ void operator()(T *gradInput, const T *input, const T *gradOutput) const { +- const T z = THCNumerics::exp(- *input); +- *gradInput = *gradOutput * z / (1.f + z); ++ const T max = fmaxType(0.f, -*input); ++ const T z = THCNumerics::exp(-max) + THCNumerics::exp(-*input -max); ++ T max_deriv = 0.f; ++ T sign = -1.f; ++ if (*input < 0.f){ ++ max_deriv = -1.f; ++ sign = 1.f; ++ } ++ *gradInput = *gradOutput * (-max_deriv - sign*((z - 1.f)/z)); + } + }; + +@@ -25,11 +34,14 @@ template <> + struct logSigmoid_updateOutput_functor { + __device__ __forceinline__ void operator()(half* output, const half *input) const { + #ifdef CUDA_HALF_INSTRUCTIONS +- const half one = __float2half(1.f); +- *output = __hneg(THCNumerics::log(one + THCNumerics::exp(__hneg(*input)))); ++ const half max = fmaxType(__float2half(0.f), __hneg(*input)); ++ const half z = THCNumerics::exp(__hneg(max)) + THCNumerics::exp(__hneg(*input) - max); ++ *output = __hneg(max + THCNumerics::log(z)); + #else + float in = __half2float(*input); +- *output = __float2half(-THCNumerics::log(1.f + THCNumerics::exp(-in))); ++ float max = fmaxType(0.f, -in); ++ float z = THCNumerics::exp(-max) + THCNumerics::exp(-in - max); ++ *output = __float2half(-(max + THCNumerics::log(z))); + #endif + } + }; +@@ -39,12 +51,28 @@ struct logSigmoid_updateGradInput_functor { + __device__ __forceinline__ void operator()(half* gradInput, const half *input, const half *gradOutput) const { + #ifdef CUDA_HALF_INSTRUCTIONS + const half one = __float2half(1.f); +- const half in_exp = THCNumerics::exp(__hneg(*input)); +- *gradInput = hdiv(__hmul(*gradOutput, in_exp), __hadd(one, in_exp)); ++ const half zero = __float2half(0.f); ++ const half max = fmaxType(zero, __hneg(*input)); ++ const half z = THCNumerics::exp(__hneg(max)) + THCNumerics::exp(__hneg(*input) - max); ++ half max_deriv = zero; ++ half sign = __hneg(one); ++ if(*input < zero){ ++ max_deriv = __hneg(one); ++ sign = one; ++ } ++ *gradInput = __hmul(*gradOutput, (__hneg(max_deriv) - __hmul(sign, __hdiv(z - one, z)))); + #else +- const float in_exp = THCNumerics::exp(-(__half2float(*input))); ++ const float in = __half2float(*input); ++ const float max = fmaxType(0.f, -in); ++ const float z = THCNumerics::exp(-max) + THCNumerics::exp(-in - max); + const float go = __half2float(*gradOutput); +- *gradInput = __float2half(go * in_exp / (1.f + in_exp)); ++ float max_deriv = 0.f; ++ float sign = -1.f; ++ if(in < 0.f){ ++ max_deriv = -1.f; ++ sign = 1.f; ++ } ++ *gradInput = __float2half(go * (-max_deriv - sign*((z - 1.f)/z))); + #endif + } + }; +diff --git a/extra/cunn/lib/THCUNN/LookupTable.cu b/extra/cunn/lib/THCUNN/LookupTable.cu +index e626632..116639b 100644 +--- a/extra/cunn/lib/THCUNN/LookupTable.cu ++++ b/extra/cunn/lib/THCUNN/LookupTable.cu +@@ -105,7 +105,7 @@ __global__ void cunn_LookupTable_accGradParametersKernel( + int idx = blockIdx.x * 4 + threadIdx.y; + + // Each warp is responsible for an input into the LookupTable. +- // If the preceeding input has the same as this input, then the warp ++ // If the preceding input has the same as this input, then the warp + // exits immediately. The warp also processes subsequent inputs with the + // same value. + // +diff --git a/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu +new file mode 100644 +index 0000000..77d9811 +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu +@@ -0,0 +1,8 @@ ++#include "THCUNN.h" ++#include "im2col.h" ++ ++#include "THCHalf.h" ++#include "THCHalfAutoNumerics.cuh" ++ ++#include "generic/SpatialFullDilatedConvolution.cu" ++#include "THCGenerateFloatTypes.h" +diff --git a/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu b/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu +index f584dcf..979d370 100644 +--- a/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu ++++ b/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu +@@ -9,8 +9,12 @@ + + template + __global__ void cuda_VolumetricAveragePooling_updateOutput( +- THCDeviceTensor input, THCDeviceTensor output, +- int kT, int kH, int kW, int dT, int dH, int dW, Acctype normFactor, int offsetZ) ++ THCDeviceTensor input, ++ THCDeviceTensor output, ++ int kT, int kH, int kW, ++ int dT, int dH, int dW, ++ int padT, int padH, int padW, ++ bool count_include_pad, int offsetZ) + { + int oCol = blockIdx.x * blockDim.x + threadIdx.x; + int oRow = blockIdx.y * blockDim.y + threadIdx.y; +@@ -21,32 +25,40 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( + { + Acctype sum = 0.0; + +- int iColumn = oCol * dW; +- int iRow = oRow * dH; +- int iFrame = oFrame * dT; ++ int tstart = oFrame * dT - padT; ++ int hstart = oRow * dH - padH; ++ int wstart = oCol * dW - padW; ++ int tend = min(tstart + kT, input.getSize(1) + padT); ++ int hend = min(hstart + kH, input.getSize(2) + padH); ++ int wend = min(wstart + kW, input.getSize(3) + padW); ++ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); ++ tstart = max(tstart, 0); ++ hstart = max(hstart, 0); ++ wstart = max(wstart, 0); ++ tend = min(tend, input.getSize(1)); ++ hend = min(hend, input.getSize(2)); ++ wend = min(wend, input.getSize(3)); ++ ++ Acctype divide_factor; ++ if (count_include_pad) ++ divide_factor = static_cast(pool_size); ++ else ++ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); + +- for (int frame = 0; frame < kT; ++frame) ++ int ti, hi, wi; ++ for (ti = tstart; ti < tend; ++ti) + { +- if (iFrame + frame < input.getSize(1)) ++ for (hi = hstart; hi < hend; ++hi) + { +- for (int row = 0; row < kH; ++row) ++ for (wi = wstart; wi < wend; ++wi) + { +- if (iRow + row < input.getSize(2)) +- { +- for (int column = 0; column < kW; ++column) +- { +- if (iColumn + column < input.getSize(3)) +- { +- Dtype val = input[slice][iFrame + frame][iRow + row][iColumn + column]; +- sum += val; +- } +- } +- } ++ Dtype val = input[slice][ti][hi][wi]; ++ sum += val; + } + } + } + +- output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum * normFactor); ++ output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum / divide_factor); + } + } + +@@ -54,9 +66,13 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( + // performance reasons. + // + template +-__global__ void cuda_VolumetricAveragePooling_updateOutput( +- THCDeviceTensor input, THCDeviceTensor output, +- int kT, int kH, int dT, int dH, int dW, Acctype normFactor, int offsetZ) ++__global__ void cuda_VolumetricAveragePooling_updateOutput_fixedKW( ++ THCDeviceTensor input, ++ THCDeviceTensor output, ++ int kT, int kH, ++ int dT, int dH, int dW, ++ int padT, int padH, int padW, ++ bool count_include_pad, int offsetZ) + { + int oCol = blockIdx.x * blockDim.x + threadIdx.x; + int oRow = blockIdx.y * blockDim.y + threadIdx.y; +@@ -67,45 +83,54 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( + { + Acctype sum = 0.0; + +- int iColumn = oCol * dW; +- int iRow = oRow * dH; +- int iFrame = oFrame * dT; ++ int tstart = oFrame * dT - padT; ++ int hstart = oRow * dH - padH; ++ int wstart = oCol * dW - padW; ++ int tend = min(tstart + kT, input.getSize(1) + padT); ++ int hend = min(hstart + kH, input.getSize(2) + padH); ++ int wend = min(wstart + KERNEL_WIDTH, input.getSize(3) + padW); ++ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); ++ tstart = max(tstart, 0); ++ hstart = max(hstart, 0); ++ wstart = max(wstart, 0); ++ tend = min(tend, input.getSize(1)); ++ hend = min(hend, input.getSize(2)); ++ wend = min(wend, input.getSize(3)); + +- for (int frame = 0; frame < kT; ++frame) ++ Acctype divide_factor; ++ if (count_include_pad) ++ divide_factor = static_cast(pool_size); ++ else ++ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); ++ ++ int ti, hi, wi; ++ for (ti = tstart; ti < tend; ++ti) + { +- if (iFrame + frame < input.getSize(1)) ++ for (hi = hstart; hi < hend; ++hi) + { +- for (int row = 0; row < kH; ++row) ++ for (wi = wstart; wi < wend; ++wi) + { +- if (iRow + row < input.getSize(2)) +- { +- for (int column = 0; column < KERNEL_WIDTH; ++column) +- { +- if (iColumn + column < input.getSize(3)) +- { +- Dtype val = input[slice][iFrame + frame][iRow + row][iColumn + column]; +- sum += val; +- } +- } +- } ++ Dtype val = input[slice][ti][hi][wi]; ++ sum += val; + } + } + } + +- output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum * normFactor); ++ output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum / divide_factor); + } + } + +-#define LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(KW) case KW: \ +- cuda_VolumetricAveragePooling_updateOutput<<>>( \ +- cudaInput, cudaOutput, kT, kH, dT, dH, dW, normFactor, offsetZ); \ ++#define LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(KW) case KW: \ ++ cuda_VolumetricAveragePooling_updateOutput_fixedKW<<>>( \ ++ cudaInput, cudaOutput, kT, kH, dT, dH, dW, padT, padH, padW, count_include_pad, offsetZ); \ + break + + template + __global__ void cuda_VolumetricAveragePooling_updateGradInput_Stride1( + THCDeviceTensor gradOutput, + THCDeviceTensor gradInput, +- int kT, int kH, int kW, Acctype normFactor, int offsetZ) ++ int kT, int kH, int kW, ++ Acctype normFactor, int offsetZ) + { + int iCol = blockIdx.x * blockDim.x + threadIdx.x; + int iRow = blockIdx.y * blockDim.y + threadIdx.y; +@@ -148,7 +173,10 @@ template + __global__ void cuda_VolumetricAveragePooling_updateGradInput_atomicAdd( + THCDeviceTensor gradOutput, + THCDeviceTensor gradInput, +- int kT, int kH, int kW, int dT, int dH, int dW, int offsetZ) ++ int kT, int kH, int kW, ++ int dT, int dH, int dW, ++ int padT, int padH, int padW, ++ bool count_include_pad, int offsetZ) + { + int oCol = blockIdx.x * blockDim.x + threadIdx.x; + int oRow = blockIdx.y * blockDim.y + threadIdx.y; +@@ -158,13 +186,33 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput_atomicAdd( + // guard against over-tiled threads + if (oRow < gradOutput.getSize(2) && oCol < gradOutput.getSize(3)) + { ++ int tstart = oFrame * dT - padT; ++ int hstart = oRow * dH - padH; ++ int wstart = oCol * dW - padW; ++ int tend = min(tstart + kT, gradInput.getSize(1) + padT); ++ int hend = min(hstart + kH, gradInput.getSize(2) + padH); ++ int wend = min(wstart + kW, gradInput.getSize(3) + padW); ++ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); ++ tstart = max(tstart, 0); ++ hstart = max(hstart, 0); ++ wstart = max(wstart, 0); ++ tend = min(tend, gradInput.getSize(1)); ++ hend = min(hend, gradInput.getSize(2)); ++ wend = min(wend, gradInput.getSize(3)); ++ ++ Acctype divide_factor; ++ if (count_include_pad) ++ divide_factor = static_cast(pool_size); ++ else ++ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); ++ + Dtype val = ScalarConvert::to( +- ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / (kT * kH * kW)); +- for (int iFrame = oFrame * dT; iFrame < oFrame * dT + kT; ++iFrame) ++ ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / divide_factor); ++ for (int iFrame = tstart; iFrame < tend; ++iFrame) + { +- for (int iRow = oRow * dH; iRow < oRow * dH + kH; ++iRow) ++ for (int iRow = hstart; iRow < hend; ++iRow) + { +- for (int iCol = oCol * dW; iCol < oCol * dW + kW; ++iCol) ++ for (int iCol = wstart; iCol < wend; ++iCol) + { + atomicAdd(&gradInput[slice][iFrame][iRow][iCol], val); + } +@@ -178,7 +226,9 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput( + THCDeviceTensor gradOutput, + THCDeviceTensor gradInput, + int kT, int kH, int kW, +- int dT, int dH, int dW, int offsetZ) ++ int dT, int dH, int dW, ++ int padT, int padH, int padW, ++ bool count_include_pad, int offsetZ) + { + int oCol = blockIdx.x * blockDim.x + threadIdx.x; + int oRow = blockIdx.y * blockDim.y + threadIdx.y; +@@ -188,13 +238,33 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput( + // guard against over-tiled threads + if (oRow < gradOutput.getSize(2) && oCol < gradOutput.getSize(3)) + { ++ int tstart = oFrame * dT - padT; ++ int hstart = oRow * dH - padH; ++ int wstart = oCol * dW - padW; ++ int tend = min(tstart + kT, gradInput.getSize(1) + padT); ++ int hend = min(hstart + kH, gradInput.getSize(2) + padH); ++ int wend = min(wstart + kW, gradInput.getSize(3) + padW); ++ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); ++ tstart = max(tstart, 0); ++ hstart = max(hstart, 0); ++ wstart = max(wstart, 0); ++ tend = min(tend, gradInput.getSize(1)); ++ hend = min(hend, gradInput.getSize(2)); ++ wend = min(wend, gradInput.getSize(3)); ++ ++ Acctype divide_factor; ++ if (count_include_pad) ++ divide_factor = static_cast(pool_size); ++ else ++ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); ++ + Dtype val = ScalarConvert::to( +- ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / (kT * kH * kW)); +- for (int iFrame = oFrame * dT; iFrame < oFrame * dT + kT; ++iFrame) ++ ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / divide_factor); ++ for (int iFrame = tstart; iFrame < tend; ++iFrame) + { +- for (int iRow = oRow * dH; iRow < oRow * dH + kH; ++iRow) ++ for (int iRow = hstart; iRow < hend; ++iRow) + { +- for (int iCol = oCol * dW; iCol < oCol * dW + kW; ++iCol) ++ for (int iCol = wstart; iCol < wend; ++iCol) + { + gradInput[slice][iFrame][iRow][iCol] = val; + } +diff --git a/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu b/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu +index 93c4c0f..556b5bc 100644 +--- a/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu ++++ b/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu +@@ -1,6 +1,5 @@ + #include "THCUNN.h" + #include "common.h" +-#include "vol2col.h" + #include "THCHalf.h" + #include "THCHalfAutoNumerics.cuh" + +diff --git a/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu +new file mode 100644 +index 0000000..47173f2 +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu +@@ -0,0 +1,8 @@ ++#include "THCUNN.h" ++#include "common.h" ++#include "vol2col.h" ++#include "THCHalf.h" ++#include "THCHalfAutoNumerics.cuh" ++ ++#include "generic/VolumetricFullDilatedConvolution.cu" ++#include "THCGenerateFloatTypes.h" +diff --git a/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu b/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu +new file mode 100644 +index 0000000..9300450 +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu +@@ -0,0 +1,267 @@ ++#ifndef THC_GENERIC_FILE ++#define THC_GENERIC_FILE "generic/FeatureLPPooling.cu" ++#else ++ ++#include "../common.h" ++ ++// non-batch mode: ++// [feature dim] ++// [feature dim][opt dim 1] ++// [feature dim][opt dim 1][opt dim 2] ++// ++// batch mode: ++// [batch dim][feature dim] ++// [batch dim][feature dim][opt dim 1] ++// [batch dim][feature dim][opt dim 1][opt dim 2] ++THCDeviceTensor ++THNN_(FeatureLPPooling_upcast)(THCState* state, THCTensor* t, bool batchMode) { ++ int inputDim = THCTensor_(nDimension)(state, t); ++ ++ if (inputDim == 1) { ++ // [feature dim] ++ return toDeviceTensor(state, t). ++ upcastOuter<2>().upcastInner<4>(); ++ } else if (inputDim == 2) { ++ if (batchMode) { ++ // [batch dim][feature dim] ++ return toDeviceTensor(state, t). ++ upcastInner<4>(); ++ } else { ++ // [feature dim][opt dim 1] ++ return toDeviceTensor(state, t). ++ upcastOuter<3>().upcastInner<4>(); ++ } ++ } else if (inputDim == 3) { ++ if (batchMode) { ++ // [batch dim][feature dim][opt dim 1] ++ return toDeviceTensor(state, t). ++ upcastInner<4>(); ++ } else { ++ // [feature dim][opt dim 1][opt dim 2] ++ return toDeviceTensor(state, t). ++ upcastOuter<4>(); ++ } ++ } else { ++ // inputDim == 4 ++ // [batch dim][feature dim][opt dim 1][opt dim 2] ++ THAssert(batchMode); ++ return toDeviceTensor(state, t); ++ } ++} ++ ++// Resizes `toResize` based on the output size for `src` as an input ++// tensor ++void ++THNN_(FeatureLPPooling_resizeForOutput)(THCState* state, ++ THCTensor* toResize, ++ THCTensor* input, ++ bool batchMode, ++ int width, ++ int stride) { ++ int inputDim = THCTensor_(nDimension)(state, input); ++ THAssert(inputDim >= 1 && inputDim <= 4); ++ ++ long outSize = ++ lpPoolingOutputSize(THCTensor_(size)(state, input, 0), width, stride); ++ if (batchMode) { ++ THAssert(inputDim > 1); ++ outSize = ++ lpPoolingOutputSize(THCTensor_(size)(state, input, 1), width, stride); ++ } else { ++ THAssert(inputDim < 4); ++ } ++ ++ if (inputDim == 1) { ++ THCTensor_(resize1d)(state, toResize, outSize); ++ } else if (inputDim == 2) { ++ if (batchMode) { ++ THCTensor_(resize2d)( ++ state, toResize, THCTensor_(size)(state, input, 0), outSize); ++ } else { ++ THCTensor_(resize2d)( ++ state, toResize, outSize, THCTensor_(size)(state, input, 1)); ++ } ++ } else if (inputDim == 3) { ++ if (batchMode) { ++ THCTensor_(resize3d)( ++ state, ++ toResize, ++ THCTensor_(size)(state, input, 0), outSize, ++ THCTensor_(size)(state, input, 2)); ++ } else { ++ THCTensor_(resize3d)( ++ state, ++ toResize, ++ outSize, THCTensor_(size)(state, input, 1), ++ THCTensor_(size)(state, input, 2)); ++ } ++ } else if (inputDim == 4) { ++ THCTensor_(resize4d)( ++ state, ++ toResize, ++ THCTensor_(size)(state, input, 0), outSize, ++ THCTensor_(size)(state, input, 2), THCTensor_(size)(state, input, 3)); ++ } ++} ++ ++// Makes `toResize` the same size/dimensionality as `src` ++void ++THNN_(FeatureLPPooling_resize)(THCState* state, ++ THCTensor* toResize, ++ THCTensor* src) { ++ int inputDim = THCTensor_(nDimension)(state, src); ++ THAssert(inputDim >= 1 && inputDim <= 4); ++ ++ if (inputDim == 1) { ++ THCTensor_(resize1d)(state, ++ toResize, ++ THCTensor_(size)(state, src, 0)); ++ } else if (inputDim == 2) { ++ THCTensor_(resize2d)( ++ state, ++ toResize, ++ THCTensor_(size)(state, src, 0), ++ THCTensor_(size)(state, src, 1)); ++ } else if (inputDim == 3) { ++ THCTensor_(resize3d)( ++ state, ++ toResize, ++ THCTensor_(size)(state, src, 0), ++ THCTensor_(size)(state, src, 1), ++ THCTensor_(size)(state, src, 2)); ++ } else if (inputDim == 4) { ++ THCTensor_(resize4d)( ++ state, ++ toResize, ++ THCTensor_(size)(state, src, 0), ++ THCTensor_(size)(state, src, 1), ++ THCTensor_(size)(state, src, 2), ++ THCTensor_(size)(state, src, 3)); ++ } ++} ++ ++void THNN_(FeatureLPPooling_updateOutput)(THCState* state, ++ THCTensor* inputTH, ++ THCTensor* outputTH, ++ accreal power, ++ int width, ++ int stride, ++ bool batchMode) { ++ THCUNN_assertSameGPU(state, 2, inputTH, outputTH); ++ ++ int inputDim = THCTensor_(nDimension)(state, inputTH); ++ ++ if (batchMode) { ++ THArgCheck(inputDim >= 2 && inputDim <= 4, 2, ++ "input must be 2-4 dimensions for batch mode"); ++ } else { ++ THArgCheck(inputDim >= 1 && inputDim <= 3, 2, ++ "input must be 1-3 dimensions for non-batch mode"); ++ } ++ ++ THArgCheck(TensorUtils::canUse32BitIndexMath(state, inputTH), 2, ++ "input tensor must fit into 32-bit index math"); ++ ++ THCDeviceTensor::DataType, 4> input; ++ THCDeviceTensor::DataType, 4> output; ++ ++ input = THNN_(FeatureLPPooling_upcast)(state, inputTH, batchMode); ++ ++ // Make sure the feature dimension is properly sized ++ THArgCheck(input.getSize(1) >= width, 2, ++ "input: feature dimension must be >= width"); ++ ++ // Make sure that width and stride are within range ++ THArgCheck(width >= 2 && width <= 16, 5, ++ "width must be between 2 - 16"); ++ ++ THArgCheck(stride >= 1 && stride <= 4, 6, ++ "stride must be between 1 - 4"); ++ ++ THNN_(FeatureLPPooling_resizeForOutput)( ++ state, outputTH, inputTH, batchMode, width, stride); ++ ++ output = THNN_(FeatureLPPooling_upcast)(state, outputTH, batchMode); ++ ++ bool found = runFeatureLPPoolingUpdateOutput(state, ++ input, ++ output, ++ power, ++ width, ++ stride); ++ THAssert(found); ++} ++ ++void THNN_(FeatureLPPooling_updateGradInput)(THCState* state, ++ THCTensor* gradOutputTH, ++ THCTensor* inputTH, ++ THCTensor* outputTH, ++ THCTensor* gradInputTH, ++ accreal power, ++ int width, ++ int stride, ++ bool batchMode) { ++ THArgCheck(TensorUtils::canUse32BitIndexMath(state, gradOutputTH), 2, ++ "output gradient tensor must fit into 32-bit index math"); ++ THArgCheck(TensorUtils::canUse32BitIndexMath(state, inputTH), 3, ++ "input tensor must fit into 32-bit index math"); ++ THCUNN_assertSameGPU(state, 4, gradOutputTH, inputTH, outputTH, gradInputTH); ++ ++ int inputDim = THCTensor_(nDimension)(state, inputTH); ++ ++ if (batchMode) { ++ THArgCheck(inputDim >= 2 && inputDim <= 4, 2, ++ "input must be 2-4 dimensions for batch mode"); ++ } else { ++ THArgCheck(inputDim >= 1 && inputDim <= 3, 2, ++ "input must be 1-3 dimensions for non-batch mode"); ++ } ++ ++ THCDeviceTensor::DataType, 4> gradOutput; ++ THCDeviceTensor::DataType, 4> input; ++ THCDeviceTensor::DataType, 4> output; ++ THCDeviceTensor::DataType, 4> gradInput; ++ ++ input = THNN_(FeatureLPPooling_upcast)(state, inputTH, batchMode); ++ ++ // Make sure the feature dimension is properly sized ++ THArgCheck(input.getSize(1) >= width, 3, ++ "input: feature dimension must be >= width"); ++ ++ // Make sure that width and stride are within range ++ THArgCheck(width >= 2 && width <= 16, 7, ++ "width must be between 2 - 16"); ++ ++ THArgCheck(stride >= 1 && stride <= 4, 8, ++ "stride must be between 1 - 4"); ++ ++ gradOutput = THNN_(FeatureLPPooling_upcast)(state, gradOutputTH, batchMode); ++ output = THNN_(FeatureLPPooling_upcast)(state, outputTH, batchMode); ++ ++ for (int i = 0; i < 4; ++i) { ++ THAssertMsg(output.getSize(i) == gradOutput.getSize(i), ++ "output and gradOutput sizes do not match"); ++ } ++ ++ // Make sure that the input sizes produce the output sizes ++ THArgCheck(lpPoolingOutputSize(input.getSize(1), width, stride) == ++ output.getSize(1), 3, ++ "input and output sizes do not match with respect to " ++ "width and stride"); ++ ++ // Resize `gradInput` based on `input` ++ THNN_(FeatureLPPooling_resize)(state, gradInputTH, inputTH); ++ gradInput = THNN_(FeatureLPPooling_upcast)(state, gradInputTH, batchMode); ++ ++ bool found = runFeatureLPPoolingUpdateGradInput(state, ++ gradOutput, ++ input, ++ output, ++ gradInput, ++ power, ++ width, ++ stride); ++ THAssert(found); ++} ++ ++#endif +diff --git a/extra/cunn/lib/THCUNN/generic/SparseLinear.cu b/extra/cunn/lib/THCUNN/generic/SparseLinear.cu +index 07eda62..dc4c6d4 100644 +--- a/extra/cunn/lib/THCUNN/generic/SparseLinear.cu ++++ b/extra/cunn/lib/THCUNN/generic/SparseLinear.cu +@@ -156,7 +156,7 @@ void THNN_(SparseLinear_accGradParameters)( + THCTensor_(select)(state, sel, input, 1, 0); // rowInds + THCTensor_(select)(state, cols, input, 1, 1); // colInds + THCTensor_(cadd)(state, buf, sel, batchnum, cols); // colInds * buatchdim + rowInds +- THCTensor_(sort)(state, buf, inds, buf, 0, 0); // Indicies are now in ind ++ THCTensor_(sort)(state, buf, inds, buf, 0, 0); // Indices are now in ind + THCTensor_(indexSelect)(state, buf, input, 0, inds); + + THCTensor_(resize1d)(state, values, nnz); +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu +index 4a0563d..06bf1f8 100644 +--- a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu ++++ b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu +@@ -266,7 +266,7 @@ void THNN_(SpatialConvolutionLocal_updateGradInput)( + col2im( + THCState_getCurrentStream(state), + THCTensor_(data)(state, fgradInput_n), +- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, ++ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, + 1, 1, THCTensor_(data)(state, gradInput_n) + ); + +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu +index b4ae8e5..4db0406 100644 +--- a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu ++++ b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu +@@ -302,7 +302,7 @@ void THNN_(SpatialConvolutionMM_updateGradInput)( + col2im( + THCState_getCurrentStream(state), + THCTensor_(data)(state, gradColumns), +- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, ++ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, + 1, 1, THCTensor_(data)(state, gradInput_n) + ); + } +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu +index 1fc365f..68077ed 100644 +--- a/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu ++++ b/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu +@@ -388,7 +388,7 @@ void THNN_(SpatialDepthWiseConvolution_updateGradInput)( + col2im( + THCState_getCurrentStream(state), + THCTensor_(data)(state, gradColumns), +- 1, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, ++ 1, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, + 1, 1, THCTensor_(data)(state, gradInput_i) + ); + } +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu +index 01c97c9..ff764f6 100644 +--- a/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu ++++ b/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu +@@ -296,7 +296,7 @@ void THNN_(SpatialDilatedConvolution_updateGradInput)( + col2im( + THCState_getCurrentStream(state), + THCTensor_(data)(state, gradColumns), +- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, ++ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, + dilationH, dilationW, + THCTensor_(data)(state, gradInput_n) + ); +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu +index 76abb90..af9a473 100644 +--- a/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu ++++ b/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu +@@ -2,65 +2,6 @@ + #define THC_GENERIC_FILE "generic/SpatialFullConvolution.cu" + #else + +-static inline void THNN_(SpatialFullConvolution_shapeCheck)( +- THCState *state, +- THCTensor *input, THCTensor *gradOutput, +- THCTensor *weight, THCTensor *bias, +- int kH, int kW, int dH, int dW, int padH, int padW, +- int adjH, int adjW) { +- THArgCheck(kW > 0 && kH > 0, 9, +- "kernel size should be greater than zero, but got kH: %d kW: %d", kH, kW); +- THArgCheck(dW > 0 && dH > 0, 11, +- "stride should be greater than zero, but got dH: %d dW: %d", dH, dW); +- THArgCheck(adjW < dW && adjH < dH, 15, +- "output adjustment must be smaller than stride, but got adjH: %d adjW: %d dH: %d dW: %d", +- adjH, adjW, dH, dW); +- THArgCheck(THCTensor_(isContiguous)(state, weight), 4, +- "weight tensor has to be contiguous"); +- THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, +- "bias tensor has to be contiguous"); +- THCUNN_argCheck(state, weight->nDimension == 2 || weight->nDimension == 4, 5, weight, +- "2D or 4D weight tensor expected, but got: %s"); +- +- if (bias != NULL) { +- THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); +- } +- +- int ndim = input->nDimension; +- int dimf = 0; +- int dimh = 1; +- int dimw = 2; +- +- if (ndim == 4) { +- dimf++; +- dimh++; +- dimw++; +- } +- +- THCUNN_argCheck(state, ndim == 3 || ndim == 4, 2, input, +- "3D or 4D input tensor expected but got: %s"); +- +- long nInputPlane = weight->size[0]; +- long inputHeight = input->size[dimh]; +- long inputWidth = input->size[dimw]; +- long nOutputPlane = weight->size[1]; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- +- if (outputWidth < 1 || outputHeight < 1) +- THError("Given input size: (%d x %d x %d). " +- "Calculated output size: (%d x %d x %d). Output size is too small", +- nInputPlane,inputHeight,inputWidth,nOutputPlane,outputHeight,outputWidth); +- +- THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); +- +- if (gradOutput != NULL) { +- THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); +- THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); +- THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); +- } +-} +- + void THNN_(SpatialFullConvolution_updateOutput)( + THCState *state, + THCTensor *input, +@@ -74,133 +15,9 @@ void THNN_(SpatialFullConvolution_updateOutput)( + int padW, int padH, + int adjW, int adjH) + { +- +- int nInputPlane = THCTensor_(size)(state, weight, 0); +- int nOutputPlane = THCTensor_(size)(state, weight, 1); +- +- THCUNN_assertSameGPU(state, 6, input, output, weight, +- bias, columns, ones); +- THNN_(SpatialFullConvolution_shapeCheck) +- (state, input, NULL, weight, bias, kH, kW, dH, dW, padH, padW, adjH, adjW); +- +- input = THCTensor_(newContiguous)(state, input); +- weight = THCTensor_(newContiguous)(state, weight); +- bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; +- +- int batch = 1; +- if (input->nDimension == 3) { +- // Force batch +- batch = 0; +- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); +- } +- +- long inputWidth = input->size[3]; +- long inputHeight = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Resize output +- THCTensor_(resize4d)(state, output, batchSize, nOutputPlane, outputHeight, outputWidth); +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); +- +- // Define a buffer of ones, for bias accumulation +- // Note: this buffer can be shared with other modules, it only ever gets increased, +- // and always contains ones. +- if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { +- // Resize plane and fill with ones... +- THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); +- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); +- } +- +- // Helpers +- THCTensor *input_n = THCTensor_(new)(state); +- THCTensor *output_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per output: +- THCTensor_(select)(state, input_n, input, 0, elt); +- THCTensor_(select)(state, output_n, output, 0, elt); +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m = weight->size[1] * weight->size[2] * weight->size[3]; +- long n = columns->size[1]; +- long k = weight->size[0]; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 'n', 't', +- n, m, k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, input_n), n, +- THCTensor_(data)(state, weight), m, +- ScalarConvert::to(0), +- THCTensor_(data)(state, columns), n +- ); +- +- // Unpack columns back into input: +- col2im( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, columns), +- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, +- 1, 1, THCTensor_(data)(state, output_n) +- ); +- +- // Do Bias after: +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m_ = nOutputPlane; +- long n_ = outputHeight * outputWidth; +- long k_ = 1; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- if (bias) { +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 't', 'n', +- n_, m_, k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, ones), k_, +- THCTensor_(data)(state, bias), k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, output_n), n_ +- ); +- } +- } +- +- // Free +- THCTensor_(free)(state, input_n); +- THCTensor_(free)(state, output_n); +- +- // Resize output +- if (batch == 0) { +- THCTensor_(resize3d)(state, output, nOutputPlane, outputHeight, outputWidth); +- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, weight); +- if (bias) THCTensor_(free)(state, bias); +- ++ THNN_(SpatialFullDilatedConvolution_updateOutput)( ++ state, input, output, weight, bias, columns, ones, ++ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH); + } + + void THNN_(SpatialFullConvolution_updateGradInput)( +@@ -215,98 +32,9 @@ void THNN_(SpatialFullConvolution_updateGradInput)( + int padW, int padH, + int adjW, int adjH) + { +- int nInputPlane = THCTensor_(size)(state, weight, 0); +- int nOutputPlane = THCTensor_(size)(state, weight, 1); +- +- THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, +- gradColumns, gradInput); +- THNN_(SpatialFullConvolution_shapeCheck) +- (state, input, gradOutput, weight, NULL, kH, kW, dH, dW, padH, padW, adjH, adjW); +- +- input = THCTensor_(newContiguous)(state, input); +- gradOutput = THCTensor_(newContiguous)(state, gradOutput); +- weight = THCTensor_(newContiguous)(state, weight); +- int batch = 1; +- if (input->nDimension == 3) { +- // Force batch +- batch = 0; +- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); +- THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); +- } +- +- long inputWidth = input->size[3]; +- long inputHeight = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Resize output +- THCTensor_(resize4d)(state, gradInput, batchSize, nInputPlane, inputHeight, inputWidth); +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH, inputHeight*inputWidth); +- +- // Helpers +- THCTensor *gradInput_n = THCTensor_(new)(state); +- THCTensor *gradOutput_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per sample: +- THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); +- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); +- +- // Extract columns: +- im2col( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, gradOutput_n), +- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, +- 1, 1, THCTensor_(data)(state, gradColumns) +- ); +- +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m = weight->size[0]; +- long n = gradColumns->size[1]; +- long k = weight->size[1] * weight->size[2] * weight->size[3]; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 'n', 'n', +- n, m, k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradColumns), n, +- THCTensor_(data)(state, weight), k, +- ScalarConvert::to(0), +- THCTensor_(data)(state, gradInput_n), n +- ); +- } +- +- +- // Free +- THCTensor_(free)(state, gradInput_n); +- THCTensor_(free)(state, gradOutput_n); +- +- // Resize output +- if (batch == 0) { +- THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); +- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); +- THCTensor_(resize3d)(state, gradInput, nInputPlane, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, gradOutput); +- THCTensor_(free)(state, weight); ++ THNN_(SpatialFullDilatedConvolution_updateGradInput)( ++ state, input, gradOutput, gradInput, weight, gradColumns, ++ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH); + } + + +@@ -324,139 +52,10 @@ void THNN_(SpatialFullConvolution_accGradParameters)( + int adjW, int adjH, + accreal scale_) + { +- real scale = ScalarConvert::to(scale_); +- int nInputPlane = THCTensor_(size)(state, gradWeight, 0); +- int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); +- +- THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, +- gradBias, columns, ones); +- THNN_(SpatialFullConvolution_shapeCheck) +- (state, input, gradOutput, gradWeight, gradBias, kH, kW, dH, dW, padH, padW, adjH, adjW); +- +- THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); +- if (gradBias) +- THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); +- input = THCTensor_(newContiguous)(state, input); +- gradOutput = THCTensor_(newContiguous)(state, gradOutput); +- int batch = 1; +- if (input->nDimension == 3) { +- // Force batch +- batch = 0; +- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); +- THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); +- } +- +- long inputWidth = input->size[3]; +- long inputHeight = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Define a buffer of ones, for bias accumulation +- if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { +- // Resize plane and fill with ones... +- THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); +- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); +- } +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); +- +- // Helpers +- THCTensor *input_n = THCTensor_(new)(state); +- THCTensor *gradOutput_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per output: +- THCTensor_(select)(state, input_n, input, 0, elt); +- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); +- +- // Extract columns: +- im2col( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, gradOutput_n), +- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, +- 1, 1, THCTensor_(data)(state, columns) +- ); +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long n = columns->size[0]; // nOutputPlane * kh * kw +- long m = input_n->size[0]; // nInputPlane +- long k = columns->size[1]; // inputHeight * inputWidth +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 't', 'n', +- n, m, k, +- scale, +- THCTensor_(data)(state, columns), k, +- THCTensor_(data)(state, input_n), k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradWeight), n +- ); +- +- // Do Bias: +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m_ = nOutputPlane; +- long k_ = outputHeight * outputWidth; +- +- // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) +- if (gradBias) { +- #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemv( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemv( +- #endif +- state, +- 't', +- k_, m_, +- scale, +- THCTensor_(data)(state, gradOutput_n), k_, +- THCTensor_(data)(state, ones), 1, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradBias), 1 +- ); +- #endif +- #ifdef THC_REAL_IS_HALF +- THCudaBlas_Hgemm( +- state, +- 't', 'n', +- m_, 1, k_, +- scale, +- THCTensor_(data)(state, gradOutput_n), k_, +- THCTensor_(data)(state, ones), k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradBias), m_ +- ); +- #endif +- } +- } +- +- // Free +- THCTensor_(free)(state, input_n); +- THCTensor_(free)(state, gradOutput_n); +- +- // Resize +- if (batch == 0) { +- THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); +- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, gradOutput); ++ THNN_(SpatialFullDilatedConvolution_accGradParameters)( ++ state, input, gradOutput, gradWeight, gradBias, ++ columns, ones, ++ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH, scale_); + } + + #endif +diff --git a/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu +new file mode 100644 +index 0000000..aafd07e +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu +@@ -0,0 +1,469 @@ ++#ifndef THC_GENERIC_FILE ++#define THC_GENERIC_FILE "generic/SpatialFullDilatedConvolution.cu" ++#else ++ ++static inline void THNN_(SpatialFullDilatedConvolution_shapeCheck)( ++ THCState *state, ++ THCTensor *input, THCTensor *gradOutput, ++ THCTensor *weight, THCTensor *bias, ++ int kH, int kW, int dH, int dW, int padH, int padW, ++ int dilationH, int dilationW, ++ int adjH, int adjW) { ++ THArgCheck(kW > 0 && kH > 0, 9, ++ "kernel size should be greater than zero, but got kH: %d kW: %d", kH, kW); ++ THArgCheck(dW > 0 && dH > 0, 11, ++ "stride should be greater than zero, but got dH: %d dW: %d", dH, dW); ++ THArgCheck(dilationW > 0 && dilationH > 0, 15, ++ "dilation should be greater than zero, but got dilationH: %d, dilationW: %d", ++ dilationH, dilationW); ++ THArgCheck((adjW < dW || adjW < dilationW) && (adjH < dH || adjH < dilationH), 15, ++ "output padding must be smaller than either stride or dilation, but got adjH: %d adjW: %d dH: %d dW: %d dilationH: %d dilationW: %d", ++ adjH, adjW, dH, dW, dilationH, dilationW); ++ THArgCheck(THCTensor_(isContiguous)(state, weight), 4, ++ "weight tensor has to be contiguous"); ++ THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, ++ "bias tensor has to be contiguous"); ++ THCUNN_argCheck(state, weight->nDimension == 2 || weight->nDimension == 4, 5, weight, ++ "2D or 4D weight tensor expected, but got: %s"); ++ ++ if (bias != NULL) { ++ THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); ++ } ++ ++ int ndim = input->nDimension; ++ int dimf = 0; ++ int dimh = 1; ++ int dimw = 2; ++ ++ if (ndim == 4) { ++ dimf++; ++ dimh++; ++ dimw++; ++ } ++ ++ THCUNN_argCheck(state, ndim == 3 || ndim == 4, 2, input, ++ "3D or 4D input tensor expected but got: %s"); ++ ++ long nInputPlane = weight->size[0]; ++ long inputHeight = input->size[dimh]; ++ long inputWidth = input->size[dimw]; ++ long nOutputPlane = weight->size[1]; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ if (outputWidth < 1 || outputHeight < 1) ++ THError("Given input size: (%d x %d x %d). " ++ "Calculated output size: (%d x %d x %d). Output size is too small", ++ nInputPlane,inputHeight,inputWidth,nOutputPlane,outputHeight,outputWidth); ++ ++ THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); ++ ++ if (gradOutput != NULL) { ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); ++ } ++} ++ ++void THNN_(SpatialFullDilatedConvolution_updateOutput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *output, ++ THCTensor *weight, ++ THCTensor *bias, ++ THCTensor *columns, ++ THCTensor *ones, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH) ++{ ++ ++ int nInputPlane = THCTensor_(size)(state, weight, 0); ++ int nOutputPlane = THCTensor_(size)(state, weight, 1); ++ ++ THCUNN_assertSameGPU(state, 6, input, output, weight, ++ bias, columns, ones); ++ THNN_(SpatialFullDilatedConvolution_shapeCheck) ++ (state, input, NULL, weight, bias, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); ++ ++ input = THCTensor_(newContiguous)(state, input); ++ weight = THCTensor_(newContiguous)(state, weight); ++ bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; ++ ++ int batch = 1; ++ if (input->nDimension == 3) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); ++ } ++ ++ long inputWidth = input->size[3]; ++ long inputHeight = input->size[2]; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Resize output ++ THCTensor_(resize4d)(state, output, batchSize, nOutputPlane, outputHeight, outputWidth); ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); ++ ++ // Define a buffer of ones, for bias accumulation ++ // Note: this buffer can be shared with other modules, it only ever gets increased, ++ // and always contains ones. ++ if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { ++ // Resize plane and fill with ones... ++ THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); ++ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); ++ } ++ ++ // Helpers ++ THCTensor *input_n = THCTensor_(new)(state); ++ THCTensor *output_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per output: ++ THCTensor_(select)(state, input_n, input, 0, elt); ++ THCTensor_(select)(state, output_n, output, 0, elt); ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m = weight->size[1] * weight->size[2] * weight->size[3]; ++ long n = columns->size[1]; ++ long k = weight->size[0]; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 'n', 't', ++ n, m, k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, input_n), n, ++ THCTensor_(data)(state, weight), m, ++ ScalarConvert::to(0), ++ THCTensor_(data)(state, columns), n ++ ); ++ ++ // Unpack columns back into input: ++ col2im( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, columns), ++ nOutputPlane, outputHeight, outputWidth, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, ++ dilationH, dilationW, THCTensor_(data)(state, output_n) ++ ); ++ ++ // Do Bias after: ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m_ = nOutputPlane; ++ long n_ = outputHeight * outputWidth; ++ long k_ = 1; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ if (bias) { ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 't', 'n', ++ n_, m_, k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, ones), k_, ++ THCTensor_(data)(state, bias), k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, output_n), n_ ++ ); ++ } ++ } ++ ++ // Free ++ THCTensor_(free)(state, input_n); ++ THCTensor_(free)(state, output_n); ++ ++ // Resize output ++ if (batch == 0) { ++ THCTensor_(resize3d)(state, output, nOutputPlane, outputHeight, outputWidth); ++ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, weight); ++ if (bias) THCTensor_(free)(state, bias); ++ ++} ++ ++void THNN_(SpatialFullDilatedConvolution_updateGradInput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradInput, ++ THCTensor *weight, ++ THCTensor *gradColumns, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH) ++{ ++ int nInputPlane = THCTensor_(size)(state, weight, 0); ++ int nOutputPlane = THCTensor_(size)(state, weight, 1); ++ ++ THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, ++ gradColumns, gradInput); ++ THNN_(SpatialFullDilatedConvolution_shapeCheck) ++ (state, input, gradOutput, weight, NULL, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); ++ ++ input = THCTensor_(newContiguous)(state, input); ++ gradOutput = THCTensor_(newContiguous)(state, gradOutput); ++ weight = THCTensor_(newContiguous)(state, weight); ++ int batch = 1; ++ if (input->nDimension == 3) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); ++ THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); ++ } ++ ++ long inputWidth = input->size[3]; ++ long inputHeight = input->size[2]; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Resize output ++ THCTensor_(resize4d)(state, gradInput, batchSize, nInputPlane, inputHeight, inputWidth); ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH, inputHeight*inputWidth); ++ ++ // Helpers ++ THCTensor *gradInput_n = THCTensor_(new)(state); ++ THCTensor *gradOutput_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per sample: ++ THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); ++ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); ++ ++ // Extract columns: ++ im2col( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, gradOutput_n), ++ nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, ++ dilationH, dilationW, THCTensor_(data)(state, gradColumns) ++ ); ++ ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m = weight->size[0]; ++ long n = gradColumns->size[1]; ++ long k = weight->size[1] * weight->size[2] * weight->size[3]; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 'n', 'n', ++ n, m, k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradColumns), n, ++ THCTensor_(data)(state, weight), k, ++ ScalarConvert::to(0), ++ THCTensor_(data)(state, gradInput_n), n ++ ); ++ } ++ ++ ++ // Free ++ THCTensor_(free)(state, gradInput_n); ++ THCTensor_(free)(state, gradOutput_n); ++ ++ // Resize output ++ if (batch == 0) { ++ THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); ++ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); ++ THCTensor_(resize3d)(state, gradInput, nInputPlane, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, gradOutput); ++ THCTensor_(free)(state, weight); ++} ++ ++ ++void THNN_(SpatialFullDilatedConvolution_accGradParameters)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradWeight, ++ THCTensor *gradBias, ++ THCTensor *columns, ++ THCTensor *ones, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH, ++ accreal scale_) ++{ ++ real scale = ScalarConvert::to(scale_); ++ int nInputPlane = THCTensor_(size)(state, gradWeight, 0); ++ int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); ++ ++ THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, ++ gradBias, columns, ones); ++ THNN_(SpatialFullDilatedConvolution_shapeCheck) ++ (state, input, gradOutput, gradWeight, gradBias, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); ++ ++ THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); ++ if (gradBias) ++ THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); ++ input = THCTensor_(newContiguous)(state, input); ++ gradOutput = THCTensor_(newContiguous)(state, gradOutput); ++ int batch = 1; ++ if (input->nDimension == 3) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); ++ THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); ++ } ++ ++ long inputWidth = input->size[3]; ++ long inputHeight = input->size[2]; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Define a buffer of ones, for bias accumulation ++ if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { ++ // Resize plane and fill with ones... ++ THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); ++ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); ++ } ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); ++ ++ // Helpers ++ THCTensor *input_n = THCTensor_(new)(state); ++ THCTensor *gradOutput_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per output: ++ THCTensor_(select)(state, input_n, input, 0, elt); ++ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); ++ ++ // Extract columns: ++ im2col( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, gradOutput_n), ++ nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, ++ dilationH, dilationW, THCTensor_(data)(state, columns) ++ ); ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long n = columns->size[0]; // nOutputPlane * kh * kw ++ long m = input_n->size[0]; // nInputPlane ++ long k = columns->size[1]; // inputHeight * inputWidth ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 't', 'n', ++ n, m, k, ++ scale, ++ THCTensor_(data)(state, columns), k, ++ THCTensor_(data)(state, input_n), k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradWeight), n ++ ); ++ ++ // Do Bias: ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m_ = nOutputPlane; ++ long k_ = outputHeight * outputWidth; ++ ++ // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) ++ if (gradBias) { ++ #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemv( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemv( ++ #endif ++ state, ++ 't', ++ k_, m_, ++ scale, ++ THCTensor_(data)(state, gradOutput_n), k_, ++ THCTensor_(data)(state, ones), 1, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradBias), 1 ++ ); ++ #endif ++ #ifdef THC_REAL_IS_HALF ++ THCudaBlas_Hgemm( ++ state, ++ 't', 'n', ++ m_, 1, k_, ++ scale, ++ THCTensor_(data)(state, gradOutput_n), k_, ++ THCTensor_(data)(state, ones), k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradBias), m_ ++ ); ++ #endif ++ } ++ } ++ ++ // Free ++ THCTensor_(free)(state, input_n); ++ THCTensor_(free)(state, gradOutput_n); ++ ++ // Resize ++ if (batch == 0) { ++ THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); ++ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, gradOutput); ++} ++ ++#endif +diff --git a/extra/cunn/lib/THCUNN/generic/THCUNN.h b/extra/cunn/lib/THCUNN/generic/THCUNN.h +index e770dff..df186b1 100644 +--- a/extra/cunn/lib/THCUNN/generic/THCUNN.h ++++ b/extra/cunn/lib/THCUNN/generic/THCUNN.h +@@ -123,6 +123,26 @@ TH_API void THNN_(ELU_updateGradInput)( + accreal alpha, + bool inplace); + ++TH_API void THNN_(FeatureLPPooling_updateOutput)( ++ THCState* state, ++ THCTensor* inputTH, ++ THCTensor* outputTH, ++ accreal power, ++ int width, ++ int stride, ++ bool batchMode); ++ ++TH_API void THNN_(FeatureLPPooling_updateGradInput)( ++ THCState* state, ++ THCTensor* gradOutputTH, ++ THCTensor* inputTH, ++ THCTensor* outputTH, ++ THCTensor* gradInputTH, ++ accreal power, ++ int width, ++ int stride, ++ bool batchMode); ++ + TH_API void THNN_(HardTanh_updateOutput)( + THCState *state, + THCTensor *input, +@@ -743,6 +763,48 @@ TH_API void THNN_(SpatialDilatedConvolution_accGradParameters)( + int dilationW, int dilationH, + accreal scale); + ++TH_API void THNN_(SpatialFullDilatedConvolution_updateOutput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *output, ++ THCTensor *weight, ++ THCTensor *bias, // [OPTIONAL] ++ THCTensor *columns, ++ THCTensor *ones, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH); ++ ++TH_API void THNN_(SpatialFullDilatedConvolution_updateGradInput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradInput, ++ THCTensor *weight, ++ THCTensor *gradColumns, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH); ++ ++TH_API void THNN_(SpatialFullDilatedConvolution_accGradParameters)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradWeight, ++ THCTensor *gradBias, // [OPTIONAL] ++ THCTensor *columns, ++ THCTensor *ones, ++ int kW, int kH, ++ int dW, int dH, ++ int padW, int padH, ++ int dilationW, int dilationH, ++ int adjW, int adjH, ++ accreal scale); ++ + TH_API void THNN_(SpatialDilatedMaxPooling_updateOutput)( + THCState *state, + THCTensor *input, +@@ -1177,7 +1239,10 @@ TH_API void THNN_(VolumetricAveragePooling_updateOutput)( + THCTensor *input, + THCTensor *output, + int kT, int kW, int kH, +- int dT, int dW, int dH); ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ bool ceil_mode, ++ bool count_include_pad); + + TH_API void THNN_(VolumetricAveragePooling_updateGradInput)( + THCState *state, +@@ -1185,7 +1250,10 @@ TH_API void THNN_(VolumetricAveragePooling_updateGradInput)( + THCTensor *gradOutput, + THCTensor *gradInput, + int kT, int kW, int kH, +- int dT, int dW, int dH); ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ bool ceil_mode, ++ bool count_include_pad); + + TH_API void THNN_(VolumetricConvolution_updateOutput)( + THCState *state, +@@ -1259,6 +1327,46 @@ TH_API void THNN_(VolumetricDilatedConvolution_accGradParameters)( + int dilationT, int dilationW, int dilationH, + accreal scale); + ++TH_API void THNN_(VolumetricFullDilatedConvolution_updateOutput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *output, ++ THCTensor *weight, ++ THCTensor *bias, // [OPTIONAL] ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH); ++ ++TH_API void THNN_(VolumetricFullDilatedConvolution_updateGradInput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradInput, ++ THCTensor *weight, ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH); ++ ++TH_API void THNN_(VolumetricFullDilatedConvolution_accGradParameters)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradWeight, ++ THCTensor *gradBias, // [OPTIONAL] ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH, ++ accreal scale); ++ + TH_API void THNN_(VolumetricDilatedMaxPooling_updateOutput)( + THCState *state, + THCTensor *input, +diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu b/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu +index 7a6c595..828a0f6 100644 +--- a/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu ++++ b/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu +@@ -6,12 +6,11 @@ static inline void THNN_(VolumetricAveragePooling_shapeCheck)( + THCState *state, + THCTensor *input, + THCTensor *gradOutput, +- int kT, +- int kW, +- int kH, +- int dT, +- int dW, +- int dH) { ++ int kT, int kW, int kH, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ bool ceil_mode) ++{ + int inputSlices; + int inputTime; + int inputHeight; +@@ -66,11 +65,42 @@ static inline void THNN_(VolumetricAveragePooling_shapeCheck)( + THArgCheck(false, 2, "4D or 5D tensor expected, but got: %d", input->nDimension); + } + +- int outputTime = (inputTime - kT) / dT + 1; +- int outputHeight = (inputHeight - kH) / dH + 1; +- int outputWidth = (inputWidth - kW) / dW + 1; ++ // The second argument is the index of padH. ++ THArgCheck(kT/2 >= padT && kW/2 >= padW && kH/2 >= padH, 11, ++ "pad should not be greater than half of kernel size, but got " ++ "padT = %d, padW = %d, padH = %d, kT = %d, kW = %d, kH = %d", ++ padT, padW, padH, kT, kW, kH); ++ ++ int outputTime; ++ int outputHeight; ++ int outputWidth; ++ ++ if (ceil_mode) ++ { ++ outputTime = ceil(float(inputTime - kT + 2*padT) / float(dT)) + 1; ++ outputHeight = ceil(float(inputHeight - kH + 2*padH) / float(dH)) + 1; ++ outputWidth = ceil(float(inputWidth - kW + 2*padW) / float(dW)) + 1; ++ } ++ else ++ { ++ outputTime = floor(float(inputTime - kT + 2*padT) / float(dT)) + 1; ++ outputHeight = floor(float(inputHeight - kH + 2*padH) / float(dH)) + 1; ++ outputWidth = floor(float(inputWidth - kW + 2*padW) / float(dW)) + 1; ++ } ++ if (padT || padW || padH) ++ { ++ // ensure that the last pooling starts inside the image ++ // needed to avoid problems in ceil mode ++ if ((outputTime - 1)*dT >= inputTime + padT) ++ --outputTime; ++ if ((outputHeight - 1)*dH >= inputHeight + padH) ++ --outputHeight; ++ if ((outputWidth - 1)*dW >= inputWidth + padW) ++ --outputWidth; ++ } + +- if (gradOutput != NULL) { ++ if (gradOutput != NULL) ++ { + THCUNN_check_dim_size(state, gradOutput, ndim, dimN, inputSlices); + THCUNN_check_dim_size(state, gradOutput, ndim, dimt, outputTime); + THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); +@@ -83,7 +113,10 @@ void THNN_(VolumetricAveragePooling_updateOutput)( + THCTensor *input, + THCTensor *output, + int kT, int kW, int kH, +- int dT, int dW, int dH) ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ bool ceil_mode, ++ bool count_include_pad) + { + int batchSize; + int inputSlices; +@@ -103,7 +136,8 @@ void THNN_(VolumetricAveragePooling_updateOutput)( + } + + THNN_(VolumetricAveragePooling_shapeCheck) +- (state, input, NULL, kT, kW, kH, dT, dW, dH); ++ (state, input, NULL, kT, kW, kH, dT, dW, dH, ++ padT, padW, padH, ceil_mode); + + if (THCTensor_(nDimension)(state, input) == 4) + { +@@ -124,9 +158,33 @@ void THNN_(VolumetricAveragePooling_updateOutput)( + inputWidth = THCTensor_(size)(state, input, 4); + } + +- int outputTime = (inputTime - kT) / dT + 1; +- int outputHeight = (inputHeight - kH) / dH + 1; +- int outputWidth = (inputWidth - kW) / dW + 1; ++ int outputTime; ++ int outputHeight; ++ int outputWidth; ++ ++ if (ceil_mode) ++ { ++ outputTime = ceil(float(inputTime - kT + 2*padT) / float(dT)) + 1; ++ outputHeight = ceil(float(inputHeight - kH + 2*padH) / float(dH)) + 1; ++ outputWidth = ceil(float(inputWidth - kW + 2*padW) / float(dW)) + 1; ++ } ++ else ++ { ++ outputTime = floor(float(inputTime - kT + 2*padT) / float(dT)) + 1; ++ outputHeight = floor(float(inputHeight - kH + 2*padH) / float(dH)) + 1; ++ outputWidth = floor(float(inputWidth - kW + 2*padW) / float(dW)) + 1; ++ } ++ if (padT || padH || padW) ++ { ++ // ensure that the last pooling starts inside the image ++ // needed to avoid problems in ceil mode ++ if ((outputTime - 1)*dT >= inputTime + padT) ++ --outputTime; ++ if ((outputHeight - 1)*dH >= inputHeight + padH) ++ --outputHeight; ++ if ((outputWidth - 1)*dW >= inputWidth + padW) ++ --outputWidth; ++ } + + if (input->nDimension == 4) /* 4D */ + { +@@ -164,7 +222,6 @@ void THNN_(VolumetricAveragePooling_updateOutput)( + THCCeilDiv(outputHeight, static_cast(block.y)), + totalZ > 65535 ? 65535 : totalZ); + +- accreal normFactor = ScalarConvert::to(1) / static_cast(kT * kH * kW); + switch (kW) + { + LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(1); +@@ -180,7 +237,8 @@ void THNN_(VolumetricAveragePooling_updateOutput)( + cudaOutput, + kT, kH, kW, + dT, dH, dW, +- normFactor, ++ padT, padH, padW, ++ count_include_pad, + offsetZ + ); + break; +@@ -198,11 +256,14 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( + THCTensor *gradOutput, + THCTensor *gradInput, + int kT, int kW, int kH, +- int dT, int dW, int dH) ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ bool ceil_mode, ++ bool count_include_pad) + { +- + THNN_(VolumetricAveragePooling_shapeCheck) +- (state, input, gradOutput, kT, kW, kH, dT, dW, dH); ++ (state, input, gradOutput, kT, kW, kH, dT, dW, dH, ++ padT, padW, padH, ceil_mode); + bool kernelsOverlap = (dT < kT) || (dH < kH) || (dW < kW); + + // Resize and initialize result tensor. +@@ -266,7 +327,8 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( + + // Optimizing for stride 1 is probably only of limited value, but this + // specialization yields 3x speedup over the atomicAdd implementation. +- if (dT == 1 && dH == 1 && dW == 1) ++ // Padding must be 0, otherwise, pool size may change. ++ if (dT == 1 && dH == 1 && dW == 1 && padT == 0 && padH == 0 && padW == 0) + { + int totalZ = inputTime * inputSlices * batchSize; + int offsetZ = 0; +@@ -286,20 +348,21 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( + int totalZ = outputTime * inputSlices * batchSize; + int offsetZ = 0; + while (totalZ > 0) { +- + dim3 grid(THCCeilDiv(outputWidth, static_cast(block.x)), + THCCeilDiv(outputHeight, static_cast(block.y)), + totalZ > 65535 ? 65535 : totalZ); + if (kernelsOverlap) +- { +- cuda_VolumetricAveragePooling_updateGradInput_atomicAdd<<>>( +- cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, offsetZ); +- } ++ { ++ cuda_VolumetricAveragePooling_updateGradInput_atomicAdd<<>>( ++ cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, ++ padT, padH, padW, count_include_pad, offsetZ); ++ } + else +- { +- cuda_VolumetricAveragePooling_updateGradInput<<>>( +- cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, offsetZ); +- } ++ { ++ cuda_VolumetricAveragePooling_updateGradInput<<>>( ++ cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, ++ padT, padH, padW, count_include_pad, offsetZ); ++ } + THCudaCheck(cudaGetLastError()); + totalZ -= 65535; + offsetZ += 65535; +diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu +index 45bb0f6..1203733 100644 +--- a/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu ++++ b/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu +@@ -310,6 +310,7 @@ void THNN_(VolumetricDilatedConvolution_updateGradInput)( + THCState_getCurrentStream(state), + THCTensor_(data)(state, gradColumns), + nInputPlane, inputDepth, inputHeight, inputWidth, ++ outputDepth, outputHeight, outputWidth, + kT, kH, kW, padT, padH, padW, dT, dH, dW, + dilationT, dilationH, dilationW, + THCTensor_(data)(state, gradInput_n) +diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu +index 9dd266c..9837a2d 100644 +--- a/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu ++++ b/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu +@@ -2,75 +2,6 @@ + #define THC_GENERIC_FILE "generic/VolumetricFullConvolution.cu" + #else + +-static inline void THNN_(VolumetricFullConvolution_shapeCheck)( +- THCState *state, +- THCTensor *input, +- THCTensor *gradOutput, +- THCTensor *weight, +- THCTensor *bias, +- int dT, int dW, int dH, +- int padT, int padW, int padH, +- int adjT, int adjW, int adjH) { +- THCUNN_argCheck(state, input->nDimension == 4 || input->nDimension == 5, 2, input, +- "4D or 5D (batch mode) tensor expected for input, but got: %s"); +- // number of input & output planes and kernel size is indirectly defined by the weight tensor +- THCUNN_argCheck(state, weight->nDimension == 5, 4, weight, +- "5D (nOutputPlane x nInputPlane x kT x kH x kW) tensor " +- "expected for weight, but got: %s"); +- THArgCheck(THCTensor_(isContiguous)(state, weight), 4, +- "weight tensor has to be contiguous"); +- THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, +- "bias tensor has to be contiguous"); +- THArgCheck(dT > 0 && dW > 0 && dH > 0, 8, +- "stride should be greater than zero, but got dT: %d dH: %d dW: %d", dT, dH, dW); +- THArgCheck(adjT < dT && adjW < dW && adjH < dH, 14, +- "output adjustment must be smaller than stride, but got " +- "adjT: %d adjH: %d adjW: %d dT: %d dH: %d dW: %d", +- adjT, adjH, adjW, dT, dH, dW); +- +- int ndim = input->nDimension; +- int nInputPlane = THCTensor_(size)(state, weight, 0); +- int nOutputPlane = THCTensor_(size)(state, weight, 1); +- const int kT = (int)weight->size[2]; +- const int kH = (int)weight->size[3]; +- const int kW = (int)weight->size[4]; +- +- if (bias != NULL) { +- THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); +- } +- +- int dimf = 0; +- int dimd = 1; +- int dimh = 2; +- int dimw = 3; +- +- if (ndim == 5) { +- dimf++; +- dimd++; +- dimh++; +- dimw++; +- } +- +- long inputWidth = input->size[dimw]; +- long inputHeight = input->size[dimh]; +- long inputDepth = input->size[dimd]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; +- +- if (outputDepth < 1 || outputWidth < 1 || outputHeight < 1) +- THError("Given input size: (%dx%dx%dx%d). Calculated output size: (%dx%dx%dx%d). Output size is too small", +- nInputPlane,inputDepth,inputHeight,inputWidth,nOutputPlane,outputDepth,outputHeight,outputWidth); +- +- THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); +- if (gradOutput != NULL) { +- THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); +- THCUNN_check_dim_size(state, gradOutput, ndim, dimd, outputDepth); +- THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); +- THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); +- } +-} +- + void THNN_(VolumetricFullConvolution_updateOutput)( + THCState *state, + THCTensor *input, +@@ -83,144 +14,9 @@ void THNN_(VolumetricFullConvolution_updateOutput)( + int padT, int padW, int padH, + int adjT, int adjW, int adjH) + { +- +- THCTensor *columns = finput; +- THCTensor *ones = fgradInput; +- +- int nInputPlane = THCTensor_(size)(state, weight, 0); +- int nOutputPlane = THCTensor_(size)(state, weight, 1); +- const int kT = (int)weight->size[2]; +- const int kH = (int)weight->size[3]; +- const int kW = (int)weight->size[4]; +- +- THCUNN_assertSameGPU(state, 6, input, output, weight, +- bias, columns, ones); +- THNN_(VolumetricFullConvolution_shapeCheck)( +- state, input, NULL, weight, bias, +- dT, dW, dH, padT, padW, padH, +- adjT, adjW, adjH); +- +- input = THCTensor_(newContiguous)(state, input); +- weight = THCTensor_(newContiguous)(state, weight); +- bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; +- +- int batch = 1; +- if (input->nDimension == 4) { +- // Force batch +- batch = 0; +- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); +- } +- +- long inputWidth = input->size[4]; +- long inputHeight = input->size[3]; +- long inputDepth = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Resize output +- THCTensor_(resize5d)(state, output, batchSize, nOutputPlane, outputDepth, outputHeight, outputWidth); +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); +- +- // Define a buffer of ones, for bias accumulation +- // Note: this buffer can be shared with other modules, it only ever gets increased, +- // and always contains ones. +- if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { +- // Resize plane and fill with ones... +- THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); +- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); +- } +- +- // Helpers +- THCTensor *input_n = THCTensor_(new)(state); +- THCTensor *output_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per output: +- THCTensor_(select)(state, input_n, input, 0, elt); +- THCTensor_(select)(state, output_n, output, 0, elt); +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; +- long n = columns->size[1]; +- long k = weight->size[0]; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 'n', 't', +- n, m, k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, input_n), n, +- THCTensor_(data)(state, weight), m, +- ScalarConvert::to(0), +- THCTensor_(data)(state, columns), n +- ); +- +- // Unpack columns back into input: +- col2vol( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, columns), +- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, +- 1,1,1, +- THCTensor_(data)(state, output_n) +- ); +- +- // Do Bias after: +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m_ = nOutputPlane; +- long n_ = outputDepth * outputHeight * outputWidth; +- long k_ = 1; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- if (bias) { +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 't', 'n', +- n_, m_, k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, ones), k_, +- THCTensor_(data)(state, bias), k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, output_n), n_ +- ); +- } +- } +- +- // Free +- THCTensor_(free)(state, input_n); +- THCTensor_(free)(state, output_n); +- +- // Resize output +- if (batch == 0) { +- THCTensor_(resize4d)(state, output, nOutputPlane, outputDepth, outputHeight, outputWidth); +- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, weight); +- if (bias) THCTensor_(free)(state, bias); +- ++ THNN_(VolumetricFullDilatedConvolution_updateOutput)( ++ state, input, output, weight, bias, finput, fgradInput, ++ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH); + } + + void THNN_(VolumetricFullConvolution_updateGradInput)( +@@ -235,109 +31,9 @@ void THNN_(VolumetricFullConvolution_updateGradInput)( + int padT, int padW, int padH, + int adjT, int adjW, int adjH) + { +- THCTensor *gradColumns = finput; +- +- int nInputPlane = THCTensor_(size)(state, weight, 0); +- int nOutputPlane = THCTensor_(size)(state, weight, 1); +- const int kT = (int)weight->size[2]; +- const int kH = (int)weight->size[3]; +- const int kW = (int)weight->size[4]; +- +- THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, +- gradColumns, gradInput); +- THNN_(VolumetricFullConvolution_shapeCheck)( +- state, input, gradOutput, weight, NULL, +- dT, dW, dH, padT, padW, padH, +- adjT, adjW, adjH); +- +- input = THCTensor_(newContiguous)(state, input); +- gradOutput = THCTensor_(newContiguous)(state, gradOutput); +- weight = THCTensor_(newContiguous)(state, weight); +- +- int batch = 1; +- if (input->nDimension == 4) { +- // Force batch +- batch = 0; +- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); +- THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); +- } +- +- long inputWidth = input->size[4]; +- long inputHeight = input->size[3]; +- long inputDepth = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Resize output +- THCTensor_(resize5d)(state, gradInput, batchSize, nInputPlane, inputDepth, inputHeight, inputWidth); +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); +- +- // Helpers +- THCTensor *gradInput_n = THCTensor_(new)(state); +- THCTensor *gradOutput_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per sample: +- THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); +- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); +- +- // Extract columns: +- vol2col( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, gradOutput_n), +- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, +- 1,1,1, +- THCTensor_(data)(state, gradColumns) +- ); +- +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m = weight->size[0]; +- long n = gradColumns->size[1]; +- long k = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 'n', 'n', +- n, m, k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradColumns), n, +- THCTensor_(data)(state, weight), k, +- ScalarConvert::to(0), +- THCTensor_(data)(state, gradInput_n), n +- ); +- } +- +- +- // Free +- THCTensor_(free)(state, gradInput_n); +- THCTensor_(free)(state, gradOutput_n); +- +- // Resize output +- if (batch == 0) { +- THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); +- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); +- THCTensor_(resize4d)(state, gradInput, nInputPlane, inputDepth, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, gradOutput); +- THCTensor_(free)(state, weight); ++ THNN_(VolumetricFullDilatedConvolution_updateGradInput)( ++ state, input, gradOutput, gradInput, weight, finput, fgradInput, ++ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH); + } + + +@@ -354,152 +50,9 @@ void THNN_(VolumetricFullConvolution_accGradParameters)( + int adjT, int adjW, int adjH, + accreal scale_) + { +- real scale = ScalarConvert::to(scale_); +- THCTensor *columns = finput; +- THCTensor *ones = fgradInput; +- +- int nInputPlane = THCTensor_(size)(state, gradWeight, 0); +- int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); +- const int kT = (int)gradWeight->size[2]; +- const int kH = (int)gradWeight->size[3]; +- const int kW = (int)gradWeight->size[4]; +- +- THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, +- gradBias, columns, ones); +- THNN_(VolumetricFullConvolution_shapeCheck)( +- state, input, gradOutput, gradWeight, +- gradBias, dT, dW, dH, padT, padW, padH, +- adjT, adjW, adjH); +- +- THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); +- if (gradBias) +- THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); +- +- input = THCTensor_(newContiguous)(state, input); +- gradOutput = THCTensor_(newContiguous)(state, gradOutput); +- +- int batch = 1; +- if (input->nDimension == 4) { +- // Force batch +- batch = 0; +- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); +- THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); +- } +- +- long inputWidth = input->size[4]; +- long inputHeight = input->size[3]; +- long inputDepth = input->size[2]; +- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; +- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; +- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; +- +- // Batch size + input planes +- long batchSize = input->size[0]; +- +- // Define a buffer of ones, for bias accumulation +- if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { +- // Resize plane and fill with ones... +- THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); +- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); +- } +- +- // Resize temporary columns +- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); +- +- // Helpers +- THCTensor *input_n = THCTensor_(new)(state); +- THCTensor *gradOutput_n = THCTensor_(new)(state); +- +- // For each elt in batch, do: +- for (int elt = 0; elt < batchSize; elt ++) { +- // Matrix mulitply per output: +- THCTensor_(select)(state, input_n, input, 0, elt); +- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); +- +- // Extract columns: +- vol2col( +- THCState_getCurrentStream(state), +- THCTensor_(data)(state, gradOutput_n), +- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, +- 1,1,1, +- THCTensor_(data)(state, columns) +- ); +- +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long n = columns->size[0]; // nOutputPlane * kt * kh * kw +- long m = input_n->size[0]; // nInputPlane +- long k = columns->size[1]; // inputHeight * inputWidth +- +- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemm( +- #elif defined(THC_REAL_IS_HALF) +- THCudaBlas_Hgemm( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemm( +- #endif +- state, +- 't', 'n', +- n, m, k, +- scale, +- THCTensor_(data)(state, columns), k, +- THCTensor_(data)(state, input_n), k, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradWeight), n +- ); +- +- // Do Bias: +- // M,N,K are dims of matrix A and B +- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) +- long m_ = nOutputPlane; +- long k_ = outputDepth * outputHeight * outputWidth; +- +- // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) +- if (gradBias) { +- #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) +- #ifdef THC_REAL_IS_FLOAT +- THCudaBlas_Sgemv( +- #elif defined(THC_REAL_IS_DOUBLE) +- THCudaBlas_Dgemv( +- #endif +- state, +- 't', +- k_, m_, +- scale, +- THCTensor_(data)(state, gradOutput_n), k_, +- THCTensor_(data)(state, ones), 1, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradBias), 1 +- ); +- #endif +- #ifdef THC_REAL_IS_HALF +- THCudaBlas_Hgemm( +- state, +- 't', 'n', +- m_, 1, k_, +- scale, +- THCTensor_(data)(state, gradOutput_n), k_, +- THCTensor_(data)(state, ones), k_, +- ScalarConvert::to(1), +- THCTensor_(data)(state, gradBias), m_ +- ); +- #endif +- } +- } +- +- // Free +- THCTensor_(free)(state, input_n); +- THCTensor_(free)(state, gradOutput_n); +- +- // Resize +- if (batch == 0) { +- THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); +- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); +- } +- +- THCTensor_(free)(state, input); +- THCTensor_(free)(state, gradOutput); ++ THNN_(VolumetricFullDilatedConvolution_accGradParameters)( ++ state, input, gradOutput, gradWeight, gradBias, finput, fgradInput, ++ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH, scale_); + } + + #endif +diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu +new file mode 100644 +index 0000000..94fcc6c +--- /dev/null ++++ b/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu +@@ -0,0 +1,516 @@ ++#ifndef THC_GENERIC_FILE ++#define THC_GENERIC_FILE "generic/VolumetricFullDilatedConvolution.cu" ++#else ++ ++static inline void THNN_(VolumetricFullDilatedConvolution_shapeCheck)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *weight, ++ THCTensor *bias, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH) { ++ THCUNN_argCheck(state, input->nDimension == 4 || input->nDimension == 5, 2, input, ++ "4D or 5D (batch mode) tensor expected for input, but got: %s"); ++ // number of input & output planes and kernel size is indirectly defined by the weight tensor ++ THCUNN_argCheck(state, weight->nDimension == 5, 4, weight, ++ "5D (nOutputPlane x nInputPlane x kT x kH x kW) tensor " ++ "expected for weight, but got: %s"); ++ THArgCheck(THCTensor_(isContiguous)(state, weight), 4, ++ "weight tensor has to be contiguous"); ++ THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, ++ "bias tensor has to be contiguous"); ++ THArgCheck(dT > 0 && dW > 0 && dH > 0, 8, ++ "stride should be greater than zero, but got dT: %d dH: %d dW: %d", dT, dH, dW); ++ THArgCheck(dilationT > 0 && dilationW > 0 && dilationH > 0, 15, ++ "dilation should be greater than zero, but got dilationT: %d, dilationH: %d, dilationW: %d", ++ dilationT, dilationH, dilationW); ++ THArgCheck((adjT < dT || adjT < dilationT) ++ && (adjW < dW || adjW < dilationW) ++ && (adjH < dH || adjH < dilationH), 15, ++ "output padding must be smaller than either stride or dilation," ++ " but got adjT: %d adjH: %d adjW: %d dT: %d dH: %d dW: %d " ++ "dilationT: %d dilationH: %d dilationW: %d", ++ adjT, adjH, adjW, dT, dH, dW, dilationT, dilationH, dilationW); ++ ++ int ndim = input->nDimension; ++ int nInputPlane = THCTensor_(size)(state, weight, 0); ++ int nOutputPlane = THCTensor_(size)(state, weight, 1); ++ const int kT = (int)weight->size[2]; ++ const int kH = (int)weight->size[3]; ++ const int kW = (int)weight->size[4]; ++ ++ if (bias != NULL) { ++ THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); ++ } ++ ++ int dimf = 0; ++ int dimd = 1; ++ int dimh = 2; ++ int dimw = 3; ++ ++ if (ndim == 5) { ++ dimf++; ++ dimd++; ++ dimh++; ++ dimw++; ++ } ++ ++ long inputWidth = input->size[dimw]; ++ long inputHeight = input->size[dimh]; ++ long inputDepth = input->size[dimd]; ++ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ if (outputDepth < 1 || outputWidth < 1 || outputHeight < 1) ++ THError("Given input size: (%dx%dx%dx%d). Calculated output size: (%dx%dx%dx%d). Output size is too small", ++ nInputPlane,inputDepth,inputHeight,inputWidth,nOutputPlane,outputDepth,outputHeight,outputWidth); ++ ++ THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); ++ if (gradOutput != NULL) { ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimd, outputDepth); ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); ++ THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); ++ } ++} ++ ++void THNN_(VolumetricFullDilatedConvolution_updateOutput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *output, ++ THCTensor *weight, ++ THCTensor *bias, ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH) ++{ ++ ++ THCTensor *columns = finput; ++ THCTensor *ones = fgradInput; ++ ++ int nInputPlane = THCTensor_(size)(state, weight, 0); ++ int nOutputPlane = THCTensor_(size)(state, weight, 1); ++ const int kT = (int)weight->size[2]; ++ const int kH = (int)weight->size[3]; ++ const int kW = (int)weight->size[4]; ++ ++ THCUNN_assertSameGPU(state, 6, input, output, weight, ++ bias, columns, ones); ++ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( ++ state, input, NULL, weight, bias, ++ dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, ++ adjT, adjW, adjH); ++ ++ input = THCTensor_(newContiguous)(state, input); ++ weight = THCTensor_(newContiguous)(state, weight); ++ bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; ++ ++ int batch = 1; ++ if (input->nDimension == 4) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); ++ } ++ ++ long inputWidth = input->size[4]; ++ long inputHeight = input->size[3]; ++ long inputDepth = input->size[2]; ++ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Resize output ++ THCTensor_(resize5d)(state, output, batchSize, nOutputPlane, outputDepth, outputHeight, outputWidth); ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); ++ ++ // Define a buffer of ones, for bias accumulation ++ // Note: this buffer can be shared with other modules, it only ever gets increased, ++ // and always contains ones. ++ if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { ++ // Resize plane and fill with ones... ++ THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); ++ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); ++ } ++ ++ // Helpers ++ THCTensor *input_n = THCTensor_(new)(state); ++ THCTensor *output_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per output: ++ THCTensor_(select)(state, input_n, input, 0, elt); ++ THCTensor_(select)(state, output_n, output, 0, elt); ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; ++ long n = columns->size[1]; ++ long k = weight->size[0]; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 'n', 't', ++ n, m, k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, input_n), n, ++ THCTensor_(data)(state, weight), m, ++ ScalarConvert::to(0), ++ THCTensor_(data)(state, columns), n ++ ); ++ ++ // Unpack columns back into input: ++ col2vol( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, columns), ++ nOutputPlane, outputDepth, outputHeight, outputWidth, ++ inputDepth, inputHeight, inputWidth, ++ kT, kH, kW, padT, padH, padW, dT, dH, dW, ++ dilationT, dilationH, dilationW, ++ THCTensor_(data)(state, output_n) ++ ); ++ ++ // Do Bias after: ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m_ = nOutputPlane; ++ long n_ = outputDepth * outputHeight * outputWidth; ++ long k_ = 1; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ if (bias) { ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 't', 'n', ++ n_, m_, k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, ones), k_, ++ THCTensor_(data)(state, bias), k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, output_n), n_ ++ ); ++ } ++ } ++ ++ // Free ++ THCTensor_(free)(state, input_n); ++ THCTensor_(free)(state, output_n); ++ ++ // Resize output ++ if (batch == 0) { ++ THCTensor_(resize4d)(state, output, nOutputPlane, outputDepth, outputHeight, outputWidth); ++ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, weight); ++ if (bias) THCTensor_(free)(state, bias); ++ ++} ++ ++void THNN_(VolumetricFullDilatedConvolution_updateGradInput)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradInput, ++ THCTensor *weight, ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH) ++{ ++ THCTensor *gradColumns = finput; ++ ++ int nInputPlane = THCTensor_(size)(state, weight, 0); ++ int nOutputPlane = THCTensor_(size)(state, weight, 1); ++ const int kT = (int)weight->size[2]; ++ const int kH = (int)weight->size[3]; ++ const int kW = (int)weight->size[4]; ++ ++ THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, ++ gradColumns, gradInput); ++ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( ++ state, input, gradOutput, weight, NULL, ++ dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, ++ adjT, adjW, adjH); ++ ++ input = THCTensor_(newContiguous)(state, input); ++ gradOutput = THCTensor_(newContiguous)(state, gradOutput); ++ weight = THCTensor_(newContiguous)(state, weight); ++ ++ int batch = 1; ++ if (input->nDimension == 4) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); ++ THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); ++ } ++ ++ long inputWidth = input->size[4]; ++ long inputHeight = input->size[3]; ++ long inputDepth = input->size[2]; ++ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Resize output ++ THCTensor_(resize5d)(state, gradInput, batchSize, nInputPlane, inputDepth, inputHeight, inputWidth); ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); ++ ++ // Helpers ++ THCTensor *gradInput_n = THCTensor_(new)(state); ++ THCTensor *gradOutput_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per sample: ++ THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); ++ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); ++ ++ // Extract columns: ++ vol2col( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, gradOutput_n), ++ nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, ++ dilationT, dilationH, dilationW, ++ THCTensor_(data)(state, gradColumns) ++ ); ++ ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m = weight->size[0]; ++ long n = gradColumns->size[1]; ++ long k = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 'n', 'n', ++ n, m, k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradColumns), n, ++ THCTensor_(data)(state, weight), k, ++ ScalarConvert::to(0), ++ THCTensor_(data)(state, gradInput_n), n ++ ); ++ } ++ ++ ++ // Free ++ THCTensor_(free)(state, gradInput_n); ++ THCTensor_(free)(state, gradOutput_n); ++ ++ // Resize output ++ if (batch == 0) { ++ THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); ++ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); ++ THCTensor_(resize4d)(state, gradInput, nInputPlane, inputDepth, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, gradOutput); ++ THCTensor_(free)(state, weight); ++} ++ ++ ++void THNN_(VolumetricFullDilatedConvolution_accGradParameters)( ++ THCState *state, ++ THCTensor *input, ++ THCTensor *gradOutput, ++ THCTensor *gradWeight, ++ THCTensor *gradBias, ++ THCTensor *finput, ++ THCTensor *fgradInput, ++ int dT, int dW, int dH, ++ int padT, int padW, int padH, ++ int dilationT, int dilationW, int dilationH, ++ int adjT, int adjW, int adjH, ++ accreal scale_) ++{ ++ real scale = ScalarConvert::to(scale_); ++ THCTensor *columns = finput; ++ THCTensor *ones = fgradInput; ++ ++ int nInputPlane = THCTensor_(size)(state, gradWeight, 0); ++ int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); ++ const int kT = (int)gradWeight->size[2]; ++ const int kH = (int)gradWeight->size[3]; ++ const int kW = (int)gradWeight->size[4]; ++ ++ THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, ++ gradBias, columns, ones); ++ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( ++ state, input, gradOutput, gradWeight, ++ gradBias, dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, ++ adjT, adjW, adjH); ++ ++ THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); ++ if (gradBias) ++ THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); ++ ++ input = THCTensor_(newContiguous)(state, input); ++ gradOutput = THCTensor_(newContiguous)(state, gradOutput); ++ ++ int batch = 1; ++ if (input->nDimension == 4) { ++ // Force batch ++ batch = 0; ++ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); ++ THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); ++ } ++ ++ long inputWidth = input->size[4]; ++ long inputHeight = input->size[3]; ++ long inputDepth = input->size[2]; ++ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; ++ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; ++ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; ++ ++ // Batch size + input planes ++ long batchSize = input->size[0]; ++ ++ // Define a buffer of ones, for bias accumulation ++ if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { ++ // Resize plane and fill with ones... ++ THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); ++ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); ++ } ++ ++ // Resize temporary columns ++ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); ++ ++ // Helpers ++ THCTensor *input_n = THCTensor_(new)(state); ++ THCTensor *gradOutput_n = THCTensor_(new)(state); ++ ++ // For each elt in batch, do: ++ for (int elt = 0; elt < batchSize; elt ++) { ++ // Matrix mulitply per output: ++ THCTensor_(select)(state, input_n, input, 0, elt); ++ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); ++ ++ // Extract columns: ++ vol2col( ++ THCState_getCurrentStream(state), ++ THCTensor_(data)(state, gradOutput_n), ++ nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, ++ dilationT, dilationH, dilationW, ++ THCTensor_(data)(state, columns) ++ ); ++ ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long n = columns->size[0]; // nOutputPlane * kt * kh * kw ++ long m = input_n->size[0]; // nInputPlane ++ long k = columns->size[1]; // inputHeight * inputWidth ++ ++ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemm( ++ #elif defined(THC_REAL_IS_HALF) ++ THCudaBlas_Hgemm( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemm( ++ #endif ++ state, ++ 't', 'n', ++ n, m, k, ++ scale, ++ THCTensor_(data)(state, columns), k, ++ THCTensor_(data)(state, input_n), k, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradWeight), n ++ ); ++ ++ // Do Bias: ++ // M,N,K are dims of matrix A and B ++ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) ++ long m_ = nOutputPlane; ++ long k_ = outputDepth * outputHeight * outputWidth; ++ ++ // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) ++ if (gradBias) { ++ #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) ++ #ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_Sgemv( ++ #elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_Dgemv( ++ #endif ++ state, ++ 't', ++ k_, m_, ++ scale, ++ THCTensor_(data)(state, gradOutput_n), k_, ++ THCTensor_(data)(state, ones), 1, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradBias), 1 ++ ); ++ #endif ++ #ifdef THC_REAL_IS_HALF ++ THCudaBlas_Hgemm( ++ state, ++ 't', 'n', ++ m_, 1, k_, ++ scale, ++ THCTensor_(data)(state, gradOutput_n), k_, ++ THCTensor_(data)(state, ones), k_, ++ ScalarConvert::to(1), ++ THCTensor_(data)(state, gradBias), m_ ++ ); ++ #endif ++ } ++ } ++ ++ // Free ++ THCTensor_(free)(state, input_n); ++ THCTensor_(free)(state, gradOutput_n); ++ ++ // Resize ++ if (batch == 0) { ++ THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); ++ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); ++ } ++ ++ THCTensor_(free)(state, input); ++ THCTensor_(free)(state, gradOutput); ++} ++ ++#endif +diff --git a/extra/cunn/lib/THCUNN/im2col.h b/extra/cunn/lib/THCUNN/im2col.h +index ba57263..060525f 100644 +--- a/extra/cunn/lib/THCUNN/im2col.h ++++ b/extra/cunn/lib/THCUNN/im2col.h +@@ -104,13 +104,10 @@ __global__ void col2im_kernel(const int n, const Dtype* data_col, + template + void col2im(cudaStream_t stream, const Dtype* data_col, const int channels, + const int height, const int width, ++ const int output_height, const int output_width, + const int patch_h, const int patch_w, const int pad_h, + const int pad_w, const int stride_h, const int stride_w, + const int dilation_h, const int dilation_w, Dtype* data_im) { +- int height_col = (height + 2 * pad_h - (dilation_h * (patch_h - 1) + 1)) +- / stride_h + 1; +- int width_col = (width + 2 * pad_w - (dilation_w * (patch_w - 1) + 1)) +- / stride_w + 1; + int num_kernels = channels * height * width; + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. +@@ -118,7 +115,7 @@ void col2im(cudaStream_t stream, const Dtype* data_col, const int channels, + num_kernels, data_col, height, width, channels, + patch_h, patch_w, pad_h, pad_w, stride_h, stride_w, + dilation_h, dilation_w, +- height_col, width_col, data_im ++ output_height, output_width, data_im + ); + THCudaCheck(cudaGetLastError()); + } +diff --git a/extra/cunn/lib/THCUNN/vol2col.h b/extra/cunn/lib/THCUNN/vol2col.h +index 15b110e..12c4838 100644 +--- a/extra/cunn/lib/THCUNN/vol2col.h ++++ b/extra/cunn/lib/THCUNN/vol2col.h +@@ -120,14 +120,12 @@ __global__ void vol2im_kernel(const int n, const Dtype* data_col, + template + void col2vol(cudaStream_t stream, const Dtype* data_col, const int channels, + const int depth, const int height, const int width, ++ const int output_depth, const int output_height, const int output_width, + const int patch_t, const int patch_h, const int patch_w, + const int pad_t, const int pad_h, const int pad_w, + const int stride_t, const int stride_h, const int stride_w, + const int dilation_t, const int dilation_h, const int dilation_w, + Dtype* data_vol) { +- int depth_col = (depth + 2 * pad_t - (dilation_t * (patch_t - 1) + 1)) / stride_t + 1; +- int height_col = (height + 2 * pad_h - (dilation_h * (patch_h - 1) + 1)) / stride_h + 1; +- int width_col = (width + 2 * pad_w - (dilation_w * (patch_w - 1) + 1)) / stride_w + 1; + int num_kernels = channels * depth * height * width; + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. +@@ -135,7 +133,7 @@ void col2vol(cudaStream_t stream, const Dtype* data_col, const int channels, + num_kernels, data_col, depth, height, width, channels, + patch_t, patch_h, patch_w, pad_t, pad_h, pad_w, stride_t, stride_h, stride_w, + dilation_t, dilation_h, dilation_w, +- depth_col, height_col, width_col, data_vol ++ output_depth, output_height, output_width, data_vol + ); + THCudaCheck(cudaGetLastError()); + } +diff --git a/extra/cunn/test.lua b/extra/cunn/test.lua +index 7903aa6..02b63d4 100644 +--- a/extra/cunn/test.lua ++++ b/extra/cunn/test.lua +@@ -978,9 +978,9 @@ local function BatchNormalization_backward(moduleName, mode, inputSize, backward + end + end + +-local function testBatchNormalization(name, dim, k) ++local function testBatchNormalization(name, dim, k, batchsize) + local function inputSize() +- local inputSize = { torch.random(2,32), torch.random(1, k) } ++ local inputSize = { batchsize or torch.random(2,32), torch.random(1, k) } + for i=1,dim do + table.insert(inputSize, torch.random(1,k)) + end +@@ -1005,6 +1005,7 @@ end + + function cunntest.BatchNormalization() + testBatchNormalization('BatchNormalization', 0, 128) ++ testBatchNormalization('BatchNormalization', 0, 128, 1) -- test batchsize=1 + end + + function cunntest.SpatialBatchNormalization() +@@ -4437,6 +4438,84 @@ function cunntest.SpatialUpSamplingBilinear_backward_batch() + end + end + ++function cunntest.UpSampling_forward_batch() ++ local minibatch = torch.random(1, 10) ++ local f = torch.random(3, 10) ++ local d = torch.random(3, 10) ++ local h = torch.random(3, 10) ++ local w = torch.random(3, 10) ++ local scale = torch.random(2,5) ++ ++ for k, typename in ipairs(typenames) do ++ for _,mode in pairs({'nearest','linear'}) do ++ for dim = 4,5 do ++ local input ++ if (dim == 4) then ++ input = torch.randn(minibatch, f, h, w):type(typename) ++ else ++ input = torch.randn(minibatch, f, d, h, w):type(typename) ++ end ++ ++ local ctype = t2cpu[typename] ++ input = makeNonContiguous(input:type(ctype)) ++ local sconv = nn.UpSampling(scale, mode):type(ctype) ++ local groundtruth = sconv:forward(input) ++ ++ input = makeNonContiguous(input:type(typename)) ++ local gconv = sconv:clone():type(typename) ++ local rescuda = gconv:forward(input) ++ ++ local error = rescuda:double() - groundtruth:double() ++ mytester:assertlt(error:abs():max(), precision_forward_type(precision_forward, typename), ++ string.format('error on state (forward) with %s', typename)) ++ end ++ end ++ end ++end ++ ++function cunntest.UpSampling_backward_batch() ++ local minibatch = torch.random(1, 10) ++ local f = torch.random(3, 10) ++ local d = torch.random(3, 10) ++ local h = torch.random(3, 10) ++ local w = torch.random(3, 10) ++ local scale = torch.random(2,4) ++ ++ for k, typename in ipairs(typenames) do ++ for _,mode in pairs({'nearest','linear'}) do ++ for dim = 4,5 do ++ local input, gradOutput ++ if (dim == 4) then ++ input = torch.randn(minibatch, f, h, w):type(typename) ++ gradOutput = torch.randn(minibatch, f, h*scale, w*scale):type(typename) ++ else ++ input = torch.randn(minibatch, f, d, h, w):type(typename) ++ gradOutput = torch.randn(minibatch, f, d*scale, h*scale, w*scale):type(typename) ++ end ++ ++ local ctype = t2cpu[typename] ++ input = makeNonContiguous(input:type(ctype)) ++ gradOutput = makeNonContiguous(gradOutput:type(ctype)) ++ local sconv = nn.UpSampling(scale, mode):type(ctype) ++ sconv:forward(input) ++ sconv:zeroGradParameters() ++ local groundgrad = sconv:backward(input, gradOutput) ++ ++ input = makeNonContiguous(input:type(typename)) ++ gradOutput = makeNonContiguous(gradOutput:type(typename)) ++ local gconv = sconv:clone():type(typename) ++ gconv:forward(input) ++ gconv:zeroGradParameters() ++ local rescuda = gconv:backward(input, gradOutput) ++ ++ local error = rescuda:double() - groundgrad:double() ++ mytester:assertlt(error:abs():max(), precision_backward_type(precision_backward, typename), ++ string.format('error on state (backward) with %s', typename)) ++ end ++ end ++ end ++end ++ + function cunntest.l1cost() + local size = math.random(300,500) + +@@ -5203,6 +5282,149 @@ function cunntest.VolumetricAveragePooling_backward() + end + end + ++function cunntest.FeatureLPPooling_forward() ++ for tries = 1, 5 do ++ local batch_mode = {true, false} ++ batch_mode = batch_mode[math.random(1, 2)] ++ local power = {2, 3} ++ power = power[math.random(1, 2)] ++ ++ local dims = math.random(1, 3) ++ ++ if batch_mode then ++ dims = dims + 1 ++ end ++ ++ local width = torch.random(2, 16) ++ local stride = torch.random(1, 4) ++ ++ local output_size = torch.random(1, 100) ++ local input_size = (output_size - 1) * stride + width ++ ++ local baseInput = nil ++ if dims == 1 then ++ baseInput = torch.Tensor(input_size):uniform() ++ elseif dims == 2 then ++ if batch_mode then ++ baseInput = torch.Tensor(math.random(1, 5), input_size):uniform() ++ else ++ baseInput = torch.Tensor(input_size, math.random(1, 5)):uniform() ++ end ++ elseif dims == 3 then ++ if batch_mode then ++ baseInput = torch.Tensor(math.random(1, 5), input_size, ++ math.random(1, 5)):uniform() ++ else ++ baseInput = torch.Tensor(input_size, math.random(1, 5), ++ math.random(1, 5)):uniform() ++ end ++ else ++ baseInput = torch.Tensor(math.random(1, 5), input_size, ++ math.random(1, 5), math.random(1, 5)):uniform() ++ end ++ ++ for k, typename in ipairs(typenames) do ++ local input = baseInput:type(typename) ++ ++ local ctype = t2cpu[typename] ++ input = makeNonContiguous(input:type(ctype)) ++ local sconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(ctype) ++ local groundtruth = sconv:forward(input) ++ ++ input = makeNonContiguous(input:type(typename)) ++ local gconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(typename) ++ local rescuda = gconv:forward(input) ++ ++ local error = rescuda:double() - groundtruth:double() ++ mytester:assertlt(error:abs():max(), ++ precision_forward_type(precision_forward, typename), ++ string.format('error on state (forward) with %s', typename)) ++ end ++ end ++end ++ ++function cunntest.FeatureLPPooling_backward() ++ for tries = 1, 5 do ++ local batch_mode = {true, false} ++ batch_mode = batch_mode[math.random(1, 2)] ++ local power = {2, 3} ++ power = power[math.random(1, 2)] ++ ++ local dims = math.random(1, 3) ++ ++ if batch_mode then ++ dims = dims + 1 ++ end ++ ++ local width = torch.random(2, 16) ++ local stride = torch.random(1, 4) ++ ++ local output_size = torch.random(1, 100) ++ local input_size = (output_size - 1) * stride + width ++ ++ local baseInput = nil ++ local baseGradOutput = nil ++ ++ if dims == 1 then ++ baseInput = torch.Tensor(input_size):uniform() ++ baseGradOutput = torch.Tensor(output_size):uniform() ++ elseif dims == 2 then ++ local a = math.random(1, 5) ++ if batch_mode then ++ baseInput = torch.Tensor(a, input_size):uniform() ++ baseGradOutput = torch.Tensor(a, output_size):uniform() ++ else ++ baseInput = torch.Tensor(input_size, a):uniform() ++ baseGradOutput = torch.Tensor(output_size, a):uniform() ++ end ++ elseif dims == 3 then ++ local a = math.random(1, 5) ++ local b = math.random(1, 5) ++ if batch_mode then ++ baseInput = torch.Tensor(a, input_size, b):uniform() ++ baseGradOutput = torch.Tensor(a, output_size, b):uniform() ++ else ++ baseInput = torch.Tensor(input_size, a, b):uniform() ++ baseGradOutput = torch.Tensor(output_size, a, b):uniform() ++ end ++ else ++ local a = math.random(1, 5) ++ local b = math.random(1, 5) ++ local c = math.random(1, 5) ++ baseInput = torch.Tensor(a, input_size, b, c):uniform() ++ baseGradOutput = torch.Tensor(a, output_size, b, c):uniform() ++ end ++ ++ for k, typename in ipairs(typenames) do ++ local input = baseInput:type(typename) ++ local gradOutput = baseGradOutput:type(typename) ++ local ctype = t2cpu[typename] ++ input = makeNonContiguous(input:type(ctype)) ++ gradOutput = makeNonContiguous(gradOutput:type(ctype)) ++ ++ local sconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(ctype) ++ if ceil_mode then sconv:ceil() end ++ sconv:forward(input) ++ sconv:zeroGradParameters() ++ local groundgrad = sconv:backward(input, gradOutput) ++ ++ input = makeNonContiguous(input:type(typename)) ++ gradOutput = makeNonContiguous(gradOutput:type(typename)) ++ local gconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(typename) ++ if ceil_mode then gconv:ceil() end ++ ++ gconv:forward(input) ++ gconv:zeroGradParameters() ++ local rescuda = gconv:backward(input, gradOutput) ++ ++ local error = rescuda:double() - groundgrad:double() ++ ++ mytester:assertlt(error:abs():max(), precision_backward_type(precision_backward, typename), ++ string.format('error on state (backward) with %s', typename)) ++ end ++ end ++end ++ + function cunntest.CMul_forward_batch() + local bs = math.random(8,32) + local nini = math.random(1,100) +Submodule extra/cutorch caf84f3..5e9d86c: +diff --git a/extra/cutorch/lib/THC/CMakeLists.txt b/extra/cutorch/lib/THC/CMakeLists.txt +index 1ea6039..c07a1e4 100644 +--- a/extra/cutorch/lib/THC/CMakeLists.txt ++++ b/extra/cutorch/lib/THC/CMakeLists.txt +@@ -41,6 +41,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.9.3") + endif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + ++ ++if(CUDA_VERSION VERSION_GREATER "8.0") ++ LIST(APPEND CUDA_NVCC_FLAGS "-D__CUDA_NO_HALF_OPERATORS__") ++endif(CUDA_VERSION VERSION_GREATER "8.0") ++ + IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + IF(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.7" OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "4.7" ) + SET(CXX_VERSION "c++11") +@@ -90,6 +95,10 @@ ENDIF(MAGMA_FOUND) + IF ($ENV{TH_BINARY_BUILD}) + MESSAGE(STATUS "TH_BINARY_BUILD detected. Statically linking libstdc++") + SET(CMAKE_CXX_FLAGS "-static-libstdc++ ${CMAKE_CXX_FLAGS}") ++ IF (UNIX AND NOT APPLE) ++ # hiding statically linked library symbols, this flag is not available for the linker under MACOSX ++ SET(CMAKE_CXX_FLAGS "-Wl,--exclude-libs,libstdc++.a ${CMAKE_CXX_FLAGS}") ++ ENDIF(UNIX AND NOT APPLE) + ENDIF() + + IF(APPLE) +@@ -226,6 +235,21 @@ ELSE() + + IF(USE_MAGMA) + TARGET_LINK_LIBRARIES(THC ${MAGMA_LIBRARIES}) ++ IF ($ENV{TH_BINARY_BUILD}) ++ # because magma is linked statically and it wants a BLAS, ++ # we need to link the BLAS lib against THC. Usually TH will ++ # load a BLAS library and it's all fine, but in the binary builds, ++ # TH uses static linkage to MKL, so it doesn't have all symbols that ++ # magma needs. So in this case, explicitly find a BLAS and link against it ++ # just like in TH ++ SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../TH/cmake ${CMAKE_MODULE_PATH}) ++ FIND_PACKAGE(BLAS) ++ IF(BLAS_FOUND) ++ TARGET_LINK_LIBRARIES(THC "${BLAS_LIBRARIES};${BLAS_LIBRARIES};${BLAS_LIBRARIES}") ++ ELSE(BLAS_FOUND) ++ MESSAGE(FATAL_ERROR "Binary build needs blas to be found here") ++ ENDIF(BLAS_FOUND) ++ ENDIF($ENV{TH_BINARY_BUILD}) + ENDIF(USE_MAGMA) + + IF(NOT THC_SO_VERSION) +diff --git a/extra/cutorch/lib/THC/THCApply.cuh b/extra/cutorch/lib/THC/THCApply.cuh +index a47e303..e49a153 100644 +--- a/extra/cutorch/lib/THC/THCApply.cuh ++++ b/extra/cutorch/lib/THC/THCApply.cuh +@@ -109,16 +109,16 @@ inline bool getApplyGrid(THCState* state, ptrdiff_t totalElements, dim3& grid) { + return false; + } + +- // Assume a reasonable number of SMs if no state is available +- int numSM = +- state ? THCState_getCurrentDeviceProperties(state)->multiProcessorCount : 15; +- +- // 16 warps per block * 4 per SM gives 64 warps per SM at maximum, +- // which seems to be a good sweetspot for latency hiding +- grid = dim3(min((long long) THCCeilDiv(totalElements, +- (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK), +- 4LL * numSM)); ++ if(THCState_getCurrentDeviceProperties(state)->major < 3){ ++ grid = dim3(min((long long) THCCeilDiv(totalElements, ++ (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK), (long long) 64*1024-1)); ++ return true; ++ } ++ ++ grid = dim3((long long) THCCeilDiv(totalElements, ++ (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK) ); + return true; ++ + } + + template major >= 5){ ++ THCublasCheck(cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH)); ++ THCublasCheck(cublasGemmEx(handle, opa, opb, ++ i_m, i_n, i_k, &fAlpha, ++ a, CUDA_R_16F, i_lda, b, CUDA_R_16F, ++ i_ldb, &fBeta, c, CUDA_R_16F, i_ldc, ++ CUDA_R_32F, CUBLAS_GEMM_DFALT_TENSOR_OP)); ++ THCublasCheck(cublasSetMathMode(handle, CUBLAS_DEFAULT_MATH)); ++ }else{ ++ THCublasCheck(cublasSgemmEx(handle, opa, opb, ++ i_m, i_n, i_k, &fAlpha, ++ a, CUDA_R_16F, i_lda, b, CUDA_R_16F, ++ i_ldb, &fBeta, c, CUDA_R_16F, i_ldc)); ++ } ++#endif ++ return; + } +- +- return; +- } + THError("Cublas_Hgemm only supports m, n, k, lda, ldb, ldc" + "with th bound [val] <= %d", INT_MAX); + } +@@ -344,6 +354,31 @@ void THCudaBlas_SgemmBatched(THCState *state, char transa, char transb, long m, + (int)batchCount)); + } + ++#if CUDA_VERSION >= 8000 ++void THCudaBlas_SgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, ++ float alpha, const float *a, long lda, long strideA, const float *b, long ldb, long strideB, ++ float beta, float *c, long ldc, long strideC, long batchCount) ++{ ++ if( (m >= INT_MAX) || (n >= INT_MAX) || (k >= INT_MAX) || (lda >= INT_MAX) || (ldb >= INT_MAX) || (ldc >= INT_MAX) || (batchCount >= INT_MAX) ) ++ ++ { ++ THError("Cublas_SgemmStridedBatched only supports m, n, k, lda, ldb, ldc, batchCount" ++ "with the bound [val] <= %d", INT_MAX); ++ } ++ ++ adjustLd(transa, transb, m, n, k, &lda, &ldb, &ldc); ++ cublasOperation_t opa = convertTransToCublasOperation(transa); ++ cublasOperation_t opb = convertTransToCublasOperation(transb); ++ ++ cublasHandle_t handle = THCState_getCurrentBlasHandle(state); ++ cublasSetStream(handle, THCState_getCurrentStream(state)); ++ THCublasCheck(cublasSgemmStridedBatched(handle, ++ opa, opb, (int)m, (int)n, (int)k, ++ &alpha, a, (int)lda, strideA, b, (int)ldb, strideB, &beta, c, (int)ldc, strideC, ++ (int)batchCount)); ++} ++#endif ++ + void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, long n, long k, + double alpha, const double *a[], long lda, const double *b[], long ldb, + double beta, double *c[], long ldc, long batchCount) +@@ -366,6 +401,30 @@ void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, + (int)batchCount)); + } + ++#if CUDA_VERSION >= 8000 ++void THCudaBlas_DgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, ++ double alpha, const double *a, long lda, long strideA, const double *b, long ldb, long strideB, ++ double beta, double *c, long ldc, long strideC, long batchCount) ++{ ++ if( (m >= INT_MAX) || (n >= INT_MAX) || (k >= INT_MAX) || (lda >= INT_MAX) || (ldb >= INT_MAX) || (ldc >= INT_MAX) || (batchCount >= INT_MAX) ) ++ { ++ THError("Cublas_DgemmBatched only supports m, n, k, lda, ldb, ldc, batchCount" ++ "with the bound [val] <= %d", INT_MAX); ++ } ++ ++ adjustLd(transa, transb, m, n, k, &lda, &ldb, &ldc); ++ cublasOperation_t opa = convertTransToCublasOperation(transa); ++ cublasOperation_t opb = convertTransToCublasOperation(transb); ++ ++ cublasHandle_t handle = THCState_getCurrentBlasHandle(state); ++ cublasSetStream(handle, THCState_getCurrentStream(state)); ++ THCublasCheck(cublasDgemmStridedBatched(handle, ++ opa, opb, (int)m, (int)n, (int)k, ++ &alpha, a, (int)lda, strideA, b, (int)ldb, strideB, &beta, c, (int)ldc, strideC, ++ (int)batchCount)); ++} ++#endif ++ + /* Inverse */ + void THCudaBlas_Sgetrf(THCState *state, int n, float **a, int lda, int *pivot, int *info, int batchSize) { + if( (n >= INT_MAX) || (lda >= INT_MAX) || (batchSize >= INT_MAX) ) +diff --git a/extra/cutorch/lib/THC/THCBlas.h b/extra/cutorch/lib/THC/THCBlas.h +index 25246b1..6398eba 100644 +--- a/extra/cutorch/lib/THC/THCBlas.h ++++ b/extra/cutorch/lib/THC/THCBlas.h +@@ -31,7 +31,14 @@ THC_API void THCudaBlas_SgemmBatched(THCState *state, char transa, char transb, + THC_API void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, long n, long k, + double alpha, const double *a[], long lda, const double *b[], long ldb, + double beta, double *c[], long ldc, long batchCount); +- ++#if CUDA_VERSION >= 8000 ++THC_API void THCudaBlas_SgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, ++ float alpha, const float *a, long lda, long strideA, const float *b, long ldb, long strideB, ++ float beta, float *c, long ldc, long strideC, long batchCount); ++THC_API void THCudaBlas_DgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, ++ double alpha, const double *a, long lda, long strideA, const double *b, long ldb, long strideB, ++ double beta, double *c, long ldc, long strideC, long batchCount); ++#endif + /* Inverse */ + THC_API void THCudaBlas_Sgetrf(THCState *state, int n, float **a, int lda, int *pivot, int *info, int batchSize); + THC_API void THCudaBlas_Dgetrf(THCState *state, int n, double **a, int lda, int *pivot, int *info, int batchSize); +diff --git a/extra/cutorch/lib/THC/THCCachingHostAllocator.h b/extra/cutorch/lib/THC/THCCachingHostAllocator.h +index 05513ac..8fb0ec9 100644 +--- a/extra/cutorch/lib/THC/THCCachingHostAllocator.h ++++ b/extra/cutorch/lib/THC/THCCachingHostAllocator.h +@@ -22,7 +22,7 @@ + THC_API THAllocator THCCachingHostAllocator; + + // Records an event in the specified stream. The allocation 'ptr' will not be +-// re-used until the event has occured. ++// re-used until the event has occurred. + THC_API cudaError_t THCCachingHostAllocator_recordEvent(void *ptr, THCStream *stream); + + // Releases cached pinned memory allocations via cudaHostFree +diff --git a/extra/cutorch/lib/THC/THCDeviceUtils.cuh b/extra/cutorch/lib/THC/THCDeviceUtils.cuh +index 8052860..4ae2bee 100644 +--- a/extra/cutorch/lib/THC/THCDeviceUtils.cuh ++++ b/extra/cutorch/lib/THC/THCDeviceUtils.cuh +@@ -43,7 +43,6 @@ __device__ __forceinline__ unsigned int ACTIVE_MASK() + #endif + } + +- + __device__ __forceinline__ int WARP_BALLOT(int predicate, unsigned int mask = 0xffffffff) + { + #if CUDA_VERSION >= 9000 +diff --git a/extra/cutorch/lib/THC/THCHalf.h b/extra/cutorch/lib/THC/THCHalf.h +index d5bd5c1..bb21b9d 100644 +--- a/extra/cutorch/lib/THC/THCHalf.h ++++ b/extra/cutorch/lib/THC/THCHalf.h +@@ -15,7 +15,7 @@ + + #if CUDA_VERSION >= 9000 + #ifndef __cplusplus +- typedef __half_raw half; ++typedef __half_raw half; + #endif + #endif + +diff --git a/extra/cutorch/lib/THC/THCNumerics.cuh b/extra/cutorch/lib/THC/THCNumerics.cuh +index ba86e8f..a36ff14 100644 +--- a/extra/cutorch/lib/THC/THCNumerics.cuh ++++ b/extra/cutorch/lib/THC/THCNumerics.cuh +@@ -44,6 +44,7 @@ struct THCNumerics { + static inline __host__ __device__ bool eq(char a, char b) { return a == b; } + static inline __host__ __device__ bool ne(char a, char b) { return a != b; } + ++ static inline __host__ __device__ char neg(char a) { return -a; } + static inline __host__ __device__ char add(char a, char b) { return a + b; } + static inline __host__ __device__ char mul(char a, char b) { return a * b; } + static inline __host__ __device__ char sub(char a, char b) { return a - b; } +@@ -63,6 +64,7 @@ struct THCNumerics { + static inline __host__ __device__ bool eq(short a, short b) { return a == b; } + static inline __host__ __device__ bool ne(short a, short b) { return a != b; } + ++ static inline __host__ __device__ short neg(short a) { return -a; } + static inline __host__ __device__ short add(short a, short b) { return a + b; } + static inline __host__ __device__ short mul(short a, short b) { return a * b; } + static inline __host__ __device__ short sub(short a, short b) { return a - b; } +@@ -82,6 +84,7 @@ struct THCNumerics { + static inline __host__ __device__ bool eq(int a, int b) { return a == b; } + static inline __host__ __device__ bool ne(int a, int b) { return a != b; } + ++ static inline __host__ __device__ int neg(int a) { return -a; } + static inline __host__ __device__ int add(int a, int b) { return a + b; } + static inline __host__ __device__ int mul(int a, int b) { return a * b; } + static inline __host__ __device__ int sub(int a, int b) { return a - b; } +@@ -101,6 +104,7 @@ struct THCNumerics { + static inline __host__ __device__ bool eq(long a, long b) { return a == b; } + static inline __host__ __device__ bool ne(long a, long b) { return a != b; } + ++ static inline __host__ __device__ long neg(long a) { return -a; } + static inline __host__ __device__ long add(long a, long b) { return a + b; } + static inline __host__ __device__ long mul(long a, long b) { return a * b; } + static inline __host__ __device__ long sub(long a, long b) { return a - b; } +diff --git a/extra/cutorch/lib/THC/THCReduce.cuh b/extra/cutorch/lib/THC/THCReduce.cuh +index b7df49b..cae6cf1 100644 +--- a/extra/cutorch/lib/THC/THCReduce.cuh ++++ b/extra/cutorch/lib/THC/THCReduce.cuh +@@ -10,6 +10,7 @@ + + #include "THCTensorTypeUtils.cuh" + #include "THCReduceApplyUtils.cuh" ++#include "THCNumerics.cuh" + + // Threads per thread block + #define THC_NONCONTIG_REDUCE_BLOCK_SIZE 32 * 16 +@@ -23,7 +24,9 @@ __device__ __forceinline__ IndexType getReduceNoncontigDimSliceIndex() { + // Kernel that handles an entire reduction of a slice of a tensor per each thread + template + #if __CUDA_ARCH__ >= 350 +@@ -35,17 +38,18 @@ kernelReduceNoncontigDim_shared(TensorInfo out, + IndexType reductionStride, + IndexType reductionSize, + IndexType totalSlices, +- T init, ++ AccT init, + ModifyOp modifyOp, +- ReduceOp reduceOp) { ++ ReduceOp reduceOp, ++ ReduceAccOp reduceAccOp) { + + IndexType sliceIndex = blockIdx.x * blockDim.x + threadIdx.x; + IndexType sliceStride = gridDim.x * blockDim.x; + +- __shared__ T local_reduce[THC_NONCONTIG_REDUCE_BLOCK_SIZE]; +- T* shmem = &local_reduce[threadIdx.x + threadIdx.y * blockDim.x]; ++ __shared__ AccT local_reduce[THC_NONCONTIG_REDUCE_BLOCK_SIZE]; ++ AccT* shmem = &local_reduce[threadIdx.x + threadIdx.y * blockDim.x]; + T load_reg[4]; +- T local_reg; ++ AccT local_reg; + + for(;sliceIndex out, + load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); + load_reg[2] = modifyOp(in.data[inOffset + (i + blockDim.y * 2) * reductionStride]); + load_reg[3] = modifyOp(in.data[inOffset + (i + blockDim.y * 3) * reductionStride]); +- +- local_reg = reduceOp(local_reg, +- reduceOp( +- reduceOp(load_reg[0], load_reg[1]), +- reduceOp(load_reg[2], load_reg[3]) +- ) +- ); +- ++ local_reg = reduceOp(local_reg, load_reg[0]); ++ local_reg = reduceOp(local_reg, load_reg[1]); ++ local_reg = reduceOp(local_reg, load_reg[2]); ++ local_reg = reduceOp(local_reg, load_reg[3]); + }else if(i + blockDim.y * 2 < reductionSize){ + load_reg[0] = modifyOp(in.data[inOffset + (i + blockDim.y * 0) * reductionStride]); + load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); + load_reg[2] = modifyOp(in.data[inOffset + (i + blockDim.y * 2) * reductionStride]); +- +- local_reg = reduceOp( +- reduceOp(load_reg[0], load_reg[1]), +- reduceOp(load_reg[2], local_reg) +- ); +- +- }else if( (i + blockDim.y) < reductionSize){ ++ local_reg = reduceOp(local_reg, load_reg[0]); ++ local_reg = reduceOp(local_reg, load_reg[1]); ++ local_reg = reduceOp(local_reg, load_reg[2]); ++ }else if( (i + blockDim.y) < reductionSize){ + load_reg[0] = modifyOp(in.data[inOffset + (i + blockDim.y * 0) * reductionStride]); + load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); +- local_reg = reduceOp( +- local_reg, reduceOp(load_reg[0], load_reg[1]) +- ); +- ++ local_reg = reduceOp(local_reg, load_reg[0]); ++ local_reg = reduceOp(local_reg, load_reg[1]); + }else if(i + blockDim.y * 0 < reductionSize){ + local_reg = reduceOp(local_reg, modifyOp(in.data[inOffset + i * reductionStride])); + } +@@ -100,15 +95,15 @@ kernelReduceNoncontigDim_shared(TensorInfo out, + while(dimy > 1){ + __syncthreads(); + if( threadIdx.y == 0 && (dimy%2 != 0) ){ +- *shmem = reduceOp(*shmem, *(shmem + (dimy-1) * blockDim.x) ); ++ *shmem = reduceAccOp(*shmem, *(shmem + (dimy-1) * blockDim.x) ); + } + if(threadIdx.y < dimy/2){ +- *shmem = reduceOp(*shmem, *(shmem + (dimy/2)*blockDim.x) ); ++ *shmem = reduceAccOp(*shmem, *(shmem + (dimy/2)*blockDim.x) ); + } + dimy /= 2; + } + if(threadIdx.y == 0) +- out.data[outOffset] = *shmem; ++ out.data[outOffset] = ScalarConvert::to(*shmem); + } + } + +@@ -116,7 +111,9 @@ kernelReduceNoncontigDim_shared(TensorInfo out, + // Kernel that handles an entire reduction of a slice of a tensor per each thread + template + #if __CUDA_ARCH__ >= 350 +@@ -128,9 +125,10 @@ kernelReduceNoncontigDim(TensorInfo out, + IndexType reductionStride, + IndexType reductionSize, + IndexType totalSlices, +- T init, ++ AccT init, + ModifyOp modifyOp, +- ReduceOp reduceOp) { ++ ReduceOp reduceOp, ++ ReduceAccOp reduceAccOp) { + const IndexType sliceIndex = getReduceNoncontigDimSliceIndex(); + + if (sliceIndex >= totalSlices) { +@@ -146,7 +144,7 @@ kernelReduceNoncontigDim(TensorInfo out, + + // For each point in reductionSize, reduce into `r` + IndexType inOffset = inBaseOffset; +- T r = init; ++ AccT r = init; + + for (IndexType i = 0; i < reductionSize; ++i) { + r = reduceOp(r, modifyOp(in.data[inOffset])); +@@ -154,7 +152,7 @@ kernelReduceNoncontigDim(TensorInfo out, + } + + // Write out reduced value +- out.data[outOffset] = r; ++ out.data[outOffset] = ScalarConvert::to(r); + } + + template +@@ -167,7 +165,9 @@ __device__ __forceinline__ IndexType getReduceContigDimSliceIndex() { + // each block + template + __global__ void +@@ -175,9 +175,10 @@ kernelReduceContigDim(TensorInfo out, + TensorInfo in, + IndexType reductionSize, + IndexType totalSlices, +- T init, ++ AccT init, + ModifyOp modifyOp, +- ReduceOp reduceOp) { ++ ReduceOp reduceOp, ++ ReduceAccOp reduceAccOp) { + const IndexType sliceIndex = getReduceContigDimSliceIndex(); + + if (sliceIndex >= totalSlices) { +@@ -195,7 +196,7 @@ kernelReduceContigDim(TensorInfo out, + // Each thread in the block will reduce some subset of elements in + // the slice. The elements are guaranteed contiguous starting at + // `inBaseOffset`. +- T r = init; ++ AccT r = init; + for (IndexType i = threadIdx.x; i < reductionSize; i += blockDim.x) { + r = reduceOp(r, modifyOp(in.data[inBaseOffset + i])); + } +@@ -203,12 +204,12 @@ kernelReduceContigDim(TensorInfo out, + // Reduce within the block + // FIXME: extern name + extern __shared__ char smemChar[]; +- T* smem = (T*) smemChar; +- r = reduceBlock(smem, blockDim.x, r, reduceOp, init); ++ AccT* smem = (AccT*) smemChar; ++ r = reduceBlock(smem, blockDim.x, r, reduceAccOp, init); + + if (threadIdx.x == 0) { + // Write out reduced value +- out.data[outOffset] = r; ++ out.data[outOffset] = ScalarConvert::to(r); + } + } + +@@ -254,13 +255,18 @@ inline bool getContigReduceGrid(ptrdiff_t elements, dim3& grid) { + + // Performs a reduction out[..., 0, ...] = reduce_i(modify(in[..., i, ...])) for + // all in where i and the out's 0 are indexed at dimension `dim` +-template ++template + bool THC_reduceDim(THCState* state, + TensorType* out, + TensorType* in, + const ModifyOp& modifyOp, + const ReduceOp& reduceOp, +- typename TensorUtils::DataType init, ++ const ReduceAccOp& reduceAccOp, ++ AccT init, + int dim, + int keepdim) { + ptrdiff_t inElements = TensorUtils::getNumElements(state, in); +@@ -292,7 +298,7 @@ bool THC_reduceDim(THCState* state, + } + + block = getContigReduceBlock(outElements, reductionSize); +- smemSize = sizeof(typename TensorUtils::DataType) * block.x; ++ smemSize = sizeof(AccT) * block.x; + } else { + if (!getNoncontigReduceGrid(outElements, grid)) { + return false; +@@ -335,27 +341,31 @@ bool THC_reduceDim(THCState* state, + // index can be similarly collapsed. That is what this unrolling is for. + #define HANDLE_CASE(TYPE, OUT, IN) \ + if (contigReduction) { \ +- kernelReduceContigDim::DataType, \ ++ AccT, \ + TYPE, OUT, IN> \ + <<>>( \ + outInfo, inInfo, reductionSize, \ +- (TYPE) outElements, init, modifyOp, reduceOp); \ ++ (TYPE) outElements, init, modifyOp, reduceOp, reduceAccOp); \ + } else { \ + if(block.y == 1){ \ +- kernelReduceNoncontigDim::DataType, \ ++ AccT, \ + TYPE, OUT, IN> \ + <<>>( \ + outInfo, inInfo, reductionStride, reductionSize, \ +- (TYPE) outElements, init, modifyOp, reduceOp); \ ++ (TYPE) outElements, init, modifyOp, reduceOp, reduceAccOp); \ + }else{ \ +- kernelReduceNoncontigDim_shared::DataType, \ ++ AccT, \ + TYPE, OUT, IN> \ + <<>>( \ + outInfo, inInfo, reductionStride, reductionSize, \ +- (TYPE) outElements, init, modifyOp, reduceOp); \ ++ (TYPE) outElements, init, modifyOp, reduceOp, \ ++ reduceAccOp); \ + } \ + } \ + +@@ -409,7 +419,6 @@ bool THC_reduceDim(THCState* state, + getTensorInfo(state, in); + inInfo.reduceDim(dim); + inInfo.collapseDims(); +- + HANDLE_OUT_CASE(unsigned int, outInfo.dims, inInfo.dims); + } else { + TensorInfo::DataType, +diff --git a/extra/cutorch/lib/THC/THCScanUtils.cuh b/extra/cutorch/lib/THC/THCScanUtils.cuh +index ce9619d..9a487ca 100644 +--- a/extra/cutorch/lib/THC/THCScanUtils.cuh ++++ b/extra/cutorch/lib/THC/THCScanUtils.cuh +@@ -2,6 +2,7 @@ + #define THC_SCAN_UTILS_INC + + #include "THCAsmUtils.cuh" ++#include "THCDeviceUtils.cuh" + + // Collection of in-kernel scan / prefix sum utilities + +diff --git a/extra/cutorch/lib/THC/THCTensorConv.cu b/extra/cutorch/lib/THC/THCTensorConv.cu +index c8c1ad6..b8f3263 100644 +--- a/extra/cutorch/lib/THC/THCTensorConv.cu ++++ b/extra/cutorch/lib/THC/THCTensorConv.cu +@@ -663,7 +663,7 @@ THC_API void THCudaTensor_conv2DRevgerm(THCState *state, THCudaTensor *output, f + float *output_data = THCudaTensor_data(state, output); + + // kernel is called multiple times +- // (the arbitrary split below is just here to make sure we dont go over 256 threads) ++ // (the arbitrary split below is just here to make sure we don't go over 256 threads) + for (int sl=0; sl dst, + + template + struct LinearIndexCalcData { ++ // sizes for the Tensor dims (from the Tensor, for bounds checking) ++ IndexType baseSizes[Dims]; + // sizes for Tensor dims (either from the Tensor, or the size of the adv indexer at that dim) + IndexType sizes[Dims]; + // strides for the Tensor we are indexing into +@@ -373,6 +375,7 @@ __device__ __forceinline__ long calculateOffset( + indexAtDim = index - nextIndex * sizeAtDim; + } + ++ assert(indexAtDim < data.baseSizes[dim]); + offset += indexAtDim * strideAtDim; + index = nextIndex; + } +diff --git a/extra/cutorch/lib/THC/THCTensorMath.cuh b/extra/cutorch/lib/THC/THCTensorMath.cuh +index ae8f5db..202090e 100644 +--- a/extra/cutorch/lib/THC/THCTensorMath.cuh ++++ b/extra/cutorch/lib/THC/THCTensorMath.cuh +@@ -26,6 +26,24 @@ __global__ void THCTensor_copyToDiagonal(T* a, T* b, ptrdiff_t start, ptrdiff_t + #define CAT_ARRAY_BATCH_SIZE 1024 + #define CAT_ARRAY_MAX_INPUT_DIMS 4 + ++inline bool getCatGrid(THCState* state, ptrdiff_t nTensors, dim3& grid) { ++ int curDevice = -1; ++ cudaGetDevice(&curDevice); ++ ++ if (curDevice == -1) { ++ return false; ++ } ++ ++ // Assume a reasonable number of SMs if no state is available ++ int numSM = ++ state ? THCState_getCurrentDeviceProperties(state)->multiProcessorCount : 15; ++ //X dim of grid for cat array cooperates on a single tensor in the cat. ++ //Given half of the GPU, full utilization will always occur. ++ grid = dim3( 2LL * numSM, (long long) nTensors ); ++ ++ return true; ++} ++ + // Similar to any other IndexToOffset calculation for copying along a given dimension. + template + struct CatArrIndexToOffset { +@@ -77,6 +95,9 @@ struct OutputTensorSizeStride { + * + * The most important assumption made is that the input tensors are contiguous. + */ ++ ++ ++ + template + __global__ void CatArrayBatchedCopy( + T* output, +@@ -84,19 +105,26 @@ __global__ void CatArrayBatchedCopy( + OutputTensorSizeStride os, + const int concatDim, + IndexType dimStride) { +- T* data = inputs[blockIdx.y].input; +- IndexType offset = inputs[blockIdx.y].offset; +- IndexType dimSize = inputs[blockIdx.y].dimSize; +- IndexType nElements = inputs[blockIdx.y].nElements; +- IndexType dataOffset = offset * dimStride; +- +- for (IndexType linearIndex = blockIdx.x * blockDim.x + threadIdx.x; +- linearIndex < nElements; +- linearIndex += gridDim.x * blockDim.x) { ++ ++ IndexType tid = blockIdx.x * blockDim.x + threadIdx.x; ++ IndexType nElements = inputs[blockIdx.y].nElements; ++ ++ if(tid >= nElements) return; ++ ++ T* data = inputs[blockIdx.y].input; ++ IndexType offset = inputs[blockIdx.y].offset; ++ IndexType dimSize = inputs[blockIdx.y].dimSize; ++ IndexType dataOffset = offset * dimStride; ++ ++ IndexType stride = gridDim.x * blockDim.x; ++ ++ while( tid < nElements){ + IndexType elementOffset = CatArrIndexToOffset::compute( +- os.outputSize, os.outputStride, dimSize, concatDim, linearIndex); +- output[dataOffset + elementOffset] = data[linearIndex]; +- } ++ os.outputSize, os.outputStride, dimSize, concatDim, tid); ++ output[dataOffset + elementOffset] = data[tid]; ++ ++ tid += stride; ++ } + } + + #endif +diff --git a/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh b/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh +index 6ab010a..a37645d 100644 +--- a/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh ++++ b/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh +@@ -264,60 +264,47 @@ struct TensorMulOp { + }; + #endif // CUDA_HALF_TENSOR + +-template ++template + struct TensorPowOp { + TensorPowOp(T v) : val(v) {} + __device__ __forceinline__ void operator()(T* out, T* in) { +- *out = powf((float) *in, (float) val); ++ if (StaticExp == 1) { ++ *out = *in; ++ } else if (StaticExp == 2) { ++ *out = THCNumerics::mul(*in, *in); ++ } else if (StaticExp == 3) { ++ *out = THCNumerics::mul(*in, *in); ++ *out = THCNumerics::mul(*out, *in); ++ } else if (StaticExp == -1) { ++ *out = THCNumerics::cinv(*in); ++ } else if (StaticExp == -2) { ++ *out = THCNumerics::mul(*in, *in); ++ *out = THCNumerics::cinv(*out); ++ } else { ++ *out = THCNumerics::pow(*in, val); ++ } + } + + __device__ __forceinline__ void operator()(T* v) { +- *v = powf((float) *v, (float) val); ++ if (StaticExp == 1) { ++ *v = *v; ++ } else if (StaticExp == 2) { ++ *v = THCNumerics::mul(*v, *v); ++ } else if (StaticExp == 3) { ++ *v = THCNumerics::mul(THCNumerics::mul(*v, *v), *v); ++ } else if (StaticExp == -1) { ++ *v = THCNumerics::cinv(*v); ++ } else if (StaticExp == -2) { ++ *v = THCNumerics::mul(*v, *v); ++ *v = THCNumerics::cinv(*v); ++ } else { ++ *v = THCNumerics::pow(*v, val); ++ } + } + + const T val; + }; + +-template <> +-struct TensorPowOp { +- TensorPowOp(double v) : val(v) {} +- +- __device__ __forceinline__ void operator()(double* out, double* in) { +- *out = pow(*in, val); +- } +- +- __device__ __forceinline__ void operator()(double* v) { +- *v = pow(*v, val); +- } +- +- const double val; +-}; +- +-#ifdef CUDA_HALF_TENSOR +-template <> +-struct TensorPowOp { +- TensorPowOp(half v) : val(v) {} +- +- __device__ __forceinline__ void operator()(half* out, half* in) { +- // No fp16 pow function yet +- float fin = __half2float(*in); +- float fval = __half2float(val); +- float fout = powf(fin, fval); +- *out = __float2half(fout); +- } +- +- __device__ __forceinline__ void operator()(half* v) { +- // No fp16 pow function yet +- float fv = __half2float(*v); +- float fval = __half2float(val); +- float fout = powf(fv, fval); +- *v = __float2half(fout); +- } +- +- const half val; +-}; +-#endif // CUDA_HALF_TENSOR +- + template + struct TensorTPowOp { + TensorTPowOp(T v) : val(v) {} +diff --git a/extra/cutorch/lib/THC/THCTensorMode.cuh b/extra/cutorch/lib/THC/THCTensorMode.cuh +index b67ac2a..4d062e7 100644 +--- a/extra/cutorch/lib/THC/THCTensorMode.cuh ++++ b/extra/cutorch/lib/THC/THCTensorMode.cuh +@@ -245,7 +245,7 @@ __global__ void computeMode( + // Finally, we need to find the "an" index of the mode in the input Tensor. The API does + // not constrain which index we pick, so it can be any of the indices that contain the mode. + // We will do a reduction to find the index. We go back to using the (index, flag) buffer +- // arrangment. First, we mark indices that are equal to the mode, i.e B[i] = true if ++ // arrangement. First, we mark indices that are equal to the mode, i.e B[i] = true if + // input[i] == mode, and initialize C[i] to be the index + // + // Again we reduce 2 elements in the thread's registers prior to the block-wide reduction +diff --git a/extra/cutorch/lib/THC/THCTensorRandom.cuh b/extra/cutorch/lib/THC/THCTensorRandom.cuh +index 96263da..8d9f6cc 100644 +--- a/extra/cutorch/lib/THC/THCTensorRandom.cuh ++++ b/extra/cutorch/lib/THC/THCTensorRandom.cuh +@@ -92,7 +92,7 @@ condDiv(T *q, long *J, long inputsize, T q_max) { + // Normalizes the L1 norm of every row to 1; used by multinomial + template + __global__ void renormRowsL1(T* dist, long rows, long cols) { +- extern __shared__ __align__(sizeof(T)) unsigned char my_smem[]; ++ extern __shared__ unsigned char my_smem[]; + T *smem = reinterpret_cast(my_smem); + + for (long row = blockIdx.x; row < rows; row += gridDim.x) { +@@ -153,7 +153,7 @@ sampleMultinomialOnce(long* dest, + int categories, + T* sampled, + T* dist) { +- extern __shared__ __align__(sizeof(AccT)) unsigned char my_smem[]; ++ extern __shared__ unsigned char my_smem[]; + __shared__ bool found; + + // Shared Memory hold blockdim.x T for holding the cumulative sum, +diff --git a/extra/cutorch/lib/THC/generic/THCTensorIndex.cu b/extra/cutorch/lib/THC/generic/THCTensorIndex.cu +index a9ed28e..ac42cf5 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorIndex.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorIndex.cu +@@ -535,6 +535,7 @@ void THCTensor_(calculateAdvancedIndexingOffsets)( + { \ + LinearIndexCalcData data; \ + for (int i = 0; i < DIMS; ++i) { \ ++ data.baseSizes[i] = THCTensor_(size)(state, indexed, i); \ + data.sizes[i] = indexers[i] != NULL ? \ + THCudaLongTensor_nElement(state, indexers[i]) : \ + THCTensor_(size)(state, indexed, i); \ +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMath.cu b/extra/cutorch/lib/THC/generic/THCTensorMath.cu +index 0eed5a9..ceb6f2d 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMath.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMath.cu +@@ -43,6 +43,14 @@ THCTensor_(zeros)(THCState *state, THCTensor *r_, THLongStorage *size) + THCTensor_(zero)(state, r_); + } + ++THC_API void ++THCTensor_(zerosLike)(THCState *state, THCTensor *r_, THCTensor *input) ++{ ++ THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, r_, input)); ++ THCTensor_(resizeAs)(state, r_, input); ++ THCTensor_(zero)(state, r_); ++} ++ + THC_API void + THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size) + { +@@ -51,6 +59,14 @@ THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size) + THCTensor_(fill)(state, r_, ScalarConvert::to(1)); + } + ++THC_API void ++THCTensor_(onesLike)(THCState *state, THCTensor *r_, THCTensor *input) ++{ ++ THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, r_, input)); ++ THCTensor_(resizeAs)(state, r_, input); ++ THCTensor_(fill)(state, r_, ScalarConvert::to(1)); ++} ++ + THC_API void + THCTensor_(reshape)(THCState *state, THCTensor *r_, THCTensor *t, THLongStorage *size) + { +@@ -191,7 +207,7 @@ void THCTensor_(catArray)(THCState *state, THCTensor *result, + + // Template Declarations for dim = 1, 2, 3, 4 + #define HANDLE_CASE(DIMS) \ +- CatArrayBatchedCopy<<stream>>>(data, d_inputs, param, cat_dimension, param.outputStride[cat_dimension]); ++ CatArrayBatchedCopy<<stream>>>(data, d_inputs, param, cat_dimension, param.outputStride[cat_dimension]); + + // Now we loop + offset = 0; +@@ -227,15 +243,12 @@ void THCTensor_(catArray)(THCState *state, THCTensor *result, + // is based on. + dim3 applyBlock = getApplyBlock(); + +- // We also re-use the applyGrid - but note that we use the maximum number of +- // elements for a given tensor in this grouping to determine the count +- dim3 applyGrid; +- getApplyGrid(state, cohortMax, applyGrid); ++ //Get grid where x dim fills half gpu and y dim is number of tensors. ++ //This will have cating two tensors fill the entire grid, but prevent ++ //many threads from needlessly load meta data if their sizes is small. ++ dim3 catGrid; ++ getCatGrid(state, j, catGrid); + +- // Next, we set our grid's y component to be the number of tensors in +- // the batch. This will allow the kernel to determine which input +- // tensor it is responsible for copying +- applyGrid.y = j; + + switch (maxDim) { + case 1: +@@ -376,6 +389,28 @@ void THCTensor_(diag)(THCState *state, THCTensor *self_, THCTensor *src_, long k + THCudaCheck(cudaGetLastError()); + } + ++void THCTensor_(eye)(THCState *state, THCTensor *self_, long n, long m) ++{ ++ THCAssertSameGPU(THCTensor_(checkGPU)(state, 1, self_)); ++ THArgCheck(n > 0, 1, "invalid argument"); ++ ++ if(m <= 0) ++ m = n; ++ ++ THCTensor_(resize2d)(state, self_, n, m); ++ THCTensor_(zero)(state, self_); ++ ++ long sz = THMin(n, m); ++ long stride = THCTensor_(stride)(state, self_, 0) + ++ THCTensor_(stride)(state, self_, 1); ++ ++ THCTensor *diag = THCTensor_(newWithStorage1d)(state, self_->storage, ++ self_->storageOffset, sz, stride); ++ ++ THCTensor_(fill)(state, diag, ScalarConvert::to(1)); ++ THCTensor_(free)(state, diag); ++} ++ + accreal THCTensor_(trace)(THCState *state, THCTensor *src_) { + THCAssertSameGPU(THCTensor_(checkGPU)(state, 1, src_)); + THArgCheck((src_->nDimension == 2), 1, "expected a matrix"); +@@ -449,4 +484,15 @@ void THCTensor_(range)(THCState *state, THCTensor *r_, accreal xmin, accreal xma + THCudaCheck(cudaGetLastError()); + } + ++void THCTensor_(arange)(THCState* state, THCTensor *r_, accreal xmin, accreal xmax, accreal step) { ++#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) ++ int m = fmod(xmax - xmin, step) == 0; ++#else ++ int m = (xmax - xmin) % step == 0; ++#endif ++ if (m) ++ xmax -= step; ++ THCTensor_(range)(state, r_, xmin, xmax, step); ++} ++ + #endif +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMath.h b/extra/cutorch/lib/THC/generic/THCTensorMath.h +index aae6775..26a7a49 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMath.h ++++ b/extra/cutorch/lib/THC/generic/THCTensorMath.h +@@ -6,7 +6,9 @@ THC_API void THCTensor_(fill)(THCState *state, THCTensor *self, real value); + THC_API void THCTensor_(zero)(THCState *state, THCTensor *self); + + THC_API void THCTensor_(zeros)(THCState *state, THCTensor *r_, THLongStorage *size); ++THC_API void THCTensor_(zerosLike)(THCState *state, THCTensor *r_, THCTensor* input); + THC_API void THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size); ++THC_API void THCTensor_(onesLike)(THCState *state, THCTensor *r_, THCTensor* input); + THC_API void THCTensor_(reshape)(THCState *state, THCTensor *r_, THCTensor *t, THLongStorage *size); + THC_API ptrdiff_t THCTensor_(numel)(THCState *state, THCTensor *t); + THC_API void THCTensor_(cat)(THCState *state, THCTensor *result, THCTensor *ta, THCTensor *tb, int dimension); +@@ -16,6 +18,7 @@ THC_API void THCTensor_(nonzero)(THCState* state, THCudaLongTensor *tensor, THCT + THC_API void THCTensor_(tril)(THCState *state, THCTensor *self, THCTensor *src, long k); + THC_API void THCTensor_(triu)(THCState *state, THCTensor *self, THCTensor *src, long k); + THC_API void THCTensor_(diag)(THCState *state, THCTensor *self, THCTensor *src, long k); ++THC_API void THCTensor_(eye)(THCState *state, THCTensor *self, long n, long m); + THC_API accreal THCTensor_(trace)(THCState *state, THCTensor *self); + + #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) +@@ -26,5 +29,6 @@ THC_API void THCTensor_(logspace)(THCState *state, THCTensor *r_, real a, real b + #endif + + THC_API void THCTensor_(range)(THCState *state, THCTensor *r_, accreal xmin, accreal xmax, accreal step); ++THC_API void THCTensor_(arange)(THCState *state, THCTensor *r_, accreal xmin, accreal xmax, accreal step); + + #endif +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu b/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu +index 61c255a..8dc542e 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu +@@ -492,13 +492,15 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, + ldc = result_->stride[2]; + } + +- if (batch1->stride[transpose_result ? 2 : 1] == 1) ++ if (batch1->stride[transpose_result ? 2 : 1] == 1 && ++ batch1->stride[transpose_result ? 1 : 2] != 0) + { + transpose_batch1 = 'n'; + batch1_ = batch1; + lda = batch1_->stride[transpose_result ? 1 : 2]; + } +- else if (batch1->stride[transpose_result ? 1 : 2] == 1) ++ else if (batch1->stride[transpose_result ? 1 : 2] == 1 && ++ batch1->stride[transpose_result ? 2 : 1] != 0) + { + transpose_batch1 = 't'; + batch1_ = batch1; +@@ -511,13 +513,15 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, + lda = batch1_->stride[1]; + } + +- if (batch2->stride[transpose_result ? 2 : 1] == 1) ++ if (batch2->stride[transpose_result ? 2 : 1] == 1 && ++ batch2->stride[transpose_result ? 1 : 2] != 0) + { + transpose_batch2 = 'n'; + batch2_ = batch2; + ldb = batch2_->stride[transpose_result ? 1 : 2]; + } +- else if (batch2->stride[transpose_result ? 1 : 2] == 1) ++ else if (batch2->stride[transpose_result ? 1 : 2] == 1 && ++ batch2->stride[transpose_result ? 2 : 1] != 0) + { + transpose_batch2 = 't'; + batch2_ = batch2; +@@ -529,14 +533,14 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, + batch2_ = THCTensor_(newContiguous)(state, batch2); + ldb = batch2_->stride[1]; + } +- + long num_batches = result_->size[0]; + + #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) + // Compute pointers to matrices in each batch. ++#if CUDA_VERSION < 8000 + size_t matrices_size = num_batches * sizeof(real*); + +- // Copy pointers to device. ++// Copy pointers to device. + const real **d_matrices1, **d_matrices2; + real **d_result_matrices; + THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, matrices_size)); +@@ -555,7 +559,6 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, + createBatchGemmBuffer<<>>( + (const real**)d_result_matrices, THCTensor_(data)(state,result_), + result_->stride[0], num_batches); +- + #ifdef THC_REAL_IS_FLOAT + THCudaBlas_SgemmBatched( + state, +@@ -589,6 +592,38 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, + THCudaFree(state, d_matrices1); + THCudaFree(state, d_matrices2); + THCudaFree(state, d_result_matrices); ++ ++#else ++#ifdef THC_REAL_IS_FLOAT ++ THCudaBlas_SgemmStridedBatched( ++ state, ++ transpose_batch1, ++ transpose_batch2, ++ result_->size[transpose_result ? 2 : 1], ++ result_->size[transpose_result ? 1 : 2], ++ batch1_->size[transpose_result ? 1 : 2], ++ alpha, ++ THCTensor_(data)(state, batch1_), lda, batch1_->stride[0], ++ THCTensor_(data)(state, batch2_), ldb, batch2_->stride[0], ++ beta, ++ THCTensor_(data)(state, result_), ldc, result_->stride[0], ++ num_batches); ++#elif defined(THC_REAL_IS_DOUBLE) ++ THCudaBlas_DgemmStridedBatched( ++ state, ++ transpose_batch1, ++ transpose_batch2, ++ result_->size[transpose_result ? 2 : 1], ++ result_->size[transpose_result ? 1 : 2], ++ batch1_->size[transpose_result ? 1 : 2], ++ alpha, ++ THCTensor_(data)(state, batch1_), lda, batch1_->stride[0], ++ THCTensor_(data)(state, batch2_), ldb, batch2_->stride[0], ++ beta, ++ THCTensor_(data)(state, result_), ldc, result_->stride[0], ++ num_batches); ++#endif ++#endif + + #elif defined(THC_REAL_IS_HALF) + // Currently no HgemmBatched in Cublas +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu b/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu +index c35a83e..5e6b782 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu +@@ -347,10 +347,10 @@ THC_API void THCTensor_(gesvd2)(THCState *state, THCTensor *ru_, THCTensor *rs_, + + THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) + { +-#ifdef USE_MAGMA + THArgCheck(a->nDimension == 2, 2, "A should be 2 dimensional"); + THArgCheck(a->size[0] == a->size[1], 2, "A should be square"); + ++#ifdef USE_MAGMA + int info; + int n = a->size[0]; + int lwork = n * magma_get_sgetri_nb(n); +@@ -391,37 +391,23 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) + magma_free_pinned(ipiv); + THCTensor_(freeCopyTo)(state, input, ra_); + #else +- THArgCheck(a->nDimension == 2, 2, "A should be 2 dimensional"); +- THArgCheck(a->size[0] == a->size[1], 2, "A should be square"); +- + int n = a->size[0]; + + // input +- THCTensor *input = THCTensor_(newColumnMajor)(state, ra_, a); +- // output +- THCTensor *output = THCTensor_(newColumnMajor)(state, ra_, a); ++ THCTensor *input = THCTensor_(newColumnMajor)(state, a, a); ++ THCTensor_(resizeNd)(state, ra_, 2, input->size, input->stride); + +- size_t matrices_size = sizeof(real*); +- +- real **matrices1 = (real **)THAlloc(matrices_size); +- const real **matrices1_const = (const real **)THAlloc(matrices_size); +- real **matrices2 = (real **)THAlloc(matrices_size); +- matrices1[0] = THCTensor_(data)(state, input); +- matrices1_const[0] = THCTensor_(data)(state, input); +- matrices2[0] = THCTensor_(data)(state, output); ++ real *matrices1[1] = { THCTensor_(data)(state, input) }; ++ real *matrices2[1] = { THCTensor_(data)(state, ra_) }; + + // Copy pointers to device. + real **d_matrices1, **d_matrices2; +- const real **d_matrices1_const; +- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, matrices_size)); +- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1_const, matrices_size)); +- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices2, matrices_size)); ++ THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, sizeof(real*))); ++ THCudaCheck(THCudaMalloc(state, (void**)&d_matrices2, sizeof(real*))); + +- THCudaCheck(cudaMemcpyAsync(d_matrices1, matrices1, matrices_size, +- cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); +- THCudaCheck(cudaMemcpyAsync(d_matrices1_const, matrices1_const, matrices_size, ++ THCudaCheck(cudaMemcpyAsync(d_matrices1, matrices1, sizeof(real*), + cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); +- THCudaCheck(cudaMemcpyAsync(d_matrices2, matrices2, matrices_size, ++ THCudaCheck(cudaMemcpyAsync(d_matrices2, matrices2, sizeof(real*), + cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); + int info; + int *info_gpu; +@@ -446,11 +432,13 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) + + // Inverse + #if defined(THC_REAL_IS_FLOAT) +- THCudaBlas_Sgetri(state, n, d_matrices1_const, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); ++ THCudaBlas_Sgetri(state, n, (const real**)d_matrices1, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); + #else +- THCudaBlas_Dgetri(state, n, d_matrices1_const, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); ++ THCudaBlas_Dgetri(state, n, (const real**)d_matrices1, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); + #endif + ++ THCudaCheck(cudaMemcpy(&info, info_gpu, sizeof(int), cudaMemcpyDeviceToHost)); ++ + if (info > 0) + THError("CUBLAS getri : U(%d,%d) is 0, U is singular", info, info); + else if (info < 0) +@@ -460,10 +448,9 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) + THCudaCheck(THCudaFree(state, info_gpu)); + + THCudaCheck(THCudaFree(state, d_matrices1)); +- THCudaCheck(THCudaFree(state, d_matrices1_const)); + THCudaCheck(THCudaFree(state, d_matrices2)); + +- THCTensor_(freeCopyTo)(state, output, input); ++ THCTensor_(free)(state, input); + #endif + } + +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu +index cdf4b82..a2c714a 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu +@@ -46,7 +46,6 @@ IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(rsqrt, THCNumerics::rsqrt, Real) + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( ceil, THCNumerics::ceil, Real) + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(floor, THCNumerics::floor, Real) + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(trunc, THCNumerics::trunc, Real) +-IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( neg, THCNumerics::neg, Real) + + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( acos, THCNumerics::acos, Real) + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( cosh, THCNumerics::cosh, Real) +@@ -61,6 +60,13 @@ IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( cinv, THCNumerics::cinv, Real) + + #endif + ++#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) || \ ++ defined(THC_REAL_IS_SHORT) || defined(THC_REAL_IS_INT) || defined(THC_REAL_IS_LONG) ++ ++IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( neg, THCNumerics::neg, Real) ++ ++#endif ++ + IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( abs, THCNumerics::abs, Real) + + #undef IMPLEMENT_CUDA_TENSOR_BASIC_FUNC_ +@@ -160,14 +166,60 @@ void THCTensor_(sigmoid)(THCState* state, THCTensor* self_, THCTensor* src) { + void THCTensor_(pow)(THCState *state, THCTensor *self_, THCTensor *src, real value) { + THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self_, src)); + if (self_ == src) { +- if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { +- THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ if (THCNumerics::eq(value, ScalarConvert::to(1))) { ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(2))) { ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(3))) { ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(-1))) { ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(-2))) { ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else { ++ // fallback implementation using pow ++ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } + } + } else { + THCTensor_(resizeAs)(state, self_, src); + +- if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { +- THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ if (THCNumerics::eq(value, ScalarConvert::to(1))) { ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(2))) { ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(3))) { ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(-1))) { ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else if (THCNumerics::eq(value, ScalarConvert::to(-2))) { ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } ++ } else { ++ // fallback implementation using pow ++ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { ++ THArgCheck(false, 2, CUTORCH_DIM_WARNING); ++ } + } + } + +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h +index 17171c0..cba627c 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h ++++ b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h +@@ -30,11 +30,17 @@ THC_API void THCTensor_(trunc)(THCState *state, THCTensor *self, THCTensor *src) + THC_API void THCTensor_(frac)(THCState *state, THCTensor *self, THCTensor *src); + THC_API void THCTensor_(lerp)(THCState *state, THCTensor *result, THCTensor *a, THCTensor *b, real w); + +-THC_API void THCTensor_(neg)(THCState *state, THCTensor *self, THCTensor *src); + THC_API void THCTensor_(cinv)(THCState *state, THCTensor *self, THCTensor *src); + + #endif + ++#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) || \ ++ defined(THC_REAL_IS_SHORT) || defined(THC_REAL_IS_INT) || defined(THC_REAL_IS_LONG) ++ ++THC_API void THCTensor_(neg)(THCState *state, THCTensor *self, THCTensor *src); ++ ++#endif ++ + THC_API void THCTensor_(abs)(THCState *state, THCTensor *self, THCTensor *src); + THC_API void THCTensor_(sign)(THCState *state, THCTensor *self, THCTensor *src); + THC_API void THCTensor_(clamp)(THCState *state, THCTensor *self, THCTensor *src, real min_value, real max_value); +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu b/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu +index 846e7fd..a72562e 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu +@@ -7,8 +7,9 @@ THCTensor_(sum)(THCState* state, THCTensor *self, THCTensor *src, long dimension + THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); + if (!THC_reduceDim(state, self, src, + thrust::identity(), +- ReduceAdd(), +- ScalarConvert::to(0), ++ ReduceAdd(), ++ ReduceAdd(), ++ ScalarConvert::to(0), + dimension, + keepdim)) { + THArgCheck(false, 2, CUTORCH_DIM_WARNING); +@@ -22,8 +23,9 @@ THCTensor_(prod)(THCState* state, THCTensor *self, THCTensor *src, long dimensio + THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); + if (!THC_reduceDim(state, self, src, + thrust::identity(), +- ReduceMultiply(), +- ScalarConvert::to(1), ++ ReduceMultiply(), ++ ReduceMultiply(), ++ ScalarConvert::to(1), + dimension, + keepdim)) { + THArgCheck(false, 2, CUTORCH_DIM_WARNING); +@@ -161,23 +163,23 @@ THCTensor_(norm)(THCState *state, THCTensor* self, THCTensor* src, real value, l + THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); + if (THCNumerics::eq(value, ScalarConvert::to(0.0))) { + THC_reduceDim(state, self, src, +- TensorNonZeroOp(), ReduceAdd(), +- ScalarConvert::to(0.0), dimension, keepdim); ++ TensorNonZeroOp(), ReduceAdd(), ReduceAdd(), ++ ScalarConvert::to(0.0), dimension, keepdim); + } else if (THCNumerics::eq(value, ScalarConvert::to(1.0))) { + THC_reduceDim(state, self, src, +- TensorNormOp(value), ReduceAdd(), +- ScalarConvert::to(0.0), dimension, keepdim); ++ TensorNormOp(value), ReduceAdd(), ReduceAdd(), ++ ScalarConvert::to(0.0), dimension, keepdim); + + } else if (THCNumerics::eq(value, ScalarConvert::to(2.0))) { + THC_reduceDim(state, self, src, +- TensorNormOp(value), ReduceAdd(), +- ScalarConvert::to(0.0), dimension, keepdim); ++ TensorNormOp(value), ReduceAdd(), ReduceAdd(), ++ ScalarConvert::to(0.0), dimension, keepdim); + THCTensor_(pow)(state, self, self, ScalarConvert::to(0.5)); + + } else { + THC_reduceDim(state, self, src, +- TensorNormOp(value), ReduceAdd(), +- ScalarConvert::to(0.0), dimension, keepdim); ++ TensorNormOp(value), ReduceAdd(), ReduceAdd(), ++ ScalarConvert::to(0.0), dimension, keepdim); + THCTensor_(pow)(state, self, self, THCNumerics::cinv(value)); + } + +diff --git a/extra/cutorch/lib/THC/generic/THCTensorMode.cu b/extra/cutorch/lib/THC/generic/THCTensorMode.cu +index e5a17f2..4cbcac0 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorMode.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorMode.cu +@@ -190,6 +190,10 @@ THC_API void THCTensor_(mode)(THCState *state, + if (sliceSize == 1) { + THCTensor_(copy)(state, values, input); + THCudaLongTensor_fill(state, indices, TH_INDEX_BASE); ++ if (!keepdim) { ++ THCTensor_(squeeze1d)(state, values, values, dimension); ++ THCudaLongTensor_squeeze1d(state, indices, indices, dimension); ++ } + return; + } + +diff --git a/extra/cutorch/lib/THC/generic/THCTensorRandom.cu b/extra/cutorch/lib/THC/generic/THCTensorRandom.cu +index b85c5d2..0da1e06 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorRandom.cu ++++ b/extra/cutorch/lib/THC/generic/THCTensorRandom.cu +@@ -34,6 +34,28 @@ THC_API void THCTensor_(normal)(THCState* state, THCTensor *self_, double mean, + THCTensor_(freeCopyTo)(state, self, self_); + }; + ++THC_API void THCTensor_(normal_means)(THCState *state, THCTensor *self, THCTensor *means, double stddev) { ++ THCTensor_(resizeAs)(state, self, means); ++ THCTensor_(normal)(state, self, 0, stddev); ++ THCTensor_(cadd)(state, self, self, ScalarConvert::to(1), means); ++} ++ ++THC_API void THCTensor_(normal_stddevs)(THCState *state, THCTensor *self, double mean, THCTensor *stddevs) ++{ ++ THCTensor_(resizeAs)(state, self, stddevs); ++ THCTensor_(normal)(state, self, 0, 1); ++ THCTensor_(cmul)(state, self, self, stddevs); ++ THCTensor_(add)(state, self, self, ScalarConvert::to(mean)); ++} ++ ++THC_API void THCTensor_(normal_means_stddevs)(THCState *state, THCTensor *self, THCTensor *means, THCTensor *stddevs) ++{ ++ THCTensor_(resizeAs)(state, self, means); ++ THCTensor_(normal)(state, self, 0, 1); ++ THCTensor_(cmul)(state, self, self, stddevs); ++ THCTensor_(cadd)(state, self, self, ScalarConvert::to(1), means); ++} ++ + THC_API void THCTensor_(logNormal)(THCState* state, THCTensor *self_, double mean, double stdv) + { + +diff --git a/extra/cutorch/lib/THC/generic/THCTensorRandom.h b/extra/cutorch/lib/THC/generic/THCTensorRandom.h +index ad23e78..22f5a92 100644 +--- a/extra/cutorch/lib/THC/generic/THCTensorRandom.h ++++ b/extra/cutorch/lib/THC/generic/THCTensorRandom.h +@@ -8,6 +8,9 @@ THC_API void THCTensor_(uniform)(struct THCState *state, THCTensor *self, double + THC_API void THCTensor_(rand)(THCState *state, THCTensor *r_, THLongStorage *size); + THC_API void THCTensor_(randn)(THCState *state, THCTensor *r_, THLongStorage *size); + THC_API void THCTensor_(normal)(struct THCState *state, THCTensor *self, double mean, double stdv); ++THC_API void THCTensor_(normal_means)(struct THCState *state, THCTensor *self, THCTensor *means, double stddev); ++THC_API void THCTensor_(normal_stddevs)(struct THCState *state, THCTensor *self, double mean, THCTensor *stddevs); ++THC_API void THCTensor_(normal_means_stddevs)(struct THCState *state, THCTensor *self, THCTensor *means, THCTensor *stddevs); + THC_API void THCTensor_(logNormal)(struct THCState *state, THCTensor *self, double mean, double stdv); + THC_API void THCTensor_(exponential)(struct THCState *state, THCTensor *self, double lambda); + THC_API void THCTensor_(cauchy)(struct THCState *state, THCTensor *self, double median, double sigma); +Submodule extra/lua-cjson e8972ac..718f272: +diff --git a/extra/lua-cjson/LICENSE b/extra/lua-cjson/LICENSE +index 747a8bf..4883748 100644 +--- a/extra/lua-cjson/LICENSE ++++ b/extra/lua-cjson/LICENSE +@@ -1,4 +1,4 @@ +-Copyright (c) 2010-2012 Mark Pulford ++Copyright (c) 2010-2012 Mark Pulford + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the +diff --git a/extra/lua-cjson/README.adoc b/extra/lua-cjson/README.adoc +index e50534c..d84701a 100644 +--- a/extra/lua-cjson/README.adoc ++++ b/extra/lua-cjson/README.adoc +@@ -1,5 +1,5 @@ + = Lua CJSON = +-Mark Pulford ++Mark Pulford + + The Lua CJSON module provides JSON support for Lua. + +@@ -20,7 +20,7 @@ Please read +manual.adoc+ for installation instructions and the API + manual. + + The current stable version of this software is available from the +-http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. ++https://kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. + + Feel free to email me if you have any patches, suggestions, or comments. + +diff --git a/extra/lua-cjson/fpconv.c b/extra/lua-cjson/fpconv.c +index 3ecb8f7..0e21748 100644 +--- a/extra/lua-cjson/fpconv.c ++++ b/extra/lua-cjson/fpconv.c +@@ -1,6 +1,6 @@ + /* fpconv - Floating point conversion routines + * +- * Copyright (c) 2011-2012 Mark Pulford ++ * Copyright (c) 2011-2012 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the +diff --git a/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec b/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec +index 154e333..4f6db08 100644 +--- a/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec ++++ b/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec +@@ -2,7 +2,7 @@ package = "lua-cjson" + version = "2.1devel-1" + + source = { +- url = "http://www.kyne.com.au/~mark/software/download/lua-cjson-2.1devel.zip", ++ url = "http://www.kyne.au/~mark/software/download/lua-cjson-2.1devel.zip", + } + + description = { +@@ -15,7 +15,7 @@ description = { + (infinity, NaN,..) + - No dependencies on other libraries + ]], +- homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", ++ homepage = "http://www.kyne.au/~mark/software/lua-cjson.php", + license = "MIT" + } + +diff --git a/extra/lua-cjson/lua-cjson.spec b/extra/lua-cjson/lua-cjson.spec +index 3d3cb16..8a7a5cd 100644 +--- a/extra/lua-cjson/lua-cjson.spec ++++ b/extra/lua-cjson/lua-cjson.spec +@@ -9,8 +9,8 @@ Summary: A fast JSON encoding/parsing module for Lua + + Group: Development/Libraries + License: MIT +-URL: http://www.kyne.com.au/~mark/software/lua-cjson/ +-Source0: http://www.kyne.com.au/~mark/software/lua-cjson/download/lua-cjson-%{version}.tar.gz ++URL: http://www.kyne.au/~mark/software/lua-cjson/ ++Source0: http://www.kyne.au/~mark/software/lua-cjson/download/lua-cjson-%{version}.tar.gz + BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) + + BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} +@@ -57,24 +57,24 @@ rm -rf "$RPM_BUILD_ROOT" + + + %changelog +-* Thu Mar 1 2012 Mark Pulford - 2.1.0-1 ++* Thu Mar 1 2012 Mark Pulford - 2.1.0-1 + - Update for 2.1.0 + +-* Sun Jan 22 2012 Mark Pulford - 2.0.0-1 ++* Sun Jan 22 2012 Mark Pulford - 2.0.0-1 + - Update for 2.0.0 + - Install lua2json / json2lua utilities + +-* Wed Nov 27 2011 Mark Pulford - 1.0.4-1 ++* Wed Nov 27 2011 Mark Pulford - 1.0.4-1 + - Update for 1.0.4 + +-* Wed Sep 15 2011 Mark Pulford - 1.0.3-1 ++* Wed Sep 15 2011 Mark Pulford - 1.0.3-1 + - Update for 1.0.3 + +-* Sun May 29 2011 Mark Pulford - 1.0.2-1 ++* Sun May 29 2011 Mark Pulford - 1.0.2-1 + - Update for 1.0.2 + +-* Sun May 10 2011 Mark Pulford - 1.0.1-1 ++* Sun May 10 2011 Mark Pulford - 1.0.1-1 + - Update for 1.0.1 + +-* Sun May 1 2011 Mark Pulford - 1.0-1 ++* Sun May 1 2011 Mark Pulford - 1.0-1 + - Initial package +diff --git a/extra/lua-cjson/lua/cjson/util.lua b/extra/lua-cjson/lua/cjson/util.lua +index 5bb0d7d..13c889d 100644 +--- a/extra/lua-cjson/lua/cjson/util.lua ++++ b/extra/lua-cjson/lua/cjson/util.lua +@@ -2,7 +2,7 @@ local json = require "cjson" + + -- Various common routines used by the Lua CJSON package + -- +--- Mark Pulford ++-- Mark Pulford + + -- Determine with a Lua table can be treated as an array. + -- Explicitly returns "not an array" for very sparse arrays. +diff --git a/extra/lua-cjson/lua_cjson.c b/extra/lua-cjson/lua_cjson.c +index 22f33f1..3d1123e 100644 +--- a/extra/lua-cjson/lua_cjson.c ++++ b/extra/lua-cjson/lua_cjson.c +@@ -1,6 +1,6 @@ + /* Lua CJSON - JSON support for Lua + * +- * Copyright (c) 2010-2012 Mark Pulford ++ * Copyright (c) 2010-2012 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the +diff --git a/extra/lua-cjson/manual.adoc b/extra/lua-cjson/manual.adoc +index 83303a3..0be3d02 100644 +--- a/extra/lua-cjson/manual.adoc ++++ b/extra/lua-cjson/manual.adoc +@@ -1,5 +1,5 @@ + = Lua CJSON 2.1devel Manual = +-Mark Pulford ++Mark Pulford + :revdate: August 2016 + + Overview +@@ -21,7 +21,7 @@ Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for + details. + + The current stable version of this software is available from the +-http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. ++https://kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. + + Feel free to email me if you have any patches, suggestions, or comments. + +@@ -29,8 +29,8 @@ Feel free to email me if you have any patches, suggestions, or comments. + Installation + ------------ + +-Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, Lua 5.3, +-or http://www.luajit.org[LuaJIT] to build. ++Lua CJSON requires either https://lua.org[Lua] 5.1, Lua 5.2, Lua 5.3, ++or https://luajit.org[LuaJIT] to build. + + The build method can be selected from 4 options: + +@@ -63,7 +63,7 @@ cp cjson.so $LUA_MODULE_DIRECTORY + CMake + ~~~~~ + +-http://www.cmake.org[CMake] can generate build configuration for many ++https://cmake.org[CMake] can generate build configuration for many + different platforms (including Unix and Windows). + + First, generate the makefile for your platform using CMake. If CMake is +@@ -88,14 +88,14 @@ make + cp cjson.so $LUA_MODULE_DIRECTORY + + Review the +-http://www.cmake.org/cmake/help/documentation.html[CMake documentation] ++https://cmake.org/documentation/[CMake documentation] + for further details. + + + RPM + ~~~ + +-Linux distributions using http://rpm.org[RPM] can create a package via ++Linux distributions using https://rpm.org[RPM] can create a package via + the included RPM spec file. Ensure the +rpm-build+ package (or similar) + has been installed. + +@@ -109,7 +109,7 @@ rpm -Uvh $LUA_CJSON_RPM + LuaRocks + ~~~~~~~~ + +-http://luarocks.org[LuaRocks] can be used to install and manage Lua ++https://luarocks.org[LuaRocks] can be used to install and manage Lua + modules on a wide range of platforms (including Windows). + + First, extract the Lua CJSON source package. +@@ -125,7 +125,7 @@ LuaRocks does not support platform specific configuration for Solaris. + On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in + the rockspec before building this module. + +-Review the http://luarocks.org/en/Documentation[LuaRocks documentation] ++Review the https://github.com/luarocks/luarocks/wiki/Documentation[LuaRocks documentation] + for further details. + + +@@ -151,7 +151,7 @@ Built-in floating point conversion + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Lua CJSON may be built with David Gay's +-http://www.netlib.org/fp/[floating point conversion routines]. This can ++https://netlib.org/fp/[floating point conversion routines]. This can + increase overall performance by up to 50% on some platforms when + converting a large amount of numeric data. However, this option reduces + portability and is disabled by default. +@@ -380,8 +380,8 @@ ensure all Lua strings passed to +cjson.encode+ are UTF-8. + Base64 is commonly used to encode binary data as the most efficient + encoding under UTF-8 can only reduce the encoded size by a further + ~8%. Lua Base64 routines can be found in the +-http://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and +-http://www.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. ++https://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and ++https://web.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. + ========= + + Lua CJSON uses a heuristic to determine whether to encode a Lua table as +@@ -606,8 +606,8 @@ Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. + References + ---------- + +-- http://tools.ietf.org/html/rfc4627[RFC 4627] +-- http://www.json.org/[JSON website] ++- https://datatracker.ietf.org/doc/html/rfc4627[RFC 4627] ++- https://json.org/json-en.html[JSON website] + + + // vi:ft=asciidoc tw=72: +diff --git a/extra/lua-cjson/performance.adoc b/extra/lua-cjson/performance.adoc +index a36412d..e44a703 100644 +--- a/extra/lua-cjson/performance.adoc ++++ b/extra/lua-cjson/performance.adoc +@@ -1,6 +1,6 @@ + JSON module performance comparison under Lua + ============================================ +-Mark Pulford ++Mark Pulford + :revdate: January 22, 2012 + + This performance comparison aims to provide a guide of relative +@@ -26,7 +26,7 @@ http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]:: + https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]:: + - C wrapper for the YAJL library + +-http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]:: ++http://www.kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]:: + - C implementation with no dependencies on other libraries + + +diff --git a/extra/lua-cjson/strbuf.c b/extra/lua-cjson/strbuf.c +index ac779e4..7c59e1b 100644 +--- a/extra/lua-cjson/strbuf.c ++++ b/extra/lua-cjson/strbuf.c +@@ -1,6 +1,6 @@ + /* strbuf - String buffer routines + * +- * Copyright (c) 2010-2012 Mark Pulford ++ * Copyright (c) 2010-2012 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the +diff --git a/extra/lua-cjson/strbuf.h b/extra/lua-cjson/strbuf.h +index d861108..1b980c3 100644 +--- a/extra/lua-cjson/strbuf.h ++++ b/extra/lua-cjson/strbuf.h +@@ -1,6 +1,6 @@ + /* strbuf - String buffer routines + * +- * Copyright (c) 2010-2012 Mark Pulford ++ * Copyright (c) 2010-2012 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the +diff --git a/extra/lua-cjson/tests/bench.lua b/extra/lua-cjson/tests/bench.lua +index 648020b..52a491a 100755 +--- a/extra/lua-cjson/tests/bench.lua ++++ b/extra/lua-cjson/tests/bench.lua +@@ -5,7 +5,7 @@ + -- + -- Your Mileage May Vary. + -- +--- Mark Pulford ++-- Mark Pulford + + local json_module = os.getenv("JSON_MODULE") or "cjson" + +diff --git a/extra/lua-cjson/tests/test.lua b/extra/lua-cjson/tests/test.lua +index 9690db4..5b9b98a 100755 +--- a/extra/lua-cjson/tests/test.lua ++++ b/extra/lua-cjson/tests/test.lua +@@ -2,7 +2,7 @@ + + -- Lua CJSON tests + -- +--- Mark Pulford ++-- Mark Pulford + -- + -- Note: The output of this script is easier to read with "less -S" + +Submodule extra/luaffifb 610ce4d..a1cb731: +diff --git a/extra/luaffifb/call_x64.h b/extra/luaffifb/call_x64.h +index 5cf89a0..afbf084 100644 +--- a/extra/luaffifb/call_x64.h ++++ b/extra/luaffifb/call_x64.h +@@ -19,110 +19,118 @@ + */ + + static const unsigned char build_actionlist[2157] = { +- 72,139,141,233,255,72,137,132,253,36,233,255,221,133,233,255,217,133,233, +- 255,252,243,15,126,133,233,255,252,243,15,90,133,233,255,221,156,253,36,233, +- 255,217,156,253,36,233,255,102,15,214,132,253,36,233,255,252,242,15,90,192, +- 102,15,214,132,253,36,233,255,252,242,15,90,192,102,15,126,132,253,36,233, +- 255,85,72,137,229,65,84,72,129,252,236,239,102,15,214,69,252,240,102,15,214, +- 77,232,102,15,214,85,224,102,15,214,93,216,102,15,214,101,208,102,15,214, +- 109,200,102,15,214,117,192,102,15,214,125,184,72,137,125,176,72,137,117,168, +- 72,137,85,160,72,137,77,152,76,137,69,144,76,137,77,136,255,73,188,237,237, +- 255,72,199,194,237,72,199,198,237,76,137,231,232,251,1,0,255,72,199,194,237, +- 72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251,1,0,255,72, +- 199,194,237,72,199,198,237,76,137,231,232,251,1,0,72,186,237,237,72,199,198, +- 252,255,252,255,252,255,252,255,76,137,231,232,251,1,1,255,72,137,8,72,199, +- 198,252,254,252,255,252,255,252,255,76,137,231,232,251,1,2,255,72,186,237, +- 237,72,199,198,0,0,0,0,76,137,231,232,251,1,1,255,72,137,8,255,102,15,214, +- 0,255,217,24,255,217,88,4,255,102,15,214,64,8,255,76,137,231,232,251,1,3, +- 255,15,182,201,72,137,206,76,137,231,232,251,1,4,255,15,182,201,255,15,190, +- 201,255,72,137,206,76,137,231,232,251,1,5,255,15,183,201,255,15,191,201,255, +- 72,137,206,76,137,231,232,251,1,6,255,72,185,237,237,72,199,194,237,72,199, +- 198,237,76,137,231,232,251,1,7,255,72,199,194,237,72,199,198,252,254,252, +- 255,252,255,252,255,76,137,231,232,251,1,0,72,185,237,237,72,199,194,252, +- 255,252,255,252,255,252,255,72,199,198,252,254,252,255,252,255,252,255,76, +- 137,231,232,251,1,8,72,137,68,36,32,72,199,198,252,252,252,255,252,255,252, +- 255,76,137,231,232,251,1,9,72,139,68,36,32,255,72,199,194,237,72,199,198, +- 252,254,252,255,252,255,252,255,76,137,231,232,251,1,0,72,185,237,237,72, +- 199,194,252,255,252,255,252,255,252,255,72,199,198,252,254,252,255,252,255, +- 252,255,76,137,231,232,251,1,10,137,68,36,32,72,199,198,252,252,252,255,252, +- 255,252,255,76,137,231,232,251,1,9,139,68,36,32,255,72,199,198,252,254,252, +- 255,252,255,252,255,76,137,231,232,251,1,9,255,72,199,198,252,255,252,255, +- 252,255,252,255,76,137,231,232,251,1,11,255,72,199,198,252,255,252,255,252, +- 255,252,255,76,137,231,232,251,1,12,255,137,68,36,32,72,199,198,252,253,252, +- 255,252,255,252,255,76,137,231,232,251,1,9,139,68,36,32,255,72,199,198,252, +- 255,252,255,252,255,252,255,76,137,231,232,251,1,13,255,72,199,198,252,255, +- 252,255,252,255,252,255,76,137,231,232,251,1,14,255,72,137,68,36,32,72,199, +- 198,252,253,252,255,252,255,252,255,76,137,231,232,251,1,9,72,139,68,36,32, +- 255,72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251,1,15,72, +- 137,68,36,32,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251, +- 1,9,72,139,68,36,32,255,72,199,198,252,255,252,255,252,255,252,255,76,137, +- 231,232,251,1,16,102,15,214,68,36,32,72,199,198,252,253,252,255,252,255,252, +- 255,76,137,231,232,251,1,9,255,252,242,15,90,68,36,32,255,252,243,15,126, +- 68,36,32,255,72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251, +- 1,17,102,15,214,68,36,32,72,199,198,252,253,252,255,252,255,252,255,76,137, +- 231,232,251,1,9,252,243,15,126,68,36,32,255,72,199,198,252,255,252,255,252, +- 255,252,255,76,137,231,232,251,1,18,102,15,214,68,36,32,102,15,214,76,36, +- 40,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251,1,9,252, +- 243,15,126,68,36,32,252,243,15,126,76,36,40,255,72,139,141,233,72,199,194, +- 252,255,252,255,252,255,252,255,76,137,230,72,137,207,232,251,1,18,72,131, +- 252,236,4,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251,1, +- 9,255,76,139,101,252,248,72,137,252,236,93,194,236,255,85,72,137,229,65,84, +- 65,85,73,137,252,252,76,137,231,232,251,1,19,73,137,197,72,129,252,248,239, +- 255,15,141,244,248,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,248, +- 2,15,142,244,247,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,255,15, +- 141,244,247,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,255,248,1, +- 255,72,193,224,4,72,41,196,72,129,252,236,239,255,72,186,237,237,72,199,198, +- 0,0,0,0,76,137,231,232,251,1,1,72,131,252,236,16,255,72,185,237,237,72,199, +- 194,237,72,199,198,237,76,137,231,232,251,1,8,255,72,185,237,237,72,199,194, +- 237,72,199,198,237,76,137,231,232,251,1,21,255,72,185,237,237,72,199,194, +- 237,72,199,198,237,76,137,231,232,251,1,10,255,72,199,198,237,76,137,231, +- 232,251,1,12,255,15,182,192,255,15,190,192,255,15,183,192,255,15,191,192, +- 255,72,199,198,237,76,137,231,232,251,1,12,131,252,248,0,15,149,208,15,182, +- 192,255,72,199,198,237,76,137,231,232,251,1,11,255,72,199,198,237,76,137, +- 231,232,251,1,15,255,72,199,198,237,76,137,231,232,251,1,13,255,72,199,198, +- 237,76,137,231,232,251,1,14,255,72,199,198,237,76,137,231,232,251,1,16,255, +- 72,199,198,237,76,137,231,232,251,1,18,255,252,243,15,126,193,255,72,141, +- 132,253,36,233,72,131,252,236,4,72,199,194,237,76,137,230,72,137,199,232, +- 251,1,18,255,72,199,198,237,76,137,231,232,251,1,17,255,72,199,198,237,76, +- 137,231,232,251,1,17,137,4,36,217,4,36,255,137,20,36,217,4,36,255,72,137, +- 224,72,129,192,239,73,137,192,72,199,193,237,76,137,252,234,72,199,198,237, +- 76,137,231,232,251,1,22,255,72,137,224,72,129,192,239,73,137,192,72,199,193, +- 237,76,137,252,234,72,199,198,237,76,137,231,232,251,1,23,255,72,137,224, +- 72,129,192,239,73,137,193,73,199,192,237,72,199,193,237,76,137,252,234,72, +- 199,198,237,76,137,231,232,251,1,24,255,72,185,237,237,139,1,72,137,199,232, +- 251,1,25,255,72,131,196,32,255,252,243,15,126,188,253,36,233,255,252,243, +- 15,126,180,253,36,233,255,252,243,15,126,172,253,36,233,255,252,243,15,126, +- 164,253,36,233,255,252,243,15,126,156,253,36,233,255,252,243,15,126,148,253, +- 36,233,255,252,243,15,126,140,253,36,233,255,252,243,15,126,132,253,36,233, +- 255,76,139,140,253,36,233,255,76,139,132,253,36,233,255,72,139,140,253,36, +- 233,255,72,139,148,253,36,233,255,72,139,180,253,36,233,255,72,139,60,36, +- 255,72,129,196,239,255,176,8,255,232,251,1,26,72,131,252,236,48,255,72,137, +- 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237, +- 76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76,139,109,252, +- 240,76,139,101,252,248,72,137,252,236,93,195,255,72,137,68,36,32,232,251, +- 1,27,72,185,237,237,137,1,72,139,68,36,32,72,137,198,76,137,231,232,251,1, +- 28,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195, +- 255,72,137,68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199, +- 198,0,0,0,0,76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76, +- 139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,102,15,214,68, +- 36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237,76, +- 137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76,139,109,252,240, +- 76,139,101,252,248,72,137,252,236,93,195,255,102,15,214,76,36,40,102,15,214, +- 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237, +- 76,137,231,232,251,1,1,72,139,76,36,40,72,137,72,8,72,139,76,36,32,72,137, +- 8,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195, +- 255,232,251,1,27,72,185,237,237,137,1,184,0,0,0,0,76,139,109,252,240,76,139, +- 101,252,248,72,137,252,236,93,195,255,15,182,192,137,68,36,32,232,251,1,27, +- 72,185,237,237,137,1,139,68,36,32,72,137,198,76,137,231,232,251,1,4,184,1, +- 0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,137, +- 68,36,32,232,251,1,27,72,185,237,237,137,1,139,68,36,32,72,137,198,76,137, +- 231,232,251,1,5,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137, ++ 72,139,141,233,255,72,137,132,253,36,233,255,221.0,133,233,255,217.0,133, ++ 233,255,252,243.0,15.0,126,133,233,255,252,243.0,15.0,90,133,233,255,221.0, ++ 156,253,36,233,255,217.0,156,253,36,233,255,102.0,15.0,214,132,253,36,233, ++ 255,252,242.0,15.0,90,192,102.0,15.0,214,132,253,36,233,255,252,242.0,15.0, ++ 90,192,102.0,15.0,126,132,253,36,233,255,85,72,137,229,65,84,72,129.0,252, ++ 236,239,102.0,15.0,214,69,252,240,102.0,15.0,214,77,232,102.0,15.0,214,85, ++ 224,102.0,15.0,214,93,216,102.0,15.0,214,101,208,102.0,15.0,214,109,200,102.0, ++ 15.0,214,117,192,102.0,15.0,214,125,184,72,137,125,176,72,137,117,168,72, ++ 137,85,160,72,137,77,152,76,137,69,144,76,137,77,136,255,73,188,237,237,255, ++ 72,199.0,194,237,72,199.0,198,237,76,137,231,232,251,1,0,255,72,199.0,194, ++ 237,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, ++ 1,0,255,72,199.0,194,237,72,199.0,198,237,76,137,231,232,251,1,0,72,186,237, ++ 237,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, ++ 1,1,255,72,137,8,72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137, ++ 231,232,251,1,2,255,72,186,237,237,72,199.0,198,0,0.0,0.0,0.0,76,137,231, ++ 232,251,1,1,255,72,137,8,255,102.0,15.0,214,0,255,217.0,24,255,217.0,88,4, ++ 255,102.0,15.0,214,64,8,255,76,137,231,232,251,1,3,255,15.0,182,201,72,137, ++ 206,76,137,231,232,251,1,4,255,15.0,182,201,255,15.0,190,201,255,72,137,206, ++ 76,137,231,232,251,1,5,255,15.0,183,201,255,15.0,191,201,255,72,137,206,76, ++ 137,231,232,251,1,6,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237, ++ 76,137,231,232,251,1,7,255,72,199.0,194,237,72,199.0,198,252,254,252,255.0, ++ 252,255.0,252,255.0,76,137,231,232,251,1,0,72,185,237,237,72,199.0,194,252, ++ 255,252,255.0,252,255.0,252,255.0,72,199.0,198,252,254,252,255.0,252,255.0, ++ 252,255.0,76,137,231,232,251,1,8,72,137,68,36,32,72,199.0,198,252,252,252, ++ 255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,72,139,68,36,32,255,72,199.0, ++ 194,237,72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137,231,232, ++ 251,1,0,72,185,237,237,72,199.0,194,252,255,252,255.0,252,255.0,252,255.0, ++ 72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,10, ++ 137,68,36,32,72,199.0,198,252,252,252,255.0,252,255.0,252,255.0,76,137,231, ++ 232,251,1,9,139,68,36,32,255,72,199.0,198,252,254,252,255.0,252,255.0,252, ++ 255.0,76,137,231,232,251,1,9,255,72,199.0,198,252,255,252,255.0,252,255.0, ++ 252,255.0,76,137,231,232,251,1,11,255,72,199.0,198,252,255,252,255.0,252, ++ 255.0,252,255.0,76,137,231,232,251,1,12,255,137,68,36,32,72,199.0,198,252, ++ 253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,139,68,36,32,255, ++ 72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,13, ++ 255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, ++ 1,14,255,72,137,68,36,32,72,199.0,198,252,253,252,255.0,252,255.0,252,255.0, ++ 76,137,231,232,251,1,9,72,139,68,36,32,255,72,199.0,198,252,255,252,255.0, ++ 252,255.0,252,255.0,76,137,231,232,251,1,15,72,137,68,36,32,72,199.0,198, ++ 252,253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,72,139,68,36, ++ 32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232, ++ 251,1,16,102.0,15.0,214,68,36,32,72,199.0,198,252,253,252,255.0,252,255.0, ++ 252,255.0,76,137,231,232,251,1,9,255,252,242.0,15.0,90,68,36,32,255,252,243.0, ++ 15.0,126,68,36,32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0, ++ 76,137,231,232,251,1,17,102.0,15.0,214,68,36,32,72,199.0,198,252,253,252, ++ 255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,252,243.0,15.0,126,68,36, ++ 32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232, ++ 251,1,18,102.0,15.0,214,68,36,32,102.0,15.0,214,76,36,40,72,199.0,198,252, ++ 253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,252,243.0,15.0,126, ++ 68,36,32,252,243.0,15.0,126,76,36,40,255,72,139,141,233,72,199.0,194,252, ++ 255,252,255.0,252,255.0,252,255.0,76,137,230,72,137,207,232,251,1,18,72,131.0, ++ 252,236,4,72,199.0,198,252,253,252,255.0,252,255.0,252,255.0,76,137,231,232, ++ 251,1,9,255,76,139,101,252,248,72,137,252,236,93,194,236,255,85,72,137,229, ++ 65,84,65,85,73,137,252,252,76,137,231,232,251,1,19,73,137,197,72,129.0,252, ++ 248,239,255,15.0,141,244,248.0,102,184,0,0.0,72,190,237,237,76,137,231,232, ++ 251,1,20,248,2,15.0,142,244,247.0,102,184,0,0.0,72,190,237,237,76,137,231, ++ 232,251,1,20,255,15.0,141,244,247.0,102,184,0,0.0,72,190,237,237,76,137,231, ++ 232,251,1,20,255,248,1,255,72,193.0,224,4,72,41,196,72,129.0,252,236,239, ++ 255,72,186,237,237,72,199.0,198,0,0.0,0.0,0.0,76,137,231,232,251,1,1,72,131.0, ++ 252,236,16,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137,231, ++ 232,251,1,8,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137,231, ++ 232,251,1,21,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137, ++ 231,232,251,1,10,255,72,199.0,198,237,76,137,231,232,251,1,12,255,15.0,182, ++ 192,255,15.0,190,192,255,15.0,183,192,255,15.0,191,192,255,72,199.0,198,237, ++ 76,137,231,232,251,1,12,131.0,252,248,0,15.0,149.0,208,15.0,182,192,255,72, ++ 199.0,198,237,76,137,231,232,251,1,11,255,72,199.0,198,237,76,137,231,232, ++ 251,1,15,255,72,199.0,198,237,76,137,231,232,251,1,13,255,72,199.0,198,237, ++ 76,137,231,232,251,1,14,255,72,199.0,198,237,76,137,231,232,251,1,16,255, ++ 72,199.0,198,237,76,137,231,232,251,1,18,255,252,243.0,15.0,126,193,255,72, ++ 141,132,253,36,233,72,131.0,252,236,4,72,199.0,194,237,76,137,230,72,137, ++ 199,232,251,1,18,255,72,199.0,198,237,76,137,231,232,251,1,17,255,72,199.0, ++ 198,237,76,137,231,232,251,1,17,137,4,36,217.0,4,36,255,137,20,36,217.0,4, ++ 36,255,72,137,224,72,129.0,192,239,73,137,192,72,199.0,193,237,76,137,252, ++ 234,72,199.0,198,237,76,137,231,232,251,1,22,255,72,137,224,72,129.0,192, ++ 239,73,137,192,72,199.0,193,237,76,137,252,234,72,199.0,198,237,76,137,231, ++ 232,251,1,23,255,72,137,224,72,129.0,192,239,73,137,193,73,199.0,192,237, ++ 72,199.0,193,237,76,137,252,234,72,199.0,198,237,76,137,231,232,251,1,24, ++ 255,72,185,237,237,139,1,72,137,199,232,251,1,25,255,72,131.0,196,32,255, ++ 252,243.0,15.0,126,188,253,36,233,255,252,243.0,15.0,126,180,253,36,233,255, ++ 252,243.0,15.0,126,172,253,36,233,255,252,243.0,15.0,126,164,253,36,233,255, ++ 252,243.0,15.0,126,156,253,36,233,255,252,243.0,15.0,126,148,253,36,233,255, ++ 252,243.0,15.0,126,140,253,36,233,255,252,243.0,15.0,126,132,253,36,233,255, ++ 76,139,140,253,36,233,255,76,139,132,253,36,233,255,72,139,140,253,36,233, ++ 255,72,139,148,253,36,233,255,72,139,180,253,36,233,255,72,139,60,36,255, ++ 72,129.0,196,239,255,176,8,255,232,251,1,26,72,131.0,252,236,48,255,72,137, ++ 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199.0,198,237, ++ 76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0.0,0.0,0.0,76,139, ++ 109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,72,137,68,36,32, ++ 232,251,1,27,72,185,237,237,137,1,72,139,68,36,32,72,137,198,76,137,231,232, ++ 251,1,28,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, ++ 236,93,195,255,72,137,68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237, ++ 237,72,199.0,198,0,0.0,0.0,0.0,76,137,231,232,251,1,1,72,139,76,36,32,72, ++ 137,8,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, ++ 236,93,195,255,102.0,15.0,214,68,36,32,232,251,1,27,72,185,237,237,137,1, ++ 72,186,237,237,72,199.0,198,237,76,137,231,232,251,1,1,72,139,76,36,32,72, ++ 137,8,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, ++ 236,93,195,255,102.0,15.0,214,76,36,40,102.0,15.0,214,68,36,32,232,251,1, ++ 27,72,185,237,237,137,1,72,186,237,237,72,199.0,198,237,76,137,231,232,251, ++ 1,1,72,139,76,36,40,72,137,72,8,72,139,76,36,32,72,137,8,184,1,0.0,0.0,0.0, ++ 76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,232,251,1, ++ 27,72,185,237,237,137,1,184,0,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252, ++ 248,72,137,252,236,93,195,255,15.0,182,192,137,68,36,32,232,251,1,27,72,185, ++ 237,237,137,1,139,68,36,32,72,137,198,76,137,231,232,251,1,4,184,1,0.0,0.0, ++ 0.0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,137,68, ++ 36,32,232,251,1,27,72,185,237,237,137,1,139,68,36,32,72,137,198,76,137,231, ++ 232,251,1,5,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137, + 252,236,93,195,255,137,68,36,32,232,251,1,27,72,185,237,237,137,1,139,68, +- 36,32,72,137,198,76,137,231,232,251,1,6,184,1,0,0,0,76,139,109,252,240,76, +- 139,101,252,248,72,137,252,236,93,195,255,252,243,15,90,192,102,15,214,68, +- 36,32,232,251,1,27,72,185,237,237,137,1,252,243,15,126,68,36,32,76,137,231, +- 232,251,1,3,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252, +- 236,93,195,255 ++ 36,32,72,137,198,76,137,231,232,251,1,6,184,1,0.0,0.0,0.0,76,139,109,252, ++ 240,76,139,101,252,248,72,137,252,236,93,195,255,252,243.0,15.0,90,192,102.0, ++ 15.0,214,68,36,32,232,251,1,27,72,185,237,237,137,1,252,243.0,15.0,126,68, ++ 36,32,76,137,231,232,251,1,3,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139, ++ 101,252,248,72,137,252,236,93,195,255 + }; + + static const char *const globnames[] = { +@@ -228,6 +236,8 @@ void compile_globals(struct jit* jit, lua_State* L) + * stack + */ + ++ ++ + compile(Dst, L, NULL, LUA_NOREF); + } + +@@ -355,7 +365,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int + reg->off += 8; + } else { + dasm_put(Dst, 1, reg->off); ++#if defined __amd64__ || defined _WIN64 ++ /* The parameters to a function on stack are always 8 byte aligned. */ ++ reg->off += 8; ++#else + reg->off += 4; ++#endif + } + } + +diff --git a/extra/luaffifb/call_x86.dasc b/extra/luaffifb/call_x86.dasc +index 4a72b98..404683b 100755 +--- a/extra/luaffifb/call_x86.dasc ++++ b/extra/luaffifb/call_x86.dasc +@@ -506,7 +506,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int + reg->off += 8; + } else { + | mov ecx, [rbp + reg->off] ++#if defined __amd64__ || defined _WIN64 ++ /* The parameters to a function on stack are always 8 byte aligned. */ ++ reg->off += 8; ++#else + reg->off += 4; ++#endif + } + } + +diff --git a/extra/luaffifb/call_x86.h b/extra/luaffifb/call_x86.h +index 4611a3c..05094ab 100755 +--- a/extra/luaffifb/call_x86.h ++++ b/extra/luaffifb/call_x86.h +@@ -20,94 +20,101 @@ + + static const unsigned char build_actionlist[1915] = { + 139,141,233,255,139,141,233,139,149,233,255,137,132,253,36,233,255,137,132, +- 253,36,233,137,148,253,36,233,255,221,133,233,255,217,133,233,255,252,243, +- 15,126,133,233,255,252,243,15,90,133,233,255,221,156,253,36,233,255,217,156, +- 253,36,233,255,102,15,214,132,253,36,233,255,252,242,15,90,192,102,15,214, +- 132,253,36,233,255,252,242,15,90,192,102,15,126,132,253,36,233,255,85,137, +- 229,87,129,252,236,239,255,137,77,252,248,137,85,252,244,255,191,237,255, +- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,0,255,199,68,36,8,237, +- 199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,0,255,199, +- 68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,0,199,68,36,8,237,199,68, +- 36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,1,255,137,8,199, +- 68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,2,255,199,68, +- 36,8,237,199,68,36,4,0,0,0,0,137,60,36,232,251,1,1,255,137,8,137,80,4,255, +- 137,8,255,102,15,214,0,255,217,24,255,217,88,4,255,221,24,255,221,88,8,255, +- 221,92,36,4,137,60,36,232,251,1,3,255,15,182,201,137,76,36,4,137,60,36,232, +- 251,1,4,255,15,182,201,255,15,190,201,255,137,76,36,4,137,60,36,232,251,1, +- 5,255,15,183,201,255,15,191,201,255,137,76,36,4,137,60,36,232,251,1,6,255, +- 199,68,36,12,0,0,0,0,199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1, +- 7,255,199,68,36,8,237,199,68,36,4,252,254,252,255,252,255,252,255,137,60, +- 36,232,251,1,0,199,68,36,12,237,199,68,36,8,252,255,252,255,252,255,252,255, +- 199,68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,8,137,68, +- 36,32,199,68,36,4,252,252,252,255,252,255,252,255,137,60,36,232,251,1,9,139, +- 68,36,32,255,199,68,36,8,237,199,68,36,4,252,254,252,255,252,255,252,255, +- 137,60,36,232,251,1,0,199,68,36,12,237,199,68,36,8,252,255,252,255,252,255, +- 252,255,199,68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,10, +- 137,68,36,32,199,68,36,4,252,252,252,255,252,255,252,255,137,60,36,232,251, +- 1,9,139,68,36,32,255,199,68,36,4,252,254,252,255,252,255,252,255,137,60,36, +- 232,251,1,9,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232, +- 251,1,11,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251, +- 1,12,255,137,68,36,32,199,68,36,4,252,253,252,255,252,255,252,255,137,60, +- 36,232,251,1,9,139,68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255, +- 137,60,36,232,251,1,13,255,199,68,36,4,252,255,252,255,252,255,252,255,137, +- 60,36,232,251,1,14,255,137,68,36,32,137,84,36,36,199,68,36,4,252,253,252, +- 255,252,255,252,255,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199, +- 68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,15,137,68,36, +- 32,199,68,36,4,252,253,252,255,252,255,252,255,137,60,36,232,251,1,9,139, +- 68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251, +- 1,16,255,221,92,36,32,199,68,36,4,252,253,252,255,252,255,252,255,137,60, +- 36,232,251,1,9,221,68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255, +- 137,60,36,232,251,1,17,137,68,36,32,137,84,36,36,199,68,36,4,252,253,252, +- 255,252,255,252,255,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199, +- 68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,18,102,15,214, +- 68,36,32,102,15,214,76,36,40,199,68,36,4,252,253,252,255,252,255,252,255, +- 137,60,36,232,251,1,9,252,243,15,126,68,36,32,252,243,15,126,76,36,40,255, +- 139,141,233,199,68,36,8,252,255,252,255,252,255,252,255,137,124,36,4,137, +- 12,36,232,251,1,18,131,252,236,4,199,68,36,4,252,253,252,255,252,255,252, +- 255,137,60,36,232,251,1,9,255,139,125,252,252,137,252,236,93,194,236,255, +- 85,137,229,87,86,139,189,233,131,252,236,16,137,60,36,232,251,1,19,137,198, +- 129,252,248,239,255,15,141,244,248,102,184,0,0,199,68,36,4,237,137,60,36, +- 232,251,1,20,248,2,15,142,244,247,102,184,0,0,199,68,36,4,237,137,60,36,232, +- 251,1,20,255,15,141,244,247,102,184,0,0,199,68,36,4,237,137,60,36,232,251, +- 1,20,255,248,1,255,193,224,4,41,196,129,252,236,239,255,199,68,36,8,237,199, +- 68,36,4,0,0,0,0,137,60,36,232,251,1,1,131,252,236,16,255,199,68,36,12,237, +- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,8,255,199,68,36,12,237, +- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,21,255,199,68,36,12,237, +- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,10,255,199,68,36,4,237, +- 137,60,36,232,251,1,12,255,15,182,192,255,15,190,192,255,15,183,192,255,15, +- 191,192,255,199,68,36,4,237,137,60,36,232,251,1,12,131,252,248,0,15,149,208, +- 15,182,192,255,199,68,36,4,237,137,60,36,232,251,1,11,255,199,68,36,4,237, +- 137,60,36,232,251,1,15,255,199,68,36,4,237,137,60,36,232,251,1,13,255,199, +- 68,36,4,237,137,60,36,232,251,1,14,255,199,68,36,4,237,137,60,36,232,251, +- 1,16,255,199,68,36,4,237,137,60,36,232,251,1,18,255,252,243,15,126,193,255, +- 141,132,253,36,233,131,252,236,4,199,68,36,8,237,137,124,36,4,137,4,36,232, +- 251,1,18,255,199,68,36,4,237,137,60,36,232,251,1,17,255,199,68,36,4,237,137, +- 60,36,232,251,1,17,137,4,36,217,4,36,255,137,20,36,217,4,36,255,137,224,129, +- 192,239,137,68,36,12,137,116,36,8,199,68,36,4,237,137,60,36,232,251,1,22, +- 255,185,237,139,1,137,4,36,232,251,1,23,255,131,196,32,255,139,148,253,36, +- 233,255,139,12,36,255,129,196,239,255,232,251,1,24,131,252,236,48,255,137, +- 68,36,32,232,251,1,25,185,237,137,1,199,68,36,8,237,199,68,36,4,237,137,60, +- 36,232,251,1,1,139,76,36,32,137,8,184,1,0,0,0,139,117,252,248,139,125,252, +- 252,137,252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68, +- 36,32,137,68,36,4,137,60,36,232,251,1,26,184,1,0,0,0,139,117,252,248,139, +- 125,252,252,137,252,236,93,195,255,137,84,36,36,137,68,36,32,232,251,1,25, +- 185,237,137,1,199,68,36,8,237,199,68,36,4,0,0,0,0,137,60,36,232,251,1,1,139, +- 76,36,36,139,84,36,32,137,72,4,137,16,184,1,0,0,0,139,117,252,248,139,125, +- 252,252,137,252,236,93,195,255,137,68,36,32,137,84,36,36,232,251,1,25,185, +- 237,137,1,199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,1,139,76,36, +- 32,137,8,139,76,36,36,137,72,4,184,1,0,0,0,139,117,252,248,139,125,252,252, +- 137,252,236,93,195,255,131,252,236,4,232,251,1,25,185,237,137,1,184,1,0,0, +- 0,139,117,252,248,139,125,252,252,137,252,236,93,195,255,232,251,1,25,185, +- 237,137,1,184,0,0,0,0,139,117,252,248,139,125,252,252,137,252,236,93,195, +- 255,15,182,192,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68, +- 36,4,137,60,36,232,251,1,4,184,1,0,0,0,139,117,252,248,139,125,252,252,137, +- 252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137, +- 68,36,4,137,60,36,232,251,1,5,184,1,0,0,0,139,117,252,248,139,125,252,252, ++ 253,36,233,137,148,253,36,233,255,221.0,133,233,255,217.0,133,233,255,252, ++ 243.0,15.0,126,133,233,255,252,243.0,15.0,90,133,233,255,221.0,156,253,36, ++ 233,255,217.0,156,253,36,233,255,102.0,15.0,214,132,253,36,233,255,252,242.0, ++ 15.0,90,192,102.0,15.0,214,132,253,36,233,255,252,242.0,15.0,90,192,102.0, ++ 15.0,126,132,253,36,233,255,85,137,229,87,129.0,252,236,239,255,137,77,252, ++ 248,137,85,252,244,255,191,237,255,199.0,68,36,8,237,199.0,68,36,4,237,137, ++ 60,36,232,251,1,0,255,199.0,68,36,8,237,199.0,68,36,4,252,255,252,255.0,252, ++ 255.0,252,255.0,137,60,36,232,251,1,0,255,199.0,68,36,8,237,199.0,68,36,4, ++ 237,137,60,36,232,251,1,0,199.0,68,36,8,237,199.0,68,36,4,252,255,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,1,255,137,8,199.0,68,36,4,252,254, ++ 252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,2,255,199.0,68,36,8,237, ++ 199.0,68,36,4,0,0.0,0.0,0.0,137,60,36,232,251,1,1,255,137,8,137,80,4,255, ++ 137,8,255,102.0,15.0,214,0,255,217.0,24,255,217.0,88,4,255,221.0,24,255,221.0, ++ 88,8,255,221.0,92,36,4,137,60,36,232,251,1,3,255,15.0,182,201,137,76,36,4, ++ 137,60,36,232,251,1,4,255,15.0,182,201,255,15.0,190,201,255,137,76,36,4,137, ++ 60,36,232,251,1,5,255,15.0,183,201,255,15.0,191,201,255,137,76,36,4,137,60, ++ 36,232,251,1,6,255,199.0,68,36,12,0,0.0,0.0,0.0,199.0,68,36,8,237,199.0,68, ++ 36,4,237,137,60,36,232,251,1,7,255,199.0,68,36,8,237,199.0,68,36,4,252,254, ++ 252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,0,199.0,68,36,12,237,199.0, ++ 68,36,8,252,255,252,255.0,252,255.0,252,255.0,199.0,68,36,4,252,254,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,8,137,68,36,32,199.0,68,36,4,252, ++ 252,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36,32,255, ++ 199.0,68,36,8,237,199.0,68,36,4,252,254,252,255.0,252,255.0,252,255.0,137, ++ 60,36,232,251,1,0,199.0,68,36,12,237,199.0,68,36,8,252,255,252,255.0,252, ++ 255.0,252,255.0,199.0,68,36,4,252,254,252,255.0,252,255.0,252,255.0,137,60, ++ 36,232,251,1,10,137,68,36,32,199.0,68,36,4,252,252,252,255.0,252,255.0,252, ++ 255.0,137,60,36,232,251,1,9,139,68,36,32,255,199.0,68,36,4,252,254,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,9,255,199.0,68,36,4,252,255,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,11,255,199.0,68,36,4,252,255,252, ++ 255.0,252,255.0,252,255.0,137,60,36,232,251,1,12,255,137,68,36,32,199.0,68, ++ 36,4,252,253,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36, ++ 32,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232, ++ 251,1,13,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36, ++ 232,251,1,14,255,137,68,36,32,137,84,36,36,199.0,68,36,4,252,253,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199.0, ++ 68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,15,137, ++ 68,36,32,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137,60,36,232, ++ 251,1,9,139,68,36,32,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0, ++ 137,60,36,232,251,1,16,255,221.0,92,36,32,199.0,68,36,4,252,253,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,9,221.0,68,36,32,255,199.0,68,36, ++ 4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,17,137,68,36, ++ 32,137,84,36,36,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137,60, ++ 36,232,251,1,9,139,68,36,32,139,84,36,36,255,199.0,68,36,4,252,255,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,18,102.0,15.0,214,68,36,32,102.0, ++ 15.0,214,76,36,40,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137, ++ 60,36,232,251,1,9,252,243.0,15.0,126,68,36,32,252,243.0,15.0,126,76,36,40, ++ 255,139,141,233,199.0,68,36,8,252,255,252,255.0,252,255.0,252,255.0,137,124, ++ 36,4,137,12,36,232,251,1,18,131.0,252,236,4,199.0,68,36,4,252,253,252,255.0, ++ 252,255.0,252,255.0,137,60,36,232,251,1,9,255,139,125,252,252,137,252,236, ++ 93,194,236,255,85,137,229,87,86,139,189,233,131.0,252,236,16,137,60,36,232, ++ 251,1,19,137,198,129.0,252,248,239,255,15.0,141,244,248.0,102,184,0,0.0,199.0, ++ 68,36,4,237,137,60,36,232,251,1,20,248,2,15.0,142,244,247.0,102,184,0,0.0, ++ 199.0,68,36,4,237,137,60,36,232,251,1,20,255,15.0,141,244,247.0,102,184,0, ++ 0.0,199.0,68,36,4,237,137,60,36,232,251,1,20,255,248,1,255,193.0,224,4,41, ++ 196,129.0,252,236,239,255,199.0,68,36,8,237,199.0,68,36,4,0,0.0,0.0,0.0,137, ++ 60,36,232,251,1,1,131.0,252,236,16,255,199.0,68,36,12,237,199.0,68,36,8,237, ++ 199.0,68,36,4,237,137,60,36,232,251,1,8,255,199.0,68,36,12,237,199.0,68,36, ++ 8,237,199.0,68,36,4,237,137,60,36,232,251,1,21,255,199.0,68,36,12,237,199.0, ++ 68,36,8,237,199.0,68,36,4,237,137,60,36,232,251,1,10,255,199.0,68,36,4,237, ++ 137,60,36,232,251,1,12,255,15.0,182,192,255,15.0,190,192,255,15.0,183,192, ++ 255,15.0,191,192,255,199.0,68,36,4,237,137,60,36,232,251,1,12,131.0,252,248, ++ 0,15.0,149.0,208,15.0,182,192,255,199.0,68,36,4,237,137,60,36,232,251,1,11, ++ 255,199.0,68,36,4,237,137,60,36,232,251,1,15,255,199.0,68,36,4,237,137,60, ++ 36,232,251,1,13,255,199.0,68,36,4,237,137,60,36,232,251,1,14,255,199.0,68, ++ 36,4,237,137,60,36,232,251,1,16,255,199.0,68,36,4,237,137,60,36,232,251,1, ++ 18,255,252,243.0,15.0,126,193,255,141,132,253,36,233,131.0,252,236,4,199.0, ++ 68,36,8,237,137,124,36,4,137,4,36,232,251,1,18,255,199.0,68,36,4,237,137, ++ 60,36,232,251,1,17,255,199.0,68,36,4,237,137,60,36,232,251,1,17,137,4,36, ++ 217.0,4,36,255,137,20,36,217.0,4,36,255,137,224,129.0,192,239,137,68,36,12, ++ 137,116,36,8,199.0,68,36,4,237,137,60,36,232,251,1,22,255,185,237,139,1,137, ++ 4,36,232,251,1,23,255,131.0,196,32,255,139,148,253,36,233,255,139,12,36,255, ++ 129.0,196,239,255,232,251,1,24,131.0,252,236,48,255,137,68,36,32,232,251, ++ 1,25,185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,237,137,60,36,232,251, ++ 1,1,139,76,36,32,137,8,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252, + 137,252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36, +- 32,137,68,36,4,137,60,36,232,251,1,6,184,1,0,0,0,139,117,252,248,139,125, +- 252,252,137,252,236,93,195,255,221,92,36,4,232,251,1,25,185,237,137,1,137, +- 60,36,232,251,1,3,184,1,0,0,0,139,117,252,248,139,125,252,252,137,252,236, +- 93,195,255 ++ 32,137,68,36,4,137,60,36,232,251,1,26,184,1,0.0,0.0,0.0,139,117,252,248,139, ++ 125,252,252,137,252,236,93,195,255,137,84,36,36,137,68,36,32,232,251,1,25, ++ 185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,0,0.0,0.0,0.0,137,60,36,232, ++ 251,1,1,139,76,36,36,139,84,36,32,137,72,4,137,16,184,1,0.0,0.0,0.0,139,117, ++ 252,248,139,125,252,252,137,252,236,93,195,255,137,68,36,32,137,84,36,36, ++ 232,251,1,25,185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,237,137,60,36, ++ 232,251,1,1,139,76,36,32,137,8,139,76,36,36,137,72,4,184,1,0.0,0.0,0.0,139, ++ 117,252,248,139,125,252,252,137,252,236,93,195,255,131.0,252,236,4,232,251, ++ 1,25,185,237,137,1,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137, ++ 252,236,93,195,255,232,251,1,25,185,237,137,1,184,0,0.0,0.0,0.0,139,117,252, ++ 248,139,125,252,252,137,252,236,93,195,255,15.0,182,192,137,68,36,32,232, ++ 251,1,25,185,237,137,1,139,68,36,32,137,68,36,4,137,60,36,232,251,1,4,184, ++ 1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93,195,255,137, ++ 68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68,36,4,137,60,36,232, ++ 251,1,5,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93, ++ 195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68,36,4, ++ 137,60,36,232,251,1,6,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137, ++ 252,236,93,195,255,221.0,92,36,4,232,251,1,25,185,237,137,1,137,60,36,232, ++ 251,1,3,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93, ++ 195,255 + }; + + static const char *const globnames[] = { +@@ -211,6 +218,8 @@ void compile_globals(struct jit* jit, lua_State* L) + * stack + */ + ++ ++ + compile(Dst, L, NULL, LUA_NOREF); + } + +@@ -338,7 +347,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int + reg->off += 8; + } else { + dasm_put(Dst, 0, reg->off); ++#if defined __amd64__ || defined _WIN64 ++ /* The parameters to a function on stack are always 8 byte aligned. */ ++ reg->off += 8; ++#else + reg->off += 4; ++#endif + } + } + +diff --git a/extra/luaffifb/ffi.h b/extra/luaffifb/ffi.h +index dabdc9b..49584b1 100644 +--- a/extra/luaffifb/ffi.h ++++ b/extra/luaffifb/ffi.h +@@ -105,7 +105,7 @@ static char* luaL_prepbuffsize(luaL_Buffer* B, size_t sz) { + } + return luaL_prepbuffer(B); + } +-#elif LUA_VERSION_NUM == 503 ++#elif LUA_VERSION_NUM >= 503 + static void (lua_remove)(lua_State *L, int idx) { + lua_remove(L, idx); + } +diff --git a/extra/luaffifb/test.c b/extra/luaffifb/test.c +index 20f5d13..2e8c612 100644 +--- a/extra/luaffifb/test.c ++++ b/extra/luaffifb/test.c +@@ -718,3 +718,10 @@ void test_call_pppppiiifii(void* p1, void* p2, void* p3, void* p4, void* p5, int + sprintf(buf, "%p %p %p %p %p %d %d %d %0.1f %d %d", p1, p2, p3, p4, p5, i1, i2, i3, f4, i5, i6); + } + ++typedef int (*cb_t)(char i1, char i2, char i3, char i4, char i5, char i6, char i7, char i8, char i9, char i10); ++EXPORT void test_callback_cccccccccc(cb_t func); ++void test_callback_cccccccccc(cb_t func) ++{ ++ func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ++} ++ +diff --git a/extra/luaffifb/test.lua b/extra/luaffifb/test.lua +index 8a9b718..5151857 100644 +--- a/extra/luaffifb/test.lua ++++ b/extra/luaffifb/test.lua +@@ -925,6 +925,8 @@ void test_call_pppppiiiiii(void* p1, void* p2, void* p3, void* p4, void* p5, int + void test_call_pppppffffff(void* p1, void* p2, void* p3, void* p4, void* p5, float f1, float f2, float f3, float f4, float f5, float f6); + void test_call_pppppiifiii(void* p1, void* p2, void* p3, void* p4, void* p5, int i1, int i2, float f3, int i4, int i5, int i6); + void test_call_pppppiiifii(void* p1, void* p2, void* p3, void* p4, void* p5, int i1, int i2, int i3, float i4, int i5, int i6); ++typedef int (*cb_t)(char i1, char i2, char i3, char i4, char i5, char i6, char i7, char i8, char i9, char i10); ++void test_callback_cccccccccc(cb_t func); + ]] + + ffi.C.test_call_echo("input") +@@ -947,6 +949,15 @@ assert(ffi.C.buf == "0x1 0x2 0x3 0x4 0x5 6 7 8.5 9 10 11") + ffi.C.test_call_pppppiiifii(ptr(1), ptr(2), ptr(3), ptr(4), ptr(5), 6, 7, 8, 9.5, 10, 11) + assert(ffi.C.buf == "0x1 0x2 0x3 0x4 0x5 6 7 8 9.5 10 11") + ++ ++local function callback(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10) ++ local t = {i1, i2, i3, i4, i5, i6, i7, i8, i9, i10} ++ for i, v in ipairs(t) do ++ assert(i == v) ++ end ++end ++ffi.C.test_callback_cccccccccc(callback) ++ + local sum = ffi.C.add_dc(ffi.new('complex', 1, 2), ffi.new('complex', 3, 5)) + assert(ffi.istype('complex', sum)) + +Submodule extra/luafilesystem 3c4e563..0951178: +diff --git a/extra/luafilesystem/.github/workflows/ci.yml b/extra/luafilesystem/.github/workflows/ci.yml +new file mode 100644 +index 0000000..2e8364e +--- /dev/null ++++ b/extra/luafilesystem/.github/workflows/ci.yml +@@ -0,0 +1,78 @@ ++name: ci ++ ++on: ++ pull_request: ++ push: ++ branches: ++ - master ++ ++jobs: ++ TestMatrix: ++ strategy: ++ matrix: ++ lua-version: ["5.4", "5.3", "5.2", "5.1", "luajit"] ++ os: ["ubuntu-latest"] ++ libflag: ["-shared --coverage"] ++ include: ++ - os: "macos-latest" ++ lua-version: "5.4" ++ libflag: "-bundle -undefined dynamic_lookup -all_load --coverage" ++ - os: "windows-latest" ++ toolchain: "msvc" ++ lua-version: "5.4" ++ - os: "windows-latest" ++ lua-version: "luajit" ++ runs-on: ${{ matrix.os }} ++ ++ steps: ++ - uses: actions/checkout@master ++ ++ - name: Setup MSVC ++ # 'luarocks/gh-actions-lua' step requires msvc to build PUC-Rio Lua ++ # versions on Windows (LuaJIT will be built using MinGW/gcc). ++ if: ${{ matrix.toolchain == 'msvc' }} ++ uses: ilammy/msvc-dev-cmd@v1 ++ ++ - uses: luarocks/gh-actions-lua@master ++ with: ++ luaVersion: ${{ matrix.lua-version }} ++ ++ - uses: luarocks/gh-actions-luarocks@master ++ ++ - name: Prep luacov ++ run: | ++ luarocks install luacov ++ ++ - name: Prep luacov-coveralls ++ # TODO Windows coverage ++ if: ${{ matrix.os != 'windows-latest' }} ++ run: | ++ pip install --user cpp-coveralls ++ # install luacov-coveralls, but avoid installing luafilesystem ++ luarocks install lua-path ++ luarocks install dkjson ++ luarocks install luacov-coveralls --server=https://luarocks.org/dev --deps-mode=none ++ ++ - name: Unix Build ++ if: ${{ matrix.os != 'windows-latest' }} ++ run: | ++ luarocks make CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="${{ matrix.libflag }}" ++ ++ - name: Windows Build ++ # TODO Windows coverage ++ if: ${{ matrix.os == 'windows-latest' }} ++ run: | ++ luarocks make ++ ++ - name: Test ++ run: | ++ lua -lluacov tests/test.lua ++ ++ - name: Coverage ++ # TODO Windows coverage ++ if: ${{ matrix.os != 'windows-latest' }} ++ run: | ++ export MY_PYTHON_VER=$(python -c 'import sys; print(".".join(sys.version.split(".")[0:2]))') ++ export PATH="/Users/runner/Library/Python/$MY_PYTHON_VER/bin:$PATH" ++ coveralls -b . -i src --dump c.report.json ++ luacov-coveralls -j c.report.json -v -t ${{ secrets.GITHUB_TOKEN }} +diff --git a/extra/luafilesystem/.travis.yml b/extra/luafilesystem/.travis.yml +deleted file mode 100644 +index 04d46ac..0000000 +--- a/extra/luafilesystem/.travis.yml ++++ /dev/null +@@ -1,35 +0,0 @@ +-language: c +- +-sudo: false +- +-env: +- - LUA="lua 5.1" +- - LUA="lua 5.2" +- - LUA="lua 5.3" +- - LUA="luajit 2.0" +- +-before_install: +- - pip install --user cpp-coveralls hererocks +- - hererocks env --$LUA --luarocks latest +- - export PATH="$PWD/env/bin:$PATH" +- - luarocks install Lua-cURL --server=https://luarocks.org/dev +- - luarocks install lua-path +- - luarocks install lua-cjson +- - luarocks install luacov +- # install luacov-coveralls, but avoids installing luafilesystem +- - luarocks install luacov-coveralls --server=https://luarocks.org/dev --deps-mode=none +- +-install: +- - luarocks make rockspecs/luafilesystem-cvs-3.rockspec CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="-shared --coverage" +- +-script: +- - lua -lluacov tests/test.lua +- +-after_success: +- - coveralls -b . -i src --dump c.report.json +- - luacov-coveralls -j c.report.json -v +- +-notifications: +- email: +- on_success: change +- on_failure: always +diff --git a/extra/luafilesystem/LICENSE b/extra/luafilesystem/LICENSE +index 8475345..07ccdb1 100644 +--- a/extra/luafilesystem/LICENSE ++++ b/extra/luafilesystem/LICENSE +@@ -1,4 +1,5 @@ +-Copyright © 2003-2014 Kepler Project. ++Copyright © 2003-2010 Kepler Project. ++Copyright © 2010-2022 The LuaFileSystem authors. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation +diff --git a/extra/luafilesystem/Makefile b/extra/luafilesystem/Makefile +index e50d5a0..a7312a4 100644 +--- a/extra/luafilesystem/Makefile ++++ b/extra/luafilesystem/Makefile +@@ -12,14 +12,14 @@ OBJS= src/$T.o + lib: src/lfs.so + + src/lfs.so: $(OBJS) +- MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; $(CC) $(LIB_OPTION) -o src/lfs.so $(OBJS) ++ MACOSX_DEPLOYMENT_TARGET=$(MACOSX_DEPLOYMENT_TARGET); export MACOSX_DEPLOYMENT_TARGET; $(CC) $(LIB_OPTION) -o src/lfs.so $(OBJS) + + test: lib + LUA_CPATH=./src/?.so lua tests/test.lua + + install: +- mkdir -p $(LUA_LIBDIR) +- cp src/lfs.so $(LUA_LIBDIR) ++ mkdir -p $(DESTDIR)$(LUA_LIBDIR) ++ cp src/lfs.so $(DESTDIR)$(LUA_LIBDIR) + + clean: + rm -f src/lfs.so $(OBJS) +diff --git a/extra/luafilesystem/README.md b/extra/luafilesystem/README.md +index e2a806a..7e008b8 100644 +--- a/extra/luafilesystem/README.md ++++ b/extra/luafilesystem/README.md +@@ -1,12 +1,11 @@ +-[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENCE.txt) +-[![Build Status](https://travis-ci.org/keplerproject/luafilesystem.svg?branch=master)](https://travis-ci.org/keplerproject/luafilesystem) ++[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) ++[![Build Status](https://github.com/lunarmodules/luafilesystem/actions/workflows/ci.yml/badge.svg)](https://github.com/lunarmodules/luafilesystem/actions) + [![Build status](https://ci.appveyor.com/api/projects/status/y04s4ms7u16trw8e?svg=true)](https://ci.appveyor.com/project/ignacio/luafilesystem) +-[![Coverage Status](https://coveralls.io/repos/keplerproject/luafilesystem/badge.png)](https://coveralls.io/r/keplerproject/luafilesystem) ++[![Coverage Status](https://coveralls.io/repos/lunarmodules/luafilesystem/badge.png)](https://coveralls.io/r/lunarmodules/luafilesystem) + + # LuaFileSystem - File System Library for Lua +-Copyright 2003-2016 Kepler Project + +-http://keplerproject.github.io/luafilesystem ++https://lunarmodules.github.io/luafilesystem + + # Description + +@@ -14,7 +13,7 @@ LuaFileSystem is a Lua library developed to complement the set of functions + related to file systems offered by the standard Lua distribution. + + LuaFileSystem offers a portable way to access the underlying directory structure and file attributes. +-LuaFileSystem is free software and uses the same license as Lua 5.1 ++LuaFileSystem is free software and uses the same license as Lua 5.x (MIT). + + # LuaRocks Installation + +@@ -24,4 +23,4 @@ luarocks install luafilesystem + + # Documentation + +-Please check the documentation at doc/us/ for more information. ++Please check the at `docs/` for more information, also available at the [project website](https://lunarmodules.github.io/luafilesystem). +diff --git a/extra/luafilesystem/appveyor.yml b/extra/luafilesystem/appveyor.yml +index 29e6442..9717ebe 100644 +--- a/extra/luafilesystem/appveyor.yml ++++ b/extra/luafilesystem/appveyor.yml +@@ -1,18 +1,25 @@ + version: 0.0.1.{build}-test + +-# Use default image unless needed +-#os: +-#- Windows Server 2012 R2 +- + shallow_clone: true + ++matrix: ++ fast_finish: true ++ + environment: + matrix: +- - LUA: "lua 5.1" +- - LUA: "lua 5.2 --compat none" +- - LUA: "lua 5.3 --compat none" +- - LUA: "luajit 2.0" +- - LUA: "luajit 2.1" ++ - LUAT: "lua" ++ LUAV: "5.1" ++ - LUAT: "lua" ++ LUAV: "5.2" ++ HEREROCKS_FLAGS: "--compat none" ++ - LUAT: "lua" ++ LUAV: "5.3" ++ HEREROCKS_FLAGS: "--compat none" ++ - LUAT: "lua" ++ LUAV: "5.4" ++ HEREROCKS_FLAGS: "--compat none" ++ - LUAT: "luajit" ++ LUAV: "2.1" + + # Abuse this section so we can have a matrix with different Compiler versions + configuration: +@@ -20,17 +27,17 @@ configuration: + - vs_32 + - vs_64 + +-install: +- - set PATH=%CD%\env\bin;C:\Python27\Scripts;C:\MinGW\bin;%PATH% +- - pip install hererocks +- - hererocks env --%LUA% --target %configuration% --luarocks latest +- + before_build: +-# @todo +-- echo "Installing external deps" ++ - set PATH=C:\MinGW\bin;%PATH% ++ - set PATH=C:\Python37;C:\Python37\Scripts;%PATH% # Add directory containing 'pip' to PATH ++ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install --upgrade certifi ) ++ - FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "import certifi;print(certifi.where())"`) DO ( SET SSL_CERT_FILE=%%F ) ++ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install hererocks ) ++ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( hererocks lua_install-%LUAV% --%LUAT% %LUAV% %HEREROCKS_FLAGS% --luarocks latest --target=%configuration% ) ++ - call lua_install-%LUAV%\bin\activate + + build_script: +-- luarocks make rockspecs/luafilesystem-cvs-3.rockspec ++- luarocks make + + before_test: + +@@ -38,5 +45,9 @@ test_script: + - echo "Testing..." + - lua tests/test.lua + +-after_test: +-# @todo ++cache: ++ - lua_install-5.4 ++ - lua_install-5.3 ++ - lua_install-5.2 ++ - lua_install-5.1 ++ - lua_install-2.1 +diff --git a/extra/luafilesystem/config b/extra/luafilesystem/config +index cfd4c6a..33fe514 100644 +--- a/extra/luafilesystem/config ++++ b/extra/luafilesystem/config +@@ -3,21 +3,25 @@ + # Default installation prefix + PREFIX=/usr/local + ++LUA_VERSION = 5.1 ++ + # System's libraries directory (where binary libraries are installed) +-LUA_LIBDIR= $(PREFIX)/lib/lua/5.1 ++LUA_LIBDIR= $(PREFIX)/lib/lua/$(LUA_VERSION) + + # Lua includes directory +-LUA_INC= $(PREFIX)/include ++LUA_INC += -I$(PREFIX)/include ++LUA_INC += -I/usr/include/lua$(LUA_VERSION) -I/usr/include/lua/$(LUA_VERSION) + + # OS dependent + LIB_OPTION= -shared #for Linux + #LIB_OPTION= -bundle -undefined dynamic_lookup #for MacOS X + +-LIBNAME= $T.so.$V ++# Minimum runtime OS version on macOS ++MACOSX_DEPLOYMENT_TARGET= 10.5 + + # Compilation directives + WARN= -O2 -Wall -fPIC -W -Waggregate-return -Wcast-align -Wmissing-prototypes -Wnested-externs -Wshadow -Wwrite-strings -pedantic +-INCS= -I$(LUA_INC) ++INCS= $(LUA_INC) + CFLAGS= $(WARN) $(INCS) + CC= gcc + +diff --git a/extra/luafilesystem/config.win b/extra/luafilesystem/config.win +index 50e81f6..6aeaa60 100644 +--- a/extra/luafilesystem/config.win ++++ b/extra/luafilesystem/config.win +@@ -1,14 +1,14 @@ ++LUA_VERSION= 5.1 ++ + # Installation directories + # System's libraries directory (where binary libraries are installed) +-LUA_LIBDIR= "c:\lua5.1" ++LUA_LIBDIR= "c:\lua$(LUA_VERSION)" + + # Lua includes directory +-LUA_INC= "c:\lua5.1\include" ++LUA_INC= "c:\lua$(LUA_VERSION)\include" + + # Lua library +-LUA_LIB= "c:\lua5.1\lua5.1.lib" +- +-LIBNAME= $T.dll ++LUA_LIB= "c:\lua$(LUA_VERSION)\lua$(LUA_VERSION).lib" + + # Compilation directives + WARN= /O2 +diff --git a/extra/luafilesystem/doc/us/doc.css b/extra/luafilesystem/doc/us/doc.css +deleted file mode 100644 +index e816a7e..0000000 +--- a/extra/luafilesystem/doc/us/doc.css ++++ /dev/null +@@ -1,212 +0,0 @@ +-body { +- margin-left: 1em; +- margin-right: 1em; +- font-family: arial, helvetica, geneva, sans-serif; +- background-color:#ffffff; margin:0px; +-} +- +-code { +- font-family: "Andale Mono", monospace; +-} +- +-tt { +- font-family: "Andale Mono", monospace; +-} +- +-body, td, th { font-size: 11pt; } +- +-h1, h2, h3, h4 { margin-left: 0em; } +- +-textarea, pre, tt { font-size:10pt; } +-body, td, th { color:#000000; } +-small { font-size:0.85em; } +-h1 { font-size:1.5em; } +-h2 { font-size:1.25em; } +-h3 { font-size:1.15em; } +-h4 { font-size:1.06em; } +- +-a:link { font-weight:bold; color: #004080; text-decoration: none; } +-a:visited { font-weight:bold; color: #006699; text-decoration: none; } +-a:link:hover { text-decoration:underline; } +-hr { color:#cccccc } +-img { border-width: 0px; } +- +-h3 { padding-top: 1em; } +- +-p { margin-left: 1em; } +- +-p.name { +- font-family: "Andale Mono", monospace; +- padding-top: 1em; +- margin-left: 0em; +-} +- +-blockquote { margin-left: 3em; } +- +-.example { +- background-color: rgb(245, 245, 245); +- border-top-width: 1px; +- border-right-width: 1px; +- border-bottom-width: 1px; +- border-left-width: 1px; +- border-top-style: solid; +- border-right-style: solid; +- border-bottom-style: solid; +- border-left-style: solid; +- border-top-color: silver; +- border-right-color: silver; +- border-bottom-color: silver; +- border-left-color: silver; +- padding: 1em; +- margin-left: 1em; +- margin-right: 1em; +- font-family: "Andale Mono", monospace; +- font-size: smaller; +-} +- +-hr { +- margin-left: 0em; +- background: #00007f; +- border: 0px; +- height: 1px; +-} +- +-ul { list-style-type: disc; } +- +-table.index { border: 1px #00007f; } +-table.index td { text-align: left; vertical-align: top; } +-table.index ul { padding-top: 0em; margin-top: 0em; } +- +-table { +- border: 1px solid black; +- border-collapse: collapse; +- margin-left: auto; +- margin-right: auto; +-} +- +-th { +- border: 1px solid black; +- padding: 0.5em; +-} +- +-td { +- border: 1px solid black; +- padding: 0.5em; +-} +-div.header, div.footer { margin-left: 0em; } +- +-#container { +- margin-left: 1em; +- margin-right: 1em; +- background-color: #f0f0f0; +-} +- +-#product { +- text-align: center; +- border-bottom: 1px solid #cccccc; +- background-color: #ffffff; +-} +- +-#product big { +- font-size: 2em; +-} +- +-#product_logo { +-} +- +-#product_name { +-} +- +-#product_description { +-} +- +-#main { +- background-color: #f0f0f0; +- border-left: 2px solid #cccccc; +-} +- +-#navigation { +- float: left; +- width: 12em; +- margin: 0; +- vertical-align: top; +- background-color: #f0f0f0; +- overflow:visible; +-} +- +-#navigation h1 { +- background-color:#e7e7e7; +- font-size:1.1em; +- color:#000000; +- text-align:left; +- margin:0px; +- padding:0.2em; +- border-top:1px solid #dddddd; +- border-bottom:1px solid #dddddd; +-} +- +-#navigation ul { +- font-size:1em; +- list-style-type: none; +- padding: 0; +- margin: 1px; +-} +- +-#navigation li { +- text-indent: -1em; +- margin: 0em 0em 0em 0.5em; +- display: block; +- padding: 3px 0px 0px 12px; +-} +- +-#navigation li li a { +- padding: 0px 3px 0px -1em; +-} +- +-#content { +- margin-left: 12em; +- padding: 1em; +- border-left: 2px solid #cccccc; +- border-right: 2px solid #cccccc; +- background-color: #ffffff; +-} +- +-#about { +- clear: both; +- margin: 0; +- padding: 5px; +- border-top: 2px solid #cccccc; +- background-color: #ffffff; +-} +- +-@media print { +- body { +- font: 10pt "Times New Roman", "TimeNR", Times, serif; +- } +- a { +- font-weight:bold; color: #004080; text-decoration: underline; +- } +- #main { +- background-color: #ffffff; border-left: 0px; +- } +- #container { +- margin-left: 2%; margin-right: 2%; background-color: #ffffff; +- } +- #content { +- margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; +- } +- #navigation { +- display: none; +- } +- #product_logo { +- display: none; +- } +- #about img { +- display: none; +- } +- .example { +- font-family: "Andale Mono", monospace; +- font-size: 8pt; +- page-break-inside: avoid; +- } +-} +diff --git a/extra/luafilesystem/docs/doc.css b/extra/luafilesystem/docs/doc.css +new file mode 100644 +index 0000000..f233ce4 +--- /dev/null ++++ b/extra/luafilesystem/docs/doc.css +@@ -0,0 +1,209 @@ ++body { ++ color: #47555c; ++ font-size: 16px; ++ font-family: "Open Sans", sans-serif; ++ margin: 0; ++ padding: 0; ++ background: #eff4ff; ++} ++ ++a:link { color: #008fee; } ++a:visited { color: #008fee; } ++a:hover { color: #22a7ff; } ++ ++h1 { font-size:26px; } ++h2 { font-size:24px; } ++h3 { font-size:18px; } ++h4 { font-size:16px; } ++ ++hr { ++ height: 1px; ++ background: #c1cce4; ++ border: 0px; ++ margin: 20px 0; ++} ++ ++code { ++ font-family: "Open Sans Mono", "Andale Mono", monospace; ++} ++ ++tt { ++ font-family: "Open Sans Mono", "Andale Mono", monospace; ++} ++ ++body, td, th { ++} ++ ++textarea, pre, tt { ++ font-family: "Open Sans Mono", "Andale Mono", monospace; ++} ++ ++img { ++ border-width: 0px; ++} ++ ++.example { ++ background-color: #323744; ++ color: white; ++ font-size: 16px; ++ padding: 16px 24px; ++ border-radius: 2px; ++} ++ ++div.header, div.footer { ++} ++ ++#container { ++} ++ ++#product { ++ background-color: white; ++ padding: 10px; ++ height: 130px; ++ border-bottom: solid #d3dbec 1px; ++} ++ ++#product big { ++ font-size: 42px; ++} ++#product strong { ++ font-weight: normal; ++} ++ ++#product_logo { ++ float: right; ++} ++ ++#product_name { ++ padding-top: 15px; ++ padding-left: 30px; ++ font-size: 42px; ++ font-weight: normal; ++} ++ ++#product_description { ++ padding-left: 30px; ++ color: #757779; ++} ++ ++#main { ++ background: #eff4ff; ++ margin: 0; ++} ++ ++#navigation { ++ width: 100%; ++ background-color: rgb(44,62,103); ++ padding: 10px; ++ margin: 0; ++} ++ ++#navigation h1 { ++ display: none; ++} ++ ++#navigation a:hover { ++ text-decoration: underline; ++} ++ ++#navigation ul li a { ++ color: rgb(136, 208, 255); ++ font-weight: bold; ++ text-decoration: none; ++} ++ ++#navigation ul li li a { ++ color: rgb(136, 208, 255); ++ font-weight: normal; ++ text-decoration: none; ++} ++ ++#navigation ul { ++ display: inline; ++ color: white; ++ padding: 0px; ++ padding-top: 10px; ++ padding-bottom: 10px; ++} ++ ++#navigation li { ++ display: inline; ++ list-style-type: none; ++ padding-left: 5px; ++ padding-right: 5px; ++} ++ ++#navigation li { ++ padding: 10px; ++ padding: 10px; ++} ++ ++#navigation li li { ++} ++ ++#navigation li:hover a { ++ color: rgb(166, 238, 255); ++} ++ ++#content { ++ padding: 20px; ++ width: 800px; ++ margin-left: auto; ++ margin-right: auto; ++} ++ ++#about { ++ display: none; ++} ++ ++dl.reference { ++ background-color: white; ++ padding: 20px; ++ border: solid #d3dbec 1px; ++} ++ ++dl.reference dt { ++ padding: 5px; ++ padding-top: 25px; ++ color: #637bbc; ++} ++ ++dl.reference dl dt { ++ padding-top: 5px; ++ color: #637383; ++} ++ ++dl.reference dd { ++} ++ ++@media print { ++ body { ++ font: 10pt "Times New Roman", "TimeNR", Times, serif; ++ } ++ a { ++ font-weight:bold; color: #004080; text-decoration: underline; ++ } ++ #main { ++ background-color: #ffffff; border-left: 0px; ++ } ++ #container { ++ margin-left: 2%; margin-right: 2%; background-color: #ffffff; ++ } ++ #content { ++ margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; ++ } ++ #navigation { ++ display: none; ++ } ++ #product_logo { ++ display: none; ++ } ++ #about img { ++ display: none; ++ } ++ .example { ++ font-family: "Andale Mono", monospace; ++ font-size: 8pt; ++ page-break-inside: avoid; ++ } ++} +diff --git a/extra/luafilesystem/doc/us/examples.html b/extra/luafilesystem/docs/examples.html +similarity index 88% +rename from doc/us/examples.html +rename to docs/examples.html +index 68756c8..a41bd2a 100644 +--- a/extra/luafilesystem/doc/us/examples.html ++++ b/extra/luafilesystem/docs/examples.html +@@ -13,7 +13,7 @@ + +
+ +@@ -44,10 +44,10 @@ + + +
  • Examples
  • +-
  • Project ++
  • Project + +
  • +
  • License
  • +diff --git a/extra/luafilesystem/doc/us/index.html b/extra/luafilesystem/docs/index.html +similarity index 73% +rename from doc/us/index.html +rename to docs/index.html +index 3ed43f2..34e0730 100644 +--- a/extra/luafilesystem/doc/us/index.html ++++ b/extra/luafilesystem/docs/index.html +@@ -13,7 +13,7 @@ + +
    + +@@ -44,10 +44,10 @@ + + +
  • Examples
  • +-
  • Project ++
  • Project + +
  • +
  • License
  • +@@ -70,17 +70,39 @@ the underlying directory structure and file attributes.

    + +

    Status

    + +-

    Current version is 1.6.3. It works with Lua 5.1, 5.2 and 5.3.

    ++

    Current version is 1.8.0. It works with Lua 5.1, 5.2, 5.3 and 5.4, and it runs on various ++flavors of Unix (including Linux, BSDs, macOS) and Windows.

    + +

    Download

    + +-

    LuaFileSystem source can be downloaded from its +-Github +-page.

    ++

    LuaFileSystem can be installed using LuaRocks: ++ ++

    ++$ luarocks install luafilesystem
    ++
    ++ ++

    Its source can be found at its GitHub page.

    + +

    History

    + +
    ++
    Version 1.8.0 [22/Apr/2020]
    ++
      ++
    • Lua 5.4 support
    • ++
    • lfs.link and lfs.symlinkattributes now work on Windows
    • ++
    • MACOSX_DEPLOYMENT_TARGET is configurable in the Makefile
    • ++
    • Fallback to _POSIX_PATH_MAX when MAXPATHLEN is not avaliable
    • ++
    • Fixed memory leak in case of realloc failure
    • ++
    ++ ++
    Version 1.7.0 [15/Sep/2017]
    ++
      ++
    • symlinkattributes function now provides 'target' field, containing name of the file that the symlink points to.
    • ++
    • attributes, symlinkattributes, touch, mkdir, and rmdir functions now return system-dependent error code as the third value on error.
    • ++
    • Fixed detection of closed files for Lua 5.2+ in setmode, lock, and unlock functions.
    • ++
    • Fixed various compiler warnings.
    • ++
    ++ +
    Version 1.6.3 [15/Jan/2015]
    +
      +
    • Lua 5.3 support.
    • +@@ -188,10 +210,11 @@ page.

      + +

      Credits

      + +-

      LuaFileSystem was designed by Roberto Ierusalimschy, +-André Carregal and Tomás Guisasola as part of the +-Kepler Project, +-which holds its copyright. LuaFileSystem is currently maintained by Fábio Mascarenhas.

      ++

      The LuaFileSystem library was originally designed and ++implemented by Roberto Ierusalimschy, André Carregal and ++Tomás Guisasola. It was then maintained by Fábio ++Mascarenhas for several years and has since been maintained ++by many contributors -- see the Git history for detailed credits.

      + +
    + +diff --git a/extra/luafilesystem/doc/us/license.html b/extra/luafilesystem/docs/license.html +similarity index 74% +rename from doc/us/license.html +rename to docs/license.html +index 5048c11..78704b4 100644 +--- a/extra/luafilesystem/doc/us/license.html ++++ b/extra/luafilesystem/docs/license.html +@@ -13,7 +13,7 @@ + +
    + +@@ -44,10 +44,10 @@ + + +
  • Examples
  • +-
  • Project ++
  • Project + +
  • +
  • License
  • +@@ -58,33 +58,21 @@ + +

    License

    + +-

    +-LuaFileSystem is free software: it can be used for both academic +-and commercial purposes at absolutely no cost. There are no +-royalties or GNU-like "copyleft" restrictions. LuaFileSystem +-qualifies as +-Open Source +-software. +-Its licenses are compatible with +-GPL. +-LuaFileSystem is not in the public domain and the +-Kepler Project +-keep its copyright. +-The legal details are below. +-

    +- +

    The spirit of the license is that you are free to use + LuaFileSystem for any purpose at no cost without having to ask us. + The only requirement is that if you do use LuaFileSystem, then you + should give us credit by including the appropriate copyright notice + somewhere in your product or its documentation.

    + +-

    The LuaFileSystem library is designed and implemented by Roberto +-Ierusalimschy, André Carregal and Tomás Guisasola. +-The implementation is not derived from licensed software.

    ++

    The LuaFileSystem library was originally designed and ++implemented by Roberto Ierusalimschy, André Carregal and ++Tomás Guisasola, and has since been maintained over the years ++by many people -- see the Git history for detailed credits. ++The implementation is not derived from any other licensed software.

    + +
    +-

    Copyright © 2003 Kepler Project.

    ++

    Copyright © 2003 - 2010 Kepler Project.

    ++

    Copyright © 2010 - 2022 The LuaFileSystem authors.

    + +

    Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation +diff --git a/extra/luafilesystem/doc/us/luafilesystem.png b/extra/luafilesystem/docs/luafilesystem.png +similarity index 100% +rename from doc/us/luafilesystem.png +rename to docs/luafilesystem.png +diff --git a/extra/luafilesystem/doc/us/manual.html b/extra/luafilesystem/docs/manual.html +similarity index 90% +rename from doc/us/manual.html +rename to docs/manual.html +index 0ecb625..0f84797 100644 +--- a/extra/luafilesystem/doc/us/manual.html ++++ b/extra/luafilesystem/docs/manual.html +@@ -13,7 +13,7 @@ + +

    + +@@ -44,10 +44,10 @@ + + +
  • Examples
  • +-
  • Project ++
  • Project + +
  • +
  • License
  • +@@ -102,15 +102,15 @@ LuaFileSystem offers the following functions: +

    + +
    +-
    lfs.attributes (filepath [, aname | atable])
    ++
    lfs.attributes (filepath [, request_name | result_table])
    +
    Returns a table with the file attributes corresponding to +- filepath (or nil followed by an error message ++ filepath (or nil followed by an error message and a system-dependent error code + in case of error). + If the second optional argument is given and is a string, then only the value of the + named attribute is returned (this use is equivalent to +- lfs.attributes(filepath)[aname], but the table is not created ++ lfs.attributes(filepath)[request_name], but the table is not created + and only one attribute is retrieved from the O.S.). +- if a table is passed as the second argument, it is filled with attributes and returned instead of a new table. ++ if a table is passed as the second argument, it (result_table) is filled with attributes and returned instead of a new table. + The attributes are described as follows; + attribute mode is a string, all the others are numbers, + and the time related attributes use the same time reference of +@@ -222,14 +222,14 @@ LuaFileSystem offers the following functions: +
    lfs.mkdir (dirname)
    +
    Creates a new directory. The argument is the name of the new + directory.
    +- Returns true if the operation was successful; +- in case of error, it returns nil plus an error string. ++ Returns true in case of success or nil, an error message and ++ a system-dependent error code in case of error. +
    + +
    lfs.rmdir (dirname)
    +
    Removes an existing directory. The argument is the name of the directory.
    +- Returns true if the operation was successful; +- in case of error, it returns nil plus an error string.
    ++ Returns true in case of success or nil, an error message and ++ a system-dependent error code in case of error. + +
    lfs.setmode (file, mode)
    +
    Sets the writing mode for a file. The mode string can be either "binary" or "text". +@@ -239,7 +239,7 @@ LuaFileSystem offers the following functions: + setting the mode has no effect, and the mode is always returned as binary. +
    + +-
    lfs.symlinkattributes (filepath [, aname])
    ++
    lfs.symlinkattributes (filepath [, request_name])
    +
    Identical to lfs.attributes except that + it obtains information about the link itself (not the file it refers to). + It also adds a target field, containing +@@ -257,8 +257,8 @@ LuaFileSystem offers the following functions: + Lua standard function os.time). + If the modification time is omitted, the access time provided is used; + if both times are omitted, the current time is used.
    +- Returns true if the operation was successful; +- in case of error, it returns nil plus an error string. ++ Returns true in case of success or nil, an error message and ++ a system-dependent error code in case of error. +
    + +
    lfs.unlock (filehandle[, start[, length]])
    +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.3-1.rockspec b/extra/luafilesystem/luafilesystem-scm-1.rockspec +similarity index 76% +rename from rockspecs/luafilesystem-1.6.3-1.rockspec +rename to luafilesystem-scm-1.rockspec +index 89b25d4..19b6225 100644 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.3-1.rockspec ++++ b/extra/luafilesystem/luafilesystem-scm-1.rockspec +@@ -1,8 +1,7 @@ +-package = "LuaFileSystem" +-version = "1.6.3-1" ++package = "luafilesystem" ++version = "scm-1" + source = { +- url = "git://github.com/keplerproject/luafilesystem", +- tag = "v_1_6_3", ++ url = "git+https://github.com/lunarmodules/luafilesystem" + } + description = { + summary = "File System Library for the Lua Programming Language", +@@ -12,7 +11,7 @@ description = { + distribution. LuaFileSystem offers a portable way to access the + underlying directory structure and file attributes. + ]], +- license = "MIT/X11", ++ license = "MIT/X11" + } + dependencies = { + "lua >= 5.1" +@@ -23,6 +22,7 @@ build = { + lfs = "src/lfs.c" + }, + copy_directories = { +- "doc", "tests" ++ "docs", ++ "tests" + } + } +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec +deleted file mode 100644 +index d4d484f..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +-version = "1.3.0-1" +-source = { +- url = "http://luaforge.net/frs/download.php/2679/luafilesystem-1.3.0.tar.gz" +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- type = "make", +- build_variables = { +- LUA_INC = "$(LUA_INCDIR)", +- LIB_OPTION = "$(LIBFLAG)" +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec +deleted file mode 100644 +index b693618..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +-version = "1.4.0-1" +-source = { +- url = "http://luaforge.net/frs/download.php/3158/luafilesystem-1.4.0.tar.gz" +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- type = "make", +- build_variables = { +- LUA_INC = "$(LUA_INCDIR)", +- LIB_OPTION = "$(LIBFLAG)" +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec +deleted file mode 100644 +index f7ed871..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec ++++ /dev/null +@@ -1,43 +0,0 @@ +-package = "LuaFileSystem" +-version = "1.4.0-2" +-source = { +- url = "http://luaforge.net/frs/download.php/3158/luafilesystem-1.4.0.tar.gz" +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- platforms = { +- unix = { +- type = "make", +- build_variables = { +- LIB_OPTION = "$(LIBFLAG)", +- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +- }, +- win32 = { +- type = "make", +- build_variables = { +- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", +- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)", +- LUA_DIR = "$(LUADIR)", +- BIN_DIR = "$(BINDIR)" +- } +- } +- } +-} +\ No newline at end of file +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec +deleted file mode 100644 +index db3a3eb..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec ++++ /dev/null +@@ -1,43 +0,0 @@ +-package = "LuaFileSystem" +-version = "1.4.1-1" +-source = { +- url = "http://luaforge.net/frs/download.php/3345/luafilesystem-1.4.1.tar.gz", +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- platforms = { +- unix = { +- type = "make", +- build_variables = { +- LIB_OPTION = "$(LIBFLAG)", +- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR) $(STAT64)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +- }, +- win32 = { +- type = "make", +- build_variables = { +- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", +- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)", +- LUA_DIR = "$(LUADIR)", +- BIN_DIR = "$(BINDIR)" +- } +- } +- } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec +deleted file mode 100644 +index 1194711..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec ++++ /dev/null +@@ -1,43 +0,0 @@ +-package = "LuaFileSystem" +-version = "1.4.1rc1-1" +-source = { +- url = "http://luafilesystem.luaforge.net/luafilesystem-1.4.1rc1.tar.gz", +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- platforms = { +- unix = { +- type = "make", +- build_variables = { +- LIB_OPTION = "$(LIBFLAG)", +- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR) $(STAT64)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +- }, +- win32 = { +- type = "make", +- build_variables = { +- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", +- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)", +- LUA_DIR = "$(LUADIR)", +- BIN_DIR = "$(BINDIR)" +- } +- } +- } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec +deleted file mode 100644 +index 7cfe92b..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec ++++ /dev/null +@@ -1,26 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "1.4.2-1" +- +-source = { +- url = "http://luaforge.net/frs/download.php/3931/luafilesystem-1.4.2.tar.gz", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "module", +- modules = { lfs = "src/lfs.c" } +-} +\ No newline at end of file +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec +deleted file mode 100644 +index 1170ad2..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "1.5.0-1" +- +-source = { +- url = "http://cloud.github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.5.0.tar.gz", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "module", +- modules = { lfs = "src/lfs.c" }, +- copy_directories = { "doc", "tests" } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec +deleted file mode 100644 +index 82d349c..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "1.6.0-1" +- +-source = { +- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.0.tar.gz", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "builtin", +- modules = { lfs = "src/lfs.c" }, +- copy_directories = { "doc", "tests" } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec +deleted file mode 100644 +index 7f45e33..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "1.6.1-1" +- +-source = { +- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.1.tar.gz", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "builtin", +- modules = { lfs = "src/lfs.c" }, +- copy_directories = { "doc", "tests" } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec +deleted file mode 100644 +index 1c11efc..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "1.6.2-1" +- +-source = { +- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.2.tar.gz", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "builtin", +- modules = { lfs = "src/lfs.c" }, +- copy_directories = { "doc", "tests" } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec +deleted file mode 100644 +index a02d4f1..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec ++++ /dev/null +@@ -1,44 +0,0 @@ +-package = "LuaFileSystem" +-version = "cvs-1" +-source = { +- url = "cvs://:pserver:anonymous:@cvs.luaforge.net:/cvsroot/luafilesystem", +- cvs_tag = "HEAD" +-} +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +-dependencies = { +- "lua >= 5.1" +-} +-build = { +- platforms = { +- unix = { +- type = "make", +- build_variables = { +- LIB_OPTION = "$(LIBFLAG)", +- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)" +- } +- }, +- win32 = { +- type = "make", +- build_variables = { +- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", +- CFLAGS = "$(CFLAGS) /I$(LUA_INCDIR)", +- }, +- install_variables = { +- LUA_LIBDIR = "$(LIBDIR)", +- LUA_DIR = "$(LUADIR)", +- BIN_DIR = "$(BINDIR)" +- } +- } +- } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec +deleted file mode 100644 +index 651c7cf..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec ++++ /dev/null +@@ -1,26 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "cvs-2" +- +-source = { +- url = "git://github.com/keplerproject/luafilesystem.git", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1" +-} +- +-build = { +- type = "module", +- modules = { lfs = "src/lfs.c" } +-} +diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec +deleted file mode 100644 +index a4388cd..0000000 +--- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec ++++ /dev/null +@@ -1,27 +0,0 @@ +-package = "LuaFileSystem" +- +-version = "cvs-3" +- +-source = { +- url = "git://github.com/keplerproject/luafilesystem.git", +-} +- +-description = { +- summary = "File System Library for the Lua Programming Language", +- detailed = [[ +- LuaFileSystem is a Lua library developed to complement the set of +- functions related to file systems offered by the standard Lua +- distribution. LuaFileSystem offers a portable way to access the +- underlying directory structure and file attributes. +- ]] +-} +- +-dependencies = { +- "lua >= 5.1, < 5.4" +-} +- +-build = { +- type = "builtin", +- modules = { lfs = "src/lfs.c" }, +- copy_directories = { "doc", "tests" } +-} +diff --git a/extra/luafilesystem/src/lfs.c b/extra/luafilesystem/src/lfs.c +index 25122a3..b51470c 100644 +--- a/extra/luafilesystem/src/lfs.c ++++ b/extra/luafilesystem/src/lfs.c +@@ -1,34 +1,26 @@ + /* + ** LuaFileSystem +-** Copyright Kepler Project 2003 - 2016 (http://keplerproject.github.io/luafilesystem) ++** File system manipulation library + ** +-** File system manipulation library. +-** This library offers these functions: +-** lfs.attributes (filepath [, attributename | attributetable]) +-** lfs.chdir (path) +-** lfs.currentdir () +-** lfs.dir (path) +-** lfs.link (old, new[, symlink]) +-** lfs.lock (fh, mode) +-** lfs.lock_dir (path) +-** lfs.mkdir (path) +-** lfs.rmdir (path) +-** lfs.setmode (filepath, mode) +-** lfs.symlinkattributes (filepath [, attributename]) +-** lfs.touch (filepath [, atime [, mtime]]) +-** lfs.unlock (fh) ++** Copyright (C) 2003-2010 Kepler Project. ++** Copyright (C) 2010-2022 The LuaFileSystem authors. ++** (http://lunarmodules.github.io/luafilesystem) + */ + + #ifndef LFS_DO_NOT_USE_LARGE_FILE + #ifndef _WIN32 + #ifndef _AIX +-#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ ++#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ + #else +-#define _LARGE_FILES 1 /* AIX */ ++#define _LARGE_FILES 1 /* AIX */ + #endif + #endif + #endif + ++#ifdef _WIN32 ++#define _WIN32_WINNT 0x600 ++#endif ++ + #ifndef LFS_DO_NOT_USE_LARGE_FILE + #define _LARGEFILE64_SOURCE + #endif +@@ -41,26 +33,39 @@ + #include + + #ifdef _WIN32 +- #include +- #include +- #include +- #include +- #ifdef __BORLANDC__ +- #include +- #else +- #include +- #endif +- #include +- /* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ +- #define LFS_MAXPATHLEN MAX_PATH ++ ++#include ++#include ++#include ++#include ++ ++#ifdef __BORLANDC__ ++#include ++#else ++#include ++#endif ++ ++#include ++ ++/* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ ++#define LFS_MAXPATHLEN MAX_PATH ++ ++#else ++ ++#include ++#include ++#include ++#include ++#include ++#include /* for MAXPATHLEN */ ++ ++#ifdef MAXPATHLEN ++#define LFS_MAXPATHLEN MAXPATHLEN + #else +- #include +- #include +- #include +- #include +- #include +- #include /* for MAXPATHLEN */ +- #define LFS_MAXPATHLEN MAXPATHLEN ++#include /* for _POSIX_PATH_MAX */ ++#define LFS_MAXPATHLEN _POSIX_PATH_MAX ++#endif ++ + #endif + + #include +@@ -69,10 +74,10 @@ + + #include "lfs.h" + +-#define LFS_VERSION "1.6.3" ++#define LFS_VERSION "1.8.0" + #define LFS_LIBNAME "lfs" + +-#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ ++#if LUA_VERSION_NUM >= 503 /* Lua 5.3+ */ + + #ifndef luaL_optlong + #define luaL_optlong luaL_optinteger +@@ -80,8 +85,10 @@ + + #endif + +-#if LUA_VERSION_NUM < 502 +-# define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l)) ++#if LUA_VERSION_NUM >= 502 ++#define new_lib(L, l) (luaL_newlib(L, l)) ++#else ++#define new_lib(L, l) (lua_newtable(L), luaL_register(L, NULL, l)) + #endif + + /* Define 'strerror' for systems that do not implement it */ +@@ -91,73 +98,163 @@ + + #define DIR_METATABLE "directory metatable" + typedef struct dir_data { +- int closed; ++ int closed; + #ifdef _WIN32 +- intptr_t hFile; +- char pattern[MAX_PATH+1]; ++ intptr_t hFile; ++ char pattern[MAX_PATH + 1]; + #else +- DIR *dir; ++ DIR *dir; + #endif + } dir_data; + + #define LOCK_METATABLE "lock metatable" + + #ifdef _WIN32 +- #ifdef __BORLANDC__ +- #define lfs_setmode(file, m) (setmode(_fileno(file), m)) +- #define STAT_STRUCT struct stati64 +- #else +- #define lfs_setmode(file, m) (_setmode(_fileno(file), m)) +- #define STAT_STRUCT struct _stati64 +- #endif ++ ++#ifdef __BORLANDC__ ++#define lfs_setmode(file, m) (setmode(_fileno(file), m)) ++#define STAT_STRUCT struct stati64 ++#else ++#define lfs_setmode(file, m) (_setmode(_fileno(file), m)) ++#define STAT_STRUCT struct _stati64 ++#endif ++ ++#ifndef _S_IFLNK ++#define _S_IFLNK 0x400 ++#endif ++ ++#ifndef S_ISDIR ++#define S_ISDIR(mode) (mode&_S_IFDIR) ++#endif ++#ifndef S_ISREG ++#define S_ISREG(mode) (mode&_S_IFREG) ++#endif ++#ifndef S_ISLNK ++#define S_ISLNK(mode) (mode&_S_IFLNK) ++#endif ++#ifndef S_ISSOCK ++#define S_ISSOCK(mode) (0) ++#endif ++#ifndef S_ISFIFO ++#define S_ISFIFO(mode) (0) ++#endif ++#ifndef S_ISCHR ++#define S_ISCHR(mode) (mode&_S_IFCHR) ++#endif ++#ifndef S_ISBLK ++#define S_ISBLK(mode) (0) ++#endif ++ + #define STAT_FUNC _stati64 +-#define LSTAT_FUNC STAT_FUNC ++#define LSTAT_FUNC lfs_win32_lstat ++ + #else ++ + #define _O_TEXT 0 + #define _O_BINARY 0 + #define lfs_setmode(file, m) ((void)file, (void)m, 0) + #define STAT_STRUCT struct stat + #define STAT_FUNC stat + #define LSTAT_FUNC lstat ++ ++#endif ++ ++#ifdef _WIN32 ++#define lfs_mkdir _mkdir ++#else ++#define lfs_mkdir(path) (mkdir((path), \ ++ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH)) ++#endif ++ ++#ifdef _WIN32 ++ ++static int lfs_win32_pusherror(lua_State * L) ++{ ++ int en = GetLastError(); ++ lua_pushnil(L); ++ if (en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) ++ lua_pushstring(L, "File exists"); ++ else ++ lua_pushstring(L, strerror(en)); ++ return 2; ++} ++ ++#define TICKS_PER_SECOND 10000000 ++#define EPOCH_DIFFERENCE 11644473600LL ++static time_t windowsToUnixTime(FILETIME ft) ++{ ++ ULARGE_INTEGER uli; ++ uli.LowPart = ft.dwLowDateTime; ++ uli.HighPart = ft.dwHighDateTime; ++ return (time_t) (uli.QuadPart / TICKS_PER_SECOND - EPOCH_DIFFERENCE); ++} ++ ++static int lfs_win32_lstat(const char *path, STAT_STRUCT * buffer) ++{ ++ WIN32_FILE_ATTRIBUTE_DATA win32buffer; ++ if (GetFileAttributesEx(path, GetFileExInfoStandard, &win32buffer)) { ++ if (!(win32buffer.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { ++ return STAT_FUNC(path, buffer); ++ } ++ buffer->st_mode = _S_IFLNK; ++ buffer->st_dev = 0; ++ buffer->st_ino = 0; ++ buffer->st_nlink = 0; ++ buffer->st_uid = 0; ++ buffer->st_gid = 0; ++ buffer->st_rdev = 0; ++ buffer->st_atime = windowsToUnixTime(win32buffer.ftLastAccessTime); ++ buffer->st_mtime = windowsToUnixTime(win32buffer.ftLastWriteTime); ++ buffer->st_ctime = windowsToUnixTime(win32buffer.ftCreationTime); ++ buffer->st_size = 0; ++ return 0; ++ } else { ++ return 1; ++ } ++} ++ + #endif + + /* + ** Utility functions + */ +-static int pusherror(lua_State *L, const char *info) ++static int pusherror(lua_State * L, const char *info) + { +- lua_pushnil(L); +- if (info==NULL) +- lua_pushstring(L, strerror(errno)); +- else +- lua_pushfstring(L, "%s: %s", info, strerror(errno)); +- lua_pushinteger(L, errno); +- return 3; ++ lua_pushnil(L); ++ if (info == NULL) ++ lua_pushstring(L, strerror(errno)); ++ else ++ lua_pushfstring(L, "%s: %s", info, strerror(errno)); ++ lua_pushinteger(L, errno); ++ return 3; + } + +-static int pushresult(lua_State *L, int i, const char *info) ++static int pushresult(lua_State * L, int res, const char *info) + { +- if (i==-1) +- return pusherror(L, info); +- lua_pushinteger(L, i); +- return 1; ++ if (res == -1) { ++ return pusherror(L, info); ++ } else { ++ lua_pushboolean(L, 1); ++ return 1; ++ } + } + + + /* + ** This function changes the working (current) directory + */ +-static int change_dir (lua_State *L) { +- const char *path = luaL_checkstring(L, 1); +- if (chdir(path)) { +- lua_pushnil (L); +- lua_pushfstring (L,"Unable to change working directory to '%s'\n%s\n", +- path, chdir_error); +- return 2; +- } else { +- lua_pushboolean (L, 1); +- return 1; +- } ++static int change_dir(lua_State * L) ++{ ++ const char *path = luaL_checkstring(L, 1); ++ if (chdir(path)) { ++ lua_pushnil(L); ++ lua_pushfstring(L, "Unable to change working directory to '%s'\n%s\n", ++ path, chdir_error); ++ return 2; ++ } else { ++ lua_pushboolean(L, 1); ++ return 1; ++ } + } + + /* +@@ -165,56 +262,62 @@ static int change_dir (lua_State *L) { + ** If unable to get the current directory, it returns nil + ** and a string describing the error + */ +-static int get_dir (lua_State *L) { ++static int get_dir(lua_State * L) ++{ + #ifdef NO_GETCWD +- lua_pushnil(L); +- lua_pushstring(L, "Function 'getcwd' not provided by system"); +- return 2; ++ lua_pushnil(L); ++ lua_pushstring(L, "Function 'getcwd' not provided by system"); ++ return 2; + #else +- char *path = NULL; +- /* Passing (NULL, 0) is not guaranteed to work. Use a temp buffer and size instead. */ +- size_t size = LFS_MAXPATHLEN; /* initial buffer size */ +- int result; +- while (1) { +- path = realloc(path, size); +- if (!path) /* failed to allocate */ +- return pusherror(L, "get_dir realloc() failed"); +- if (getcwd(path, size) != NULL) { +- /* success, push the path to the Lua stack */ +- lua_pushstring(L, path); +- result = 1; +- break; +- } +- if (errno != ERANGE) { /* unexpected error */ +- result = pusherror(L, "get_dir getcwd() failed"); +- break; +- } +- /* ERANGE = insufficient buffer capacity, double size and retry */ +- size *= 2; ++ char *path = NULL; ++ /* Passing (NULL, 0) is not guaranteed to work. ++ Use a temp buffer and size instead. */ ++ size_t size = LFS_MAXPATHLEN; /* initial buffer size */ ++ int result; ++ while (1) { ++ char *path2 = (char *)realloc(path, size); ++ if (!path2) { /* failed to allocate */ ++ result = pusherror(L, "get_dir realloc() failed"); ++ break; + } +- free(path); +- return result; ++ path = path2; ++ if (getcwd(path, (int)size) != NULL) { ++ /* success, push the path to the Lua stack */ ++ lua_pushstring(L, path); ++ result = 1; ++ break; ++ } ++ if (errno != ERANGE) { /* unexpected error */ ++ result = pusherror(L, "get_dir getcwd() failed"); ++ break; ++ } ++ /* ERANGE = insufficient buffer capacity, double size and retry */ ++ size *= 2; ++ } ++ free(path); ++ return result; + #endif + } + + /* + ** Check if the given element on the stack is a file and returns it. + */ +-static FILE *check_file (lua_State *L, int idx, const char *funcname) { ++static FILE *check_file(lua_State * L, int idx, const char *funcname) ++{ + #if LUA_VERSION_NUM == 501 +- FILE **fh = (FILE **)luaL_checkudata (L, idx, "FILE*"); +- if (*fh == NULL) { +- luaL_error (L, "%s: closed file", funcname); +- return 0; +- } else +- return *fh; +-#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 503 +- luaL_Stream *fh = (luaL_Stream *)luaL_checkudata (L, idx, "FILE*"); +- if (fh->closef == 0 || fh->f == NULL) { +- luaL_error (L, "%s: closed file", funcname); +- return 0; +- } else +- return fh->f; ++ FILE **fh = (FILE **) luaL_checkudata(L, idx, "FILE*"); ++ if (*fh == NULL) { ++ luaL_error(L, "%s: closed file", funcname); ++ return 0; ++ } else ++ return *fh; ++#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504 ++ luaL_Stream *fh = (luaL_Stream *) luaL_checkudata(L, idx, "FILE*"); ++ if (fh->closef == 0 || fh->f == NULL) { ++ luaL_error(L, "%s: closed file", funcname); ++ return 0; ++ } else ++ return fh->f; + #else + #error unsupported Lua version + #endif +@@ -224,90 +327,114 @@ static FILE *check_file (lua_State *L, int idx, const char *funcname) { + /* + ** + */ +-static int _file_lock (lua_State *L, FILE *fh, const char *mode, const long start, long len, const char *funcname) { +- int code; ++static int _file_lock(lua_State * L, FILE * fh, const char *mode, ++ const long start, long len, const char *funcname) ++{ ++ int code; + #ifdef _WIN32 +- /* lkmode valid values are: +- LK_LOCK Locks the specified bytes. If the bytes cannot be locked, the program immediately tries again after 1 second. If, after 10 attempts, the bytes cannot be locked, the constant returns an error. +- LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, the constant returns an error. +- LK_NBRLCK Same as _LK_NBLCK. +- LK_RLCK Same as _LK_LOCK. +- LK_UNLCK Unlocks the specified bytes, which must have been previously locked. +- +- Regions should be locked only briefly and should be unlocked before closing a file or exiting the program. +- +- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp +- */ +- int lkmode; +- switch (*mode) { +- case 'r': lkmode = LK_NBLCK; break; +- case 'w': lkmode = LK_NBLCK; break; +- case 'u': lkmode = LK_UNLCK; break; +- default : return luaL_error (L, "%s: invalid mode", funcname); +- } +- if (!len) { +- fseek (fh, 0L, SEEK_END); +- len = ftell (fh); +- } +- fseek (fh, start, SEEK_SET); ++ /* lkmode valid values are: ++ LK_LOCK Locks the specified bytes. If the bytes cannot be locked, ++ the program immediately tries again after 1 second. ++ If, after 10 attempts, the bytes cannot be locked, ++ the constant returns an error. ++ LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, ++ the constant returns an error. ++ LK_NBRLCK Same as _LK_NBLCK. ++ LK_RLCK Same as _LK_LOCK. ++ LK_UNLCK Unlocks the specified bytes, which must have been ++ previously locked. ++ ++ Regions should be locked only briefly and should be unlocked ++ before closing a file or exiting the program. ++ ++ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp ++ */ ++ int lkmode; ++ switch (*mode) { ++ case 'r': ++ lkmode = LK_NBLCK; ++ break; ++ case 'w': ++ lkmode = LK_NBLCK; ++ break; ++ case 'u': ++ lkmode = LK_UNLCK; ++ break; ++ default: ++ return luaL_error(L, "%s: invalid mode", funcname); ++ } ++ if (!len) { ++ fseek(fh, 0L, SEEK_END); ++ len = ftell(fh); ++ } ++ fseek(fh, start, SEEK_SET); + #ifdef __BORLANDC__ +- code = locking (fileno(fh), lkmode, len); ++ code = locking(fileno(fh), lkmode, len); + #else +- code = _locking (fileno(fh), lkmode, len); ++ code = _locking(fileno(fh), lkmode, len); + #endif + #else +- struct flock f; +- switch (*mode) { +- case 'w': f.l_type = F_WRLCK; break; +- case 'r': f.l_type = F_RDLCK; break; +- case 'u': f.l_type = F_UNLCK; break; +- default : return luaL_error (L, "%s: invalid mode", funcname); +- } +- f.l_whence = SEEK_SET; +- f.l_start = (off_t)start; +- f.l_len = (off_t)len; +- code = fcntl (fileno(fh), F_SETLK, &f); ++ struct flock f; ++ switch (*mode) { ++ case 'w': ++ f.l_type = F_WRLCK; ++ break; ++ case 'r': ++ f.l_type = F_RDLCK; ++ break; ++ case 'u': ++ f.l_type = F_UNLCK; ++ break; ++ default: ++ return luaL_error(L, "%s: invalid mode", funcname); ++ } ++ f.l_whence = SEEK_SET; ++ f.l_start = (off_t) start; ++ f.l_len = (off_t) len; ++ code = fcntl(fileno(fh), F_SETLK, &f); + #endif +- return (code != -1); ++ return (code != -1); + } + + #ifdef _WIN32 + typedef struct lfs_Lock { + HANDLE fd; + } lfs_Lock; +-static int lfs_lock_dir(lua_State *L) { +- size_t pathl; HANDLE fd; ++static int lfs_lock_dir(lua_State * L) ++{ ++ size_t pathl; ++ HANDLE fd; + lfs_Lock *lock; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); +- ln = (char*)malloc(pathl + strlen(lockfile) + 1); +- if(!ln) { +- lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; +- } +- strcpy(ln, path); strcat(ln, lockfile); +- if((fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_NEW, +- FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) { +- int en = GetLastError(); +- free(ln); lua_pushnil(L); +- if(en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) +- lua_pushstring(L, "File exists"); +- else +- lua_pushstring(L, strerror(en)); +- return 2; ++ ln = (char *) malloc(pathl + strlen(lockfile) + 1); ++ if (!ln) { ++ lua_pushnil(L); ++ lua_pushstring(L, strerror(errno)); ++ return 2; + } ++ strcpy(ln, path); ++ strcat(ln, lockfile); ++ fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, ++ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + free(ln); +- lock = (lfs_Lock*)lua_newuserdata(L, sizeof(lfs_Lock)); ++ if (fd == INVALID_HANDLE_VALUE) { ++ return lfs_win32_pusherror(L); ++ } ++ lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); + lock->fd = fd; +- luaL_getmetatable (L, LOCK_METATABLE); +- lua_setmetatable (L, -2); ++ luaL_getmetatable(L, LOCK_METATABLE); ++ lua_setmetatable(L, -2); + return 1; + } +-static int lfs_unlock_dir(lua_State *L) { +- lfs_Lock *lock = (lfs_Lock *)luaL_checkudata(L, 1, LOCK_METATABLE); +- if(lock->fd != INVALID_HANDLE_VALUE) { ++ ++static int lfs_unlock_dir(lua_State * L) ++{ ++ lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); ++ if (lock->fd != INVALID_HANDLE_VALUE) { + CloseHandle(lock->fd); +- lock->fd=INVALID_HANDLE_VALUE; ++ lock->fd = INVALID_HANDLE_VALUE; + } + return 0; + } +@@ -315,30 +442,38 @@ static int lfs_unlock_dir(lua_State *L) { + typedef struct lfs_Lock { + char *ln; + } lfs_Lock; +-static int lfs_lock_dir(lua_State *L) { ++static int lfs_lock_dir(lua_State * L) ++{ + lfs_Lock *lock; + size_t pathl; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); +- lock = (lfs_Lock*)lua_newuserdata(L, sizeof(lfs_Lock)); +- ln = (char*)malloc(pathl + strlen(lockfile) + 1); +- if(!ln) { +- lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; ++ lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); ++ ln = (char *) malloc(pathl + strlen(lockfile) + 1); ++ if (!ln) { ++ lua_pushnil(L); ++ lua_pushstring(L, strerror(errno)); ++ return 2; + } +- strcpy(ln, path); strcat(ln, lockfile); +- if(symlink("lock", ln) == -1) { +- free(ln); lua_pushnil(L); +- lua_pushstring(L, strerror(errno)); return 2; ++ strcpy(ln, path); ++ strcat(ln, lockfile); ++ if (symlink("lock", ln) == -1) { ++ free(ln); ++ lua_pushnil(L); ++ lua_pushstring(L, strerror(errno)); ++ return 2; + } + lock->ln = ln; +- luaL_getmetatable (L, LOCK_METATABLE); +- lua_setmetatable (L, -2); ++ luaL_getmetatable(L, LOCK_METATABLE); ++ lua_setmetatable(L, -2); + return 1; + } +-static int lfs_unlock_dir(lua_State *L) { +- lfs_Lock *lock = (lfs_Lock *)luaL_checkudata(L, 1, LOCK_METATABLE); +- if(lock->ln) { ++ ++static int lfs_unlock_dir(lua_State * L) ++{ ++ lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); ++ if (lock->ln) { + unlink(lock->ln); + free(lock->ln); + lock->ln = NULL; +@@ -347,9 +482,10 @@ static int lfs_unlock_dir(lua_State *L) { + } + #endif + +-static int lfs_g_setmode (lua_State *L, FILE *f, int arg) { +- static const int mode[] = {_O_BINARY, _O_TEXT}; +- static const char *const modenames[] = {"binary", "text", NULL}; ++static int lfs_g_setmode(lua_State * L, FILE * f, int arg) ++{ ++ static const int mode[] = { _O_BINARY, _O_TEXT }; ++ static const char *const modenames[] = { "binary", "text", NULL }; + int op = luaL_checkoption(L, arg, NULL, modenames); + int res = lfs_setmode(f, mode[op]); + if (res != -1) { +@@ -368,7 +504,8 @@ static int lfs_g_setmode (lua_State *L, FILE *f, int arg) { + } + } + +-static int lfs_f_setmode(lua_State *L) { ++static int lfs_f_setmode(lua_State * L) ++{ + return lfs_g_setmode(L, check_file(L, 1, "setmode"), 2); + } + +@@ -379,19 +516,20 @@ static int lfs_f_setmode(lua_State *L) { + ** @param #3 Number with start position (optional). + ** @param #4 Number with length (optional). + */ +-static int file_lock (lua_State *L) { +- FILE *fh = check_file (L, 1, "lock"); +- const char *mode = luaL_checkstring (L, 2); +- const long start = (long) luaL_optinteger (L, 3, 0); +- long len = (long) luaL_optinteger (L, 4, 0); +- if (_file_lock (L, fh, mode, start, len, "lock")) { +- lua_pushboolean (L, 1); +- return 1; +- } else { +- lua_pushnil (L); +- lua_pushfstring (L, "%s", strerror(errno)); +- return 2; +- } ++static int file_lock(lua_State * L) ++{ ++ FILE *fh = check_file(L, 1, "lock"); ++ const char *mode = luaL_checkstring(L, 2); ++ const long start = (long) luaL_optinteger(L, 3, 0); ++ long len = (long) luaL_optinteger(L, 4, 0); ++ if (_file_lock(L, fh, mode, start, len, "lock")) { ++ lua_pushboolean(L, 1); ++ return 1; ++ } else { ++ lua_pushnil(L); ++ lua_pushfstring(L, "%s", strerror(errno)); ++ return 2; ++ } + } + + +@@ -401,18 +539,19 @@ static int file_lock (lua_State *L) { + ** @param #2 Number with start position (optional). + ** @param #3 Number with length (optional). + */ +-static int file_unlock (lua_State *L) { +- FILE *fh = check_file (L, 1, "unlock"); +- const long start = (long) luaL_optinteger (L, 2, 0); +- long len = (long) luaL_optinteger (L, 3, 0); +- if (_file_lock (L, fh, "u", start, len, "unlock")) { +- lua_pushboolean (L, 1); +- return 1; +- } else { +- lua_pushnil (L); +- lua_pushfstring (L, "%s", strerror(errno)); +- return 2; +- } ++static int file_unlock(lua_State * L) ++{ ++ FILE *fh = check_file(L, 1, "unlock"); ++ const long start = (long) luaL_optinteger(L, 2, 0); ++ long len = (long) luaL_optinteger(L, 3, 0); ++ if (_file_lock(L, fh, "u", start, len, "unlock")) { ++ lua_pushboolean(L, 1); ++ return 1; ++ } else { ++ lua_pushnil(L); ++ lua_pushfstring(L, "%s", strerror(errno)); ++ return 2; ++ } + } + + +@@ -422,16 +561,40 @@ static int file_unlock (lua_State *L) { + ** @param #2 Name of link. + ** @param #3 True if link is symbolic (optional). + */ +-static int make_link(lua_State *L) ++static int make_link(lua_State * L) + { ++ const char *oldpath = luaL_checkstring(L, 1); ++ const char *newpath = luaL_checkstring(L, 2); + #ifndef _WIN32 +- const char *oldpath = luaL_checkstring(L, 1); +- const char *newpath = luaL_checkstring(L, 2); +- return pushresult(L, +- (lua_toboolean(L,3) ? symlink : link)(oldpath, newpath), NULL); ++ return pushresult(L, ++ (lua_toboolean(L, 3) ? symlink : link) (oldpath, ++ newpath), ++ NULL); + #else +- errno = ENOSYS; /* = "Function not implemented" */ +- return pushresult(L, -1, "make_link is not supported on Windows"); ++ int symbolic = lua_toboolean(L, 3); ++ STAT_STRUCT oldpathinfo; ++ int is_dir = 0; ++ if (STAT_FUNC(oldpath, &oldpathinfo) == 0) { ++ is_dir = S_ISDIR(oldpathinfo.st_mode) != 0; ++ } ++ if (!symbolic && is_dir) { ++ lua_pushnil(L); ++ lua_pushstring(L, ++ "hard links to directories are not supported on Windows"); ++ return 2; ++ } ++ ++ int result = symbolic ? CreateSymbolicLink(newpath, oldpath, is_dir) ++ : CreateHardLink(newpath, oldpath, NULL); ++ ++ if (result) { ++ return pushresult(L, result, NULL); ++ } else { ++ lua_pushnil(L); ++ lua_pushstring(L, symbolic ? "make_link CreateSymbolicLink() failed" ++ : "make_link CreateHardLink() failed"); ++ return 2; ++ } + #endif + } + +@@ -440,22 +603,10 @@ static int make_link(lua_State *L) + ** Creates a directory. + ** @param #1 Directory path. + */ +-static int make_dir (lua_State *L) { +- const char *path = luaL_checkstring (L, 1); +- int fail; +-#ifdef _WIN32 +- fail = _mkdir (path); +-#else +- fail = mkdir (path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | +- S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH ); +-#endif +- if (fail) { +- lua_pushnil (L); +- lua_pushfstring (L, "%s", strerror(errno)); +- return 2; +- } +- lua_pushboolean (L, 1); +- return 1; ++static int make_dir(lua_State * L) ++{ ++ const char *path = luaL_checkstring(L, 1); ++ return pushresult(L, lfs_mkdir(path), NULL); + } + + +@@ -463,65 +614,57 @@ static int make_dir (lua_State *L) { + ** Removes a directory. + ** @param #1 Directory path. + */ +-static int remove_dir (lua_State *L) { +- const char *path = luaL_checkstring (L, 1); +- int fail; +- +- fail = rmdir (path); +- +- if (fail) { +- lua_pushnil (L); +- lua_pushfstring (L, "%s", strerror(errno)); +- return 2; +- } +- lua_pushboolean (L, 1); +- return 1; ++static int remove_dir(lua_State * L) ++{ ++ const char *path = luaL_checkstring(L, 1); ++ return pushresult(L, rmdir(path), NULL); + } + + + /* + ** Directory iterator + */ +-static int dir_iter (lua_State *L) { ++static int dir_iter(lua_State * L) ++{ + #ifdef _WIN32 +- struct _finddata_t c_file; ++ struct _finddata_t c_file; + #else +- struct dirent *entry; ++ struct dirent *entry; + #endif +- dir_data *d = (dir_data *)luaL_checkudata (L, 1, DIR_METATABLE); +- luaL_argcheck (L, d->closed == 0, 1, "closed directory"); ++ dir_data *d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE); ++ luaL_argcheck(L, d->closed == 0, 1, "closed directory"); + #ifdef _WIN32 +- if (d->hFile == 0L) { /* first entry */ +- if ((d->hFile = _findfirst (d->pattern, &c_file)) == -1L) { +- lua_pushnil (L); +- lua_pushstring (L, strerror (errno)); +- d->closed = 1; +- return 2; +- } else { +- lua_pushstring (L, c_file.name); +- return 1; +- } +- } else { /* next entry */ +- if (_findnext (d->hFile, &c_file) == -1L) { +- /* no more entries => close directory */ +- _findclose (d->hFile); +- d->closed = 1; +- return 0; +- } else { +- lua_pushstring (L, c_file.name); +- return 1; +- } +- } ++ if (d->hFile == 0L) { /* first entry */ ++ if ((d->hFile = _findfirst(d->pattern, &c_file)) == -1L) { ++ lua_pushnil(L); ++ lua_pushstring(L, strerror(errno)); ++ d->closed = 1; ++ return 2; ++ } else { ++ lua_pushstring(L, c_file.name); ++ return 1; ++ } ++ } else { /* next entry */ ++ if (_findnext(d->hFile, &c_file) == -1L) { ++ /* no more entries => close directory */ ++ _findclose(d->hFile); ++ d->closed = 1; ++ return 0; ++ } else { ++ lua_pushstring(L, c_file.name); ++ return 1; ++ } ++ } + #else +- if ((entry = readdir (d->dir)) != NULL) { +- lua_pushstring (L, entry->d_name); +- return 1; +- } else { +- /* no more entries => close directory */ +- closedir (d->dir); +- d->closed = 1; +- return 0; +- } ++ if ((entry = readdir(d->dir)) != NULL) { ++ lua_pushstring(L, entry->d_name); ++ return 1; ++ } else { ++ /* no more entries => close directory */ ++ closedir(d->dir); ++ d->closed = 1; ++ return 0; ++ } + #endif + } + +@@ -529,329 +672,375 @@ static int dir_iter (lua_State *L) { + /* + ** Closes directory iterators + */ +-static int dir_close (lua_State *L) { +- dir_data *d = (dir_data *)lua_touserdata (L, 1); ++static int dir_close(lua_State * L) ++{ ++ dir_data *d = (dir_data *) lua_touserdata(L, 1); + #ifdef _WIN32 +- if (!d->closed && d->hFile) { +- _findclose (d->hFile); +- } ++ if (!d->closed && d->hFile) { ++ _findclose(d->hFile); ++ } + #else +- if (!d->closed && d->dir) { +- closedir (d->dir); +- } ++ if (!d->closed && d->dir) { ++ closedir(d->dir); ++ } + #endif +- d->closed = 1; +- return 0; ++ d->closed = 1; ++ return 0; + } + + + /* + ** Factory of directory iterators + */ +-static int dir_iter_factory (lua_State *L) { +- const char *path = luaL_checkstring (L, 1); +- dir_data *d; +- lua_pushcfunction (L, dir_iter); +- d = (dir_data *) lua_newuserdata (L, sizeof(dir_data)); +- luaL_getmetatable (L, DIR_METATABLE); +- lua_setmetatable (L, -2); +- d->closed = 0; ++static int dir_iter_factory(lua_State * L) ++{ ++ const char *path = luaL_checkstring(L, 1); ++ dir_data *d; ++ lua_pushcfunction(L, dir_iter); ++ d = (dir_data *) lua_newuserdata(L, sizeof(dir_data)); ++ luaL_getmetatable(L, DIR_METATABLE); ++ lua_setmetatable(L, -2); ++ d->closed = 0; + #ifdef _WIN32 +- d->hFile = 0L; +- if (strlen(path) > MAX_PATH-2) +- luaL_error (L, "path too long: %s", path); +- else +- sprintf (d->pattern, "%s/*", path); ++ d->hFile = 0L; ++ if (strlen(path) > MAX_PATH - 2) ++ luaL_error(L, "path too long: %s", path); ++ else ++ sprintf(d->pattern, "%s/*", path); + #else +- d->dir = opendir (path); +- if (d->dir == NULL) +- luaL_error (L, "cannot open %s: %s", path, strerror (errno)); ++ d->dir = opendir(path); ++ if (d->dir == NULL) ++ luaL_error(L, "cannot open %s: %s", path, strerror(errno)); ++#endif ++#if LUA_VERSION_NUM >= 504 ++ lua_pushnil(L); ++ lua_pushvalue(L, -2); ++ return 4; ++#else ++ return 2; + #endif +- return 2; + } + + + /* + ** Creates directory metatable. + */ +-static int dir_create_meta (lua_State *L) { +- luaL_newmetatable (L, DIR_METATABLE); +- +- /* Method table */ +- lua_newtable(L); +- lua_pushcfunction (L, dir_iter); +- lua_setfield(L, -2, "next"); +- lua_pushcfunction (L, dir_close); +- lua_setfield(L, -2, "close"); +- +- /* Metamethods */ +- lua_setfield(L, -2, "__index"); +- lua_pushcfunction (L, dir_close); +- lua_setfield (L, -2, "__gc"); +- return 1; ++static int dir_create_meta(lua_State * L) ++{ ++ luaL_newmetatable(L, DIR_METATABLE); ++ ++ /* Method table */ ++ lua_newtable(L); ++ lua_pushcfunction(L, dir_iter); ++ lua_setfield(L, -2, "next"); ++ lua_pushcfunction(L, dir_close); ++ lua_setfield(L, -2, "close"); ++ ++ /* Metamethods */ ++ lua_setfield(L, -2, "__index"); ++ lua_pushcfunction(L, dir_close); ++ lua_setfield(L, -2, "__gc"); ++ ++#if LUA_VERSION_NUM >= 504 ++ lua_pushcfunction(L, dir_close); ++ lua_setfield(L, -2, "__close"); ++#endif ++ return 1; + } + + + /* + ** Creates lock metatable. + */ +-static int lock_create_meta (lua_State *L) { +- luaL_newmetatable (L, LOCK_METATABLE); +- +- /* Method table */ +- lua_newtable(L); +- lua_pushcfunction(L, lfs_unlock_dir); +- lua_setfield(L, -2, "free"); +- +- /* Metamethods */ +- lua_setfield(L, -2, "__index"); +- lua_pushcfunction(L, lfs_unlock_dir); +- lua_setfield(L, -2, "__gc"); +- return 1; ++static int lock_create_meta(lua_State * L) ++{ ++ luaL_newmetatable(L, LOCK_METATABLE); ++ ++ /* Method table */ ++ lua_newtable(L); ++ lua_pushcfunction(L, lfs_unlock_dir); ++ lua_setfield(L, -2, "free"); ++ ++ /* Metamethods */ ++ lua_setfield(L, -2, "__index"); ++ lua_pushcfunction(L, lfs_unlock_dir); ++ lua_setfield(L, -2, "__gc"); ++ return 1; + } + + +-#ifdef _WIN32 +- #ifndef S_ISDIR +- #define S_ISDIR(mode) (mode&_S_IFDIR) +- #endif +- #ifndef S_ISREG +- #define S_ISREG(mode) (mode&_S_IFREG) +- #endif +- #ifndef S_ISLNK +- #define S_ISLNK(mode) (0) +- #endif +- #ifndef S_ISSOCK +- #define S_ISSOCK(mode) (0) +- #endif +- #ifndef S_ISFIFO +- #define S_ISFIFO(mode) (0) +- #endif +- #ifndef S_ISCHR +- #define S_ISCHR(mode) (mode&_S_IFCHR) +- #endif +- #ifndef S_ISBLK +- #define S_ISBLK(mode) (0) +- #endif +-#endif + /* + ** Convert the inode protection mode to a string. + */ + #ifdef _WIN32 +-static const char *mode2string (unsigned short mode) { ++static const char *mode2string(unsigned short mode) ++{ + #else +-static const char *mode2string (mode_t mode) { ++static const char *mode2string(mode_t mode) ++{ + #endif +- if ( S_ISREG(mode) ) ++ if (S_ISREG(mode)) + return "file"; +- else if ( S_ISDIR(mode) ) ++ else if (S_ISDIR(mode)) + return "directory"; +- else if ( S_ISLNK(mode) ) +- return "link"; +- else if ( S_ISSOCK(mode) ) ++ else if (S_ISLNK(mode)) ++ return "link"; ++ else if (S_ISSOCK(mode)) + return "socket"; +- else if ( S_ISFIFO(mode) ) +- return "named pipe"; +- else if ( S_ISCHR(mode) ) +- return "char device"; +- else if ( S_ISBLK(mode) ) +- return "block device"; ++ else if (S_ISFIFO(mode)) ++ return "named pipe"; ++ else if (S_ISCHR(mode)) ++ return "char device"; ++ else if (S_ISBLK(mode)) ++ return "block device"; + else +- return "other"; ++ return "other"; + } + + + /* +-** Set access time and modification values for file ++** Set access time and modification values for a file. ++** @param #1 File path. ++** @param #2 Access time in seconds, current time is used if missing. ++** @param #3 Modification time in seconds, access time is used if missing. + */ +-static int file_utime (lua_State *L) { +- const char *file = luaL_checkstring (L, 1); +- struct utimbuf utb, *buf; +- +- if (lua_gettop (L) == 1) /* set to current date/time */ +- buf = NULL; +- else { +- utb.actime = (time_t)luaL_optnumber (L, 2, 0); +- utb.modtime = (time_t) luaL_optinteger (L, 3, utb.actime); +- buf = &utb; +- } +- if (utime (file, buf)) { +- lua_pushnil (L); +- lua_pushfstring (L, "%s", strerror (errno)); +- return 2; +- } +- lua_pushboolean (L, 1); +- return 1; ++static int file_utime(lua_State * L) ++{ ++ const char *file = luaL_checkstring(L, 1); ++ struct utimbuf utb, *buf; ++ ++ if (lua_gettop(L) == 1) /* set to current date/time */ ++ buf = NULL; ++ else { ++ utb.actime = (time_t) luaL_optnumber(L, 2, 0); ++ utb.modtime = (time_t) luaL_optinteger(L, 3, utb.actime); ++ buf = &utb; ++ } ++ ++ return pushresult(L, utime(file, buf), NULL); + } + + + /* inode protection mode */ +-static void push_st_mode (lua_State *L, STAT_STRUCT *info) { +- lua_pushstring (L, mode2string (info->st_mode)); ++static void push_st_mode(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushstring(L, mode2string(info->st_mode)); + } ++ + /* device inode resides on */ +-static void push_st_dev (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_dev); ++static void push_st_dev(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_dev); + } ++ + /* inode's number */ +-static void push_st_ino (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_ino); ++static void push_st_ino(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_ino); + } ++ + /* number of hard links to the file */ +-static void push_st_nlink (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_nlink); ++static void push_st_nlink(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_nlink); + } ++ + /* user-id of owner */ +-static void push_st_uid (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_uid); ++static void push_st_uid(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_uid); + } ++ + /* group-id of owner */ +-static void push_st_gid (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_gid); ++static void push_st_gid(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_gid); + } ++ + /* device type, for special file inode */ +-static void push_st_rdev (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_rdev); ++static void push_st_rdev(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_rdev); + } ++ + /* time of last access */ +-static void push_st_atime (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_atime); ++static void push_st_atime(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_atime); + } ++ + /* time of last data modification */ +-static void push_st_mtime (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_mtime); ++static void push_st_mtime(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_mtime); + } ++ + /* time of last file status change */ +-static void push_st_ctime (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer) info->st_ctime); ++static void push_st_ctime(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_ctime); + } ++ + /* file size, in bytes */ +-static void push_st_size (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_size); ++static void push_st_size(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_size); + } ++ + #ifndef _WIN32 + /* blocks allocated for file */ +-static void push_st_blocks (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_blocks); ++static void push_st_blocks(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_blocks); + } ++ + /* optimal file system I/O blocksize */ +-static void push_st_blksize (lua_State *L, STAT_STRUCT *info) { +- lua_pushinteger (L, (lua_Integer)info->st_blksize); ++static void push_st_blksize(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushinteger(L, (lua_Integer) info->st_blksize); + } + #endif + + /* +-** Convert the inode protection mode to a permission list. +-*/ ++ ** Convert the inode protection mode to a permission list. ++ */ + + #ifdef _WIN32 +-static const char *perm2string (unsigned short mode) { ++static const char *perm2string(unsigned short mode) ++{ + static char perms[10] = "---------"; + int i; +- for (i=0;i<9;i++) perms[i]='-'; +- if (mode & _S_IREAD) +- { perms[0] = 'r'; perms[3] = 'r'; perms[6] = 'r'; } +- if (mode & _S_IWRITE) +- { perms[1] = 'w'; perms[4] = 'w'; perms[7] = 'w'; } +- if (mode & _S_IEXEC) +- { perms[2] = 'x'; perms[5] = 'x'; perms[8] = 'x'; } ++ for (i = 0; i < 9; i++) ++ perms[i] = '-'; ++ if (mode & _S_IREAD) { ++ perms[0] = 'r'; ++ perms[3] = 'r'; ++ perms[6] = 'r'; ++ } ++ if (mode & _S_IWRITE) { ++ perms[1] = 'w'; ++ perms[4] = 'w'; ++ perms[7] = 'w'; ++ } ++ if (mode & _S_IEXEC) { ++ perms[2] = 'x'; ++ perms[5] = 'x'; ++ perms[8] = 'x'; ++ } + return perms; + } + #else +-static const char *perm2string (mode_t mode) { ++static const char *perm2string(mode_t mode) ++{ + static char perms[10] = "---------"; + int i; +- for (i=0;i<9;i++) perms[i]='-'; +- if (mode & S_IRUSR) perms[0] = 'r'; +- if (mode & S_IWUSR) perms[1] = 'w'; +- if (mode & S_IXUSR) perms[2] = 'x'; +- if (mode & S_IRGRP) perms[3] = 'r'; +- if (mode & S_IWGRP) perms[4] = 'w'; +- if (mode & S_IXGRP) perms[5] = 'x'; +- if (mode & S_IROTH) perms[6] = 'r'; +- if (mode & S_IWOTH) perms[7] = 'w'; +- if (mode & S_IXOTH) perms[8] = 'x'; ++ for (i = 0; i < 9; i++) ++ perms[i] = '-'; ++ if (mode & S_IRUSR) ++ perms[0] = 'r'; ++ if (mode & S_IWUSR) ++ perms[1] = 'w'; ++ if (mode & S_IXUSR) ++ perms[2] = 'x'; ++ if (mode & S_IRGRP) ++ perms[3] = 'r'; ++ if (mode & S_IWGRP) ++ perms[4] = 'w'; ++ if (mode & S_IXGRP) ++ perms[5] = 'x'; ++ if (mode & S_IROTH) ++ perms[6] = 'r'; ++ if (mode & S_IWOTH) ++ perms[7] = 'w'; ++ if (mode & S_IXOTH) ++ perms[8] = 'x'; + return perms; + } + #endif + + /* permssions string */ +-static void push_st_perm (lua_State *L, STAT_STRUCT *info) { +- lua_pushstring (L, perm2string (info->st_mode)); ++static void push_st_perm(lua_State * L, STAT_STRUCT * info) ++{ ++ lua_pushstring(L, perm2string(info->st_mode)); + } + +-typedef void (*_push_function) (lua_State *L, STAT_STRUCT *info); ++typedef void (*_push_function)(lua_State * L, STAT_STRUCT * info); + + struct _stat_members { +- const char *name; +- _push_function push; ++ const char *name; ++ _push_function push; + }; + + struct _stat_members members[] = { +- { "mode", push_st_mode }, +- { "dev", push_st_dev }, +- { "ino", push_st_ino }, +- { "nlink", push_st_nlink }, +- { "uid", push_st_uid }, +- { "gid", push_st_gid }, +- { "rdev", push_st_rdev }, +- { "access", push_st_atime }, +- { "modification", push_st_mtime }, +- { "change", push_st_ctime }, +- { "size", push_st_size }, +- { "permissions", push_st_perm }, ++ { "mode", push_st_mode }, ++ { "dev", push_st_dev }, ++ { "ino", push_st_ino }, ++ { "nlink", push_st_nlink }, ++ { "uid", push_st_uid }, ++ { "gid", push_st_gid }, ++ { "rdev", push_st_rdev }, ++ { "access", push_st_atime }, ++ { "modification", push_st_mtime }, ++ { "change", push_st_ctime }, ++ { "size", push_st_size }, ++ { "permissions", push_st_perm }, + #ifndef _WIN32 +- { "blocks", push_st_blocks }, +- { "blksize", push_st_blksize }, ++ { "blocks", push_st_blocks }, ++ { "blksize", push_st_blksize }, + #endif +- { NULL, NULL } ++ { NULL, NULL } + }; + + /* + ** Get file or symbolic link information + */ +-static int _file_info_ (lua_State *L, int (*st)(const char*, STAT_STRUCT*)) { +- STAT_STRUCT info; +- const char *file = luaL_checkstring (L, 1); +- int i; +- +- if (st(file, &info)) { +- lua_pushnil(L); +- lua_pushfstring(L, "cannot obtain information from file '%s': %s", file, strerror(errno)); +- return 2; +- } +- if (lua_isstring (L, 2)) { +- const char *member = lua_tostring (L, 2); +- for (i = 0; members[i].name; i++) { +- if (strcmp(members[i].name, member) == 0) { +- /* push member value and return */ +- members[i].push (L, &info); +- return 1; +- } +- } +- /* member not found */ +- return luaL_error(L, "invalid attribute name '%s'", member); +- } +- /* creates a table if none is given, removes extra arguments */ +- lua_settop(L, 2); +- if (!lua_istable (L, 2)) { +- lua_newtable (L); +- } +- /* stores all members in table on top of the stack */ +- for (i = 0; members[i].name; i++) { +- lua_pushstring (L, members[i].name); +- members[i].push (L, &info); +- lua_rawset (L, -3); +- } ++static int _file_info_(lua_State * L, ++ int (*st)(const char *, STAT_STRUCT *)) ++{ ++ STAT_STRUCT info; ++ const char *file = luaL_checkstring(L, 1); ++ int i; ++ ++ if (st(file, &info)) { ++ lua_pushnil(L); ++ lua_pushfstring(L, "cannot obtain information from file '%s': %s", ++ file, strerror(errno)); ++ lua_pushinteger(L, errno); ++ return 3; ++ } ++ if (lua_isstring(L, 2)) { ++ const char *member = lua_tostring(L, 2); ++ for (i = 0; members[i].name; i++) { ++ if (strcmp(members[i].name, member) == 0) { ++ /* push member value and return */ ++ members[i].push(L, &info); + return 1; ++ } ++ } ++ /* member not found */ ++ return luaL_error(L, "invalid attribute name '%s'", member); ++ } ++ /* creates a table if none is given, removes extra arguments */ ++ lua_settop(L, 2); ++ if (!lua_istable(L, 2)) { ++ lua_newtable(L); ++ } ++ /* stores all members in table on top of the stack */ ++ for (i = 0; members[i].name; i++) { ++ lua_pushstring(L, members[i].name); ++ members[i].push(L, &info); ++ lua_rawset(L, -3); ++ } ++ return 1; + } + + + /* + ** Get file information using stat. + */ +-static int file_info (lua_State *L) { +- return _file_info_ (L, STAT_FUNC); ++static int file_info(lua_State * L) ++{ ++ return _file_info_(L, STAT_FUNC); + } + + +@@ -861,62 +1050,88 @@ static int file_info (lua_State *L) { + ** Returns 1 if successful (with the target on top of the stack), + ** 0 on failure (with stack unchanged, and errno set). + */ +-static int push_link_target(lua_State *L) { ++static int push_link_target(lua_State * L) ++{ ++ const char *file = luaL_checkstring(L, 1); ++#ifdef _WIN32 ++ HANDLE h = CreateFile(file, GENERIC_READ, ++ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, ++ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ++ if (h == INVALID_HANDLE_VALUE) { ++ return lfs_win32_pusherror(L); ++ } ++#endif ++ char *target = NULL; ++ int tsize = 0, size = 256; /* size = initial buffer capacity */ ++ int ok = 0; ++ while (!ok) { ++ char *target2 = (char *)realloc(target, size); ++ if (!target2) { /* failed to allocate */ ++ break; ++ } ++ target = target2; + #ifdef _WIN32 +- errno = ENOSYS; +- return 0; ++ tsize = (int)GetFinalPathNameByHandle(h, target, size, FILE_NAME_OPENED); + #else +- const char *file = luaL_checkstring(L, 1); +- char *target = NULL; +- int tsize, size = 256; /* size = initial buffer capacity */ +- while (1) { +- target = realloc(target, size); +- if (!target) /* failed to allocate */ +- return 0; +- tsize = readlink(file, target, size); +- if (tsize < 0) { /* a readlink() error occurred */ +- free(target); +- return 0; +- } +- if (tsize < size) +- break; +- /* possibly truncated readlink() result, double size and retry */ +- size *= 2; +- } +- target[tsize] = '\0'; +- lua_pushlstring(L, target, tsize); +- free(target); +- return 1; ++ tsize = (int)readlink(file, target, size); ++#endif ++ if (tsize < 0) { /* a readlink() error occurred */ ++ break; ++ } ++ if (tsize < size) { ++#ifdef _WIN32 ++ if (tsize > 4 && strncmp(target, "\\\\?\\", 4) == 0) { ++ memmove(target, target + 4, tsize - 3); ++ tsize -= 4; ++ } + #endif ++ ok = 1; ++ break; ++ } ++ /* possibly truncated readlink() result, double size and retry */ ++ size *= 2; ++ } ++ if (ok) { ++ target[tsize] = '\0'; ++ lua_pushlstring(L, target, tsize); ++ } ++#ifdef _WIN32 ++ CloseHandle(h); ++#endif ++ free(target); ++ return ok; + } + + /* + ** Get symbolic link information using lstat. + */ +-static int link_info (lua_State *L) { +- int ret; +- if (lua_isstring (L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { +- int ok = push_link_target(L); +- return ok ? 1 : pusherror(L, "could not obtain link target"); +- } +- ret = _file_info_ (L, LSTAT_FUNC); +- if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { +- int ok = push_link_target(L); +- if (ok) { +- lua_setfield(L, -2, "target"); +- } +- } +- return ret; ++static int link_info(lua_State * L) ++{ ++ int ret; ++ if (lua_isstring(L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { ++ int ok = push_link_target(L); ++ return ok ? 1 : pusherror(L, "could not obtain link target"); ++ } ++ ret = _file_info_(L, LSTAT_FUNC); ++ if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { ++ int ok = push_link_target(L); ++ if (ok) { ++ lua_setfield(L, -2, "target"); ++ } ++ } ++ return ret; + } + + + /* + ** Assumes the table is on top of the stack. + */ +-static void set_info (lua_State *L) { +- lua_pushliteral(L, "Copyright (C) 2003-2016 Kepler Project"); +- lua_setfield(L, -2, "_COPYRIGHT"); +- lua_pushliteral(L, "LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution"); ++static void set_info(lua_State * L) ++{ ++ lua_pushliteral(L, ++ "LuaFileSystem is a Lua library developed to complement " ++ "the set of functions related to file systems offered by " ++ "the standard Lua distribution"); + lua_setfield(L, -2, "_DESCRIPTION"); + lua_pushliteral(L, "LuaFileSystem " LFS_VERSION); + lua_setfield(L, -2, "_VERSION"); +@@ -924,28 +1139,29 @@ static void set_info (lua_State *L) { + + + static const struct luaL_Reg fslib[] = { +- {"attributes", file_info}, +- {"chdir", change_dir}, +- {"currentdir", get_dir}, +- {"dir", dir_iter_factory}, +- {"link", make_link}, +- {"lock", file_lock}, +- {"mkdir", make_dir}, +- {"rmdir", remove_dir}, +- {"symlinkattributes", link_info}, +- {"setmode", lfs_f_setmode}, +- {"touch", file_utime}, +- {"unlock", file_unlock}, +- {"lock_dir", lfs_lock_dir}, +- {NULL, NULL}, ++ { "attributes", file_info }, ++ { "chdir", change_dir }, ++ { "currentdir", get_dir }, ++ { "dir", dir_iter_factory }, ++ { "link", make_link }, ++ { "lock", file_lock }, ++ { "mkdir", make_dir }, ++ { "rmdir", remove_dir }, ++ { "symlinkattributes", link_info }, ++ { "setmode", lfs_f_setmode }, ++ { "touch", file_utime }, ++ { "unlock", file_unlock }, ++ { "lock_dir", lfs_lock_dir }, ++ { NULL, NULL }, + }; + +-LFS_EXPORT int luaopen_lfs (lua_State *L) { +- dir_create_meta (L); +- lock_create_meta (L); +- luaL_newlib (L, fslib); +- lua_pushvalue(L, -1); +- lua_setglobal(L, LFS_LIBNAME); +- set_info (L); +- return 1; ++LFS_EXPORT int luaopen_lfs(lua_State * L) ++{ ++ dir_create_meta(L); ++ lock_create_meta(L); ++ new_lib(L, fslib); ++ lua_pushvalue(L, -1); ++ lua_setglobal(L, LFS_LIBNAME); ++ set_info(L); ++ return 1; + } +diff --git a/extra/luafilesystem/src/lfs.def b/extra/luafilesystem/src/lfs.def +index 8a36d41..bd8c847 100644 +--- a/extra/luafilesystem/src/lfs.def ++++ b/extra/luafilesystem/src/lfs.def +@@ -1,4 +1,4 @@ + LIBRARY lfs.dll +-VERSION 1.6 ++VERSION 1.8 + EXPORTS + luaopen_lfs +diff --git a/extra/luafilesystem/src/lfs.h b/extra/luafilesystem/src/lfs.h +index 7f7d2ab..116b892 100644 +--- a/extra/luafilesystem/src/lfs.h ++++ b/extra/luafilesystem/src/lfs.h +@@ -1,33 +1,37 @@ + /* + ** LuaFileSystem +-** Copyright Kepler Project 2003 - 2016 (http://keplerproject.github.io/luafilesystem) ++** File system manipulation library ++** ++** Copyright (C) 2003-2010 Kepler Project. ++** Copyright (C) 2010-2022 The LuaFileSystem authors. ++** (http://lunarmodules.github.io/luafilesystem) + */ + + /* Define 'chdir' for systems that do not implement it */ + #ifdef NO_CHDIR +- #define chdir(p) (-1) +- #define chdir_error "Function 'chdir' not provided by system" ++#define chdir(p) (-1) ++#define chdir_error "Function 'chdir' not provided by system" + #else +- #define chdir_error strerror(errno) ++#define chdir_error strerror(errno) + #endif + + #ifdef _WIN32 +- #define chdir(p) (_chdir(p)) +- #define getcwd(d, s) (_getcwd(d, s)) +- #define rmdir(p) (_rmdir(p)) +- #define LFS_EXPORT __declspec (dllexport) +- #ifndef fileno +- #define fileno(f) (_fileno(f)) +- #endif ++#define chdir(p) (_chdir(p)) ++#define getcwd(d, s) (_getcwd(d, s)) ++#define rmdir(p) (_rmdir(p)) ++#define LFS_EXPORT __declspec (dllexport) ++#ifndef fileno ++#define fileno(f) (_fileno(f)) ++#endif + #else +- #define LFS_EXPORT ++#define LFS_EXPORT + #endif + + #ifdef __cplusplus + extern "C" { + #endif + +-LFS_EXPORT int luaopen_lfs (lua_State *L); ++ LFS_EXPORT int luaopen_lfs(lua_State * L); + + #ifdef __cplusplus + } +diff --git a/extra/luafilesystem/tests/test.lua b/extra/luafilesystem/tests/test.lua +index 10810fe..ed154c0 100644 +--- a/extra/luafilesystem/tests/test.lua ++++ b/extra/luafilesystem/tests/test.lua +@@ -4,6 +4,8 @@ local tmp = "/tmp" + local sep = string.match (package.config, "[^\n]+") + local upper = ".." + ++local is_unix = package.config:sub(1,1) == "/" ++ + local lfs = require"lfs" + print (lfs._VERSION) + +@@ -60,6 +62,8 @@ if not attrib then + error ("could not get attributes of file `"..tmpdir.."':\n"..errmsg) + end + local f = io.open(tmpfile, "w") ++local data = "hello, file!" ++f:write(data) + f:close() + + io.write(".") +@@ -87,14 +91,42 @@ assert (new_att.modification == testdate1, "could not set modification time") + io.write(".") + io.flush() + +--- Checking link (does not work on Windows) + if lfs.link (tmpfile, "_a_link_for_test_", true) then + assert (lfs.attributes"_a_link_for_test_".mode == "file") + assert (lfs.symlinkattributes"_a_link_for_test_".mode == "link") + assert (lfs.symlinkattributes"_a_link_for_test_".target == tmpfile) + assert (lfs.symlinkattributes("_a_link_for_test_", "target") == tmpfile) ++ ++ assert (lfs.symlinkattributes(tmpfile).mode == "file") ++ + assert (lfs.link (tmpfile, "_a_hard_link_for_test_")) +- assert (lfs.attributes (tmpfile, "nlink") == 2) ++ assert (lfs.symlinkattributes"_a_hard_link_for_test_".mode == "file") ++ ++ local fd = io.open(tmpfile) ++ assert(fd:read("*a") == data) ++ fd:close() ++ ++ fd = io.open("_a_link_for_test_") ++ assert(fd:read("*a") == data) ++ fd:close() ++ ++ fd = io.open("_a_hard_link_for_test_") ++ assert(fd:read("*a") == data) ++ fd:close() ++ ++ fd = io.open("_a_hard_link_for_test_", "w+") ++ local data2 = "write in hard link" ++ fd:write(data2) ++ fd:close() ++ ++ fd = io.open(tmpfile) ++ assert(fd:read("*a") == data2) ++ fd:close() ++ ++ if is_unix then ++ assert (lfs.attributes (tmpfile, "nlink") == 2) ++ end ++ + assert (os.remove"_a_link_for_test_") + assert (os.remove"_a_hard_link_for_test_") + end +@@ -152,7 +184,10 @@ io.write(".") + io.flush() + + -- Trying to get attributes of a non-existent file +-assert (lfs.attributes ("this couldn't be an actual file") == nil, "could get attributes of a non-existent file") ++local attr_ok, err, errno = lfs.attributes("this couldn't be an actual file") ++assert(attr_ok == nil, "could get attributes of a non-existent file") ++assert(type(err) == "string", "failed lfs.attributes did not return an error message") ++assert(type(errno) == "number", "failed lfs.attributes did not return error code") + assert (type(lfs.attributes (upper)) == "table", "couldn't get attributes of upper directory") + + io.write(".") +diff --git a/extra/luafilesystem/vc6/lfs.def b/extra/luafilesystem/vc6/lfs.def +deleted file mode 100644 +index 55ec688..0000000 +--- a/extra/luafilesystem/vc6/lfs.def ++++ /dev/null +@@ -1,5 +0,0 @@ +-LIBRARY lfs.dll +-DESCRIPTION "LuaFileSystem" +-VERSION 1.2 +-EXPORTS +-luaopen_lfs +diff --git a/extra/luafilesystem/vc6/luafilesystem.dsw b/extra/luafilesystem/vc6/luafilesystem.dsw +deleted file mode 100644 +index b4bb4b3..0000000 +--- a/extra/luafilesystem/vc6/luafilesystem.dsw ++++ /dev/null +@@ -1,33 +0,0 @@ +-Microsoft Developer Studio Workspace File, Format Version 6.00 +-# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! +- +-############################################################################### +- +-Project: "luafilesystem_dll"=.\luafilesystem_dll.dsp - Package Owner=<4> +- +-Package=<5> +-{{{ +- begin source code control +- luafilesystem +- .. +- end source code control +-}}} +- +-Package=<4> +-{{{ +-}}} +- +-############################################################################### +- +-Global: +- +-Package=<5> +-{{{ +-}}} +- +-Package=<3> +-{{{ +-}}} +- +-############################################################################### +- +diff --git a/extra/luafilesystem/vc6/luafilesystem_dll.dsp b/extra/luafilesystem/vc6/luafilesystem_dll.dsp +deleted file mode 100644 +index efe6c72..0000000 +--- a/extra/luafilesystem/vc6/luafilesystem_dll.dsp ++++ /dev/null +@@ -1,127 +0,0 @@ +-# Microsoft Developer Studio Project File - Name="luafilesystem_dll" - Package Owner=<4> +-# Microsoft Developer Studio Generated Build File, Format Version 6.00 +-# ** DO NOT EDIT ** +- +-# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +- +-CFG=luafilesystem_dll - Win32 Debug +-!MESSAGE This is not a valid makefile. To build this project using NMAKE, +-!MESSAGE use the Export Makefile command and run +-!MESSAGE +-!MESSAGE NMAKE /f "luafilesystem_dll.mak". +-!MESSAGE +-!MESSAGE You can specify a configuration when running NMAKE +-!MESSAGE by defining the macro CFG on the command line. For example: +-!MESSAGE +-!MESSAGE NMAKE /f "luafilesystem_dll.mak" CFG="luafilesystem_dll - Win32 Debug" +-!MESSAGE +-!MESSAGE Possible choices for configuration are: +-!MESSAGE +-!MESSAGE "luafilesystem_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +-!MESSAGE "luafilesystem_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +-!MESSAGE +- +-# Begin Project +-# PROP AllowPerConfigDependencies 0 +-# PROP Scc_ProjName "luafilesystem_dll" +-# PROP Scc_LocalPath ".." +-CPP=cl.exe +-MTL=midl.exe +-RSC=rc.exe +- +-!IF "$(CFG)" == "luafilesystem_dll - Win32 Release" +- +-# PROP BASE Use_MFC 0 +-# PROP BASE Use_Debug_Libraries 0 +-# PROP BASE Output_Dir "Release" +-# PROP BASE Intermediate_Dir "Release" +-# PROP BASE Target_Dir "" +-# PROP Use_MFC 0 +-# PROP Use_Debug_Libraries 0 +-# PROP Output_Dir "../lib/vc6" +-# PROP Intermediate_Dir "luafilesystem_dll/Release" +-# PROP Ignore_Export_Lib 0 +-# PROP Target_Dir "" +-# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /c +-# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../external-src/lua50/include" /I "../../compat/src" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /c +-# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +-# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +-# ADD BASE RSC /l 0x416 /d "NDEBUG" +-# ADD RSC /l 0x416 /d "NDEBUG" +-BSC32=bscmake.exe +-# ADD BASE BSC32 /nologo +-# ADD BSC32 /nologo +-LINK32=link.exe +-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +-# ADD LINK32 lua50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"../bin/vc6/lfs.dll" /libpath:"../../external-src/lua50/lib/dll" +-# Begin Special Build Tool +-SOURCE="$(InputPath)" +-PostBuild_Cmds=cd ../bin/vc6 zip.exe luafilesystem-1.2-win32.zip lfs.dll +-# End Special Build Tool +- +-!ELSEIF "$(CFG)" == "luafilesystem_dll - Win32 Debug" +- +-# PROP BASE Use_MFC 0 +-# PROP BASE Use_Debug_Libraries 1 +-# PROP BASE Output_Dir "Debug" +-# PROP BASE Intermediate_Dir "Debug" +-# PROP BASE Target_Dir "" +-# PROP Use_MFC 0 +-# PROP Use_Debug_Libraries 1 +-# PROP Output_Dir "../lib/vc6" +-# PROP Intermediate_Dir "luafilesystem_dll/Debug" +-# PROP Ignore_Export_Lib 0 +-# PROP Target_Dir "" +-# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /GZ /c +-# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../external-src/lua50/include" /I "../../compat/src" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /GZ /c +-# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +-# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +-# ADD BASE RSC /l 0x416 /d "_DEBUG" +-# ADD RSC /l 0x416 /d "_DEBUG" +-BSC32=bscmake.exe +-# ADD BASE BSC32 /nologo +-# ADD BSC32 /nologo +-LINK32=link.exe +-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +-# ADD LINK32 lua50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"../bin/vc6/lfsd.dll" /pdbtype:sept /libpath:"../../external-src/lua50/lib/dll" +- +-!ENDIF +- +-# Begin Target +- +-# Name "luafilesystem_dll - Win32 Release" +-# Name "luafilesystem_dll - Win32 Debug" +-# Begin Group "Source Files" +- +-# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +-# Begin Source File +- +-SOURCE="..\..\compat\src\compat-5.1.c" +-# End Source File +-# Begin Source File +- +-SOURCE=..\src\lfs.c +-# End Source File +-# Begin Source File +- +-SOURCE=.\lfs.def +-# End Source File +-# End Group +-# Begin Group "Header Files" +- +-# PROP Default_Filter "h;hpp;hxx;hm;inl" +-# Begin Source File +- +-SOURCE="..\..\compat\src\compat-5.1.h" +-# End Source File +-# Begin Source File +- +-SOURCE=..\src\lfs.h +-# End Source File +-# End Group +-# Begin Group "Resource Files" +- +-# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +-# End Group +-# End Target +-# End Project +Submodule extra/moses ded0a9b..6fe8d76: +diff --git a/extra/moses/.gitignore b/extra/moses/.gitignore +index 6f7a8b1..a92455e 100644 +--- a/extra/moses/.gitignore ++++ b/extra/moses/.gitignore +@@ -1,10 +1,10 @@ + *.bat + tsc* + ldoc* +-telescope* +-LuaMin* ++LuaMinify* + *.ld + luacov* + *.exe + *.dll +-test* +\ No newline at end of file ++test* ++bust* +\ No newline at end of file +diff --git a/extra/moses/.luacov b/extra/moses/.luacov +new file mode 100644 +index 0000000..1d4f5bb +--- /dev/null ++++ b/extra/moses/.luacov +@@ -0,0 +1,52 @@ ++--- Global configuration file. Copy, customize and store in your ++-- project folder as '.luacov' for project specific configuration ++-- @class module ++-- @name luacov.defaults ++return { ++ ++ -- default filename to load for config options if not provided ++ -- only has effect in 'luacov.defaults.lua' ++ ['configfile'] = '.luacov', ++ ++ -- filename to store stats collected ++ ['statsfile'] = 'luacov.stats.out', ++ ++ -- filename to store report ++ ['reportfile'] = 'luacov.report.out', ++ ++ -- Run reporter on completion? (won't work for ticks) ++ runreport = true, ++ ++ -- Delete stats file after reporting? ++ deletestats = 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) ++ ['include'] = { ++ 'moses' ++ }, ++ ++ -- Patterns for files to exclude when reporting ++ -- all will be included if nothing is listed ++ -- (exclude overrules include, do not include ++ -- the .lua extension) ++ ['exclude'] = { ++ 'luacov$', ++ 'luacov.reporter$', ++ 'luacov.defaults$', ++ 'luacov.runner$', ++ 'luacov.stats$', ++ 'luacov.tick$', ++ 'mediator.*$', ++ 'busted$', ++ 'busted.*$', ++ 'luassert.*$', ++ 'pl.*$', ++ 'say.*$', ++ 'spec.*$' ++ }, ++ ++ ++} +diff --git a/extra/moses/.travis.yml b/extra/moses/.travis.yml +index d884837..90bd434 100644 +--- a/extra/moses/.travis.yml ++++ b/extra/moses/.travis.yml +@@ -1,33 +1,32 @@ +-language: c +- ++language: python + sudo: false + + env: +- global: +- - LUAROCKS=2.3.0 +- matrix: +- - LUA=lua5.1 +- - LUA=lua5.2 +- - LUA=lua5.3 +- - LUA=luajit # latest stable version (2.0.4) +- - LUA=luajit2.0 # current head of 2.0 branch +- - LUA=luajit2.1 # current head of 2.1 branc +- +-branches: +- only: +- - master ++ - LUA="lua=5.1" ++ - LUA="lua=5.2" ++ - LUA="lua=5.3" ++ - LUA="luajit=2.0" ++ - LUA="luajit=2.1" + + before_install: +- - source .travis/setenv_lua.sh +- - pip install --user cpp-coveralls +- - luarocks install Lua-cURL --server=https://luarocks.org/dev +- - luarocks install luacov-coveralls --server=https://luarocks.org/dev +- - luarocks install telescope 0.6.0 --server=http://rocks.moonscript.org ++ - pip install hererocks ++ - hererocks lua_install -r^ --$LUA ++ - export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH + +-script: "tsc -f spec/*" ++install: ++ - luarocks install busted ++ - luarocks install luacov ++ - luarocks install luacov-coveralls ++ ++script: ++ - busted --verbose --coverage + + after_success: +- - luacov-coveralls -c specs/.luacov ++ - luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install ++ ++branches: ++ except: ++ - gh-pages + + notifications: + email: +diff --git a/extra/moses/.travis/platform.sh b/extra/moses/.travis/platform.sh +deleted file mode 100644 +index 7259a7d..0000000 +--- a/extra/moses/.travis/platform.sh ++++ /dev/null +@@ -1,15 +0,0 @@ +-if [ -z "${PLATFORM:-}" ]; then +- PLATFORM=$TRAVIS_OS_NAME; +-fi +- +-if [ "$PLATFORM" == "osx" ]; then +- PLATFORM="macosx"; +-fi +- +-if [ -z "$PLATFORM" ]; then +- if [ "$(uname)" == "Linux" ]; then +- PLATFORM="linux"; +- else +- PLATFORM="macosx"; +- fi; +-fi +diff --git a/extra/moses/.travis/setenv_lua.sh b/extra/moses/.travis/setenv_lua.sh +deleted file mode 100644 +index 8d8c825..0000000 +--- a/extra/moses/.travis/setenv_lua.sh ++++ /dev/null +@@ -1,3 +0,0 @@ +-export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin +-bash .travis/setup_lua.sh +-eval `$HOME/.lua/luarocks path` +diff --git a/extra/moses/.travis/setup_lua.sh b/extra/moses/.travis/setup_lua.sh +deleted file mode 100644 +index 6dcc0c6..0000000 +--- a/extra/moses/.travis/setup_lua.sh ++++ /dev/null +@@ -1,122 +0,0 @@ +-#! /bin/bash +- +-# A script for setting up environment for travis-ci testing. +-# Sets up Lua and Luarocks. +-# LUA must be "lua5.1", "lua5.2" or "luajit". +-# luajit2.0 - master v2.0 +-# luajit2.1 - master v2.1 +- +-set -eufo pipefail +- +-LUAJIT_VERSION="2.0.4" +-LUAJIT_BASE="LuaJIT-$LUAJIT_VERSION" +- +-source .travis/platform.sh +- +-LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua +- +-LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks +- +-mkdir $HOME/.lua +- +-LUAJIT="no" +- +-if [ "$PLATFORM" == "macosx" ]; then +- if [ "$LUA" == "luajit" ]; then +- LUAJIT="yes"; +- fi +- if [ "$LUA" == "luajit2.0" ]; then +- LUAJIT="yes"; +- fi +- if [ "$LUA" == "luajit2.1" ]; then +- LUAJIT="yes"; +- fi; +-elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then +- LUAJIT="yes"; +-fi +- +-mkdir -p "$LUA_HOME_DIR" +- +-if [ "$LUAJIT" == "yes" ]; then +- +- if [ "$LUA" == "luajit" ]; then +- curl --location https://github.com/LuaJIT/LuaJIT/archive/v$LUAJIT_VERSION.tar.gz | tar xz; +- else +- git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_BASE; +- fi +- +- cd $LUAJIT_BASE +- +- if [ "$LUA" == "luajit2.1" ]; then +- git checkout v2.1; +- # force the INSTALL_TNAME to be luajit +- perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile +- fi +- +- make && make install PREFIX="$LUA_HOME_DIR" +- +- ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit +- ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua; +- +-else +- +- if [ "$LUA" == "lua5.1" ]; then +- curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz +- cd lua-5.1.5; +- elif [ "$LUA" == "lua5.2" ]; then +- curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz +- cd lua-5.2.4; +- elif [ "$LUA" == "lua5.3" ]; then +- curl http://www.lua.org/ftp/lua-5.3.2.tar.gz | tar xz +- cd lua-5.3.2; +- fi +- +- # Build Lua without backwards compatibility for testing +- perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile +- make $PLATFORM +- make INSTALL_TOP="$LUA_HOME_DIR" install; +- +- ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua +- ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac; +- +-fi +- +-cd $TRAVIS_BUILD_DIR +- +-lua -v +- +-LUAROCKS_BASE=luarocks-$LUAROCKS +- +-curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz +- +-cd $LUAROCKS_BASE +- +-if [ "$LUA" == "luajit" ]; then +- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; +-elif [ "$LUA" == "luajit2.0" ]; then +- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; +-elif [ "$LUA" == "luajit2.1" ]; then +- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR"; +-else +- ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR" +-fi +- +-make build && make install +- +-ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks +- +-cd $TRAVIS_BUILD_DIR +- +-luarocks --version +- +-rm -rf $LUAROCKS_BASE +- +-if [ "$LUAJIT" == "yes" ]; then +- rm -rf $LUAJIT_BASE; +-elif [ "$LUA" == "lua5.1" ]; then +- rm -rf lua-5.1.5; +-elif [ "$LUA" == "lua5.2" ]; then +- rm -rf lua-5.2.4; +-elif [ "$LUA" == "lua5.3" ]; then +- rm -rf lua-5.3.2; +-fi +diff --git a/extra/moses/CHANGELOG.md b/extra/moses/CHANGELOG.md +index 63d31e9..2596b64 100644 +--- a/extra/moses/CHANGELOG.md ++++ b/extra/moses/CHANGELOG.md +@@ -1,5 +1,147 @@ + # Version history + ++## Unreleased ++ ++### Additions ++ ++* Added `mapi (t, f)` in table functions. ++ ++### Fixes and improvements ++ ++* Fixed `chunk` to avoid generating chunks at index 0. ++* Made argument `f` to `chunk` optional. Defaults to `identity` ++* Fixed alias to `uniqueId` ++* Fixed `M.powerset` ++* Prevents straight calls to io and os modules for Redis compatibility ++ ++## 2.1.0 (09/12/2018) ++ ++### Breaking changes ++ ++* Renamed `toArray` to `pack` ++* Renamed `reduceby` to `reduceBy` ++* Removed `skip` as alias to `last` ++* Changed prototype : `each(t, f, ...)` is now `each(t, f)` ++* Changed prototype : `eachi(t, f, ...)` is now `eachi(t, f)` ++* Changed prototype : `adjust(t, key, f, ...)` is now `adjust(t, key, f)` ++* Changed prototype : `countf(t, f, ...)` is now `countf(t, f)` ++* Changed prototype : `map(t, f, ...)` is now `map(t, f)` ++* Changed prototype : `reduceBy(t, f, pred, state, ...)` is now `reduceBy(t, f, pred, state)` ++* Changed prototype : `select(t, f, ...)` is now `select(t, f)` ++* Changed prototype : `reject(t, f, ...)` is now `reject(t, f)` ++* Changed prototype : `all(t, f, ...)` is now `all(t, f)` ++* Changed prototype : `invoke(t, method, ...)` is now `invoke(t, method)` ++* Changed prototype : `min(t, transform, ...)` is now `min(t, transform)` ++* Changed prototype : `max(t, transform, ...)` is now `max(t, transform)` ++* Changed prototype : `countBy(t, iter, ...)` is now `countBy(t, iter)` ++* Changed prototype : `groupBy(t, iter, ...)` is now `groupBy(t, iter)` ++* Changed prototype : `selectWhile(array, f, ...)` is now `selectWhile(array, f)` ++* Changed prototype : `dropWhile(array, f, ...)` is now `dropWhile(array, f)` ++* Changed prototype : `findIndex(array, pred, ...)` is now `findIndex(array, pred)` ++* Changed prototype : `findLastIndex(array, pred, ...)` is now `findLastIndex(array, pred)` ++* Changed prototype : `chunk(array, f, ...)` is now `chunk(array, f)` ++* Changed prototype : `times(iter, n, ...)` is now `times(iter, n)` ++* Changed prototype : `template(id, ...)` is now `template(id)` ++* Changed prototype : `tap(obj, f, ...)` is now `tap(obj, f)` ++* Changed prototype : `result(obj, method, ...)` is now `result(obj, method)` ++* Changed prototype : `intersection(array, ...)` is now `intersection(array)` ++ ++### Other changes ++ ++* Renamed `array` to `tabulate`, with no alias ++* Moved `invert` to object functions ++ ++### Additions ++ ++* Added `best` in table functions ++* Added `allEqual` in table functions ++* Added `sortedk` iterator in array functions ++* Added `sortedv` iterator in array functions ++* Added `disjoint` in array functions ++* Added `nsorted` in array functions ++* Added `duplicates` in array functions ++* Added `xpairs` in array functions ++* Added `xpairsRight` in array functions ++* Added `call` in utility functions ++* Added `type` in object functions ++* Added `spreadPath` in object functions ++* Added `flattenPath` in object functions ++* Added `thread` in utility functions ++* Added `threadRight` in utility functions ++* Added `iterlen` in utility functions ++* Added `skip` in utility functions ++* Added `both` in utility functions ++* Added `either` in utility functions ++* Added `neither` in utility functions ++* Added `dispatch` in utility functions ++* Added `noarg` in utility functions ++ ++## 2.0.0 (08/23/2018) ++### Breaking changes ++* library functions now accept iterators prototyped as `f(v, k, ...)` instead of `f(k, v, ...)`. ++It improves the benefits of chaning and helps writting a clear functional-style code. ++Library functions affected with this breaking change are : ++`each`, `eachi`,`countf`, `map`, `reduceby`, `select`, `reject`, `all`, `groupBy`, `countBy`, ++`selectWhile`, `dropWhile`, `findIndex`, `findLastIndex`, `chunk`. ++* `reduceby` is now prototyped as `reduceby(t, f, pred, state)` instead of `reduceby(t, f, state, pred)`. ++* `times` is now prototyped as `times(iter, n, ...)` instead of `times(iter, n, ...)`. ++* `bindAll` was renamed to `bindall` ++* `functions` no longer accept optional `sort` third arguments ++* `sliding` was renamed to `overlapping` ++* Improved `range` to handle negative progressions and start the count from 1. ++* `memoize` no longer takes a `hash` function. ++ ++### Other changes ++* Made `shift` a default library function, and `pop` its alias. ++* Moved `shuffle` from table function to array functions ++* Made `iterator` to accept an extra optional arg `n` ++ ++### Additions ++ ++#### Added support for operators ++* Arithmetic operators : `add`, `sub`, `mul`, `div`, `mod`, `exp`, `pow` (alias to `exp`), `unm`, `neg` (alias to `unm`), `floordiv`, `intdiv` ++* Relational operators : `eq`, `neq`, `lt`, `gt`, `le`, `ge` ++* Logical operators : `land`, `lor`, `lnot` ++* Concatenation operator : `concat` ++* Length operator : `length`, `len` (alias to `length`) ++ ++#### Added functions ++* Added `adjust` in table functions ++* Added `xprod` in array functions ++* Added `prepend` in array functions ++* Added `zeros` in array functions ++* Added `ones` in array functions ++* Added `vector` in array functions ++* Added `aperture` in array functions ++* Added `sum` in array functions ++* Added `product` in array functions ++* Added `mean` in array functions ++* Added `median` in array functions ++* Added `powerset` in array functions ++* Added `zipWith` in array functions ++* Added `pairwise` in array functions ++* Added `applySpec` in utility functions ++* Added `nthArg` in utility functions ++* Added `cond` in utility functions ++* Added `castArray` in utility functions ++* Added `unary` in utility functions ++* Added `ary` in utility functions ++* Added `rearg` in utility functions ++* Added `unfold` in utility functions ++* Added `converge` in utility functions ++* Added `path` in object functions ++ ++#### Added function aliases ++* Added `update` as alias to `adjust` ++* Added `always` as alias to `constant` ++* Added `intersperse` as alias to `interpose` ++* Added `sliding` as alias to `aperture` ++* Added `tabulate` as alias to `array` ++* Added `matches` as alias to `isEqual` ++* Added `average` as alias to `mean` ++* Added `nAry` as alias to `ary` ++* Added `transposeWith` as alias to `zipWith` ++ + ## 1.6.1 (04/27/17) + + * Added `_.array` +diff --git a/extra/moses/LICENSE b/extra/moses/LICENSE +index f06dce3..d756883 100644 +--- a/extra/moses/LICENSE ++++ b/extra/moses/LICENSE +@@ -1,4 +1,4 @@ +-Copyright (c) 2012-2014 Roland Yonaba ++Copyright (c) 2012-2018 Roland Yonaba + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the +diff --git a/extra/moses/README.md b/extra/moses/README.md +index 0bf99e5..9ac401a 100644 +--- a/extra/moses/README.md ++++ b/extra/moses/README.md +@@ -1,15 +1,55 @@ + [![Build Status](https://travis-ci.org/Yonaba/Moses.png)](https://travis-ci.org/Yonaba/Moses) ++[![Latest Stable](https://img.shields.io/badge/Latest_Stable-2.1.0-blue.svg)](https://github.com/Yonaba/Moses/releases/tag/Moses-2.1.0-1) + [![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) + [![Lua](https://img.shields.io/badge/Lua-5.1%2C%205.2%2C%205.3%2C%20JIT-blue.svg)]() +-[![git-brag-stats](https://labs.turbo.run/git-brag?user=Yonaba&repo=Moses)](https://github.com/turbo/git-brag) + + A Lua utility-belt library for [functional programming](http://en.wikipedia.org/wiki/Functional_programming).
    + ++## Examples ++ ++How can I get the sum of all integers between 1 and 100 ? ++ ++```lua ++local sum = M.sum(M.range(100)) ++print(sum) -- 5050 ++```` ++Say I am looking for the length of the longest word in some array ? ++ ++```lua ++local words = {'some','words','of','different','lengths'} ++print(M.max(words, M.op.length)) -- 9 letters ++```` ++ ++What is the sum of all fibonacci numbers for n below or equal 25 ? ++ ++```lua ++local function fib(n) return n < 2 and n or fib(n - 1) + fib(n - 2) end ++local fibsum = M.sum(M.map(M.range(25), fib)) ++print(fibsum) -- 196417 ++```` ++ ++Or let us do the same, object-oriented style with chaining : ++ ++```lua ++local function fib(n) return n < 2 and n or fib(n - 1) + fib(n - 2) end ++local fibsum = M.chain(M.range(25)):map(fib):sum():value() ++print(fibsum) -- 196417 ++```` ++ ++Or even shorter : ++ ++```lua ++local fibsum = M(M.range(25)):map(fib):sum():value() ++print(fibsum) -- 196417 ++```` ++ ++Feel free to download and try it on your own! ++ + ## Download + + ### Archive +-* __zip__: [1.6.1 (stable)](http://github.com/Yonaba/Moses/archive/Moses-1.6.1-1.zip) | [dev](http://github.com/Yonaba/Moses/archive/master.zip) | [all releases](http://github.com/Yonaba/Moses/tags) +-* __tarball__: [1.6.1 (stable)](http://github.com/Yonaba/Moses/archive/Moses-1.6.1-1.tar.gz) | [dev](http://github.com/Yonaba/Moses/archive/master.tar.gz) | [all releases](http://github.com/Yonaba/Moses/tags) ++* __2.1.0__ *(latest stable)*: [zip](http://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.zip) | [tar.gz](http://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.tar.gz) ++* __Previous versions__ : [tags](http://github.com/Yonaba/Moses/tags) + + ### Bash + +@@ -18,23 +58,24 @@ git clone git://github.com/Yonaba/Moses.git + ```` + + ### LuaRocks +-``` ++ ++```` + luarocks install moses + ```` + + ### MoonRocks + +-```bash ++```` + moonrocks install moses + ```` + + ## Usage + + ```lua +-local _ = require "moses" ++local M = require "moses" + ```` + +-*Note:* the full source [moses.lua](https://github.com/Yonaba/Moses/blob/master/moses.lua) is quite heavy (~70 kiB). You can alternatively use the [minified version](https://github.com/Yonaba/Moses/blob/master/moses_min.lua) (~22 kiB). ++*Note:* the full source [moses.lua](https://github.com/Yonaba/Moses/blob/master/moses.lua) is quite heavy (~92 kiB, 3115 LOC). You can alternatively use the [minified version](https://github.com/Yonaba/Moses/blob/master/moses_min.lua) (~35 kiB, 561 LOC). + + ## Tutorial + +@@ -42,28 +83,28 @@ Find a complete set of code examples in [tutorial.md](https://github.com/Yonaba/ + + ## Documentation + +-* See *doc* folder : [doc](https://github.com/Yonaba/Moses/blob/master/doc) +-* Or browse it online : see [online doc](http://yonaba.github.io/Moses/doc). ++* Read it [online](http://yonaba.github.io/Moses/doc). + + ## Credits and Acknowledgement + + * [Jeremy Ashkenas](https://github.com/jashkenas), for the amazing [Underscore.js](http://documentcloud.github.com/underscore/) +-* [Marcus Irven](http://mirven.github.com/underscore.lua/)'s and [JTArchie](https://github.com/jtarchie/underscore-lua)'s 1-to-1 ports that also inspired this ++* [Marcus Irven](http://mirven.github.com/underscore.lua/)'s and [JT Archie](https://github.com/jtarchie/underscore-lua)'s 1-to-1 ports that also inspired this + * [Matthew Rocklin](https://github.com/mrocklin)'s [Toolz](https://github.com/pytoolz/toolz/) from which I borrowed some ideas +-* [LDoc](https://github.com/stevedonovan/ldoc/) used to generate the current HTML documentation. ++* [Steve Donovan](https://github.com/stevedonovan)'s [LDoc](https://github.com/stevedonovan/ldoc/), used to generate the current HTML documentation. ++* [Mark Langen](https://github.com/stravant)'s [LuaMinify](https://github.com/stravant/LuaMinify/), used to generate a minified version of this library. + + ## Specification + +-Run [spec tests](https://github.com/Yonaba/Moses/blob/master/spec) using [Telescope](https://github.com/norman/telescope) with the following command from the root folder: ++Run [spec tests](https://github.com/Yonaba/Moses/blob/master/spec) from Lua using [busted](https://github.com/Olivine-Labs/busted/) with the following command from the root folder: + + ```` +-tsc -f spec/* ++busted + ```` + + ## License + + This work is under [MIT-LICENSE](http://www.opensource.org/licenses/mit-license.php)
    +-Copyright (c) 2012-2017 Roland Yonaba.
    ++Copyright (c) 2012-2018 Roland Yonaba.
    + See [LICENSE](LICENSE). + + +diff --git a/extra/moses/doc/index.html b/extra/moses/doc/index.html +index bace80a..d950955 100644 +--- a/extra/moses/doc/index.html ++++ b/extra/moses/doc/index.html +@@ -4,7 +4,7 @@ + + + Moses documentation +- ++ + + + +@@ -29,6 +29,7 @@ + +

    Contents

    +
    +@@ -52,425 +53,641 @@ +

    Module moses

    +

    Utility-belt library for functional programming in Lua (source)

    +

    ++ +

    +

    Info:

    +
      +-
    • Copyright: 2012-2017
    • +-
    • Release: 1.6.1
    • ++
    • Copyright: 2012-2018
    • ++
    • Release: 2.1.0
    • +
    • License: MIT
    • +
    • Author: Roland Yonaba
    • +
    + + +-

    Table functions

    ++

    Operator functions

    + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ ++ ++ ++ ++ ++ ++
    clear (t)Clears a table.operator.add (a, b)Returns a + b.
    each (t, f[, ...])Iterates on key-value pairs, calling f (k, v) at every step.operator.concat (a, b)Returns concatenation of a and b.
    eachi (t, f[, ...])Iterates on integer key-value pairs, calling f(k, v) every step.operator.div (a, b)Returns a / b.
    at (t, ...)Collects values at given keys and return them wrapped in an array.operator.eq (a, b)Checks if a equals b.
    count (t[, value])Counts occurrences of a given value in a table.operator.exp (a, b)Returns a ^ b.
    countf (t, f[, ...])Counts occurrences validating a predicate.operator.floordiv (a, b)Performs floor division (//) between a and b.
    cycle (t, n)Loops n times through a table.operator.ge (a, b)Checks if a is greater or equal to b.
    map (t, f[, ...])Maps f (k, v) on key-value pairs, collects and returns the results.operator.gt (a, b)Checks if a is strictly greater than b.
    reduce (t, f[, state])Reduces a table, left-to-right.operator.intdiv (a, b)Performs integer division between a and b.
    reduceby (t, f, state, pred[, ...])Reduces values in a table passing a given predicate.operator.land (a, b)Returns logical a and b.
    reduceRight (t, f[, state])Reduces a table, right-to-left.operator.le (a, b)Checks if a is less or equal to b.
    mapReduce (t, f[, state])Reduces a table while saving intermediate states.operator.length (a)Returns the length of a.
    mapReduceRight (t, f[, state])Reduces a table while saving intermediate states.operator.lnot (a)Returns logical not a.
    include (t, value)Performs a linear search for a value in a table.operator.lor (a, b)Returns logical a or b.
    detect (t, value)Performs a linear search for a value in a table.operator.lt (a, b)Checks if a is strictly less than b.
    where (t, props)Returns all values having specified keys props.operator.mod (a, b)Returns a % b.
    findWhere (t, props)Returns the first value having specified keys props.operator.mul (a, b)Returns a * b.
    select (t, f[, ...])Selects and returns values passing an iterator test.operator.neq (a, b)Checks if a not equals b.
    reject (t, f[, ...])Clones a table while dropping values passing an iterator test.operator.sub (a, b)Returns a - b.
    operator.unm (a)Returns -a.
    ++

    Table functions

    ++ ++ ++ ++ + + +- ++ + + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + +-
    adjust (t, key, f)Adjusts the value at a given key using a function or a value.
    all (t, f[, ...])all (t, f)Checks if all values in a table are passing an iterator test.
    invoke (t, method[, ...])Invokes a method on each value in a table.allEqual (t[, comp])Checks if all values in a collection are equal.
    pluck (t, key)Extracts values in a table having a given key.at (t, ...)Collects values at given keys and return them wrapped in an array.
    max (t[, transform[, ...]])Returns the max value in a collection.best (t, f)Returns the best value passing a selector function.
    min (t[, transform[, ...]])Returns the min value in a collection.clear (t)Clears a table.
    shuffle (t[, seed])Returns a shuffled copy of a given collection.containsKeys (t, other)Checks if all the keys of other table exists in table t.
    same (a, b)Checks if two tables are the same.count (t[, val])Counts occurrences of a given value in a table.
    sort (t[, comp])Sorts a table, in-place.countBy (t, iter)Groups values in a collection and counts them.
    sortBy (t[, transform[, comp]])Sorts a table in-place using a transform.countf (t, f)Counts the number of values passing a predicate test.
    groupBy (t, iter[, ...])Splits a table into subsets groups.cycle (t[, n])Loops n times through a table.
    countBy (t, iter[, ...])Groups values in a collection and counts them.detect (t, value)Performs a linear search for a value in a table.
    size ([...])Counts the number of values in a collection.each (t, f)Iterates on key-value pairs, calling f (v, k) at every step.
    containsKeys (t, other)Checks if all the keys of other table exists in table t.eachi (t, f)Iterates on integer key-value pairs, calling f(v, k) every step.
    sameKeys (tA, tB)Checks if both given tables have the same keys.findWhere (t, props)Returns the first value having specified keys props.
    +-

    Array functions

    +- + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + ++
    sample (array[, n[, seed]])Samples n random values from an array.groupBy (t, iter)Splits a table into subsets groups.
    sampleProb (array, prob[, seed])Return elements from a sequence with a given probability.include (t, value)Performs a linear search for a value in a table.
    toArray (...)Converts a list of arguments to an array.invoke (t, method)Invokes a method on each value in a table.
    find (array, value[, from])Looks for the first occurrence of a given value in an array.map (t, f)Maps f (v, k) on value-key pairs, collects and returns the results.
    reverse (array)Returns an array where values are in reverse order.mapReduce (t, f[, state])Reduces a table while saving intermediate states.
    fill (array, value[, i[, j]])Replaces elements in a given array with a given value.mapReduceRight (t, f[, state])Reduces a table while saving intermediate states.
    selectWhile (array, f[, ...])Collects values from a given array.mapi (t, f)Maps f (v, k) on value-key pairs, collects and returns the results.
    dropWhile (array, f[, ...])Collects values from a given array.max (t[, transform])Returns the max value in a collection.
    sortedIndex (array, the[, comp[, sort]])Returns the index at which a value should be inserted.min (t[, transform])Returns the min value in a collection.
    indexOf (array, value)Returns the index of the first occurence of value in an array.pluck (t, key)Extracts values in a table having a given key.
    lastIndexOf (array, value)Returns the index of the last occurrence of value in an array.reduce (t, f[, state])Reduces a table, left-to-right.
    findIndex (array, predicate[, ...])Returns the first index at which a predicate returns true.reduceBy (t, f, pred[, state[, ...]])Reduces values in a table passing a given predicate.
    findLastIndex (array, predicate[, ...])Returns the last index at which a predicate returns true.reduceRight (t, f[, state])Reduces a table, right-to-left.
    addTop (array, ...)Adds all passed-in values at the top of an array.reject (t, f)Clones a table while dropping values passing an iterator test.
    push (array, ...)Pushes all passed-in values at the end of an array.same (a, b)Checks if two tables are the same.
    pop (array[, n])Removes and returns the values at the top of a given array.sameKeys (tA, tB)Checks if both given tables have the same keys.
    unshift (array[, n])Removes and returns the values at the end of a given array.select (t, f)Selects and returns values passing an iterator test.
    pull (array, ...)Removes all provided values in a given array.size ([...])Counts the number of values in a collection.
    removeRange (array[, start[, finish]])Removes values at index within the range [start, finish].sort (t[, comp])Sorts a table, in-place.
    chunk (array, f[, ...])Chunks together consecutive values.sortBy (t[, transform[, comp]])Sorts a table in-place using a transform.
    slice (array[, start[, finish]])Slices values indexed within [start, finish] range.sortedk (t[, comp])Iterates on values with respect to key order.
    first (array[, n])Returns the first N values in an array.sortedv (t[, comp])Iterates on values with respect to values order.
    initial (array[, n])Returns all values in an array excluding the last N values.where (t, props)Returns all values having specified keys props.
    ++

    Array functions

    ++ + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ ++ ++ ++ ++ + + + +- ++ + + +- +- ++ ++ + + + + + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + +-
    last (array[, n])Returns the last N values in an array.addTop (array, ...)Adds all passed-in values at the top of an array.
    rest (array[, index])Removes all values before index.aperture (array[, n])Iterator returning sliding partitions of an array.
    nth (array, index)Returns the value at a given index.append (array, other)Clones array and appends values from another array.
    chunk (array, f)Chunks together consecutive values.
    compact (array)Removes all falsy (false and nil) values.Returns all truthy values (removes falses and nils).
    flatten (array[, shallow])Flattens a nested array.concat (array[, sep[, i[, j]]])Concatenates values in a given array.
    difference (array, another)Returns values from an array not present in all passed-in args.
    union (...)Returns the duplicate-free union of all passed in arrays.disjoint (...)Checks if all passed in arrays are disjunct.
    intersection (array, ...)Returns the intersection of all passed-in arrays.dropWhile (array, f)Collects values from a given array.
    symmetricDifference (array, array2)Performs a symmetric difference.duplicates (array)Returns an array list of all duplicates in array.
    unique (array)Produces a duplicate-free version of a given array.fill (array, value[, i[, j]])Replaces elements in a given array with a given value.
    isunique (array)Checks if a given array contains distinct values.find (array, value[, from])Looks for the first occurrence of a given value in an array.
    zip (...)Merges values of each of the passed-in arrays in subsets.findIndex (array, pred)Returns the first index at which a predicate returns true.
    append (array, other)Clones array and appends other values.findLastIndex (array, pred)Returns the last index at which a predicate returns true.
    interleave (...)Interleaves arrays.first (array[, n])Returns the first N values in an array.
    interpose (value, array)Interposes value in-between consecutive pair of values in array.flatten (array[, shallow])Flattens a nested array.
    range ([from[, to[, step]]])Produces a flexible list of numbers.indexOf (array, value)Returns the index of the first occurrence of value in an array.
    rep (value, n)Creates an array list of n values, repeated.initial (array[, n])Returns all values in an array excluding the last N values.
    partition (array[, n[, pad]])Iterator returning partitions of an array.interleave (...)Interleaves arrays.
    sliding. (array[, n[, pad]])Iterator returning sliding partitions of an array.interpose (array, value)Interposes value in-between consecutive pair of values in array.
    permutation (array)Iterator returning the permutations of an array.intersection (...)Returns the intersection of all passed-in arrays.
    invert (array)Swaps keys with values.isunique (array)Checks if a given array contains distinct values.
    concat (array[, sep[, i[, j]]])Concatenates values in a given array.last (array[, n])Returns the last N values in an array.
    +-

    Utility functions

    +- + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    noop ()The no-operation function.lastIndexOf (array, value)Returns the index of the last occurrence of value in an array.
    identity (value)Returns the passed-in value.mean (array)Returns the mean of an array of numbers.
    constant (value)Creates a constant function which returns the same output on every call.median (array)Returns the median of an array of numbers.
    memoize (f[, hash])Memoizes a given function by caching the computed result.nsorted (array[, n[, comp]])Returns the n-top values satisfying a predicate.
    once (f)Returns a version of f that runs only once.nth (array, index)Returns the value at a given index.
    before (f, count)Returns a version of f that will run no more than count times.ones (n)Returns an array of n 1's.
    after (f, count)Returns a version of f that runs on the count-th call.overlapping (array[, n[, pads]])Iterator returning overlapping partitions of an array.
    compose (...)Composes functions.pack (...)Converts a list of arguments to an array.
    pipe (value, ...)Pipes a value through a series of functions.pairwise (array)Iterator returning sliding pairs of an array.
    complement (f)Returns the logical complement of a given function.partition (array[, n[, pads]])Iterator returning partitions of an array.
    juxtapose (value, ...)Calls a sequence of passed-in functions with the same argument.permutation (array)Iterator returning the permutations of an array.
    wrap (f, wrapper)Wraps f inside of the wrapper function.powerset (array)Returns the powerset of array values.
    times (n, iter, ...)Runs iter function n times.prepend (array, ...)Adds all passed-in values at the top of an array.
    bind (f, v)Binds v to be the first argument to f.product (array)Returns the product of array values.
    bind2 (f, v)Binds v to be the second argument to f.pull (array, ...)Removes all provided values in a given array.
    bindn (f, ...)Binds to be the N-first arguments to function f.push (array, ...)Pushes all passed-in values at the end of an array.
    bindAll (obj, ...)Binds methods to object.range ([from[, to[, step]]])Produces a flexible list of numbers.
    uniqueId ([template[, ...]])Generates an unique ID for the current session.removeRange (array[, start[, finish]])Removes values at an index within the range [start, finish].
    iterator (f, x)Produces an iterator which repeatedly apply a function f onto an input.rep (value, n)Creates an array list of n values, repeated.
    array (...)Iterates an iterator and returns its values in an array.rest (array[, index])Returns all values after index.
    flip (f)reverse (array)Returns an array where values are in reverse order.
    sample (array[, n[, seed]])Samples n random values from an array.
    sampleProb (array, prob[, seed])Return elements from a sequence with a given probability.
    selectWhile (array, f)Collects values from a given array.
    shift (array[, n])Removes and returns the values at the top of a given array.
    shuffle (array[, seed])Returns a shuffled copy of a given array.
    slice (array[, start[, finish]])Slices values indexed within [start, finish] range.
    sortedIndex (array, the[, comp[, sort]])Returns the index at which a value should be inserted.
    sum (array)Returns the sum of array values.
    symmetricDifference (array, array2)Performs a symmetric difference.
    union (...)Returns the duplicate-free union of all passed in arrays.
    unique (array)Produces a duplicate-free version of a given array.
    unshift (array[, n])Removes and returns the values at the end of a given array.
    vector (value, n)Returns an array of n times a given value.
    xpairs (valua, array)Creates pairs from value and array.
    xpairsRight (valua, array)Creates pairs from value and array.
    xprod (array, array2)Returns all possible pairs built from given arrays.
    zeros (n)Returns an array of n zeros.
    zip (...)Merges values of each of the passed-in arrays in subsets.
    zipWith (f, ...)Merges values using a given function.
    ++

    Utility functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + + ++ ++ ++ ++ + + + +@@ -479,10 +696,6 @@ + + + +- +- +- +- + + + +@@ -491,147 +704,207 @@ + + + +- +- +- +- +- +- ++ ++ + +-
    after (f, count)Returns a version of f that runs on the count-th call.
    applySpec (specs)Returns a function which applies specs on args.
    ary (f[, n])Returns a function which accepts up to n args.
    before (f, count)Returns a version of f that will run no more than count times.
    bind (f, v)Binds v to be the first argument to f.
    bind2 (f, v)Binds v to be the second argument to f.
    bindall (obj, ...)Binds methods to object.
    bindn (f, ...)Binds ... to be the N-first arguments to function f.
    both (...)Returns a validation function.
    call (f[, ...])Calls f with the supplied arguments.
    castArray (value)Casts value as an array if it is not one.
    complement (f)Returns the logical complement of a given function.
    compose (...)Composes functions.
    cond (conds)Returns a function which iterate over a set of conditions.
    constant (value)Creates a constant function which returns the same output on every call.
    converge (f, g, h)Converges two functions into one.
    curry (f[, n_args])Curries a function.
    dispatch (...)Returns a dispatching function.
    either (...)Returns a validation function.
    flip (f)Creates a function of f with arguments flipped in reverse order.
    identity (value)Returns the passed-in value.
    iterator (f, value[, n])Produces an iterator which repeatedly apply a function f onto an input.
    iterlen (...)Returns the length of an iterator.
    juxtapose (value, ...)Calls a sequence of passed-in functions with the same argument.
    memoize (f)Memoizes a given function by caching the computed result.
    neither (...)Returns a validation function.
    noarg (f)Returns a function with an arity of 0.
    noop ()The no operation function.
    nthArg (n)Returns a function that gets the nth argument.
    once (f)Returns a version of f that runs only once.
    over (...)Creates a function that runs transforms on all arguments it receives.
    overArgs (f, ...)Creates a function that invokes f with its arguments transformed.
    overEvery (...)Creates a validation function.
    Creates a validation function.
    overArgs (f, ...)Creates a function that invokes f with its arguments transformed.
    partial (f, ...)Partially apply a function by filling in any number of its arguments.
    Similar to partial, but from the right.
    curry (f[, n_args])Curries a function.
    time (f[, ...])Returns the execution time of f (…) and its returned values.pipe (value, ...)Pipes a value through a series of functions.
    +-

    Object functions

    +- + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + ++
    keys (obj)Returns the keys of the object properties.rearg (f, indexes)Returns a function which runs with arguments rearranged.
    values (obj)Returns the values of the object properties.skip (iter[, n])Consumes the first n values of a iterator then returns it.
    kvpairs (obj)Converts keys and values a an array-list of [k, v].tabulate (...)Iterates over an iterator and returns its values in an array.
    toObj (kvpairs)Converts an array list of kvpairs to an object.thread (value, ...)Threads value through a series of functions.
    property (key)Returns a function that will return the key property of any passed-in object.threadRight (value, ...)Threads value through a series of functions.
    propertyOf (obj)Returns a function which will return the value of an object property.time (f[, ...])Returns the execution time of f (...) and its returned values.
    toBoolean (value)Converts any given value to a booleantimes (iter[, n])Runs iter function n times.
    extend (destObj, ...)Extends an object properties.unary (f)Returns a function which accepts up to one arg.
    functions ([obj])Returns a sorted list of all methods names found in an object.unfold (f, seed)Builds a list from a seed value.
    clone (obj[, shallow])Clones a given object properties.uniqueId ([template])Generates an unique ID for the current session.
    tap (obj, f[, ...])Invokes interceptor with the object, and then returns object.wrap (f, wrapper)Wraps f inside of the wrapper function.
    ++

    Object functions

    ++ + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + + + + + +- +- ++ ++ ++ ++ ++ ++ + + + + + + +- +- ++ ++ ++ ++ ++ ++ + + + + + + +- +- ++ ++ + + +- +- ++ ++ + + + + + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ + + +- +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + + + + +- +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +
    has (obj, key)Checks if a given object implements a property.chain (value)Returns a wrapped object.
    pick (obj, ...)Returns an object copy having white-listed properties.clone (obj[, shallow])Clones a given object properties.
    omit (obj, ...)Returns an object copy without black-listed properties.extend (destObj, ...)Extends an object properties.
    template (obj[, template])Applies a template to an object, preserving non-nil properties.flattenPath (obj, ...)Flattens object under property path onto provided object.
    isEqual (objA, objB[, useMt])Performs a deep comparison test between two objects.functions ([obj])Returns a sorted list of all methods names found in an object.
    result (obj, method[, ...])Invokes an object method.has (obj, key)Checks if a given object implements a property.
    isTable (t)Checks if the given arg is a table.import ([context[, noConflict]])Imports all library functions into a context.
    isCallable (obj)Checks if the given argument is callable.invert (obj)Swaps keys with values.
    isArray (obj)Checks if the given argument is an array.
    isIterable (obj)Checks if the given object is iterable with pairs (or ipairs).isBoolean (obj)Checks if the given argument is a boolean.
    isCallable (obj)Checks if the given argument is callable.
    isEmpty ([obj])Checks if the given pbject is empty.
    isString (obj)Checks if the given argument is a string.isEqual (objA, objB[, useMt])Performs a deep comparison test between two objects.
    isFinite (obj)Checks if the given argument is a finite number.
    isFunction (obj)Checks if the given argument is a function.
    isNil (obj)Checks if the given argument is nil.isInteger (obj)Checks if the given argument is an integer.
    isNumber (obj)Checks if the given argument is a number.isIterable (obj)Checks if the given object is iterable with pairs (or ipairs).
    isNaN (obj)Checks if the given argument is NaN (see Not-A-Number).
    isFinite (obj)Checks if the given argument is a finite number.isNil (obj)Checks if the given argument is nil.
    isBoolean (obj)Checks if the given argument is a boolean.isNumber (obj)Checks if the given argument is a number.
    isInteger (obj)Checks if the given argument is an integer.isString (obj)Checks if the given argument is a string.
    chain (value)Returns a wrapped object.isTable (t)Checks if the given arg is a table.
    keys (obj)Returns the keys of the object properties.
    kvpairs (obj)Converts key-value pairs to an array-list of [k, v] pairs.
    obj:value ()Extracts the value of a wrapped object.
    import ([context[, noConflict]])Imports all library functions into a context.omit (obj, ...)Returns an object copy without black-listed properties.
    path (obj, ...)Returns the value at a given path in an object.
    pick (obj, ...)Returns an object copy having white-listed properties.
    property (key)Returns a function that will return the key property of any passed-in object.
    propertyOf (obj)Returns a function which will return the value of an object property.
    result (obj, method)Invokes an object method.
    spreadPath (obj, ...)Spreads object under property path onto provided object.
    tap (obj, f)Invokes interceptor with the object, and then returns object.
    template (obj[, template])Applies a template to an object, preserving non-nil properties.
    toBoolean (value)Converts any given value to a boolean
    toObj (kvpairs)Converts an array list of [k,v] pairs to an object.
    type (obj)Extends Lua's type function.
    values (obj)Returns the values of the object properties.
    + +@@ -639,28 +912,2190 @@ +
    + + +-

    Table functions

    ++

    Operator functions

    + +
    +
    +- +- clear (t) ++ ++ operator.add (a, b) ++
    ++
    ++ Returns a + b. Aliased as op.add. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a + b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.concat (a, b) ++
    ++
    ++ Returns concatenation of a and b. Aliased as op.concat. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a .. b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.div (a, b) ++
    ++
    ++ Returns a / b. Aliased as op.div. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a / b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.eq (a, b) ++
    ++
    ++ Checks if a equals b. Aliased as op.eq. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a == b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.exp (a, b) ++
    ++
    ++ Returns a ^ b. Aliased as op.exp, op.pow. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a ^ b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.floordiv (a, b) ++
    ++
    ++ Performs floor division (//) between a and b. It rounds the quotient towards minus infinity. ++ Aliased as op.floordiv. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a // b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.ge (a, b) ++
    ++
    ++ Checks if a is greater or equal to b. Aliased as op.ge. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a >= b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.gt (a, b) ++
    ++
    ++ Checks if a is strictly greater than b. Aliased as op.gt. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a > b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.intdiv (a, b) ++
    ++
    ++ Performs integer division between a and b. Aliased as op.intdiv. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a / b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.land (a, b) ++
    ++
    ++ Returns logical a and b. Aliased as op.land. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a and b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.le (a, b) ++
    ++
    ++ Checks if a is less or equal to b. Aliased as op.le. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a <= b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.length (a) ++
    ++
    ++ Returns the length of a. Aliased as op.len. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ ++

      a

      ++ ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.lnot (a) ++
    ++
    ++ Returns logical not a. Aliased as op.lnot. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ not a ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.lor (a, b) ++
    ++
    ++ Returns logical a or b. Aliased as op.lor. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a or b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.lt (a, b) ++
    ++
    ++ Checks if a is strictly less than b. Aliased as op.lt. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a < b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.mod (a, b) ++
    ++
    ++ Returns a % b. Aliased as op.mod. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a % b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.mul (a, b) ++
    ++
    ++ Returns a * b. Aliased as op.mul. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a * b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.neq (a, b) ++
    ++
    ++ Checks if a not equals b. Aliased as op.neq. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a ~= b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.sub (a, b) ++
    ++
    ++ Returns a - b. Aliased as op.sub. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    • b ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a - b ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ operator.unm (a) ++
    ++
    ++ Returns -a. Aliased as op.unm, op.neg. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ -a ++
    ++ ++ ++ ++ ++
    ++
    ++

    Table functions

    ++ ++
    ++
    ++ ++ adjust (t, key, f) ++
    ++
    ++ Adjusts the value at a given key using a function or a value. In case f is a function, ++ it should be prototyped f(v). It does not mutate the given table, but rather ++ returns a new array. In case the given key does not exist in t, it throws an error. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • key ++ a key ++
    • ++
    • f ++ a function, prototyped as f(v) or a value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ all (t, f) ++
    ++
    ++ Checks if all values in a table are passing an iterator test. ++
    Aliased as every ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if all values passes the predicate, false otherwise ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ allEqual (t[, comp]) ++
    ++
    ++ Checks if all values in a collection are equal. Uses an optional comp function which is used ++ to compare values and defaults to isEqual when not given. ++
    Aliased as alleq. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • comp ++ a comparison function. Defaults to isEqual ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true when all values in t are equal, false otherwise. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ at (t, ...) ++
    ++
    ++ Collects values at given keys and return them wrapped in an array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • ... ++ A variable number of keys to collect values ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array-list of values ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ best (t, f) ++
    ++
    ++ Returns the best value passing a selector function. Acts as a special case of ++ reduce, using the first value in t as an initial state. It thens folds the given table, ++ testing each of its values v and selecting the value passing the call f(state,v) every time. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the final state of reduction ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ clear (t) ++
    ++
    ++ Clears a table. All its values become nil. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the given table, cleared. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ containsKeys (t, other) ++
    ++
    ++ Checks if all the keys of other table exists in table t. It does not ++ compares values. The test is not commutative, i.e table t may contains keys ++ not existing in other. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • other ++ another table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ count (t[, val]) ++
    ++
    ++ Counts occurrences of a given value in a table. Uses isEqual to compare values. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • val ++ a value to be searched in the table. If not given, the size of the table will be returned ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the count of occurrences of the given value ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ countBy (t, iter) ++
    ++
    ++ Groups values in a collection and counts them. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • iter ++ an iterator function, prototyped as iter (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of subsets groups names paired with their count ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ countf (t, f) ++
    ++
    ++ Counts the number of values passing a predicate test. Same as count, but uses an iterator. ++ Returns the count for values passing the test f (v, k) ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the count of values validating the predicate ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ cycle (t[, n]) ++
    ++
    ++ Loops n times through a table. In case n is omitted, it will loop forever. ++ In case n is lower or equal to 0, it returns an empty function. ++
    Aliased as loop. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • n ++ the number of loops ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator function yielding value-key pairs from the passed-in table. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ detect (t, value) ++
    ++
    ++ Performs a linear search for a value in a table. Returns the key of the value if found. ++ The given value can be a function prototyped as f (v, value) which should return true when ++ any v in the table equals the value being searched. This function is similar to find, ++ which is mostly meant to work with array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • value ++ a value to search for ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the key of the value when found or nil ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ each (t, f) ++
    ++
    ++ Iterates on key-value pairs, calling f (v, k) at every step. ++
    Aliased as forEach. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ a function, prototyped as f (v, k) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ eachi (t, f) ++
    ++
    ++ Iterates on integer key-value pairs, calling f(v, k) every step.
    ++ Only applies to values located at integer keys. The table can be a sparse array. ++ Iteration will start from the lowest integer key found to the highest one. ++
    Aliased as forEachi. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ a function, prototyped as f (v, k) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ findWhere (t, props) ++
    ++
    ++ Returns the first value having specified keys props. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • props ++ a set of keys ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a value from the passed-in table ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ groupBy (t, iter) ++
    ++
    ++ Splits a table into subsets groups. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • iter ++ an iterator function, prototyped as iter (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of subsets groups ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ include (t, value) ++
    ++
    ++ Performs a linear search for a value in a table. It does not work for nested tables. ++ The given value can be a function prototyped as f (v, value) which should return true when ++ any v in the table equals the value being searched. ++
    Aliased as any, some, contains ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • value ++ a value to search for ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a boolean : true when found, false otherwise ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ invoke (t, method) ++
    ++
    ++ Invokes a method on each value in a table. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • method ++ a function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the result of the call f (v, k) ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ map (t, f) ++
    ++
    ++ Maps f (v, k) on value-key pairs, collects and returns the results.
    ++ Uses pairs to iterate over elements in t. ++
    Aliased as collect. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of results ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ mapReduce (t, f[, state]) ++
    ++
    ++ Reduces a table while saving intermediate states. Folds the table left-to-right ++ using a given iterator and an initial state. The iterator takes a state and a value, ++ and returns a new state. The result is an array of intermediate states. ++
    Aliased as mapr ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    • state ++ an initial state of reduction. Defaults to the first value in the table. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array of states ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ mapReduceRight (t, f[, state]) ++
    ++
    ++ Reduces a table while saving intermediate states. Folds the table right-to-left ++ using a given iterator and an initial state. The iterator takes a state and a value, ++ and returns a new state. The result is an array of intermediate states. ++
    Aliased as maprr ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    • state ++ an initial state of reduction. Defaults to the last value in the table. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array of states ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ mapi (t, f) ++
    ++
    ++ Maps f (v, k) on value-key pairs, collects and returns the results.
    ++ Uses ipairs to iterate over elements in t. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of results ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ max (t[, transform]) ++
    ++
    ++ Returns the max value in a collection. If a transform function is passed, it will ++ be used to evaluate values by which all objects will be sorted. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • transform ++ a transformation function, prototyped as transform (v, k), defaults to identity ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the max value found ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ min (t[, transform]) ++
    ++
    ++ Returns the min value in a collection. If a transform function is passed, it will ++ be used to evaluate values by which all objects will be sorted. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • transform ++ a transformation function, prototyped as transform (v, k), defaults to identity ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the min value found ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ pluck (t, key) ++
    ++
    ++ Extracts values in a table having a given key. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • key ++ a key, will be used to index in each value: value[key] ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array of values having the given key ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ reduce (t, f[, state]) ++
    ++
    ++ Reduces a table, left-to-right. Folds the table from the first element to the last element ++ to a single value, using a given iterator and an initial state. ++ The iterator takes a state and a value and returns a new state. ++
    Aliased as inject, foldl. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    • state ++ an initial state of reduction. Defaults to the first value in the table. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the final state of reduction ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ reduceBy (t, f, pred[, state[, ...]]) ++
    ++
    ++ Reduces values in a table passing a given predicate. Folds the table left-to-right, considering ++ only values validating a given predicate. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    • pred ++ a predicate function pred (v, k) to select values to be considered for reduction ++
    • ++
    • state ++ an initial state of reduction. Defaults to the first value in the table of selected values. ++ (optional) ++
    • ++
    • ... ++ optional args to be passed to pred ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the final state of reduction ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ reduceRight (t, f[, state]) ++
    ++
    ++ Reduces a table, right-to-left. Folds the table from the last element to the first element ++ to single value, using a given iterator and an initial state. ++ The iterator takes a state and a value, and returns a new state. ++
    Aliased as injectr, foldr. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (state, value) ++
    • ++
    • state ++ an initial state of reduction. Defaults to the last value in the table. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the final state of reduction ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ reject (t, f) ++
    ++
    ++ Clones a table while dropping values passing an iterator test. ++
    Aliased as discard ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the remaining values ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ same (a, b) ++
    ++
    ++ Checks if two tables are the same. It compares if both tables features the same values, ++ but not necessarily at the same keys. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a table ++
    • ++
    • b ++ another table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ sameKeys (tA, tB) ++
    ++
    ++ Checks if both given tables have the same keys. It does not compares values. ++ ++ ++

    Parameters:

    ++
      ++
    • tA ++ a table ++
    • ++
    • tB ++ another table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ select (t, f) ++
    ++
    ++ Selects and returns values passing an iterator test. ++
    Aliased as filter. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • f ++ an iterator function, prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the selected values ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ size ([...]) ++
    ++
    ++ Counts the number of values in a collection. If being passed more than one argument ++ it will return the count of all passed-in arguments. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ Optional variable number of arguments ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a count ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ sort (t[, comp]) ++
    ++
    ++ Sorts a table, in-place. If a comparison function is given, it will be used to sort values. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • comp ++ a comparison function prototyped as comp (a, b), defaults to < operator. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the given table, sorted. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ sortBy (t[, transform[, comp]]) ++
    ++
    ++ Sorts a table in-place using a transform. Values are ranked in a custom order of the results of ++ running transform (v) on all values. transform may also be a string name property sort by. ++ comp is a comparison function. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • transform ++ a transform function to sort elements prototyped as transform (v). Defaults to identity ++ (optional) ++
    • ++
    • comp ++ a comparison function, defaults to the < operator ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new array of sorted values ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ sortedk (t[, comp]) ++
    ++
    ++ Iterates on values with respect to key order. Keys are sorted using comp function ++ which defaults to math.min. It returns upon each call a key, value pair. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • comp ++ a comparison function. Defaults to < operator ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator function ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ sortedv (t[, comp]) ++
    ++
    ++ Iterates on values with respect to values order. Values are sorted using comp function ++ which defaults to math.min. It returns upon each call a key, value pair. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • comp ++ a comparison function. Defaults to < operator ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator function ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ where (t, props) ++
    ++
    ++ Returns all values having specified keys props. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table ++
    • ++
    • props ++ a set of keys ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array of values from the passed-in table ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++

    Array functions

    ++ ++
    ++
    ++ ++ addTop (array, ...) ++
    ++
    ++ Adds all passed-in values at the top of an array. The last elements will bubble to the ++ top of the given array. ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • ... ++ a variable number of arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the passed-in array with new values added ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ aperture (array[, n]) ++
    ++
    ++ Iterator returning sliding partitions of an array. ++
    Aliased as sliding ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • n ++ the size of partitions. Defaults to 2 (and then behaves like pairwise) ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator function ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ append (array, other) ++
    ++
    ++ Clones array and appends values from another array. ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • other ++ an array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ chunk (array, f) ++
    ++
    ++ Chunks together consecutive values. Values are chunked on the basis of the return ++ value of a provided predicate f (v, k). Consecutive elements which return ++ the same value are chunked together. Leaves the first argument untouched if it is not an array. ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • f ++ an iterator function prototyped as f (v, k). Defaults to identity. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of chunks (arrays) ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ compact (array) ++
    ++
    ++ Returns all truthy values (removes falses and nils). ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ concat (array[, sep[, i[, j]]]) ++
    ++
    ++ Concatenates values in a given array. Handles booleans as well. If sep string is ++ passed, it will be used as a separator. Passing i and j will result in concatenating ++ only values within [i, j] range. ++
    Aliased as join ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ a given array ++
    • ++
    • sep ++ a separator string, defaults to the empty string ''. ++ (optional) ++
    • ++
    • i ++ the starting index, defaults to 1. ++ (optional) ++
    • ++
    • j ++ the final index, defaults to the array length. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ difference (array, another) ++
    ++
    ++ Returns values from an array not present in all passed-in args. ++
    Aliased as without and diff ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • another ++ array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new array ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ disjoint (...) ++
    ++
    ++ Checks if all passed in arrays are disjunct. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ a variable number of arrays ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the intersection of all arrays is not empty, false otherwise. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ dropWhile (array, f) ++
    ++
    ++ Collects values from a given array. The passed-in array should not be sparse. ++ This function collects values as long as they do not satisfy a given predicate and returns on the first truthy test. ++
    Aliased as rejectWhile ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    • f ++ an iterator function prototyped as f (v, k) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new table containing all values collected ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ duplicates (array) ++
    ++
    ++ Returns an array list of all duplicates in array. ++ ++ ++

    Parameters:

    ++
      ++
    • array ++ an array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array-list of duplicates ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ fill (array, value[, i[, j]]) +
    +
    +- Clears a table. All its values become nil. ++ Replaces elements in a given array with a given value. In case i and j are given ++ it will only replaces values at indexes between [i,j]. In case j is greater than the array ++ size, it will append new values, increasing the array size. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array ++
    • ++
    • value ++ a value ++
    • ++
    • i ++ the index from which to start replacing values. Defaults to 1. ++ (optional) ++
    • ++
    • j ++ the index where to stop replacing values. Defaults to the array size. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- the given table, cleared. ++ the original array with values changed +
    + + +@@ -668,114 +3103,122 @@ + +
    +
    +- +- each (t, f[, ...]) ++ ++ find (array, value[, from]) +
    +
    +- Iterates on key-value pairs, calling f (k, v) at every step. +-
    Aliased as forEach. ++ Looks for the first occurrence of a given value in an array. Returns the value index if found. ++ Uses isEqual to compare values. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array of values +
    • +-
    • f +- a function, prototyped as f (k, v, …) ++
    • value ++ a value to lookup for +
    • +-
    • ... +- Optional args to be passed to f ++
    • from ++ the index from where the search will start. Defaults to 1. + (optional) +
    • +
    + ++

    Returns:

    ++
      ++ ++ the index of the value if found in the array, nil otherwise. ++
    + + +

    See also:

    + + + +
    +
    +- +- eachi (t, f[, ...]) ++ ++ findIndex (array, pred) +
    +
    +- Iterates on integer key-value pairs, calling f(k, v) every step.
    +- Only applies to values located at integer keys. The table can be a sparse array. +- Iteration will start from the lowest integer key found to the highest one. +-
    Aliased as forEachi. ++ Returns the first index at which a predicate returns true. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- a function, prototyped as f (k, v, …) ++
    • array ++ an array +
    • +-
    • ... +- Optional args to be passed to f +- (optional) ++
    • pred ++ a predicate function prototyped as pred (v, k) +
    • +
    + ++

    Returns:

    ++
      ++ ++ the index found or nil ++
    + + +

    See also:

    + + + +
    +
    +- +- at (t, ...) ++ ++ findLastIndex (array, pred) +
    +
    +- Collects values at given keys and return them wrapped in an array. ++ Returns the last index at which a predicate returns true. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • ... +- A variable number of keys to collect values ++
    • pred ++ a predicate function prototyped as pred (k, v) +
    • +
    + +

    Returns:

    +
      + +- an array-list of values ++ the index found or nil +
    + + ++

    See also:

    ++ + + +
    +
    +- +- count (t[, value]) ++ ++ first (array[, n]) +
    +
    +- Counts occurrences of a given value in a table. Uses isEqual to compare values. ++ Returns the first N values in an array. ++
    Aliased as head, take + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • value +- a value to be searched in the table. If not given, the size of the table will be returned ++
    • n ++ the number of values to be collected, defaults to 1. + (optional) +
    • +
    +@@ -783,37 +3226,34 @@ +

    Returns:

    +
      + +- the count of occurrences of the given value ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- countf (t, f[, ...]) ++ ++ flatten (array[, shallow]) +
    +
    +- Counts occurrences validating a predicate. Same as count, but uses an iterator. +- Returns the count for values passing the test f (k, v, …) ++ Flattens a nested array. Passing shallow will only flatten at the first level. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (k, v, …) ++
    • array ++ an array +
    • +-
    • ... +- Optional args to be passed to f ++
    • shallow ++ specifies the flattening depth. Defaults to false.` + (optional) +
    • +
    +@@ -821,67 +3261,60 @@ +

    Returns:

    +
      + +- the count of values validating the predicate ++ a flattened array +
    + + +-

    See also:

    +- + + +
    +
    +- +- cycle (t, n) ++ ++ indexOf (array, value) +
    +
    +- Loops n times through a table. In case n is omitted, it will loop forever. +- In case n is lower or equal to 0, it returns an empty function. +-
    Aliased as loop. ++ Returns the index of the first occurrence of value in an array. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • n +- the number of loops ++
    • value ++ the value to search for +
    • +
    + +

    Returns:

    +
      + +- an iterator function yielding key-value pairs from the passed-in table. ++ the index of the passed-in value +
    + + ++

    See also:

    ++ + + +
    +
    +- +- map (t, f[, ...]) ++ ++ initial (array[, n]) +
    +
    +- Maps f (k, v) on key-value pairs, collects and returns the results. +-
    Aliased as collect. ++ Returns all values in an array excluding the last N values. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (k, v, …) ++
    • array ++ an array +
    • +-
    • ... +- Optional args to be passed to f ++
    • n ++ the number of values to be left, defaults to the array length. + (optional) +
    • +
    +@@ -889,195 +3322,161 @@ +

    Returns:

    +
      + +- a table of results ++ a new array +
    + + ++

    See also:

    ++ + + +
    +
    +- +- reduce (t, f[, state]) ++ ++ interleave (...) +
    +
    +- Reduces a table, left-to-right. Folds the table from the first element to the last element +- to a single value, using a given iterator and an initial state. +- The iterator takes a state and a value and returns a new state. +-
    Aliased as inject, foldl. ++ Interleaves arrays. It returns a single array made of values from all ++ passed in arrays in their given order, interleaved. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (state, value) +-
    • +-
    • state +- an initial state of reduction. Defaults to the first value in the table. +- (optional) ++
    • ... ++ a variable list of arrays +
    • +
    + +

    Returns:

    +
      + +- the final state of reduction ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- reduceby (t, f, state, pred[, ...]) ++ ++ interpose (array, value) +
    +
    +- Reduces values in a table passing a given predicate. Folds the table left-to-right, considering +- only values validating a given predicate. ++ Interposes value in-between consecutive pair of values in array. ++
    Aliased as intersperse + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (state, value) +-
    • +-
    • state +- an initial state of reduction. +-
    • +-
    • pred +- a predicate function pred (k, v, …) to select values to be considered for reduction ++
    • array ++ an array +
    • +-
    • ... +- optional args to be passed to pred +- (optional) ++
    • value ++ a value +
    • +
    + +

    Returns:

    +
      + +- the final state of reduction ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- reduceRight (t, f[, state]) ++ ++ intersection (...) +
    +
    +- Reduces a table, right-to-left. Folds the table from the last element to the first element +- to single value, using a given iterator and an initial state. +- The iterator takes a state and a value, and returns a new state. +-
    Aliased as injectr, foldr. ++ Returns the intersection of all passed-in arrays. ++ Each value in the result is present in each of the passed-in arrays. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (state, value) +-
    • +-
    • state +- an initial state of reduction. Defaults to the last value in the table. +- (optional) ++
    • ... ++ a variable number of array arguments +
    • +
    + +

    Returns:

    +
      + +- the final state of reduction ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- mapReduce (t, f[, state]) ++ ++ isunique (array) +
    +
    +- Reduces a table while saving intermediate states. Folds the table left-to-right +- using a given iterator and an initial state. The iterator takes a state and a value, +- and returns a new state. The result is an array of intermediate states. +-
    Aliased as mapr ++ Checks if a given array contains distinct values. Such an array is made of distinct elements, ++ which only occur once in this array. ++
    Aliased as isuniq + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (state, value) +-
    • +-
    • state +- an initial state of reduction. Defaults to the first value in the table. +- (optional) ++
    • array ++ an array +
    • +
    + +

    Returns:

    +
      + +- an array of states ++ true if the given array is unique, false otherwise. +
    + + +

    See also:

    + + + +
    +
    +- +- mapReduceRight (t, f[, state]) ++ ++ last (array[, n]) +
    +
    +- Reduces a table while saving intermediate states. Folds the table right-to-left +- using a given iterator and an initial state. The iterator takes a state and a value, +- and returns a new state. The result is an array of intermediate states. +-
    Aliased as maprr ++ Returns the last N values in an array. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (state, value) ++
    • array ++ an array +
    • +-
    • state +- an initial state of reduction. Defaults to the last value in the table. ++
    • n ++ the number of values to be collected, defaults to the array length. + (optional) +
    • +
    +@@ -1085,243 +3484,227 @@ +

    Returns:

    +
      + +- an array of states ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- include (t, value) ++ ++ lastIndexOf (array, value) +
    +
    +- Performs a linear search for a value in a table. It does not work for nested tables. +- The given value can be a function prototyped as f (v, value) which should return true when +- any v in the table equals the value being searched. +-
    Aliased as any, some, contains ++ Returns the index of the last occurrence of value in an array. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +
    • value +- a value to search for ++ the value to search for +
    • +
    + +

    Returns:

    +
      + +- a boolean : true when found, false otherwise ++ the index of the last occurrence of the passed-in value or nil +
    + + +

    See also:

    + + + +
    +
    +- +- detect (t, value) ++ ++ mean (array) +
    +
    +- Performs a linear search for a value in a table. Returns the key of the value if found. +- The given value can be a function prototyped as f (v, value) which should return true when +- any v in the table equals the value being searched. ++ Returns the mean of an array of numbers. ++
    Aliased as average + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • value +- a value to search for ++
    • array ++ an array of numbers +
    • +
    + +

    Returns:

    +
      + +- the key of the value when found or nil ++ a number +
    + + +

    See also:

    + + + +
    +
    +- +- where (t, props) ++ ++ median (array) +
    +
    +- Returns all values having specified keys props. ++ Returns the median of an array of numbers. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • props +- a set of keys ++
    • array ++ an array of numbers +
    • +
    + +

    Returns:

    +
      + +- an array of values from the passed-in table ++ a number +
    + + +

    See also:

    + + + +
    +
    +- +- findWhere (t, props) ++ ++ nsorted (array[, n[, comp]]) +
    +
    +- Returns the first value having specified keys props. ++ Returns the n-top values satisfying a predicate. It takes a comparison function ++ comp used to sort array values, and then picks the top n-values. It leaves the original array untouched. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • props +- a set of keys ++
    • n ++ a number of values to retrieve. Defaults to 1. ++ (optional) ++
    • ++
    • comp ++ a comparison function. Defaults to < operator. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a value from the passed-in table ++ an array of top n values +
    + + +-

    See also:

    +- + + +
    +
    +- +- select (t, f[, ...]) ++ ++ nth (array, index) +
    +
    +- Selects and returns values passing an iterator test. +-
    Aliased as filter. ++ Returns the value at a given index. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (k, v, …) ++
    • array ++ an array +
    • +-
    • ... +- Optional args to be passed to f +- (optional) ++
    • index ++ an index +
    • +
    + +

    Returns:

    +
      + +- the selected values ++ the value at the given index +
    + + +-

    See also:

    +- + + +
    +
    +- +- reject (t, f[, ...]) ++ ++ ones (n) +
    +
    +- Clones a table while dropping values passing an iterator test. +-
    Aliased as discard ++ Returns an array of n 1's. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • f +- an iterator function, prototyped as f (k, v, …) +-
    • +-
    • ... +- Optional args to be passed to f +- (optional) ++
    • n ++ a number +
    • +
    + +

    Returns:

    +
      + +- the remaining values ++ an array +
    + + +

    See also:

    + + + +
    +
    +- +- all (t, f[, ...]) ++ ++ overlapping (array[, n[, pads]]) +
    +
    +- Checks if all values in a table are passing an iterator test. +-
    Aliased as every ++ Iterator returning overlapping partitions of an array.
    ++ If the last subsequence has lower elements than n and pad is ++ supplied, it will be adjusted to n elements with pad value. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • f +- an iterator function, prototyped as f (k, v, …) ++
    • n ++ the size of partitions. Defaults to 2. ++ (optional) +
    • +-
    • ... +- Optional args to be passed to f ++
    • pads ++ a value to adjust the last subsequence to the n elements + (optional) +
    • +
    +@@ -1329,97 +3712,93 @@ +

    Returns:

    +
      + +- true if all values passes the predicate, false otherwise ++ an iterator function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- invoke (t, method[, ...]) ++ ++ pack (...) +
    +
    +- Invokes a method on each value in a table. ++ Converts a list of arguments to an array. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • method +- a function, prototyped as f (v, …) +-
    • +
    • ... +- Optional args to be passed to method +- (optional) ++ a list of arguments +
    • +
    + +

    Returns:

    +
      + +- the result of the call f (v, …) ++ an array of all passed-in args +
    + + +-

    See also:

    +- + + +
    +
    +- +- pluck (t, key) ++ ++ pairwise (array) +
    +
    +- Extracts values in a table having a given key. ++ Iterator returning sliding pairs of an array. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • key +- a key, will be used to index in each value: value[key] ++
    • array ++ an array +
    • +
    + +

    Returns:

    +
      + +- an array of values having the given key ++ an iterator function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- max (t[, transform[, ...]]) ++ ++ partition (array[, n[, pads]]) +
    +
    +- Returns the max value in a collection. If an transformation function is passed, it will +- be used to evaluate values by which all objects will be sorted. ++ Iterator returning partitions of an array. It returns arrays of length n ++ made of values from the given array. If the last partition has lower elements than n and ++ pad is supplied, it will be adjusted to n of elements with pad value. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • transform +- a transformation function, prototyped as transform (v, …), defaults to identity ++
    • n ++ the size of partitions. Defaults to 1. + (optional) +
    • +-
    • ... +- Optional args to be passed to transform ++
    • pads ++ a value to adjust the last subsequence to the n elements + (optional) +
    • +
    +@@ -1427,108 +3806,123 @@ +

    Returns:

    +
      + +- the max value found ++ an iterator function +
    + + +

    See also:

    + + + +
    +
    +- +- min (t[, transform[, ...]]) ++ ++ permutation (array) +
    +
    +- Returns the min value in a collection. If an transformation function is passed, it will +- be used to evaluate values by which all objects will be sorted. ++ Iterator returning the permutations of an array. It returns arrays made of all values ++ from the passed-in array, with values permuted. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • transform +- a transformation function, prototyped as transform (v, …), defaults to identity +- (optional) +-
    • +-
    • ... +- Optional args to be passed to transform +- (optional) ++
    • array ++ an array +
    • +
    + +

    Returns:

    +
      + +- the min value found ++ an iterator function +
    + + +-

    See also:

    ++ ++ ++
    ++
    ++ ++ powerset (array) ++
    ++
    ++ Returns the powerset of array values. For instance, when given the set {1,2,3}, ++ returns {{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}}. ++ ++ ++

    Parameters:

    +
      +- max ++
    • array ++ an array ++
    • +
    + ++

    Returns:

    ++
      ++ ++ an array ++
    ++ ++ ++ + +
    +
    +- +- shuffle (t[, seed]) ++ ++ prepend (array, ...) +
    +
    +- Returns a shuffled copy of a given collection. If a seed is provided, it will +- be used to init the pseudo random number generator (using math.randomseed). ++ Adds all passed-in values at the top of an array. As opposed to addTop, it preserves the order ++ of the passed-in elements. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • seed +- a seed +- (optional) ++
    • ... ++ a variable number of arguments +
    • +
    + +

    Returns:

    +
      + +- a shuffled copy of the given table ++ the passed-in array with new values added +
    + + ++

    See also:

    ++ + + +
    +
    +- +- same (a, b) ++ ++ product (array) +
    +
    +- Checks if two tables are the same. It compares if both tables features the same values, +- but not necessarily at the same keys. ++ Returns the product of array values. + + +

    Parameters:

    +
      +-
    • a +- a table +-
    • +-
    • b +- another table ++
    • array ++ a given array +
    • +
    + +

    Returns:

    +
      + +- true or false ++ the product of array values +
    + + +@@ -1536,95 +3930,89 @@ + +
    +
    +- +- sort (t[, comp]) ++ ++ pull (array, ...) +
    +
    +- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. ++ Removes all provided values in a given array. ++
    Aliased as remove + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • comp +- a comparison function prototyped as comp (a, b), defaults to < operator. +- (optional) ++
    • ... ++ a variable number of values to be removed from the array +
    • +
    + +

    Returns:

    +
      + +- the initial table, sorted. ++ the passed-in array with values removed +
    + + +-

    See also:

    +- + + +
    +
    +- +- sortBy (t[, transform[, comp]]) ++ ++ push (array, ...) +
    +
    +- Sorts a table in-place using a transform. Values are ranked in a custom order of the results of +- running transform (v) on all values. transform may also be a string name property sort by. +- comp is a comparison function. ++ Pushes all passed-in values at the end of an array. + + +

    Parameters:

    +
      +-
    • t +- a table +-
    • +-
    • transform +- a transform function to sort elements prototyped as transform (v). Defaults to identity +- (optional) ++
    • array ++ an array +
    • +-
    • comp +- a comparision function, defaults to the < operator +- (optional) ++
    • ... ++ a variable number of arguments +
    • +
    + +

    Returns:

    +
      + +- a new array of sorted values ++ the passed-in array with new added values +
    + + +

    See also:

    + + + +
    +
    +- +- groupBy (t, iter[, ...]) ++ ++ range ([from[, to[, step]]]) +
    +
    +- Splits a table into subsets groups. ++ Produces a flexible list of numbers. If one value is passed, will count from 1 to that value, ++ with a default step of 1 (or -1). If two values are passed, will count from the first one to the second one, ++ using a default step of 1 (or -1). A third value passed will be considered a step value. + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • from ++ the initial value of the range ++ (optional) +
    • +-
    • iter +- an iterator function, prototyped as iter (k, v, …) ++
    • to ++ the final value of the range ++ (optional) +
    • +-
    • ... +- Optional args to be passed to iter ++
    • step ++ the step of count. Defaults to 1 or -1. + (optional) +
    • +
    +@@ -1632,7 +4020,7 @@ +

    Returns:

    +
      + +- a table of subsets groups ++ a new array of numbers +
    + + +@@ -1640,23 +4028,25 @@ + +
    +
    +- +- countBy (t, iter[, ...]) ++ ++ removeRange (array[, start[, finish]]) +
    +
    +- Groups values in a collection and counts them. ++ Removes values at an index within the range [start, finish]. ++
    Aliased as rmRange, chop + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • iter +- an iterator function, prototyped as iter (k, v, …) ++
    • start ++ the lower bound index, defaults to the first index in the array. ++ (optional) +
    • +-
    • ... +- Optional args to be passed to iter ++
    • finish ++ the upper bound index, defaults to the array length. + (optional) +
    • +
    +@@ -1664,7 +4054,7 @@ +

    Returns:

    +
      + +- a table of subsets groups names paired with their count ++ the passed-in array with values removed +
    + + +@@ -1672,107 +4062,94 @@ + +
    +
    +- +- size ([...]) ++ ++ rep (value, n) +
    +
    +- Counts the number of values in a collection. If being passed more than one argument +- it will return the count of all passed-in arguments. ++ Creates an array list of n values, repeated. + + +

    Parameters:

    +
      +-
    • ... +- Optional variable number of arguments +- (optional) ++
    • value ++ a value to be repeated ++
    • ++
    • n ++ the number of repetitions of value. +
    • +
    + +

    Returns:

    +
      + +- a count ++ a new array of n values +
    + + +-

    See also:

    +- + + +
    +
    +- +- containsKeys (t, other) ++ ++ rest (array[, index]) +
    +
    +- Checks if all the keys of other table exists in table t. It does not +- compares values. The test is not commutative, i.e table t may contains keys +- not existing in other. ++ Returns all values after index. ++
    Aliased as tail + + +

    Parameters:

    +
      +-
    • t +- a table ++
    • array ++ an array +
    • +-
    • other +- another table ++
    • index ++ an index, defaults to 1 ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- true or false ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- sameKeys (tA, tB) ++ ++ reverse (array) +
    +
    +- Checks if both given tables have the same keys. It does not compares values. ++ Returns an array where values are in reverse order. The passed-in array should not be sparse. + + +

    Parameters:

    +
      +-
    • tA +- a table +-
    • +-
    • tB +- another table ++
    • array ++ an array +
    • +
    + +

    Returns:

    +
      + +- true or false ++ a reversed array +
    + + +-

    See also:

    +- + + +
    +-
    +-

    Array functions

    +- +-
    +
    + + sample (array[, n[, seed]]) +@@ -1801,7 +4178,7 @@ +

    Returns:

    +
      + +- an array of selected values or a single value when n == 1 ++ an array of selected values +
    + + +@@ -1851,96 +4228,46 @@ + + +
    +- +- toArray (...) +-
    +-
    +- Converts a list of arguments to an array. +- +- +-

    Parameters:

    +-
      +-
    • ... +- a list of arguments +-
    • +-
    +- +-

    Returns:

    +-
      +- +- an array of all passed-in args +-
    +- +- +- +- +-
    +-
    +- +- find (array, value[, from]) ++ ++ selectWhile (array, f) +
    +
    +- Looks for the first occurrence of a given value in an array. Returns the value index if found. +- Uses isEqual to compare values. ++ Collects values from a given array. The passed-in array should not be sparse. ++ This function collects values as long as they satisfy a given predicate and returns on the first falsy test. ++
    Aliased as takeWhile + + +

    Parameters:

    +
      +
    • array +- an array of values +-
    • +-
    • value +- a value to lookup for ++ an array +
    • +-
    • from +- the index from where the search will start. Defaults to 1. +- (optional) ++
    • f ++ an iterator function prototyped as f (v, k) +
    • +
    + +

    Returns:

    +
      + +- the index of the value if found in the array, nil otherwise. ++ a new table containing all values collected +
    + + +- +- +-
    +-
    +- +- reverse (array) +-
    +-
    +- Returns an array where values are in reverse order. The passed-in array should not be sparse. +- +- +-

    Parameters:

    ++

    See also:

    + + +-

    Returns:

    +-
      +- +- a reversed array +-
    +- +- +- + +
    +
    +- +- fill (array, value[, i[, j]]) ++ ++ shift (array[, n]) +
    +
    +- Replaces elements in a given array with a given value. In case i and j are given +- it will only replaces values at indexes between [i,j]. In case j is greather than the array +- size, it will append new values, increasing the array. ++ Removes and returns the values at the top of a given array. ++
    Aliased as pop + + +

    Parameters:

    +@@ -1948,15 +4275,8 @@ +
  • array + an array +
  • +-
  • value +- a value +-
  • +-
  • i +- the index from which to start replacing values. Defaults to 1. +- (optional) +-
  • +-
  • j +- the index where to stop replacing values. Defaults to the array size. ++
  • n ++ the number of values to be popped. Defaults to 1. + (optional) +
  • + +@@ -1964,21 +4284,24 @@ +

    Returns:

    +
      + +- the original array with values changed ++ the popped values +
    + + ++

    See also:

    ++ + + +
    +
    +- +- selectWhile (array, f[, ...]) ++ ++ shuffle (array[, seed]) +
    +
    +- Collects values from a given array. The passed-in array should not be sparse. +- This function collects values as long as they satisfy a given predicate and returns on the first falsy test. +-
    Aliased as takeWhile ++ Returns a shuffled copy of a given array. If a seed is provided, it will ++ be used to init the built-in pseudo random number generator (using math.randomseed). + + +

    Parameters:

    +@@ -1986,11 +4309,8 @@ +
  • array + an array +
  • +-
  • f +- an iterator function prototyped as f (k, v, …) +-
  • +-
  • ... +- Optional args to be passed to f ++
  • seed ++ a seed + (optional) +
  • + +@@ -1998,25 +4318,20 @@ +

    Returns:

    +
      + +- a new table containing all values collected ++ a shuffled copy of the given array +
    + + +-

    See also:

    +- + + +
    +
    +- +- dropWhile (array, f[, ...]) ++ ++ slice (array[, start[, finish]]) +
    +
    +- Collects values from a given array. The passed-in array should not be sparse. +- This function collects values as long as they do not satisfy a given predicate and returns on the first truthy test. +-
    Aliased as rejectWhile ++ Slices values indexed within [start, finish] range. ++
    Aliased as M.sub + + +

    Parameters:

    +@@ -2024,11 +4339,12 @@ +
  • array + an array +
  • +-
  • f +- an iterator function prototyped as f (k,v, …) ++
  • start ++ the lower bound index, defaults to the first index in the array. ++ (optional) +
  • +-
  • ... +- Optional args to be passed to f ++
  • finish ++ the upper bound index, defaults to the array length. + (optional) +
  • + +@@ -2036,7 +4352,7 @@ +

    Returns:

    +
      + +- a new table containing all values collected ++ a new array of sliced values +
    + + +@@ -2082,75 +4398,38 @@ + +
    +
    +- +- indexOf (array, value) +-
    +-
    +- Returns the index of the first occurence of value in an array. +- +- +-

    Parameters:

    +-
      +-
    • array +- an array +-
    • +-
    • value +- the value to search for +-
    • +-
    +- +-

    Returns:

    +-
      +- +- the index of the passed-in value +-
    +- +- +-

    See also:

    +- +- +- +-
    +-
    +- +- lastIndexOf (array, value) ++ ++ sum (array) +
    +
    +- Returns the index of the last occurrence of value in an array. ++ Returns the sum of array values. + + +

    Parameters:

    +
      +
    • array +- an array +-
    • +-
    • value +- the value to search for ++ a given array +
    • +
    + +

    Returns:

    +
      + +- the index of the last occurrence of the passed-in value or nil ++ the sum of array values +
    + + +-

    See also:

    +- + + +
    +
    +- +- findIndex (array, predicate[, ...]) ++ ++ symmetricDifference (array, array2) +
    +
    +- Returns the first index at which a predicate returns true. ++ Performs a symmetric difference. Returns values from array not present in array2 and also values ++ from array2 not present in array. ++
    Aliased as symdiff + + +

    Parameters:

    +@@ -2158,72 +4437,65 @@ +
  • array + an array +
  • +-
  • predicate +- a predicate function prototyped as predicate (k, v, …) +-
  • +-
  • ... +- optional arguments to pred +- (optional) ++
  • array2 ++ another array +
  • + + +

    Returns:

    +
      + +- the index found or nil ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- findLastIndex (array, predicate[, ...]) ++ ++ union (...) +
    +
    +- Returns the last index at which a predicate returns true. ++ Returns the duplicate-free union of all passed in arrays. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • predicate +- a predicate function prototyped as predicate (k, v, …) +-
    • +
    • ... +- optional arguments to pred +- (optional) ++ a variable number of arrays arguments +
    • +
    + +

    Returns:

    +
      + +- the index found or nil ++ a new array +
    + + +

    See also:

    + + + +
    +
    +- +- addTop (array, ...) ++ ++ unique (array) +
    +
    +- Adds all passed-in values at the top of an array. The last elements will bubble to the +- top of the given array. ++ Produces a duplicate-free version of a given array. ++
    Aliased as uniq + + +

    Parameters:

    +@@ -2231,31 +4503,29 @@ +
  • array + an array +
  • +-
  • ... +- a variable number of arguments +-
  • + + +

    Returns:

    +
      + +- the passed-in array with new values added ++ a new array, duplicate-free +
    + + +

    See also:

    + + + +
    +
    +- +- push (array, ...) ++ ++ unshift (array[, n]) +
    +
    +- Pushes all passed-in values at the end of an array. ++ Removes and returns the values at the end of a given array. + + +

    Parameters:

    +@@ -2263,115 +4533,109 @@ +
  • array + an array +
  • +-
  • ... +- a variable number of arguments ++
  • n ++ the number of values to be unshifted. Defaults to 1. ++ (optional) +
  • + + +

    Returns:

    +
      + +- the passed-in array with new added values ++ the values +
    + + +

    See also:

    + + + +
    +
    +- +- pop (array[, n]) ++ ++ vector (value, n) +
    +
    +- Removes and returns the values at the top of a given array. +-
    Aliased as shift ++ Returns an array of n times a given value. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • value ++ a value +
    • +
    • n +- the number of values to be popped. Defaults to 1. +- (optional) ++ a number +
    • +
    + +

    Returns:

    +
      + +- the popped values ++ an array +
    + + +

    See also:

    + + + +
    +
    +- +- unshift (array[, n]) ++ ++ xpairs (valua, array) +
    +
    +- Removes and returns the values at the end of a given array. ++ Creates pairs from value and array. Value is always prepended to the pair. + + +

    Parameters:

    +
      ++
    • valua ++ a value ++
    • +
    • array + an array +
    • +-
    • n +- the number of values to be unshifted. Defaults to 1. +- (optional) +-
    • +
    + +

    Returns:

    +
      + +- the values ++ an array list of all pairs +
    + + +-

    See also:

    +- + + +
    +
    +- +- pull (array, ...) ++ ++ xpairsRight (valua, array) +
    +
    +- Removes all provided values in a given array. +-
    Aliased as remove ++ Creates pairs from value and array. Value is always appended as the last item to the pair. + + +

    Parameters:

    +
      ++
    • valua ++ a value ++
    • +
    • array + an array +
    • +-
    • ... +- a variable number of values to be removed from the array +-
    • +
    + +

    Returns:

    +
      + +- the passed-in array with values removed ++ an array list of all pairs +
    + + +@@ -2379,33 +4643,27 @@ + +
    +
    +- +- removeRange (array[, start[, finish]]) ++ ++ xprod (array, array2) +
    +
    +- Removes values at index within the range [start, finish]. +-
    Aliased as rmRange, chop ++ Returns all possible pairs built from given arrays. + + +

    Parameters:

    +
      +
    • array +- an array +-
    • +-
    • start +- the lower bound index, defaults to the first index in the array. +- (optional) ++ a first array +
    • +-
    • finish +- the upper bound index, defaults to the array length. +- (optional) ++
    • array2 ++ a second array +
    • +
    + +

    Returns:

    +
      + +- the passed-in array with values removed ++ an array list of all pairs +
    + + +@@ -2413,199 +4671,182 @@ + +
    +
    +- +- chunk (array, f[, ...]) ++ ++ zeros (n) +
    +
    +- Chunks together consecutive values. Values are chunked on the basis of the return +- value of a provided predicate f (k, v, …). Consecutive elements which return +- the same value are chunked together. Leaves the first argument untouched if it is not an array. ++ Returns an array of n zeros. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • f +- an iterator function prototyped as f (k, v, …) +-
    • +-
    • ... +- Optional args to be passed to f +- (optional) ++
    • n ++ a number +
    • +
    + +

    Returns:

    +
      + +- a table of chunks (arrays) ++ an array +
    + + +

    See also:

    + + + +
    +
    +- +- slice (array[, start[, finish]]) ++ ++ zip (...) +
    +
    +- Slices values indexed within [start, finish] range. +-
    Aliased as _.sub ++ Merges values of each of the passed-in arrays in subsets. ++ Only values indexed with the same key in the given arrays are merged in the same subset. ++
    Aliased as transpose + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • start +- the lower bound index, defaults to the first index in the array. +- (optional) +-
    • +-
    • finish +- the upper bound index, defaults to the array length. +- (optional) ++
    • ... ++ a variable number of array arguments +
    • +
    + +

    Returns:

    +
      + +- a new array of sliced values ++ a new array +
    + + ++

    See also:

    ++ + + +
    +
    +- +- first (array[, n]) ++ ++ zipWith (f, ...) +
    +
    +- Returns the first N values in an array. +-
    Aliased as head, take ++ Merges values using a given function. ++ Only values indexed with the same key in the given arrays are merged in the same subset. ++ Function f is used to combine values. ++
    Aliased as transposeWith + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • n +- the number of values to be collected, defaults to 1. +- (optional) ++
    • ... ++ a variable number of array arguments +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a flat array of results +
    + + +

    See also:

    + + + +
    ++
    ++

    Utility functions

    ++ ++
    +
    +- +- initial (array[, n]) ++ ++ after (f, count) +
    +
    +- Returns all values in an array excluding the last N values. ++ Returns a version of f that runs on the count-th call. ++ Useful when dealing with asynchronous tasks. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • n +- the number of values to be left, defaults to the array length. +- (optional) ++
    • count ++ the number of calls before f will start running. +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a new function +
    + + +

    See also:

    + + + +
    +
    +- +- last (array[, n]) ++ ++ applySpec (specs) +
    +
    +- Returns the last N values in an array. ++ Returns a function which applies specs on args. This function produces an object having ++ the same structure than specs by mapping each property to the result of calling its ++ associated function with the supplied arguments + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • n +- the number of values to be collected, defaults to the array length. +- (optional) ++
    • specs ++ a table +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a function +
    + + +-

    See also:

    +- + + +
    +
    +- +- rest (array[, index]) ++ ++ ary (f[, n]) +
    +
    +- Removes all values before index. +-
    Aliased as tail ++ Returns a function which accepts up to n args. It ignores any additional arguments. ++
    Aliased as nAry. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • index +- an index, defaults to 1 ++
    • n ++ a number. Defaults to 1. + (optional) +
    • +
    +@@ -2613,348 +4854,347 @@ +

    Returns:

    +
      + +- a new array ++ a function +
    + + +

    See also:

    + + + +
    +
    +- +- nth (array, index) ++ ++ before (f, count) +
    +
    +- Returns the value at a given index. ++ Returns a version of f that will run no more than count times. Next calls will ++ keep yielding the results of the count-th call. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • index +- an index ++
    • count ++ a count +
    • +
    + +

    Returns:

    +
      + +- the value at the given index ++ a new function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- compact (array) ++ ++ bind (f, v) +
    +
    +- Removes all falsy (false and nil) values. ++ Binds v to be the first argument to f. Calling f (...) will result to f (v, ...). + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function ++
    • ++
    • v ++ a value +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- flatten (array[, shallow]) ++ ++ bind2 (f, v) +
    +
    +- Flattens a nested array. Passing shallow will only flatten at the first level. ++ Binds v to be the second argument to f. Calling f (a, ...) will result to f (a, v, ...). + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • shallow +- specifies the flattening depth +- (optional) ++
    • v ++ a value +
    • +
    + +

    Returns:

    +
      + +- a new array, flattened ++ a function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- difference (array, another) ++ ++ bindall (obj, ...) +
    +
    +- Returns values from an array not present in all passed-in args. +-
    Aliased as without and diff ++ Binds methods to object. As such, whenever any of these methods is invoked, it ++ always receives the object as its first argument. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • obj ++ an abject +
    • +-
    • another +- array ++
    • ... ++ a variable number of method names +
    • +
    + +

    Returns:

    +
      + +- a new array ++ the passed-in object with all methods bound to the object itself. +
    + + +

    See also:

    + + + +
    +
    +- +- union (...) ++ ++ bindn (f, ...) +
    +
    +- Returns the duplicate-free union of all passed in arrays. ++ Binds ... to be the N-first arguments to function f.
    ++ Calling f (a1, a2, ..., aN) will result to f (..., a1, a2, ...,aN). + + +

    Parameters:

    +
      ++
    • f ++ a function ++
    • +
    • ... +- a variable number of arrays arguments ++ a variable number of arguments +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a function +
    + + +

    See also:

    + + + +
    +
    +- +- intersection (array, ...) ++ ++ both (...) +
    +
    +- Returns the intersection of all passed-in arrays. +- Each value in the result is present in each of the passed-in arrays. ++ Returns a validation function. Given a set of functions, the validation function evaluates ++ to true only when all its funcs returns true. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +
    • ... +- a variable number of array arguments ++ an array list of functions +
    • +
    + +

    Returns:

    +
      + +- a new array ++ true when all given funcs returns true with input, false otherwise +
    + + +-

    See also:

    +- + + +
    +
    +- +- symmetricDifference (array, array2) ++ ++ call (f[, ...]) +
    +
    +- Performs a symmetric difference. Returns values from array not present in array2 and also values +- from array2 not present in array. +-
    Aliased as symdiff ++ Calls f with the supplied arguments. Returns the results of f(...). + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +-
    • array2 +- another array ++
    • ... ++ a vararg list of args to f ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a new array ++ the result of f(...) call. +
    + + +-

    See also:

    +- + + +
    +
    +- +- unique (array) ++ ++ castArray (value) +
    +
    +- Produces a duplicate-free version of a given array. +-
    Aliased as uniq ++ Casts value as an array if it is not one. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • value ++ a value +
    • +
    + +

    Returns:

    +
      + +- a new array, duplicate-free ++ an array containing the given value +
    + + +-

    See also:

    +- + + +
    +
    +- +- isunique (array) ++ ++ complement (f) +
    +
    +- Checks if a given array contains distinct values. Such an array is made of distinct elements, +- which only occur once in this array. +-
    Aliased as isuniq ++ Returns the logical complement of a given function. For a given input, the returned ++ function will output false if the original function would have returned true, ++ and vice-versa. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • f ++ a function +
    • +
    + +

    Returns:

    +
      + +- true if the given array is unique, false otherwise. ++ the logical complement of the given function f. +
    + + +-

    See also:

    +- + + +
    +
    +- +- zip (...) ++ ++ compose (...) +
    +
    +- Merges values of each of the passed-in arrays in subsets. +- Only values indexed with the same key in the given arrays are merged in the same subset. +-
    Aliased as transpose ++ Composes functions. Each passed-in function consumes the return value of the function that follows. ++ In math terms, composing the functions f, g, and h produces the function f(g(h(...))). + + +

    Parameters:

    +
      +
    • ... +- a variable number of array arguments ++ a variable number of functions +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a new function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- append (array, other) ++ ++ cond (conds) +
    +
    +- Clones array and appends other values. ++ Returns a function which iterate over a set of conditions. It invokes each predicate, ++ passing it given values. It returns the value of the corresponding function of the first ++ predicate to return a non-nil value. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • other +- an array ++
    • conds ++ an array list of predicate-function pairs +
    • +
    + +

    Returns:

    +
      + +- a new array ++ the result of invoking f(...) of the first predicate to return a non-nil value +
    + + +@@ -2962,89 +5202,79 @@ + +
    +
    +- +- interleave (...) ++ ++ constant (value) +
    +
    +- Interleaves arrays. It returns a single array made of values from all +- passed in arrays in their given order, interleaved. ++ Creates a constant function which returns the same output on every call. ++
    Aliased as always + + +

    Parameters:

    +
      +-
    • ... +- a variable list of arrays ++
    • value ++ a constant value +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a constant function +
    + + +-

    See also:

    +- + + +
    +
    +- +- interpose (value, array) ++ ++ converge (f, g, h) +
    +
    +- Interposes value in-between consecutive pair of values in array. ++ Converges two functions into one. + + +

    Parameters:

    +
      +-
    • value +- a value ++
    • f ++ a function +
    • +-
    • array +- an array ++
    • g ++ a function ++
    • ++
    • h ++ a function +
    • +
    + +

    Returns:

    +
      + +- a new array ++ a new version of function f +
    + + +-

    See also:

    +- + + +
    +
    +- +- range ([from[, to[, step]]]) ++ ++ curry (f[, n_args]) +
    +
    +- Produces a flexible list of numbers. If one positive value is passed, will count from 0 to that value, +- with a default step of 1. If two values are passed, will count from the first one to the second one, with the +- same default step of 1. A third value passed will be considered a step value. ++ Curries a function. If the given function f takes multiple arguments, it returns another version of ++ f that takes a single argument (the first of the arguments to the original function) and returns a new ++ function that takes the remainder of the arguments and returns the result. + + +

    Parameters:

    +
      +-
    • from +- the initial value of the range +- (optional) +-
    • +-
    • to +- the final value of the range +- (optional) ++
    • f ++ a function +
    • +-
    • step +- the step of count ++
    • n_args ++ the number of arguments expected for f. Defaults to 2. + (optional) +
    • +
    +@@ -3052,35 +5282,38 @@ +

    Returns:

    +
      + +- a new array of numbers ++ a curried version of f +
    + + ++

    See also:

    ++ + + +
    +
    +- +- rep (value, n) ++ ++ dispatch (...) +
    +
    +- Creates an array list of n values, repeated. ++ Returns a dispatching function. When called with arguments, this function invokes each of its functions ++ in the passed-in order and returns the results of the first non-nil evaluation. + + +

    Parameters:

    +
      +-
    • value +- a value to be repeated +-
    • +-
    • n +- the number of repetitions of value. ++
    • ... ++ a vararg list of functions +
    • +
    + +

    Returns:

    +
      + +- a new array of n values ++ a dispatch function +
    + + +@@ -3088,34 +5321,25 @@ + +
    +
    +- +- partition (array[, n[, pad]]) ++ ++ either (...) +
    +
    +- Iterator returning partitions of an array. It returns arrays of length n +- made of values from the given array. If the last partition has lower elements than n and +- pad is supplied, it will be adjusted to n of elements with pad value. ++ Returns a validation function. Given a set of functions, the validation function evaluates ++ to true when at least one of its funcs returns true. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • n +- the size of partitions. Should be greater than 0. Defaults to 1. +- (optional) +-
    • +-
    • pad +- a value to adjust the last subsequence to the n elements +- (optional) ++
    • ... ++ an array list of functions +
    • +
    + +

    Returns:

    +
      + +- an iterator function ++ true when one of the given funcs returns true with input, false otherwise +
    + + +@@ -3123,34 +5347,24 @@ + +
    +
    +- +- sliding. (array[, n[, pad]]) ++ ++ flip (f) +
    +
    +- Iterator returning sliding partitions of an array. It returns overlapping subsequences +- of length n. If the last subsequence has lower elements than n and pad is +- supplied, it will be adjusted to n elements with pad value. ++ Creates a function of f with arguments flipped in reverse order. + + +

    Parameters:

    +
      +-
    • array +- an array +-
    • +-
    • n +- the size of partitions. Should be greater than 1. Defaults to 2. +- (optional) +-
    • +-
    • pad +- a value to adjust the last subsequence to the n elements +- (optional) ++
    • f ++ a function +
    • +
    + +

    Returns:

    +
      + +- an iterator function ++ a function +
    + + +@@ -3158,25 +5372,25 @@ + +
    +
    +- +- permutation (array) ++ ++ identity (value) +
    +
    +- Iterator returning the permutations of an array. It returns arrays made of all values +- from the passed-in array, with values permuted. ++ Returns the passed-in value. This function is used internally ++ as a default iterator. + + +

    Parameters:

    +
      +-
    • array +- an array ++
    • value ++ a value +
    • +
    + +

    Returns:

    +
      + +- an iterator function ++ the passed-in value +
    + + +@@ -3184,26 +5398,33 @@ + +
    +
    +- +- invert (array) ++ ++ iterator (f, value[, n]) +
    +
    +- Swaps keys with values. Produces a new array where previous keys are now values, +- while previous values are now keys. +-
    Aliased as mirror ++ Produces an iterator which repeatedly apply a function f onto an input.
    ++ Yields value, then f(value), then f(f(value)), continuously. ++
    Aliased as iter. + + +

    Parameters:

    +
      +-
    • array +- a given array ++
    • f ++ a function ++
    • ++
    • value ++ an initial input to f ++
    • ++
    • n ++ the number of times the iterator should run ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a new array ++ an iterator function +
    + + +@@ -3211,62 +5432,54 @@ + +
    +
    +- +- concat (array[, sep[, i[, j]]]) ++ ++ iterlen (...) +
    +
    +- Concatenates values in a given array. Handles booleans as well. If sep string is +- passed, it will be used as a separator. Passing i and j will result in concatenating +- only values within [i, j] range. +-
    Aliased as join ++ Returns the length of an iterator. It consumes the iterator itself. + + +

    Parameters:

    +
      +-
    • array +- a given array +-
    • +-
    • sep +- a separator string, defaults to the empty string ''. +- (optional) +-
    • +-
    • i +- the starting index, defaults to 1. +- (optional) +-
    • +-
    • j +- the final index, defaults to the array length. +- (optional) ++
    • ... ++ an iterator function (returning a generator, a state and a value) +
    • +
    + +

    Returns:

    +
      + +- a string ++ the iterator length +
    + + + + +
    +-
    +-

    Utility functions

    +- +-
    +
    +- +- noop () ++ ++ juxtapose (value, ...) +
    +
    +- The no-operation function. ++ Calls a sequence of passed-in functions with the same argument. ++ Returns a sequence of results. ++
    Aliased as juxt + + ++

    Parameters:

    ++
      ++
    • value ++ a value ++
    • ++
    • ... ++ a variable number of functions ++
    • ++
    + +

    Returns:

    +
      + +- nothing ++ a list of results +
    + + +@@ -3274,25 +5487,26 @@ + +
    +
    +- +- identity (value) ++ ++ memoize (f) +
    +
    +- Returns the passed-in value. This function is used internally +- as a default iterator. ++ Memoizes a given function by caching the computed result. ++ Useful for speeding-up slow-running functions. ++
    Aliased as cache + + +

    Parameters:

    +
      +-
    • value +- a value ++
    • f ++ a function +
    • +
    + +

    Returns:

    +
      + +- the passed-in value ++ a new function +
    + + +@@ -3300,24 +5514,25 @@ + +
    +
    +- +- constant (value) ++ ++ neither (...) +
    +
    +- Creates a constant function which returns the same output on every call. ++ Returns a validation function. Given a set of functions, the validation function evaluates ++ to true when neither of its func return true. + + +

    Parameters:

    +
      +-
    • value +- a constant value ++
    • ... ++ an array list of functions +
    • +
    + +

    Returns:

    +
      + +- a constant function ++ true when neither of the given funcs returns true with input, false otherwise +
    + + +@@ -3325,14 +5540,11 @@ + +
    +
    +- +- memoize (f[, hash]) ++ ++ noarg (f) +
    +
    +- Memoizes a given function by caching the computed result. +- Useful for speeding-up slow-running functions. If a hash function is passed, +- it will be used to compute hash keys for a set of input values for caching. +-
    Aliased as cache ++ Returns a function with an arity of 0. The new function ignores any arguments passed to it. + + +

    Parameters:

    +@@ -3340,10 +5552,6 @@ +
  • f + a function +
  • +-
  • hash +- a hash function, defaults to identity +- (optional) +-
  • + + +

    Returns:

    +@@ -3357,78 +5565,58 @@ + +
    +
    +- +- once (f) ++ ++ noop () +
    +
    +- Returns a version of f that runs only once. Successive calls to f +- will keep yielding the same output, no matter what the passed-in arguments are. +- It can be used to initialize variables. ++ The no operation function. + + +-

    Parameters:

    +-
      +-
    • f +- a function +-
    • +-
    + +

    Returns:

    +
      + +- a new function ++ nothing +
    + + +-

    See also:

    +- + + +
    +
    +- +- before (f, count) ++ ++ nthArg (n) +
    +
    +- Returns a version of f that will run no more than count times. Next calls will +- keep yielding the results of the count-th call. ++ Returns a function that gets the nth argument.
    ++ If n is negative, the nth argument from the end is returned. + + +

    Parameters:

    +
      +-
    • f +- a function +-
    • +-
    • count +- a count ++
    • n ++ a number +
    • +
    + +

    Returns:

    +
      + +- a new function ++ a function +
    + + +-

    See also:

    +- + + +
    +
    +- +- after (f, count) ++ ++ once (f) +
    +
    +- Returns a version of f that runs on the count-th call. +- Useful when dealing with asynchronous tasks. ++ Returns a version of f that runs only once. Successive calls to f ++ will keep yielding the same output, no matter what the passed-in arguments are. ++ It can be used to initialize variables. + + +

    Parameters:

    +@@ -3436,9 +5624,6 @@ +
  • f + a function +
  • +-
  • count +- the number of calls before f will start running. +-
  • + + +

    Returns:

    +@@ -3450,233 +5635,250 @@ + +

    See also:

    + + + +
    +
    +- +- compose (...) ++ ++ over (...) +
    +
    +- Composes functions. Each passed-in function consumes the return value of the function that follows. +- In math terms, composing the functions f, g, and h produces the function f(g(h(…))). ++ Creates a function that runs transforms on all arguments it receives. + + +

    Parameters:

    +
      +
    • ... +- a variable number of functions ++ a set of functions which will receive all arguments to the returned function +
    • +
    + +

    Returns:

    +
      + +- a new function ++ a function +
    + + +

    See also:

    + + + +
    +
    +- +- pipe (value, ...) ++ ++ overArgs (f, ...) +
    +
    +- Pipes a value through a series of functions. In math terms, +- given some functions f, g, and h in that order, it returns f(g(h(value))). ++ Creates a function that invokes f with its arguments transformed. 1rst arguments will be passed to ++ the 1rst transform, 2nd arg to the 2nd transform, etc. Remaining arguments will not be transformed. + + +

    Parameters:

    +
      +-
    • value +- a value ++
    • f ++ a function +
    • +
    • ... +- a variable number of functions ++ a list of transforms funcs prototyped as f (v) +
    • +
    + +

    Returns:

    +
      + +- the result of the composition of function calls. ++ the result of running f with its transformed arguments +
    + + +

    See also:

    + + + +
    +
    +- +- complement (f) ++ ++ overEvery (...) +
    +
    +- Returns the logical complement of a given function. For a given input, the returned +- function will output false if the original function would have returned true, +- and vice-versa. ++ Creates a validation function. The returned function checks if all of the given predicates return ++ truthy when invoked with the arguments it receives. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • ... ++ a list of predicate functions +
    • +
    + +

    Returns:

    +
      + +- the logical complement of the given function f. ++ a new function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- juxtapose (value, ...) ++ ++ overSome (...) +
    +
    +- Calls a sequence of passed-in functions with the same argument. +- Returns a sequence of results. +-
    Aliased as juxt ++ Creates a validation function. The return function checks if any of a given predicates return ++ truthy when invoked with the arguments it receives. + + +

    Parameters:

    +
      +-
    • value +- a value +-
    • +
    • ... +- a variable number of functions ++ a list of predicate functions +
    • +
    + +

    Returns:

    +
      + +- a list of results ++ a new function +
    + + ++

    See also:

    ++ + + +
    +
    +- +- wrap (f, wrapper) ++ ++ partial (f, ...) +
    +
    +- Wraps f inside of the wrapper function. It passes f as the first argument to wrapper. +- This allows the wrapper to execute code before and after f runs, +- adjust the arguments, and execute it conditionally. ++ Partially apply a function by filling in any number of its arguments.
    ++ One may pass a string 'M' as a placeholder in the list of arguments to specify an argument ++ that should not be pre-filled, but left open to be supplied at call-time. + + +

    Parameters:

    +
      +
    • f +- a function to be wrapped, prototyped as f (…) ++ a function +
    • +-
    • wrapper +- a wrapper function, prototyped as wrapper (f, …) ++
    • ... ++ a list of partial arguments to f +
    • +
    + +

    Returns:

    +
      + +- the results ++ a new version of function f having some of it original arguments filled +
    + + ++

    See also:

    ++ + + +
    +
    +- +- times (n, iter, ...) ++ ++ partialRight (f, ...) +
    +
    +- Runs iter function n times. Collects the results of each run and returns them in an array. ++ Similar to partial, but from the right. + + +

    Parameters:

    +
      +-
    • n +- the number of times iter should be called +-
    • +-
    • iter +- an iterator function, prototyped as iter (i, …) ++
    • f ++ a function +
    • +
    • ... +- args to be passed to iter function ++ a list of partial arguments to f +
    • +
    + +

    Returns:

    +
      + +- table an array of results ++ a new version of function f having some of it original arguments filled +
    + + ++

    See also:

    ++ + + +
    +
    +- +- bind (f, v) ++ ++ pipe (value, ...) +
    +
    +- Binds v to be the first argument to f. Calling f (…) will result to f (v, …). ++ Pipes a value through a series of functions. In math terms, ++ given some functions f, g, and h in that order, it returns f(g(h(value))). + + +

    Parameters:

    +
      +-
    • f +- a function +-
    • +-
    • v ++
    • value + a value +
    • ++
    • ... ++ a variable number of functions ++
    • +
    + +

    Returns:

    +
      + +- a function ++ the result of the composition of function calls. +
    + + +

    See also:

    + + + +
    +
    +- +- bind2 (f, v) ++ ++ rearg (f, indexes) +
    +
    +- Binds v to be the second argument to f. Calling f (a, …) will result to f (a, v, …). ++ Returns a function which runs with arguments rearranged. Arguments are passed to the ++ returned function in the order of supplied indexes at call-time. + + +

    Parameters:

    +@@ -3684,8 +5886,8 @@ +
  • f + a function +
  • +-
  • v +- a value ++
  • indexes ++ an array list of indexes +
  • + + +@@ -3696,167 +5898,154 @@ + + + +-

    See also:

    +- + + +
    +
    +- +- bindn (f, ...) ++ ++ skip (iter[, n]) +
    +
    +- Binds to be the N-first arguments to function f.
    +- Calling f (a1, a2, …, aN) will result to f (…, a1, a2, …,aN). ++ Consumes the first n values of a iterator then returns it. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • iter ++ an iterator function +
    • +-
    • ... +- a variable number of arguments ++
    • n ++ a number. Defaults to 1. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a function ++ the given iterator +
    + + +-

    See also:

    +- + + +
    +
    +- +- bindAll (obj, ...) ++ ++ tabulate (...) +
    +
    +- Binds methods to object. As such, whenever any of these methods is invoked, it +- always receives the object as its first argument. ++ Iterates over an iterator and returns its values in an array. + + +

    Parameters:

    +
      +-
    • obj +- an abject +-
    • +
    • ... +- a variable number of method names ++ an iterator function (returning a generator, a state and a value) +
    • +
    + +

    Returns:

    +
      + +- the passed-in object with all methods bound to the object itself. ++ an array of results +
    + + +-

    See also:

    +- + + +
    +
    +- +- uniqueId ([template[, ...]]) ++ ++ thread (value, ...) +
    +
    +- Generates an unique ID for the current session. If given a string template, it +- will use this template for output formatting. Otherwise, if template is a function, it +- will evaluate template (id, …). +-
    Aliased as uid. ++ Threads value through a series of functions. If a function expects more than one args, ++ it can be specified using an array list, where the first item is the function and the following ++ are the remaining args neeeded. The value is used as the first input. + + +

    Parameters:

    +
      +-
    • template +- either a string or a function template to format the ID +- (optional) ++
    • value ++ a value +
    • +
    • ... +- a variable number of arguments to be passed to template, in case it is a function. +- (optional) ++ a vararg list of functions or arrays +
    • +
    + +

    Returns:

    +
      + +- value an ID ++ a value +
    + + ++

    See also:

    ++ + + +
    +
    +- +- iterator (f, x) ++ ++ threadRight (value, ...) +
    +
    +- Produces an iterator which repeatedly apply a function f onto an input.
    +- Yields x, then f(x), then f(f(x)), continuously. ++ Threads value through a series of functions. If a function expects more than one args, ++ it can be specified using an array list, where the first item is the function and the following ++ are the remaining args neeeded. The value is used as the last input. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • value ++ a value +
    • +-
    • x +- an initial input to f ++
    • ... ++ a vararg list of functions or arrays +
    • +
    + +

    Returns:

    +
      + +- an iterator fnction +-
      Aliased as iter. ++ a value +
    + + ++

    See also:

    ++ + + +
    +
    +- +- array (...) ++ ++ time (f[, ...]) +
    +
    +- Iterates an iterator and returns its values in an array. ++ Returns the execution time of f (...) and its returned values. + + +

    Parameters:

    +
      ++
    • f ++ a function ++
    • +
    • ... +- an iterator (a function, a table and a value) ++ optional args to f ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- an array of results ++ the execution time and the results of f (...) +
    + + +@@ -3864,24 +6053,28 @@ + +
    +
    +- +- flip (f) ++ ++ times (iter[, n]) +
    +
    +- Creates a function of f with arguments flipped in reverse order. ++ Runs iter function n times. Collects the results of each run and returns them in an array. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • iter ++ an iterator function, prototyped as iter (i) ++
    • ++
    • n ++ the number of times iter should be called. Defaults to 1. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a function ++ table an array of results +
    + + +@@ -3889,17 +6082,17 @@ + +
    +
    +- +- over (...) ++ ++ unary (f) +
    +
    +- Creates a function that runs transforms on all arguments it receives. ++ Returns a function which accepts up to one arg. It ignores any additional arguments. + + +

    Parameters:

    +
      +-
    • ... +- a set of functions which will receive all arguments to the returned function ++
    • f ++ a function +
    • +
    + +@@ -3912,268 +6105,245 @@ + +

    See also:

    + + + +
    +
    +- +- overEvery (...) ++ ++ unfold (f, seed) +
    +
    +- Creates a validation function. The returned function checks if all of the given predicates return +- truthy when invoked with the arguments it receives. ++ Builds a list from a seed value. Accepts an iterator function, which ++ returns either nil to stop iteration or two values : the value to add to the list ++ of results and the seed to be used in the next call to the iterator function. + + +

    Parameters:

    +
      +-
    • ... +- a list of predicate functions ++
    • f ++ an iterator function ++
    • ++
    • seed ++ a seed value +
    • +
    + +

    Returns:

    +
      + +- a new function ++ an array of values +
    + + +-

    See also:

    +- + + +
    +
    +- +- overSome (...) ++ ++ uniqueId ([template]) +
    +
    +- Creates a validation function. The return function checks if any of a given predicates return +- truthy when invoked with the arguments it receives. ++ Generates an unique ID for the current session. If given a string template, it ++ will use this template for output formatting. Otherwise, if template is a function, it ++ will evaluate template (id). ++
    Aliased as uid. + + +

    Parameters:

    +
      +-
    • ... +- a list of predicate functions ++
    • template ++ either a string or a function template to format the ID ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a new function ++ value an ID +
    + + +-

    See also:

    +- + + +
    +
    +- +- overArgs (f, ...) ++ ++ wrap (f, wrapper) +
    +
    +- Creates a function that invokes f with its arguments transformed. 1rst arguments will be passed to +- the 1rst transform, 2nd arg to the 2nd transform, etc. Remaining arguments will not be transformed. ++ Wraps f inside of the wrapper function. It passes f as the first argument to wrapper. ++ This allows the wrapper to execute code before and after f runs, ++ adjust the arguments, and execute it conditionally. + + +

    Parameters:

    +
      +
    • f +- a function ++ a function to be wrapped, prototyped as f (...) +
    • +-
    • ... +- a list of transforms funcs prototyped as f (v) ++
    • wrapper ++ a wrapper function, prototyped as wrapper (f, ...) +
    • +
    + +

    Returns:

    +
      + +- the result of running f with its transformed arguments ++ the results +
    + + +-

    See also:

    +- + + +
    ++
    ++

    Object functions

    ++ ++
    +
    +- +- partial (f, ...) ++ ++ chain (value) +
    +
    +- Partially apply a function by filling in any number of its arguments.
    +- One may pass a string '_' as a placeholder in the list of arguments to specify an argument +- that should not be pre-filled, but left open to be supplied at call-time. ++ Returns a wrapped object. Calling library functions as methods on this object ++ will continue to return wrapped objects until obj:value is used. Can be aliased as M(value). + + +

    Parameters:

    +
      +-
    • f +- a function +-
    • +-
    • ... +- a list of partial arguments to f ++
    • value ++ a value to be wrapped +
    • +
    + +

    Returns:

    +
      + +- a new version of function f having some of it original arguments filled +-
    +- +- +-

    See also:

    +- ++ a wrapped object ++ ++ ++ + + +
    +
    +- +- partialRight (f, ...) ++ ++ clone (obj[, shallow]) +
    +
    +- Similar to partial, but from the right. ++ Clones a given object properties. If shallow is passed will also clone nested array properties. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • obj ++ an object +
    • +-
    • ... +- a list of partial arguments to f ++
    • shallow ++ whether or not nested array-properties should be cloned, defaults to false. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- a new version of function f having some of it original arguments filled ++ a copy of the passed-in object +
    + + +-

    See also:

    +- + + +
    +
    +- +- curry (f[, n_args]) ++ ++ extend (destObj, ...) +
    +
    +- Curries a function. If the given function f takes multiple arguments, it returns another version of +- f that takes a single argument (the first of the arguments to the original function) and returns a new +- function that takes the remainder of the arguments and returns the result. ++ Extends an object properties. It copies the properties of extra passed-in objects ++ into the destination object, and returns the destination object. The last objects ++ will override properties of the same name. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • destObj ++ a destination object +
    • +-
    • n_args +- the number of arguments expected for f. Defaults to 2. +- (optional) ++
    • ... ++ a list of objects +
    • +
    + +

    Returns:

    +
      + +- a curried version of f ++ the destination object extended +
    + + +-

    See also:

    +- + + +
    +
    +- +- time (f[, ...]) ++ ++ flattenPath (obj, ...) +
    +
    +- Returns the execution time of f (…) and its returned values. ++ Flattens object under property path onto provided object.
    ++ It is similar to spreadPath, but preserves object under the property path. + + +

    Parameters:

    +
      +-
    • f +- a function ++
    • obj ++ an object +
    • +
    • ... +- optional args to f +- (optional) ++ a property path given as a vararg list +
    • +
    + +

    Returns:

    +
      + +- the execution time and the results of f (…) ++ the passed-in object with changes +
    + + ++

    See also:

    ++ + + +
    +-
    +-

    Object functions

    +- +-
    +
    +- +- keys (obj) ++ ++ functions ([obj]) +
    +
    +- Returns the keys of the object properties. ++ Returns a sorted list of all methods names found in an object. If the given object ++ has a metatable implementing an __index field pointing to another table, will also recurse on this ++ table if recurseMt is provided. If obj is omitted, it defaults to the library functions. ++
    Aliased as methods. + + +

    Parameters:

    +
      +
    • obj +- an object ++ an object. Defaults to Moses library functions. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- an array ++ an array-list of methods names +
    + + +@@ -4181,11 +6351,11 @@ + +
    +
    +- +- values (obj) ++ ++ has (obj, key) +
    +
    +- Returns the values of the object properties. ++ Checks if a given object implements a property. + + +

    Parameters:

    +@@ -4193,12 +6363,15 @@ +
  • obj + an object +
  • ++
  • key ++ a key property to be checked ++
  • + + +

    Returns:

    +
      + +- an array ++ true or false +
    + + +@@ -4206,100 +6379,94 @@ + +
    +
    +- +- kvpairs (obj) ++ ++ import ([context[, noConflict]]) +
    +
    +- Converts keys and values a an array-list of [k, v]. ++ Imports all library functions into a context. + + +

    Parameters:

    +
      +-
    • obj +- an object ++
    • context ++ a context. Defaults to _ENV or _G`` (current environment). ++ (optional) ++
    • ++
    • noConflict ++ if supplied, will not import conflicting functions in the destination context. ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- an array list of key-values pairs ++ the passed-in context +
    + + +-

    See also:

    +- + + +
    +
    +- +- toObj (kvpairs) ++ ++ invert (obj) +
    +
    +- Converts an array list of kvpairs to an object. Keys are taken +- from the 1rst column in the kvpairs sequence, associated with values in the 2nd +- column ++ Swaps keys with values. Produces a new object where previous keys are now values, ++ while previous values are now keys. ++
    Aliased as mirror + + +

    Parameters:

    +
      +-
    • kvpairs +- an array-list of kvpairs ++
    • obj ++ a given object +
    • +
    + +

    Returns:

    +
      + +- an object ++ a new object +
    + + +-

    See also:

    +- + + +
    +
    +- +- property (key) ++ ++ isArray (obj) +
    +
    +- Returns a function that will return the key property of any passed-in object. ++ Checks if the given argument is an array. Assumes obj is an array ++ if is a table with consecutive integer keys starting at 1. + + +

    Parameters:

    +
      +-
    • key +- a key property name ++
    • obj ++ an object +
    • +
    + +

    Returns:

    +
      + +- a function which should accept an object as argument ++ true or false +
    + + +-

    See also:

    +- + + +
    +
    +- +- propertyOf (obj) ++ ++ isBoolean (obj) +
    +
    +- Returns a function which will return the value of an object property. ++ Checks if the given argument is a boolean. + + +

    Parameters:

    +@@ -4312,36 +6479,33 @@ +

    Returns:

    +
      + +- a function which should accept a key property argument ++ true or false +
    + + +-

    See also:

    +- + + +
    +
    +- +- toBoolean (value) ++ ++ isCallable (obj) +
    +
    +- Converts any given value to a boolean ++ Checks if the given argument is callable. Assumes obj is callable if ++ it is either a function or a table having a metatable implementing __call metamethod. + + +

    Parameters:

    +
      +-
    • value +- a value. Can be of any type ++
    • obj ++ an object +
    • +
    + +

    Returns:

    +
      + +- true if value is true, false otherwise (false or nil). ++ true or false +
    + + +@@ -4349,29 +6513,27 @@ + +
    +
    +- +- extend (destObj, ...) ++ ++ isEmpty ([obj]) +
    +
    +- Extends an object properties. It copies the properties of extra passed-in objects +- into the destination object, and returns the destination object. The last objects +- will override properties of the same name. ++ Checks if the given pbject is empty. If obj is a string, will return true ++ if #obj == 0. Otherwise, if obj is a table, will return whether or not this table ++ is empty. If obj is nil, it will return true. + + +

    Parameters:

    +
      +-
    • destObj +- a destination object +-
    • +-
    • ... +- a list of objects ++
    • obj ++ an object ++ (optional) +
    • +
    + +

    Returns:

    +
      + +- the destination object extended ++ true or false +
    + + +@@ -4379,20 +6541,27 @@ + +
    +
    +- +- functions ([obj]) ++ ++ isEqual (objA, objB[, useMt]) +
    +
    +- Returns a sorted list of all methods names found in an object. If the given object +- has a metatable implementing an __index field pointing to another table, will also recurse on this +- table if recurseMt is provided. If obj is omitted, it defaults to the library functions. +-
    Aliased as methods. ++ Performs a deep comparison test between two objects. Can compare strings, functions ++ (by reference), nil, booleans. Compares tables by reference or by values. If useMt ++ is passed, the equality operator == will be used if one of the given objects has a ++ metatable implementing __eq. ++
    Aliased as M.compare, M.matches + + +

    Parameters:

    +
      +-
    • obj +- an object. Defaults to Moses library functions. ++
    • objA ++ an object ++
    • ++
    • objB ++ another object ++
    • ++
    • useMt ++ whether or not __eq should be used, defaults to false. + (optional) +
    • +
    +@@ -4400,19 +6569,23 @@ +

    Returns:

    +
      + +- an array-list of methods names ++ true or false +
    + + ++

    See also:

    ++ + + +
    +
    +- +- clone (obj[, shallow]) ++ ++ isFinite (obj) +
    +
    +- Clones a given object properties. If shallow is passed will also clone nested array properties. ++ Checks if the given argument is a finite number. + + +

    Parameters:

    +@@ -4420,16 +6593,12 @@ +
  • obj + an object +
  • +-
  • shallow +- whether or not nested array-properties should be cloned, defaults to false. +- (optional) +-
  • + + +

    Returns:

    +
      + +- a copy of the passed-in object ++ true or false +
    + + +@@ -4437,13 +6606,11 @@ + +
    +
    +- +- tap (obj, f[, ...]) ++ ++ isFunction (obj) +
    +
    +- Invokes interceptor with the object, and then returns object. +- The primary purpose of this method is to “tap into” a method chain, in order to perform operations +- on intermediate results within the chain. ++ Checks if the given argument is a function. + + +

    Parameters:

    +@@ -4451,19 +6618,12 @@ +
  • obj + an object +
  • +-
  • f +- an interceptor function, should be prototyped as f (obj, …) +-
  • +-
  • ... +- args to be passed to f +- (optional) +-
  • + + +

    Returns:

    +
      + +- the passed-in object ++ true or false +
    + + +@@ -4471,11 +6631,11 @@ + +
    +
    +- +- has (obj, key) ++ ++ isInteger (obj) +
    +
    +- Checks if a given object implements a property. ++ Checks if the given argument is an integer. + + +

    Parameters:

    +@@ -4483,9 +6643,6 @@ +
  • obj + an object +
  • +-
  • key +- a key property to be checked +-
  • + + +

    Returns:

    +@@ -4499,12 +6656,11 @@ + +
    +
    +- +- pick (obj, ...) ++ ++ isIterable (obj) +
    +
    +- Returns an object copy having white-listed properties. +-
    Aliased as choose. ++ Checks if the given object is iterable with pairs (or ipairs). + + +

    Parameters:

    +@@ -4512,15 +6668,12 @@ +
  • obj + an object +
  • +-
  • ... +- a variable number of string keys +-
  • + + +

    Returns:

    +
      + +- the filtered object ++ true if the object can be iterated with pairs (or ipairs), false otherwise +
    + + +@@ -4528,12 +6681,11 @@ + +
    +
    +- +- omit (obj, ...) ++ ++ isNaN (obj) +
    +
    +- Returns an object copy without black-listed properties. +-
    Aliased as drop. ++ Checks if the given argument is NaN (see Not-A-Number). + + +

    Parameters:

    +@@ -4541,28 +6693,28 @@ +
  • obj + an object +
  • +-
  • ... +- a variable number of string keys +-
  • + + +

    Returns:

    +
      + +- the filtered object ++ true or false +
    + + ++

    See also:

    ++ + + +
    +
    +- +- template (obj[, template]) ++ ++ isNil (obj) +
    +
    +- Applies a template to an object, preserving non-nil properties. +-
    Aliased as defaults. ++ Checks if the given argument is nil. + + +

    Parameters:

    +@@ -4570,16 +6722,12 @@ +
  • obj + an object +
  • +-
  • template +- a template object. Defaults to an empty table {}. +- (optional) +-
  • + + +

    Returns:

    +
      + +- the passed-in object filled ++ true or false +
    + + +@@ -4587,29 +6735,18 @@ + +
    +
    +- +- isEqual (objA, objB[, useMt]) ++ ++ isNumber (obj) +
    +
    +- Performs a deep comparison test between two objects. Can compare strings, functions +- (by reference), nil, booleans. Compares tables by reference or by values. If useMt +- is passed, the equality operator == will be used if one of the given objects has a +- metatable implementing __eq. +-
    Aliased as _.compare ++ Checks if the given argument is a number. + + +

    Parameters:

    +
      +-
    • objA ++
    • obj + an object +
    • +-
    • objB +- another object +-
    • +-
    • useMt +- whether or not __eq should be used, defaults to false. +- (optional) +-
    • +
    + +

    Returns:

    +@@ -4619,16 +6756,19 @@ + + + ++

    See also:

    ++ + + +
    +
    +- +- result (obj, method[, ...]) ++ ++ isString (obj) +
    +
    +- Invokes an object method. It passes the object itself as the first argument. if method is not +- callable, will return obj[method]. ++ Checks if the given argument is a string. + + +

    Parameters:

    +@@ -4636,19 +6776,12 @@ +
  • obj + an object +
  • +-
  • method +- a string key to index in object obj. +-
  • +-
  • ... +- Optional args to be passed to method +- (optional) +-
  • + + +

    Returns:

    +
      + +- the returned value of method (obj, …) call ++ true or false +
    + + +@@ -4681,12 +6814,11 @@ + +
    +
    +- +- isCallable (obj) ++ ++ keys (obj) +
    +
    +- Checks if the given argument is callable. Assumes obj is callable if +- it is either a function or a table having a metatable implementing __call metamethod. ++ Returns the keys of the object properties. + + +

    Parameters:

    +@@ -4699,7 +6831,7 @@ +

    Returns:

    +
      + +- true or false ++ an array +
    + + +@@ -4707,12 +6839,11 @@ + +
    +
    +- +- isArray (obj) ++ ++ kvpairs (obj) +
    +
    +- Checks if the given argument is an array. Assumes obj is an array +- if is a table with consecutive integer keys starting at 1. ++ Converts key-value pairs to an array-list of [k, v] pairs. + + +

    Parameters:

    +@@ -4725,19 +6856,43 @@ +

    Returns:

    +
      + +- true or false ++ an array list of key-value pairs +
    + + ++

    See also:

    ++ + + +
    +
    +- +- isIterable (obj) ++ ++ obj:value () +
    +
    +- Checks if the given object is iterable with pairs (or ipairs). ++ Extracts the value of a wrapped object. Must be called on an chained object (see chain). ++ ++ ++ ++

    Returns:

    ++
      ++ ++ the value previously wrapped ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ omit (obj, ...) ++
    ++
    ++ Returns an object copy without black-listed properties. ++
    Aliased as drop. + + +

    Parameters:

    +@@ -4745,12 +6900,15 @@ +
  • obj + an object +
  • ++
  • ... ++ a variable number of string keys ++
  • + + +

    Returns:

    +
      + +- true if the object can be iterated with pairs (or ipairs), false otherwise ++ the filtered object +
    + + +@@ -4758,27 +6916,28 @@ + +
    +
    +- +- isEmpty ([obj]) ++ ++ path (obj, ...) +
    +
    +- Checks if the given pbject is empty. If obj is a string, will return true +- if #obj == 0. Otherwise, if obj is a table, will return whether or not this table +- is empty. If obj is nil, it will return true. ++ Returns the value at a given path in an object.
    ++ Path is given as a vararg list of keys. + + +

    Parameters:

    +
      +
    • obj + an object +- (optional) ++
    • ++
    • ... ++ a vararg list of keys +
    • +
    + +

    Returns:

    +
      + +- true or false ++ a value or nil +
    + + +@@ -4786,11 +6945,12 @@ + +
    +
    +- +- isString (obj) ++ ++ pick (obj, ...) +
    +
    +- Checks if the given argument is a string. ++ Returns an object copy having white-listed properties. ++
    Aliased as choose. + + +

    Parameters:

    +@@ -4798,12 +6958,15 @@ +
  • obj + an object +
  • ++
  • ... ++ a variable number of string keys ++
  • + + +

    Returns:

    +
      + +- true or false ++ the filtered object +
    + + +@@ -4811,36 +6974,40 @@ + +
    +
    +- +- isFunction (obj) ++ ++ property (key) +
    +
    +- Checks if the given argument is a function. ++ Returns a function that will return the key property of any passed-in object. + + +

    Parameters:

    +
      +-
    • obj +- an object ++
    • key ++ a key property name +
    • +
    + +

    Returns:

    +
      + +- true or false ++ a function which should accept an object as argument +
    + + ++

    See also:

    ++ + + +
    +
    +- +- isNil (obj) ++ ++ propertyOf (obj) +
    +
    +- Checks if the given argument is nil. ++ Returns a function which will return the value of an object property. + + +

    Parameters:

    +@@ -4853,19 +7020,24 @@ +

    Returns:

    +
      + +- true or false ++ a function which should accept a key property argument +
    + + ++

    See also:

    ++ + + +
    +
    +- +- isNumber (obj) ++ ++ result (obj, method) +
    +
    +- Checks if the given argument is a number. ++ Invokes an object method. It passes the object itself as the first argument. if method is not ++ callable, will return obj[method]. + + +

    Parameters:

    +@@ -4873,28 +7045,28 @@ +
  • obj + an object +
  • ++
  • method ++ a string key to index in object obj. ++
  • + + +

    Returns:

    +
      + +- true or false ++ the returned value of method (obj) call +
    + + +-

    See also:

    +- + + +
    +
    +- +- isNaN (obj) ++ ++ spreadPath (obj, ...) +
    +
    +- Checks if the given argument is NaN (see Not-A-Number). ++ Spreads object under property path onto provided object.
    ++ It is similar to flattenPath, but removes object under the property path. + + +

    Parameters:

    +@@ -4902,28 +7074,33 @@ +
  • obj + an object +
  • ++
  • ... ++ a property path given as a vararg list ++
  • + + +

    Returns:

    +
      + +- true or false ++ the passed-in object with changes +
    + + +

    See also:

    + + + +
    +
    +- +- isFinite (obj) ++ ++ tap (obj, f) +
    +
    +- Checks if the given argument is a finite number. ++ Invokes interceptor with the object, and then returns object. ++ The primary purpose of this method is to "tap into" a method chain, in order to perform operations ++ on intermediate results within the chain. + + +

    Parameters:

    +@@ -4931,12 +7108,15 @@ +
  • obj + an object +
  • ++
  • f ++ an interceptor function, should be prototyped as f (obj) ++
  • + + +

    Returns:

    +
      + +- true or false ++ the passed-in object +
    + + +@@ -4944,11 +7124,12 @@ + +
    +
    +- +- isBoolean (obj) ++ ++ template (obj[, template]) +
    +
    +- Checks if the given argument is a boolean. ++ Applies a template to an object, preserving non-nil properties. ++
    Aliased as defaults. + + +

    Parameters:

    +@@ -4956,12 +7137,16 @@ +
  • obj + an object +
  • ++
  • template ++ a template object. If nil, leaves obj untouched. ++ (optional) ++
  • + + +

    Returns:

    +
      + +- true or false ++ the passed-in object filled +
    + + +@@ -4969,24 +7154,24 @@ + +
    +
    +- +- isInteger (obj) ++ ++ toBoolean (value) +
    +
    +- Checks if the given argument is an integer. ++ Converts any given value to a boolean + + +

    Parameters:

    +
      +-
    • obj +- an object ++
    • value ++ a value. Can be of any type +
    • +
    + +

    Returns:

    +
      + +- true or false ++ true if value is true, false otherwise (false or nil). +
    + + +@@ -4994,44 +7179,56 @@ + +
    +
    +- +- chain (value) ++ ++ toObj (kvpairs) +
    +
    +- Returns a wrapped object. Calling library functions as methods on this object +- will continue to return wrapped objects until obj:value is used. Can be aliased as _(value). ++ Converts an array list of [k,v] pairs to an object. Keys are taken ++ from the 1rst column in the [k,v] pairs sequence, associated with values in the 2nd ++ column. + + +

    Parameters:

    +
      +-
    • value +- a value to be wrapped ++
    • kvpairs ++ an array-list of [k,v] pairs +
    • +
    + +

    Returns:

    +
      + +- a wrapped object ++ an object +
    + + ++

    See also:

    ++ + + +
    +
    +- +- obj:value () ++ ++ type (obj) +
    +
    +- Extracts the value of a wrapped object. Must be called on an chained object (see chain). ++ Extends Lua's type function. It returns the type of the given object and also recognises ++ file userdata + + ++

    Parameters:

    ++
      ++
    • obj ++ an object ++
    • ++
    + +

    Returns:

    +
      + +- the value previously wrapped ++ the given object type +
    + + +@@ -5039,29 +7236,24 @@ + +
    +
    +- +- import ([context[, noConflict]]) ++ ++ values (obj) +
    +
    +- Imports all library functions into a context. ++ Returns the values of the object properties. + + +

    Parameters:

    +
      +-
    • context +- a context. Defaults to _G (global environment) when not given. +- (optional) +-
    • +-
    • noConflict +- if supplied, will not import functions having a key existing in the destination context. +- (optional) ++
    • obj ++ an object +
    • +
    + +

    Returns:

    +
      + +- the passed-in context ++ an array of values +
    + + +@@ -5075,7 +7267,7 @@ +
    +
    + generated by LDoc 1.4.6 +-Last updated 2017-04-27 15:26:55 ++Last updated 2019-04-01 23:55:17 +
    +
    + +diff --git a/extra/moses/doc/ldoc.css b/extra/moses/doc/ldoc.css +deleted file mode 100644 +index 52c4ad2..0000000 +--- a/extra/moses/doc/ldoc.css ++++ /dev/null +@@ -1,303 +0,0 @@ +-/* BEGIN RESET +- +-Copyright (c) 2010, Yahoo! Inc. All rights reserved. +-Code licensed under the BSD License: +-http://developer.yahoo.com/yui/license.html +-version: 2.8.2r1 +-*/ +-html { +- color: #000; +- background: #FFF; +-} +-body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { +- margin: 0; +- padding: 0; +-} +-table { +- border-collapse: collapse; +- border-spacing: 0; +-} +-fieldset,img { +- border: 0; +-} +-address,caption,cite,code,dfn,em,strong,th,var,optgroup { +- font-style: inherit; +- font-weight: inherit; +-} +-del,ins { +- text-decoration: none; +-} +-li { +- margin-left: 20px; +-} +-caption,th { +- text-align: left; +-} +-h1,h2,h3,h4,h5,h6 { +- font-size: 100%; +- font-weight: bold; +-} +-q:before,q:after { +- content: ''; +-} +-abbr,acronym { +- border: 0; +- font-variant: normal; +-} +-sup { +- vertical-align: baseline; +-} +-sub { +- vertical-align: baseline; +-} +-legend { +- color: #000; +-} +-input,button,textarea,select,optgroup,option { +- font-family: inherit; +- font-size: inherit; +- font-style: inherit; +- font-weight: inherit; +-} +-input,button,textarea,select {*font-size:100%; +-} +-/* END RESET */ +- +-body { +- margin-left: 1em; +- margin-right: 1em; +- font-family: arial, helvetica, geneva, sans-serif; +- background-color: #ffffff; margin: 0px; +-} +- +-code, tt { font-family: monospace; font-size: 1.1em; } +-span.parameter { font-family:monospace; } +-span.parameter:after { content:":"; } +-span.types:before { content:"("; } +-span.types:after { content:")"; } +-.type { font-weight: bold; font-style:italic } +- +-body, p, td, th { font-size: .95em; line-height: 1.2em;} +- +-p, ul { margin: 10px 0 0 0px;} +- +-strong { font-weight: bold;} +- +-em { font-style: italic;} +- +-h1 { +- font-size: 1.5em; +- margin: 20px 0 20px 0; +-} +-h2, h3, h4 { margin: 15px 0 10px 0; } +-h2 { font-size: 1.25em; } +-h3 { font-size: 1.15em; } +-h4 { font-size: 1.06em; } +- +-a:link { font-weight: bold; color: #004080; text-decoration: none; } +-a:visited { font-weight: bold; color: #006699; text-decoration: none; } +-a:link:hover { text-decoration: underline; } +- +-hr { +- color:#cccccc; +- background: #00007f; +- height: 1px; +-} +- +-blockquote { margin-left: 3em; } +- +-ul { list-style-type: disc; } +- +-p.name { +- font-family: "Andale Mono", monospace; +- padding-top: 1em; +-} +- +-pre { +- background-color: rgb(245, 245, 245); +- border: 1px solid #C0C0C0; /* silver */ +- padding: 10px; +- margin: 10px 0 10px 0; +- overflow: auto; +- font-family: "Andale Mono", monospace; +-} +- +-pre.example { +- font-size: .85em; +-} +- +-table.index { border: 1px #00007f; } +-table.index td { text-align: left; vertical-align: top; } +- +-#container { +- margin-left: 1em; +- margin-right: 1em; +- background-color: #f0f0f0; +-} +- +-#product { +- text-align: center; +- border-bottom: 1px solid #cccccc; +- background-color: #ffffff; +-} +- +-#product big { +- font-size: 2em; +-} +- +-#main { +- background-color: #f0f0f0; +- border-left: 2px solid #cccccc; +-} +- +-#navigation { +- float: left; +- width: 14em; +- vertical-align: top; +- background-color: #f0f0f0; +- overflow: visible; +-} +- +-#navigation h2 { +- background-color:#e7e7e7; +- font-size:1.1em; +- color:#000000; +- text-align: left; +- padding:0.2em; +- border-top:1px solid #dddddd; +- border-bottom:1px solid #dddddd; +-} +- +-#navigation ul +-{ +- font-size:1em; +- list-style-type: none; +- margin: 1px 1px 10px 1px; +-} +- +-#navigation li { +- text-indent: -1em; +- display: block; +- margin: 3px 0px 0px 22px; +-} +- +-#navigation li li a { +- margin: 0px 3px 0px -1em; +-} +- +-#content { +- margin-left: 14em; +- padding: 1em; +- width: 700px; +- border-left: 2px solid #cccccc; +- border-right: 2px solid #cccccc; +- background-color: #ffffff; +-} +- +-#about { +- clear: both; +- padding: 5px; +- border-top: 2px solid #cccccc; +- background-color: #ffffff; +-} +- +-@media print { +- body { +- font: 12pt "Times New Roman", "TimeNR", Times, serif; +- } +- a { font-weight: bold; color: #004080; text-decoration: underline; } +- +- #main { +- background-color: #ffffff; +- border-left: 0px; +- } +- +- #container { +- margin-left: 2%; +- margin-right: 2%; +- background-color: #ffffff; +- } +- +- #content { +- padding: 1em; +- background-color: #ffffff; +- } +- +- #navigation { +- display: none; +- } +- pre.example { +- font-family: "Andale Mono", monospace; +- font-size: 10pt; +- page-break-inside: avoid; +- } +-} +- +-table.module_list { +- border-width: 1px; +- border-style: solid; +- border-color: #cccccc; +- border-collapse: collapse; +-} +-table.module_list td { +- border-width: 1px; +- padding: 3px; +- border-style: solid; +- border-color: #cccccc; +-} +-table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } +-table.module_list td.summary { width: 100%; } +- +- +-table.function_list { +- border-width: 1px; +- border-style: solid; +- border-color: #cccccc; +- border-collapse: collapse; +-} +-table.function_list td { +- border-width: 1px; +- padding: 3px; +- border-style: solid; +- border-color: #cccccc; +-} +-table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } +-table.function_list td.summary { width: 100%; } +- +-ul.nowrap { +- overflow:auto; +- white-space:nowrap; +-} +- +-dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} +-dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} +-dl.table h3, dl.function h3 {font-size: .95em;} +- +-/* stop sublists from having initial vertical space */ +-ul ul { margin-top: 0px; } +-ol ul { margin-top: 0px; } +-ol ol { margin-top: 0px; } +-ul ol { margin-top: 0px; } +- +-/* make the target distinct; helps when we're navigating to a function */ +-a:target + * { +- background-color: #FF9; +-} +- +- +-/* styles for prettification of source */ +-pre .comment { color: #558817; } +-pre .constant { color: #a8660d; } +-pre .escape { color: #844631; } +-pre .keyword { color: #aa5050; font-weight: bold; } +-pre .library { color: #0e7c6b; } +-pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } +-pre .string { color: #8080ff; } +-pre .number { color: #f8660d; } +-pre .operator { color: #2239a8; font-weight: bold; } +-pre .preprocessor, pre .prepro { color: #a33243; } +-pre .global { color: #800080; } +-pre .user-keyword { color: #800080; } +-pre .prompt { color: #558817; } +-pre .url { color: #272fc2; text-decoration: underline; } +- +diff --git a/extra/moses/doc/manual/tutorial.md.html b/extra/moses/doc/manual/tutorial.md.html +new file mode 100644 +index 0000000..0314339 +--- /dev/null ++++ b/extra/moses/doc/manual/tutorial.md.html +@@ -0,0 +1,3295 @@ ++ ++ ++ ++ ++ Moses documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ Moses: a utility-belt library for functional programming in Lua

    ++ ++

    Moses is a Lua utility library which provides support for functional programming. ++It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more. ++
    ++

    ++ ++

    Sections

    ++ ++ ++ ++ ++

    Drop the file moses.lua into your project and add it to your code with the require function:

    ++ ++ ++
    ++local M = require ("moses")
    ++
    ++ ++ ++

    Moses provides a large set of functions that can be classified into four categories:

    ++ ++ ++ ++

    [⬆]

    ++ ++

    ++

    Table functions

    ++ ++

    clear (t)

    ++ ++

    Clears a table. All its values becomes nil. Returns the passed-in table.

    ++ ++ ++
    ++M.clear({1,2,'hello',true}) -- => {}
    ++
    ++ ++ ++

    each (t, f)

    ++

    *Aliases: forEach*.

    ++ ++

    Iterates over each value-key pair in the passed-in table.

    ++ ++ ++
    ++M.each({4,2,1},print)
    ++
    ++-- => 4 1
    ++-- => 2 2
    ++-- => 1 3
    ++
    ++ ++ ++

    The table can be map-like (both array part and hash part).

    ++ ++ ++
    ++M.each({one = 1, two = 2, three = 3},print)
    ++
    ++-- => 1 one
    ++-- => 2 two
    ++-- => 3 three
    ++
    ++ ++ ++

    Can index and assign in an outer table or in the passed-in table:

    ++ ++ ++
    ++t = {'a','b','c'}
    ++M.each(t,function(v,i)
    ++  t[i] = v:rep(2)
    ++  print(t[i])
    ++end)
    ++
    ++-- => aa
    ++-- => bb
    ++-- => cc
    ++
    ++ ++ ++

    eachi (t, f)

    ++

    *Aliases: forEachi*.

    ++ ++

    Iterates only on integer keys in an array table. It returns value-key pairs.

    ++ ++ ++
    ++M.eachi({4,2,1},print)
    ++
    ++-- => 4 1
    ++-- => 2 2
    ++-- => 1 3
    ++
    ++ ++ ++

    The given array can be sparse, or even have a hash-like part.

    ++ ++ ++
    ++local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5}
    ++M.eachi(t,print)
    ++
    ++-- => 6 -1
    ++-- => 1 0
    ++-- => 3 1
    ++-- => 5 2
    ++
    ++ ++ ++

    at (t, ...)

    ++ ++

    Collects values at given keys and returns them in an array.

    ++ ++ ++
    ++local t = {4,5,6}
    ++M.at(t,1,3) -- => "{4,6}"
    ++
    ++local t = {a = 4, bb = true, ccc = false}
    ++M.at(t,'a', 'ccc') -- => "{4, false}"
    ++
    ++ ++ ++

    adjust (t, key, f)

    ++ ++

    Adjusts the value at a given key using a function or a value. In case f is a function, it should be prototyped f(v). ++It does not mutate the given table, but rather returns a new array.

    ++ ++ ++
    ++local t = {1,2,3}
    ++M.adjust(t, 2, math.sin) -- => {1, 0.90929, 3}
    ++
    ++local v = {x = 1}
    ++ M.adjust(t, 'x', 4) -- => {x = 4}
    ++
    ++ ++ ++

    In case the given key does not exist in t, it throws an error.

    ++ ++

    count (t [, val])

    ++ ++

    Counts the number of occurences of a given value in a table.

    ++ ++ ++
    ++M.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2
    ++M.count({1,1,2,3,3,3,2,4,3,2},2) -- => 3
    ++M.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4
    ++M.count({false, false, true},false) -- => 2
    ++M.count({false, false, true},true) -- => 1
    ++
    ++ ++ ++

    Returns the size of the list in case no value was provided.

    ++ ++ ++
    ++M.count({1,1,2,3,3}) -- => 5
    ++
    ++ ++ ++

    countf (t, f)

    ++ ++

    Counts the number of values passing an iterator test.

    ++ ++ ++
    ++M.countf({1,2,3,4,5,6}, function(v)
    ++  return v%2==0
    ++end) -- => 3
    ++
    ++M.countf({print, pairs, os, assert, ipairs}, function(v)
    ++  return type(v)=='function'
    ++end) -- => 4
    ++
    ++ ++ ++

    allEqual (t [, comp])

    ++

    *Aliases: alleq*.

    ++ ++

    Checks if all values in a collection are equal. Uses M.isEqual by default to compare values.

    ++ ++ ++
    ++M.allEqual({1,1,1,1,1}, comp) -- => true
    ++M.allEqual({1,1,2,1,1}, comp) -- => false
    ++
    ++local t1 = {1, 2, {3}}
    ++local t2 = {1, 2, {3}}
    ++M.allEqual({t1, t2}) -- => true
    ++
    ++ ++ ++

    Can take an optional comp function which will be used to compare values.

    ++ ++ ++
    ++local t1 = {x = 1, y = 0}
    ++local t2 = {x = 1, y = 0}
    ++local t3 = {x = 1, y = 2}
    ++local t4 = {x = 1, y = 2}
    ++local function compx(a, b) return a.x == b.x end
    ++local function compy(a, b) return a.y == b.y end
    ++
    ++M.allEqual({t1, t2}, compx) -- => true
    ++M.allEqual({t1, t2}, compy) -- => true
    ++M.allEqual({t3, t4}, compx) -- => true
    ++M.allEqual({t3, t4}, compy) -- => true
    ++M.allEqual({t1, t2, t3, t4}, compx) -- => true
    ++M.allEqual({t1, t2, t3, t4}, compy) -- => false
    ++
    ++ ++ ++

    cycle (t [, n = 1])

    ++

    *Aliases: loop*.

    ++ ++

    Returns a function which iterates on each value-key pair in a given table (similarly to M.each), except that it restarts iterating again n times. ++If n is not provided, it defaults to 1.

    ++ ++ ++
    ++local t = {'a','b','c'}
    ++for v in M.cycle(t, 2) do
    ++  print(v)
    ++end
    ++
    ++-- => 'a'
    ++-- => 'b'
    ++-- => 'c'
    ++-- => 'a'
    ++-- => 'b'
    ++-- => 'c'
    ++
    ++ ++ ++

    Supports array-like tables and map-like tables.

    ++ ++ ++
    ++local t = {x = 1, y = 2, z = 3}
    ++for v in M.cycle(t) do
    ++  print(v)
    ++end
    ++
    ++-- => 2
    ++-- => 1
    ++-- => 3
    ++
    ++ ++ ++

    map (t, f)

    ++

    *Aliases: collect*.

    ++ ++

    Executes a function on each value in a given array.

    ++ ++ ++
    ++M.map({1,2,3},function(v)
    ++  return v+10
    ++end) -- => "{11,12,13}"
    ++
    ++ ++ ++ ++
    ++M.map({a = 1, b = 2},function(v, k)
    ++  return k..v
    ++end) -- => "{a = 'a1', b = 'b2'}"
    ++
    ++ ++ ++

    It also maps both keys and values.

    ++ ++ ++
    ++M.map({a = 1, b = 2},function(v, k)
    ++  return k..k, v*2
    ++end) -- => "{aa = 2, bb = 4}"
    ++
    ++ ++ ++

    mapi (t, f)

    ++ ++

    Executes a function on each value in a given array.

    ++ ++ ++
    ++M.mapi({1,2,3},function(v)
    ++  return v+10
    ++end) -- => "{11,12,13}"
    ++
    ++ ++ ++

    It only works for the array-part of the given table.

    ++ ++ ++
    ++M.map({a = 1, 2, 3, 4, 5},function(v, k)
    ++  return k..v
    ++end) -- => "{'12','23','34','45'}"
    ++
    ++ ++ ++

    reduce (t, f [, state = next(t)])

    ++

    *Aliases: inject, foldl*.

    ++ ++

    Can sum all values in a table. In case state is not provided, it defaults to the first value in the given table t.

    ++ ++ ++
    ++local function add(a,b) return a+b end
    ++M.reduce({1,2,3,4},add) -- => 10
    ++
    ++ ++ ++

    Or concatenates all values.

    ++ ++ ++
    ++local function concat(a,b) return a..b end
    ++M.reduce({'a','b','c','d'},concat) -- => abcd   
    ++
    ++ ++ ++

    best (t, f)

    ++ ++

    Returns the best value passing a selector function. Acts as a special case of reduce, using the first value in t as ++an initial state. It thens folds the given table, testing each of its values v and selecting the value passing the ++call f(state,v) every time.

    ++ ++ ++
    ++local words = {'Lua', 'Programming', 'Language'}
    ++M.best(words, function(a,b) return #a > #b end) -- => 'Programming'
    ++M.best(words, function(a,b) return #a < #b end) -- => 'Lua'
    ++
    ++ ++ ++

    reduceBy (t, f, pred [, state = next(t)])

    ++ ++

    Reduces a table considering only values matching a predicate. ++For example,let us define a set of values.

    ++ ++ ++
    ++local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9}
    ++
    ++ ++ ++

    And a reduction function which will add up values.

    ++ ++ ++
    ++local function add(a,b) return a+b end
    ++
    ++ ++ ++

    We can also define some predicate functions.

    ++ ++ ++
    ++-- predicate for negative values
    ++local function neg(v) return v<=0 end
    ++
    ++-- predicate for positive values
    ++local function pos(v) return v>=0 end
    ++
    ++ ++ ++

    Then we can perform reduction considering only negative or positive values :

    ++ ++ ++
    ++M.reduceBy(val, add, neg) -- => -17
    ++M.reduceBy(val, add, pos) -- => 19
    ++
    ++ ++ ++

    An initial state can be passed in.

    ++ ++ ++
    ++M.reduceBy(val, add, neg, 17) -- => 0
    ++M.reduceBy(val, add, pos, -19) -- => 0
    ++
    ++ ++ ++

    reduceRight (t, f [, state = next(t)])

    ++

    *Aliases: injectr, foldr*.

    ++ ++

    Similar to M.reduce, but performs from right to left.

    ++ ++ ++
    ++local initial_state = 256
    ++local function div(a,b) return a/b end
    ++M.reduceRight({1,2,4,16},div,initial_state) -- => 2
    ++
    ++ ++ ++

    mapReduce (t, f [, state = next(t)])

    ++

    *Aliases: mapr*.

    ++ ++

    Reduces while saving intermediate states.

    ++ ++ ++
    ++local function concat(a,b) return a..b end
    ++M.mapReduce({'a','b','c'},concat) -- => "{'a', 'ab', 'abc'}"
    ++
    ++ ++ ++

    mapReduceRight (t, f [, state = next(t)])

    ++

    *Aliases: maprr*.

    ++ ++

    Reduces from right to left, while saving intermediate states.

    ++ ++ ++
    ++local function concat(a,b) return a..b end
    ++M.mapReduceRight({'a','b','c'},concat) -- => "{'c', 'cb', 'cba'}"
    ++
    ++ ++ ++

    include (t, value)

    ++

    *Aliases: any, some, contains*.

    ++ ++

    Looks for a value in a table.

    ++ ++ ++
    ++M.include({6,8,10,16,29},16) -- => true
    ++M.include({6,8,10,16,29},1) -- => false
    ++
    ++local complex_table = {18,{2,{3}}}
    ++local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    ++M.include(collection, complex_table) -- => true
    ++
    ++ ++ ++

    Handles iterator functions.

    ++ ++ ++
    ++local function isUpper(v) return v:upper()== v end
    ++M.include({'a','B','c'},isUpper) -- => true
    ++
    ++ ++ ++

    detect (t, value)

    ++ ++

    Returns the index of a value in a table.

    ++ ++ ++
    ++M.detect({6,8,10,16},8) -- => 2
    ++M.detect({nil,true,0,true,true},false) -- => nil
    ++
    ++local complex_table = {18,{2,6}}
    ++local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    ++M.detect(collection, complex_table) -- => 2
    ++
    ++ ++ ++

    Handles iterator functions.

    ++ ++ ++
    ++local function isUpper(v)
    ++  return v:upper()==v
    ++end
    ++M.detect({'a','B','c'},isUpper) -- => 2
    ++
    ++ ++ ++

    where (t, props)

    ++ ++

    Looks through a table and returns all the values that matches all of the key-value pairs listed in props.

    ++ ++ ++
    ++local items = {
    ++  {height = 10, weight = 8, price = 500},
    ++  {height = 10, weight = 15, price = 700},
    ++  {height = 15, weight = 15, price = 3000},
    ++  {height = 10, weight = 8, price = 3000},
    ++}
    ++M.where(items, {height = 10}) -- => {items[1], items[2], items[4]}
    ++M.where(items, {weight = 15}) -- => {items[2], items[3]}
    ++M.where(items, {prince = 3000}) -- => {items[3], items[4]}
    ++M.where(items, {height = 10, weight = 15, prince = 700}) -- => {items[2]}
    ++
    ++ ++ ++

    findWhere (t, props)

    ++ ++

    Looks through a table and returns the first value found that matches all of the key-value pairs listed in props.

    ++ ++ ++
    ++local a = {a = 1, b = 2, c = 3}
    ++local b = {a = 2, b = 3, d = 4}
    ++local c = {a = 3, b = 4, e = 5}
    ++M.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true
    ++
    ++ ++ ++

    select (t, f)

    ++

    *Aliases: filter*.

    ++ ++

    Collects values passing a validation test.

    ++ ++ ++
    ++local function isEven(v) return v%2==0 end
    ++local function isOdd(v) return v%2~=0 end
    ++
    ++M.select({1,2,3,4,5,6,7}, isEven) -- => "{2,4,6}"
    ++M.select({1,2,3,4,5,6,7}, isOdd) -- => "{1,3,5,7}"
    ++
    ++ ++ ++

    reject (t, f)

    ++

    *Aliases: reject*.

    ++ ++

    Removes all values failing (returning false or nil) a validation test:

    ++ ++ ++
    ++local function isEven(v) return v%2==0 end
    ++local function isOdd(v) return v%2~=0 end
    ++
    ++M.reject({1,2,3,4,5,6,7}, isEven) -- => "{1,3,5,7}"
    ++M.reject({1,2,3,4,5,6,7}, isOdd) -- => "{2,4,6}"
    ++
    ++ ++ ++

    all (t, f)

    ++

    *Aliases: every*.

    ++ ++

    Checks whether or not all elements pass a validation test.

    ++ ++ ++
    ++local function isEven(v) return v%2==0 end
    ++M.all({2,4,6}, isEven) -- => true
    ++
    ++ ++ ++

    invoke (t, method)

    ++ ++

    Invokes a given function on each value in a table.

    ++ ++ ++
    ++M.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}"
    ++
    ++ ++ ++

    Can reference the method of the same name in each value.

    ++ ++ ++
    ++local a, b, c, d = {id = 'a'}, {id = 'b'}, {id = 'c'}, {id = 'd'}
    ++local function call(self) return self.id end
    ++M.invoke({a,b,c,d},call) -- => "{'a','b','c','d'}"
    ++
    ++ ++ ++

    pluck (t, property)

    ++ ++

    Fetches all values indexed with specific key in a table of objects.

    ++ ++ ++
    ++local peoples = {
    ++  {name = 'John', age = 23},{name = 'Peter', age = 17},
    ++  {name = 'Steve', age = 15},{age = 33}}
    ++
    ++M.pluck(peoples,'age') -- => "{23,17,15,33}"
    ++M.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}"
    ++
    ++ ++ ++

    max (t [, transform])

    ++ ++

    Returns the maximum value in a collection.

    ++ ++ ++
    ++M.max {1,2,3} -- => 3
    ++M.max {'a','b','c'} -- => 'c'
    ++
    ++ ++ ++

    Can take an iterator function to extract a specific property.

    ++ ++ ++
    ++local peoples = {
    ++  {name = 'John', age = 23},{name = 'Peter', age = 17},
    ++  {name = 'Steve', age = 15},{age = 33}}
    ++M.max(peoples,function(people) return people.age end) -- => 33
    ++
    ++ ++ ++

    min (t [, transform])

    ++ ++

    Returns the minimum value in a collection.

    ++ ++ ++
    ++M.min {1,2,3} -- => 1
    ++M.min {'a','b','c'} -- => 'a'
    ++
    ++ ++ ++

    Can take an iterator function to extract a specific property.

    ++ ++ ++
    ++local peoples = {
    ++  {name = 'John', age = 23},{name = 'Peter', age = 17},
    ++  {name = 'Steve', age = 15},{age = 33}}
    ++M.min(peoples,function(people) return people.age end) -- => 15
    ++
    ++ ++ ++

    same (a, b)

    ++ ++

    Tests whether or not all values in each of the passed-in tables exists in both tables.

    ++ ++ ++
    ++local a = {'a','b','c','d'}
    ++local b = {'b','a','d','c'}
    ++M.same(a,b) -- => true
    ++
    ++b[#b+1] = 'e'
    ++M.same(a,b) -- => false
    ++
    ++ ++ ++

    sort (t [, comp = math.min])

    ++ ++

    Sorts a collection.

    ++ ++ ++
    ++M.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}"
    ++
    ++ ++ ++

    Handles custom comparison functions.

    ++ ++ ++
    ++M.sort({'b','a','d','c'}, function(a,b)
    ++  return a:byte() > b:byte()
    ++end) -- => "{'d','c','b','a'}"
    ++
    ++ ++ ++

    sortedk (t [, comp])

    ++ ++

    Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. ++It returns upon each call a key, value pair.

    ++ ++ ++
    ++local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
    ++for k, v in M.sortedk(tbl) do print(k, v) end
    ++
    ++-- => 1    12
    ++-- => 2 6
    ++-- => 3 5
    ++-- => 4 10
    ++-- => 5 8
    ++
    ++local function comp(a,b) return a > b end
    ++for k, v in M.sortedk(tbl, comp) do print(k, v) end
    ++
    ++-- => 5    8
    ++-- => 4 10
    ++-- => 3 5
    ++-- => 2 6
    ++-- => 1 12
    ++
    ++ ++ ++

    sortedv (t [, comp])

    ++ ++

    Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. ++It returns upon each call a key, value pair.

    ++ ++ ++
    ++local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
    ++for k, v in M.sortedv(tbl) do print(k, v) end
    ++
    ++-- => 3    5
    ++-- => 2 6
    ++-- => 5 8
    ++-- => 4 10
    ++-- => 1 12
    ++
    ++local function comp(a,b) return a > b end
    ++for k, v in M.sortedv(tbl, comp) do print(k, v) end
    ++
    ++-- => 1    12
    ++-- => 4 10
    ++-- => 5 8
    ++-- => 2 6
    ++-- => 3 5
    ++
    ++ ++ ++

    sortBy (t [, transform [, comp = math.min]])

    ++ ++

    Sorts items in a collection based on the result of running a transform function through every item in the collection.

    ++ ++ ++
    ++local r = M.sortBy({1,2,3,4,5}, math.sin)
    ++print(table.concat(r,','))
    ++
    ++-- => {5,4,3,1,2}
    ++
    ++ ++ ++

    The transform function can also be a string name property.

    ++ ++ ++
    ++local people = {
    ++    {name = 'albert', age = 40},
    ++    {name = 'louis', age = 55},
    ++    {name = 'steve', age = 35},
    ++    {name = 'henry', age = 19},
    ++}
    ++local r = M.sortBy(people, 'age')
    ++M.each(r, function(v) print(v.age, v.name) end)
    ++
    ++-- => 19   henry
    ++-- => 35    steve
    ++-- => 40    albert
    ++-- => 55    louis
    ++
    ++ ++ ++

    As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort the list of people by decreasing age order :

    ++ ++ ++
    ++local people = {
    ++    {name = 'albert', age = 40},
    ++    {name = 'louis', age = 55},
    ++    {name = 'steve', age = 35},
    ++    {name = 'henry', age = 19},
    ++}
    ++local r = M.sortBy(people, 'age', function(a,b) return a > b end)
    ++M.each(r, function(v) print(v.age, v.name) end)
    ++
    ++-- => 55   louis
    ++-- => 40    albert
    ++-- => 35    steve
    ++-- => 19    henry
    ++
    ++ ++ ++

    The transform function defaults to M.indentity and in that case, M.sortBy behaves like M.sort.

    ++ ++ ++
    ++local r = M.sortBy({1,2,3,4,5})
    ++print(table.concat(r,','))
    ++
    ++-- => {1,2,3,4,5}
    ++
    ++ ++ ++

    groupBy (t, iter)

    ++ ++

    Groups values in a collection depending on their return value when passed to a predicate test.

    ++ ++ ++
    ++M.groupBy({0,1,2,3,4,5,6},function(v)
    ++  return v%2==0 and 'even' or 'odd'
    ++end)
    ++-- => "{odd = {1,3,5}, even = {0,2,4,6}}"
    ++
    ++M.groupBy({0,'a',true, false,nil,b,0.5},type)
    ++-- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}"        
    ++
    ++ ++ ++

    countBy (t, iter)

    ++ ++

    Splits a table in subsets and provide the count for each subset.

    ++ ++ ++
    ++M.countBy({0,1,2,3,4,5,6},function(v)
    ++  return v%2==0 and 'even' or 'odd'
    ++end) -- => "{odd = 3, even = 4}"
    ++
    ++ ++ ++

    size (...)

    ++ ++

    When given a table, provides the count for the very number of values in that table.

    ++ ++ ++
    ++M.size {1,2,3} -- => 3
    ++M.size {one = 1, two = 2} -- => 2
    ++
    ++ ++ ++

    When given a vararg list of arguments, returns the count of these arguments.

    ++ ++ ++
    ++M.size(1,2,3) -- => 3
    ++M.size('a','b',{}, function() end) -- => 4
    ++
    ++ ++ ++

    containsKeys (t, other)

    ++ ++

    Checks whether a table has all the keys existing in another table.

    ++ ++ ++
    ++M.contains({1,2,3,4},{1,2,3}) -- => true
    ++M.contains({1,2,'d','b'},{1,2,3,5}) -- => true
    ++M.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true
    ++
    ++ ++ ++

    sameKeys (tA, tB)

    ++ ++

    Checks whether both tables features the same keys:

    ++ ++ ++
    ++M.sameKeys({1,2,3,4},{1,2,3}) -- => false
    ++M.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true
    ++M.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false
    ++
    ++ ++ ++

    [⬆]

    ++ ++

    ++

    Array functions

    ++ ++

    sample (array [, n = 1 [, seed]])

    ++ ++

    Samples n values from array.

    ++ ++ ++
    ++local array = M.range(1,20)
    ++local sample = M.sample(array, 3)
    ++print(table.concat(sample,','))
    ++
    ++-- => {12,11,15}
    ++
    ++ ++ ++

    n defaults to 1. In that case, a single value will be returned.

    ++ ++ ++
    ++local array = M.range(1,20)
    ++local sample = M.sample(array)
    ++print(sample)
    ++
    ++-- => 12
    ++
    ++ ++ ++

    An optional 3rd argument seed can be passed for deterministic random sampling.

    ++ ++

    sampleProb (array, prob [, seed])

    ++ ++

    Returns an array of values randomly selected from a given array. ++In case seed is provided, it is used for deterministic sampling.

    ++ ++ ++
    ++local array = M.range(1,20)
    ++local sample = M.sampleProb(array, 0.2)
    ++print(table.concat(sample,','))
    ++
    ++-- => 5,11,12,15
    ++
    ++sample = M.sampleProb(array, 0.2, os.time())
    ++print(table.concat(sample,','))
    ++
    ++-- => 1,6,10,12,15,20 (or similar)
    ++
    ++ ++ ++

    nsorted (array [, n = 1[, comp]])

    ++ ++

    Returns the n-top values satisfying a predicate. It takes a comparison function comp used to sort array values, ++and then picks the top n-values. It leaves the original array untouched.

    ++ ++ ++
    ++local function comp(a,b) return a > b end
    ++M.nsorted(array,5, comp) -- => {5,4,3,2,1}
    ++
    ++ ++ ++

    n defaults to 1 and comp defaults to the < operator.

    ++ ++ ++
    ++local array = M.range(1,20)
    ++M.nsorted(array) -- => {1}
    ++
    ++ ++ ++

    shuffle (array [, seed])

    ++ ++

    Shuffles a given array.

    ++ ++ ++
    ++local list = M.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}"
    ++M.each(list,print)
    ++
    ++ ++ ++

    pack (...)

    ++ ++

    Converts a vararg list of arguments to an array.

    ++ ++ ++
    ++M.pack(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}"
    ++
    ++ ++ ++

    find (array, value [, from = 1])

    ++ ++

    Looks for a value in a given array and returns the position of the first occurence.

    ++ ++ ++
    ++local value = {3}
    ++M.find({{4},{3},{2},{1}},value) -- => 2
    ++
    ++ ++ ++

    It can also start the search at a specific position in the array:

    ++ ++ ++
    ++-- search value 4 starting from index 3
    ++M.find({1,4,2,3,4,5},4,3) -- => 5
    ++
    ++ ++ ++

    reverse (array)

    ++ ++

    Reverses an array.

    ++ ++ ++
    ++M.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}"
    ++
    ++ ++ ++

    fill (array, value [, i = 1 [, j = #array]])

    ++ ++

    Replaces all elements in a given array with a given value.

    ++ ++ ++
    ++local array = M.range(1,5)
    ++M.fill(array, 0) -- => {0,0,0,0,0}
    ++
    ++ ++ ++

    It can start replacing value at a specific index.

    ++ ++ ++
    ++local array = M.range(1,5)
    ++M.fill(array,0,3) -- => {1,2,0,0,0}
    ++
    ++ ++ ++

    It can replace only values within a specific range.

    ++ ++ ++
    ++local array = M.range(1,5)
    ++M.fill(array,0,2,4) -- => {1,0,0,0,5}
    ++
    ++ ++ ++

    In case the upper bound index i greather than the array size, it will enlarge the array.

    ++ ++ ++
    ++local array = M.range(1,5)
    ++M.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0}
    ++
    ++ ++ ++

    zeros (n)

    ++ ++

    Returns an array of n zeros.

    ++ ++ ++
    ++M.zeros(4) -- => {0,0,0,0}
    ++
    ++ ++ ++

    ones (n)

    ++ ++

    Returns an array of n 1's.

    ++ ++ ++
    ++M.ones(3) -- => {1,1,1}
    ++
    ++ ++ ++

    vector (value, n)

    ++ ++

    Returns an array of n times a given value.

    ++ ++ ++
    ++M.vector(10, 4) -- => {10,10,10,10}
    ++
    ++ ++ ++

    selectWhile (array, f [, ...])

    ++

    *Aliases: takeWhile*.

    ++ ++

    Collects values as long as they pass a given test. Stops on the first non-passing test.

    ++ ++ ++
    ++M.selectWhile({2,4,5,8}, function(v)
    ++  return v%2==0
    ++end) -- => "{2,4}"
    ++
    ++ ++ ++

    dropWhile (array, f [, ...])

    ++

    *Aliases: rejectWhile*.

    ++ ++

    Removes values as long as they pass a given test. Stops on the first non-passing test.

    ++ ++ ++
    ++M.dropWhile({2,4,5,8}, function(v)
    ++  return v%2==0
    ++end) -- => "{5,8}"
    ++
    ++ ++ ++

    sortedIndex (array, value [, comp = math.min [, sort = nil]])

    ++ ++

    Returns the index at which a value should be inserted to preserve order.

    ++ ++ ++
    ++M.sortedIndex({1,2,3},4) -- => 4
    ++
    ++ ++ ++

    Can take a custom comparison functions.

    ++ ++ ++
    ++local comp = function(a,b) return a<b end
    ++M.sortedIndex({-5,0,4,4},3,comp) -- => 3
    ++
    ++ ++ ++

    indexOf (array, value)

    ++ ++

    Returns the index of a value in an array.

    ++ ++ ++
    ++M.indexOf({1,2,3},2) -- => 2
    ++
    ++ ++ ++

    lastIndexOf (array, value)

    ++ ++

    Returns the index of the last occurence of a given value in an array.

    ++ ++ ++
    ++M.lastIndexOf({1,2,2,3},2) -- => 3
    ++
    ++ ++ ++

    findIndex (array, pred)

    ++ ++

    Returns the first index at which a predicate passes a truth test.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6}
    ++local function multipleOf3(v) return v%3==0 end
    ++M.findIndex(array, multipleOf3) -- => 3
    ++
    ++ ++ ++

    findLastIndex (array, pred)

    ++ ++

    Returns the last index at which a predicate passes a truthy test.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6}
    ++local function multipleOf3(v) return v%3==0 end
    ++M.findLastIndex(array, multipleOf3) -- => 6
    ++
    ++ ++ ++

    addTop (array, ...)

    ++ ++

    Adds given values at the top of an array. The latter values bubbles at the top.

    ++ ++ ++
    ++local array = {1}
    ++M.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}"
    ++
    ++ ++ ++

    prepend (array, ...)

    ++ ++

    Adds given values at the top of an array, preserving the order at which elements are passed-in.

    ++ ++ ++
    ++local array = {'old_val'}
    ++M.prepend(array,1,2,3,4) -- => "{1,2,3,4,'old_val'}"
    ++
    ++ ++ ++

    push (array, ...)

    ++ ++

    Adds given values at the end of an array.

    ++ ++ ++
    ++local array = {1}
    ++M.push(array,1,2,3,4) -- => "{1,1,2,3,4}"
    ++
    ++ ++ ++

    shift (array [, n = 1])

    ++

    *Aliases: pop*.

    ++ ++

    Removes and returns the first value in an array.

    ++ ++ ++
    ++local array = {1,2,3}
    ++local shift = M.shift(array) -- => "shift = 1", "array = {2,3}"
    ++
    ++ ++

    If n is supplied, returns n values.

    ++ ++ ++
    ++local array = {1,2,3,4,5}
    ++local a, b = M.shift(array, 2) -- => "a = 1, b = 2", "array = {3,4,5}"
    ++
    ++ ++ ++

    unshift (array [, n = 1])

    ++ ++

    Removes and returns the last value in an array.

    ++ ++ ++
    ++local array = {1,2,3}
    ++local value = M.unshift(array) -- => "value = 3", "array = {1,2}"
    ++
    ++ ++ ++

    pull (array, ...)

    ++

    *Aliases: remove*.

    ++ ++

    Removes all provided values from a given array.

    ++ ++ ++
    ++M.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}"
    ++
    ++ ++ ++

    removeRange (array [, start = 1 [, finish = #array]])

    ++

    *Aliases: rmRange, M.chop*.

    ++ ++

    Trims out all values index within a range.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.removeRange(array, 3,8) -- => "{1,2,9}"
    ++
    ++ ++ ++

    chunk (array [, f])

    ++ ++

    Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return value of f(v, k, ...). Consecutive elements which return the same value are chunked together.

    ++ ++ ++
    ++local t = {1,5,2,4,3,3,4}
    ++M.chunk(t, function(v) return v%2==0 end) -- => "{{1,5},{2,4},{3,3},{4}}"
    ++
    ++ ++ ++

    If not given, f defaults to identity.

    ++ ++ ++
    ++local t = {1,5,2,4,3,3,4}
    ++M.chunk(t) -- => "{{1},{5},{2},{4},{3,3},{4}}"
    ++
    ++ ++ ++

    slice (array [, start = 1 [, finish = #array]])

    ++

    *Aliases: sub*.

    ++ ++

    Slices and returns a part of an array.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.slice(array, 3,6) -- => "{3,4,5,6}"
    ++
    ++ ++ ++

    first (array [, n = 1])

    ++

    *Aliases: head, M.take*.

    ++ ++

    Returns the first N elements in an array.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.first(array,3) -- => "{1,2,3}"
    ++
    ++ ++ ++

    initial (array [, n = #array])

    ++ ++

    Excludes the last N elements in an array.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.initial(array,5) -- => "{1,2,3,4}"
    ++
    ++ ++ ++

    last (array [, n = #array])

    ++ ++

    Returns the last N elements in an array.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.last(array,3) -- => "{7,8,9}"
    ++
    ++ ++ ++

    rest (array [, index = 1])

    ++

    *Aliases: tail*.

    ++ ++

    Returns all values after index, including the given index itself.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6,7,8,9}
    ++M.rest(array,6) -- => "{6,7,8,9}"
    ++
    ++ ++ ++

    nth (array, index)

    ++ ++

    Returns the value at index.

    ++ ++ ++
    ++local array = {1,2,3,4,5,6}
    ++M.nth(array,3) -- => "3"
    ++
    ++ ++ ++

    compact (array)

    ++ ++

    Trims out all falsy values.

    ++ ++ ++
    ++M.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}"
    ++
    ++ ++ ++

    flatten (array [, shallow = false])

    ++ ++

    Flattens a nested array.

    ++ ++ ++
    ++M.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}"
    ++
    ++ ++ ++

    When given arg shallow, flatten only at the first level.

    ++ ++ ++
    ++M.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}"
    ++
    ++ ++ ++

    difference (array, array2)

    ++

    *Aliases: without, diff*.

    ++ ++

    Returns values in the given array not present in a second array.

    ++ ++ ++
    ++local array = {1,2,'a',4,5}
    ++M.difference(array,{1,'a'}) -- => "{2,4,5}"
    ++
    ++ ++ ++

    union (...)

    ++ ++

    Produces a duplicate-free union of all passed-in arrays.

    ++ ++ ++
    ++local A = {'a'}
    ++local B = {'a',1,2,3}
    ++local C = {2,10}
    ++M.union(A,B,C) -- => "{'a',1,2,3,10}"
    ++
    ++ ++ ++

    intersection (...)

    ++ ++

    Returns the intersection (common-part) of all passed-in arrays:

    ++ ++ ++
    ++local A = {'a'}
    ++local B = {'a',1,2,3}
    ++local C = {2,10,1,'a'}
    ++M.intersection(A,B,C) -- => "{'a'}"
    ++
    ++ ++ ++

    disjoint (...)

    ++ ++

    Checks if all passed in arrays are disjoint.

    ++ ++ ++
    ++local A = {'a'}
    ++local B = {'a',1,3}
    ++local C = {3,10,2}
    ++
    ++M.disjoint(A,B) -- => false
    ++M.disjoint(A,C) -- => true
    ++M.disjoint(B,C) -- => false
    ++
    ++ ++ ++

    symmetricDifference (array, array2)

    ++

    *Aliases: symdiff,xor*.

    ++ ++

    Returns values in the first array not present in the second and also values in the second array not present in the first one.

    ++ ++ ++
    ++local array = {1,2,3}
    ++local array2 = {1,4,5}
    ++M.symmetricDifference(array, array2) -- => "{2,3,4,5}"
    ++
    ++ ++ ++

    unique (array)

    ++

    *Aliases: uniq*.

    ++ ++

    Makes an array duplicate-free.

    ++ ++ ++
    ++M.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}"
    ++
    ++ ++ ++

    isunique (array)

    ++

    *Aliases: isuniq*.

    ++ ++

    Checks if a given array contains no duplicate value.

    ++ ++ ++
    ++M.isunique({1,2,3,4,5}) -- => true
    ++M.isunique({1,2,3,4,4}) -- => false
    ++
    ++ ++ ++

    duplicates (array)

    ++ ++

    Returns an array list of all duplicates in array.

    ++ ++ ++
    ++M.duplicates({1,2,3,3,8,8,3,2,4}) -- => {2,3,8}
    ++
    ++ ++ ++

    zip (...)

    ++

    *Aliases: transpose*.

    ++ ++

    Zips values from different arrays, on the basis on their common keys.

    ++ ++ ++
    ++local names = {'Bob','Alice','James'}
    ++local ages = {22, 23}
    ++M.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}"
    ++
    ++ ++ ++

    zipWith (f, ...)

    ++

    *Aliases: transposeWith*.

    ++ ++

    Merges values using a given function. Only values indexed with the same key in the given arrays are merged in the same subset. ++Function f is used to combine values.

    ++ ++ ++
    ++local names = {'Bob','Alice','James'}; local ages = {22, 23, 25}
    ++local function introduce(name, age) return 'I am '..name..' and I am '..age..' years old.' end
    ++local t = M.zipWith(introduce,names,ages)
    ++-- => {
    ++-- =>  'I am Bob and I am 22 years old.'
    ++-- =>  'I am Alice and I am 23 years old.'
    ++-- =>  'I am James and I am 25 years old.'
    ++-- => }
    ++
    ++ ++ ++

    append (array, other)

    ++ ++

    Appends two arrays.

    ++ ++ ++
    ++M.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}"
    ++
    ++ ++ ++

    interleave (...)

    ++ ++

    Interleaves values from passed-in arrays.

    ++ ++ ++
    ++t1 = {1, 2, 3}
    ++t2 = {'a', 'b', 'c'}
    ++M.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}"
    ++
    ++ ++ ++

    interpose (array, value)

    ++

    *Aliases: intersperce*.

    ++ ++

    Interposes a value between consecutive values in an arrays.

    ++ ++ ++
    ++M.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}"
    ++
    ++ ++ ++

    range ([from [, to [, step]]])

    ++ ++

    Generates an arithmetic sequence.

    ++ ++ ++
    ++M.range(1,4) -- => "{1,2,3,4}"
    ++
    ++ ++ ++

    In case a single value is provided, it generates a sequence from 1 to that value.

    ++ ++ ++
    ++M.range(3) -- => "{1,2,3}"
    ++
    ++ ++ ++

    The incremental step can also be provided as third argument.

    ++ ++ ++
    ++M.range(0,2,0.7) -- => "{0,0.7,1.4}"
    ++
    ++ ++ ++

    It also handles negative progressions.

    ++ ++ ++
    ++M.range(-5) -- => "{-1,-2,-3,-4,-5}"
    ++M.range(5,1) -- => "{5,4,3,2,1}"
    ++
    ++ ++ ++

    rep (value, n)

    ++ ++

    Generates a list of n repetitions of a value.

    ++ ++ ++
    ++M.rep(4,3) -- => "{4,4,4}"
    ++
    ++ ++ ++

    powerset (array)

    ++ ++

    Returns the powerset of an array.

    ++ ++ ++
    ++M.powerset {1,2,3} -- => "{{1},{2},{3},{1,2},{2,3},{1,2,3}}"
    ++
    ++ ++ ++

    partition (array [, n = 1 [, pad]])

    ++

    *Aliases: part*.

    ++ ++

    Returns an iterator function for partitions of a given array.

    ++ ++ ++
    ++local t = {1,2,3,4,5,6}
    ++for p in M.partition(t,2) do
    ++  print(table.concat(p, ','))
    ++end
    ++
    ++-- => 1,2
    ++-- => 3,4
    ++-- => 5,6
    ++
    ++local t = {1,2,3,4,5,6}
    ++for p in M.partition(t,4) do
    ++  print(table.concat(p, ','))
    ++end
    ++
    ++-- => 1,2,3,4
    ++-- => 5,6
    ++
    ++ ++ ++

    In case the last partition has less elements than desired, a 3rd argument can be supplied to adjust the partition size.

    ++ ++ ++
    ++local t = {1,2,3,4,5,6}
    ++for p in M.partition(t,4,0) do
    ++  print(table.concat(p, ','))
    ++end
    ++
    ++-- => 1,2,3,4
    ++-- => 5,6,0,0
    ++
    ++ ++ ++

    overlapping (array [, n = 2 [, pad]])

    ++ ++

    Returns an iterator function which provides overlapping subsequences of a given array.

    ++ ++ ++
    ++local t = {1,2,3,4,5,6,7}
    ++for p in M.overlapping(t,3) do
    ++    print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3
    ++-- => 3,4,5
    ++-- => 5,6,7
    ++
    ++for p in M.overlapping(t,4) do
    ++    print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3,4
    ++-- => 4,5,6,7
    ++
    ++for p in M.overlapping(t,5) do
    ++    print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3,4,5
    ++-- => 5,6,7
    ++
    ++ ++ ++

    In case the last subsequence wil not match the exact desired length, it can be adjusted with a 3rd argument pad.

    ++ ++ ++
    ++local t = {1,2,3,4,5,6,7}
    ++for p in M.overlapping(t,5,0) do
    ++    print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3,4,5
    ++-- => 5,6,7,0,0
    ++
    ++ ++ ++

    aperture (array [, n = 2])

    ++

    *Aliases: sliding*.

    ++ ++

    Returns an iterator function which provides sliding partitions of a given array.

    ++ ++ ++
    ++local t = {1,2,3,4,5}
    ++for p in M.aperture(t,4) do
    ++  print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3,4
    ++-- => 2,3,4,5
    ++
    ++for p in M.aperture(t,3) do
    ++  print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2,3
    ++-- => 2,3,4
    ++-- => 3,4,5
    ++
    ++ ++ ++

    pairwise (array)

    ++ ++

    Iterator returning sliding pairs of an array.

    ++ ++ ++
    ++local t = M.range(5)
    ++for p in pairwise(t) do
    ++  print(table.concat(p,','))
    ++end
    ++
    ++-- => 1,2
    ++-- => 2,3
    ++-- => 3,4
    ++-- => 4,5
    ++
    ++ ++ ++

    permutation (array)

    ++

    *Aliases: perm*.

    ++ ++

    Returns an iterator function for permutations of a given array.

    ++ ++ ++
    ++t = {'a','b','c'}
    ++for p in M.permutation(t) do
    ++  print(table.concat(p))
    ++end
    ++
    ++-- => 'bca'
    ++-- => 'cba'
    ++-- => 'cab'
    ++-- => 'acb'
    ++-- => 'bac'
    ++-- => 'abc'
    ++
    ++ ++ ++

    concat (array [, sep = '' [, i = 1 [, j = #array]]])

    ++

    *Aliases: join*.

    ++ ++

    Concatenates a given array values:

    ++ ++ ++
    ++M.concat({'a',1,0,1,'b'}) -- => 'a101b'
    ++
    ++ ++ ++

    xprod (array, array2)

    ++ ++

    Returns all possible pairs built from given arrays.

    ++ ++ ++
    ++local t = M.xprod({1,2},{'a','b'})
    ++-- => {{1,'a'},{1,'b'},{2,'a'},{2,'b'}}
    ++
    ++ ++ ++

    xpairs (value, array)

    ++ ++

    Creates pairs from value and array. Value is always prepended to the pair.

    ++ ++ ++
    ++local t = M.xpairs(1, {1, 2, 3})
    ++-- => {{1,1},{1,2},{1,3}}
    ++
    ++ ++ ++

    xpairsRight (value, array)

    ++ ++

    Creates pairs from value and array. Value is always appended as the last item to the pair.

    ++ ++ ++
    ++local t = M.xpairsRight(1, {1, 2, 3})
    ++-- => {{1,1},{2,1},{3,1}}
    ++
    ++ ++ ++

    sum (array)

    ++ ++

    Returns the sum of array values.

    ++ ++ ++
    ++M.sum({1,2,3,4,5}) -- => 15
    ++
    ++ ++ ++

    product (array)

    ++ ++

    Returns the product of array values.

    ++ ++ ++
    ++M.product({1,2,3,4,5}) -- => 120
    ++
    ++ ++ ++

    mean (array)

    ++ ++

    Returns the mean of array values.

    ++ ++ ++
    ++M.mean({1,2,3,4,5}) -- => 3
    ++
    ++ ++ ++

    median (array)

    ++ ++

    Returns the median of array values.

    ++ ++ ++
    ++M.median({1,2,3,4,5}) -- => 3
    ++M.median({1,2,3,4}) -- => 2.5
    ++
    ++ ++ ++

    [⬆]

    ++ ++

    ++

    Utility functions

    ++ ++

    noop ()

    ++ ++

    The no-operation function. Takes nothing, returns nothing. It is being used internally.

    ++ ++ ++
    ++M.noop() -- => nil
    ++
    ++ ++ ++

    identity (value)

    ++ ++

    Returns the passed-in value.
    ++This function is internally used as a default transformation function.

    ++ ++ ++
    ++M.identity(1)-- => 1
    ++M.identity(false) -- => false
    ++M.identity('hello!') -- => 'hello!'
    ++
    ++ ++ ++

    call (f [, ...])

    ++ ++

    Calls f with the supplied arguments. Returns the results of f(...).

    ++ ++ ++
    ++M.call(math.pow, 2, 3) -- => 8
    ++M.call(string.len, 'hello' ) -- => 5
    ++M.call(table.concat, {1,2,3,4,5}, ',', 2, 4) -- => {2,3,4}
    ++
    ++ ++ ++

    constant (value)

    ++ ++

    Creates a constant function. This function will continuously yield the same output.

    ++ ++ ++
    ++local pi = M.constant(math.pi)
    ++pi(1) -- => 3.1415926535898
    ++pi(2) -- => 3.1415926535898
    ++pi(math.pi) -- => 3.1415926535898
    ++
    ++ ++ ++

    applySpec (specs)

    ++ ++

    Returns a function which applies specs on args. This function will produce an object having the same structure than specs ++by mapping each property to the result of calling its associated function with the supplied arguments.

    ++ ++ ++
    ++local stats = M.applySpec({
    ++  min = function(...) return math.min(...) end,
    ++  max = function(...) return math.max(...) end,
    ++})
    ++
    ++stats(5,4,10,1,8) -- => {min = 1, max = 10}
    ++
    ++ ++ ++

    thread (value [, ...])

    ++ ++

    Threads value through a series of functions.

    ++ ++ ++
    ++local function inc(x) return x + 1 end
    ++local function double(x) return 2 * x end
    ++local function square(x) return x * x end
    ++M.thread(2, inc, double, square) -- => 36
    ++M.thread(3, double, inc, square) -- => 49
    ++M.thread(4, square, double, inc) -- => 33
    ++M.thread(5, square, inc, double) -- => 52
    ++
    ++ ++ ++

    If a function expects more than one args, it can be specified using an array list, ++where the first item is the function and the following are the remaining args neeeded.

    ++ ++ ++
    ++local function inc(x) return x + 1 end
    ++local function add(x, y) return x * y end
    ++local function pow(x, y) return x ^ y end
    ++M.thread(2, inc, {add, 3}, {pow, 2}) -- => 36
    ++M.thread(2, {add, 4}, inc, {pow, 2}) -- => 49
    ++
    ++ ++ ++

    threadRight (value [, ...])

    ++ ++

    Threads value through a series of functions. If a function expects more than one args, ++it can be specified using an array list, where the first item is the function and the following are ++the remaining args neeeded. The value is used as the last input.

    ++ ++ ++
    ++local function inc(x) return x + 1 end
    ++local function add(x, y) return x * y end
    ++local function pow(x, y) return x ^ y end
    ++M.threadRight(2, inc, {add, 3}, {pow, 2}) -- => 64
    ++M.threadRight(2, {add, 4}, inc, {pow, 2}) -- => 128
    ++
    ++ ++ ++

    dispatch (...)

    ++ ++

    Returns a dispatching function. When called with arguments, this function invokes each of its functions ++in the passed-in order and returns the results of the first non-nil evaluation.

    ++ ++ ++
    ++local f = M.dispatch(
    ++  function() return nil end,
    ++  function (v) return v+1 end,
    ++  function (v) return 2*v end
    ++)
    ++f(5) -- => 6
    ++f(7) -- => 8
    ++
    ++ ++ ++

    memoize (f)

    ++

    *Aliases: cache*.

    ++ ++

    Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body.

    ++ ++ ++
    ++local function fibonacci(n)
    ++  return n < 2 and n or fibonacci(n-1)+fibonacci(n-2)
    ++end
    ++local mem_fibonacci = M.memoize(fibonacci)
    ++fibonacci(20) -- => 6765 (but takes some time)
    ++mem_fibonacci(20) -- => 6765 (takes less time)
    ++
    ++ ++ ++

    unfold (f, seed)

    ++ ++

    Builds a list from a seed value. Accepts an iterator function, which returns either nil to stop iteration or two values : the value to add to the list of results and the seed to be used in the next call to the iterator function.

    ++ ++ ++
    ++local function f(v)
    ++  if v < 100 then return v, v * 2 end
    ++end
    ++local t = M.unfold(f, 10) -- => {10,20,40,80}
    ++
    ++ ++ ++

    once (f)

    ++ ++

    Produces a function that runs only once. Successive calls to this function will still yield the same input.

    ++ ++ ++
    ++local sq = M.once(function(a) return a*a end)
    ++sq(1) -- => 1
    ++sq(2) -- => 1
    ++sq(3) -- => 1
    ++sq(4) -- => 1
    ++sq(5) -- => 1
    ++
    ++ ++ ++

    before (f, count)

    ++ ++

    Returns a version of f that will run no more than count times. Next calls will keep yielding the results of the (n-th)-1 call.

    ++ ++ ++
    ++local function greet(someone) return 'hello '..someone end
    ++local greetOnly3people = M.before(greet, 3)
    ++greetOnly3people('John') -- => 'hello John'
    ++greetOnly3people('Moe') -- => 'hello Moe'
    ++greetOnly3people('James') -- => 'hello James'
    ++greetOnly3people('Joseph') -- => 'hello James'
    ++greetOnly3people('Allan') -- => 'hello James'
    ++
    ++ ++ ++

    after (f, count)

    ++ ++

    Produces a function that will respond only after a given number of calls.

    ++ ++ ++
    ++local f = M.after(M.identity,3)
    ++f(1) -- => nil
    ++f(2) -- => nil
    ++f(3) -- => 3
    ++f(4) -- => 4
    ++
    ++ ++ ++

    compose (...)

    ++ ++

    Composes functions. Each function consumes the return value of the one that follows.

    ++ ++ ++
    ++local function f(x) return x^2 end
    ++local function g(x) return x+1 end
    ++local function h(x) return x/2 end
    ++local compositae = M.compose(f,g,h)
    ++compositae(10) -- => 36
    ++compositae(20) -- => 121
    ++
    ++ ++ ++

    pipe (value, ...)

    ++ ++

    Pipes a value through a series of functions.

    ++ ++ ++
    ++local function f(x) return x^2 end
    ++local function g(x) return x+1 end
    ++local function h(x) return x/2 end
    ++M.pipe(10,f,g,h) -- => 36
    ++M.pipe(20,f,g,h) -- => 121
    ++
    ++ ++ ++

    complement (f)

    ++ ++

    Returns a function which returns the logical complement of a given function.

    ++ ++ ++
    ++M.complement(function() return true end)() -- => false
    ++
    ++ ++ ++

    juxtapose (value, ...)

    ++

    *Aliases: juxt*.

    ++ ++

    Calls a sequence of functions with the same input.

    ++ ++ ++
    ++local function f(x) return x^2 end
    ++local function g(x) return x+1 end
    ++local function h(x) return x/2 end
    ++M.juxtapose(10, f, g, h) -- => 100, 11, 5
    ++
    ++ ++ ++

    wrap (f, wrapper)

    ++ ++

    Wraps a function inside a wrapper. Allows the wrapper to execute code before and after function run.

    ++ ++ ++
    ++local greet = function(name) return "hi: " .. name end
    ++local greet_backwards = M.wrap(greet, function(f,arg)
    ++  return f(arg) ..'\nhi: ' .. arg:reverse()
    ++end)
    ++greet_backwards('John')
    ++
    ++-- => hi: John
    ++-- => hi: nhoJ
    ++
    ++ ++ ++

    times (iter [, n])

    ++ ++

    Calls a given function n times.

    ++ ++ ++
    ++local f = ('Lua programming'):gmatch('.')
    ++M.times(f, 3) -- => {'L','u','a'}
    ++
    ++ ++ ++

    bind (f, v)

    ++ ++

    Binds a value to be the first argument to a function.

    ++ ++ ++
    ++local sqrt2 = M.bind(math.sqrt,2)
    ++sqrt2() -- => 1.4142135623731
    ++
    ++ ++ ++

    bind2 (f, v)

    ++ ++

    Binds a value to be the second argument to a function.

    ++ ++ ++
    ++local last2 = M.bind(M.last,2)
    ++last2({1,2,3,4,5,6}) -- => {5,6}
    ++
    ++ ++ ++

    bindn (f, ...)

    ++ ++

    Binds a variable number of values to be the first arguments to a function.

    ++ ++ ++
    ++local function out(...) return table.concat {...} end
    ++local out = M.bindn(out,'OutPut',':',' ')
    ++out(1,2,3) -- => OutPut: 123
    ++out('a','b','c','d') -- => OutPut: abcd
    ++
    ++ ++ ++

    bindall (obj, ...)

    ++ ++

    Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument.

    ++ ++ ++
    ++local window = {
    ++    setPos = function(w,x,y) w.x, w.y = x, y end,
    ++    setName = function(w,name) w.name = name end,
    ++    getName = function(w) return w.name end,
    ++}
    ++window = M.bindall(window, 'setPos', 'setName', 'getName')
    ++window.setPos(10,15)
    ++print(window.x, window.y) -- => 10,15
    ++
    ++window.setName('fooApp')
    ++print(window.name) -- => 'fooApp'
    ++
    ++print(window.getName()) -- => 'fooApp'
    ++
    ++ ++ ++

    cond (conds)

    ++ ++

    Returns a function which iterate over an array list of conditions. It invokes each predicate, passing it given values. It returns the value of the corresponding function of the first predicate to return a non-nil value

    ++ ++ ++
    ++local multipleOf = M.cond({
    ++  {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end},
    ++  {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end},
    ++  {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end},
    ++  {function() return true end, function(v) return 'could not find an answer for '..v end}
    ++})
    ++for i = 15, 20 do
    ++  print(multipleOf(i))
    ++end
    ++
    ++-- => 15 is multiple of 3
    ++-- => 16 is multiple of 2
    ++-- => could not find an answer for 17
    ++-- => 18 is multiple of 2
    ++-- => could not find an answer for 19
    ++-- => 20 is multiple of 2
    ++
    ++ ++ ++

    both (...)

    ++ ++

    Returns a validation function. Given a set of functions, the validation function ++evaluates to true only when all its funcs returns true.

    ++ ++ ++
    ++local f = M.both(
    ++    function(x) return x > 0 end,
    ++    function(x) return x < 10 end,
    ++    function(x) return x % 2 == 0 end
    ++)
    ++f(2) -- => true
    ++f(8) -- => true
    ++f(9) -- => false
    ++
    ++ ++ ++

    either (...)

    ++ ++

    Returns a validation function. Given a set of functions, the validation function ++evaluates to true when one of its funcs returns true.

    ++ ++ ++
    ++local f = M.either(
    ++    function(x) return x > 0 end,
    ++    function(x) return x % 2 == 0 end
    ++)
    ++f(0) -- => true
    ++f(-3) -- => false
    ++
    ++ ++ ++

    neither (...)

    ++ ++

    Returns a validation function. Given a set of functions, the validation function ++evaluates to true when neither of its funcs returns true.

    ++ ++ ++
    ++local f = M.neither(
    ++    function(x) return x > 10 end,
    ++    function(x) return x % 2 == 0 end
    ++)
    ++f(12) -- => false
    ++f(8) -- => false
    ++f(7) -- => true
    ++
    ++ ++ ++

    uniqueId ([template])

    ++

    *Aliases: uid*.

    ++ ++

    Returns an unique integer ID.

    ++ ++ ++
    ++M.uniqueId() -- => 1
    ++
    ++ ++ ++

    Can handle string templates for formatted output.

    ++ ++ ++
    ++M.uniqueId('ID%s') -- => 'ID2'
    ++
    ++ ++ ++

    Or a function, for the same purpose.

    ++ ++ ++
    ++local formatter = function(ID) return '$'..ID..'$' end
    ++M.uniqueId(formatter) -- => '$ID1$'
    ++
    ++ ++ ++

    iterator (f, value [, n])

    ++

    *Aliases: iter*.

    ++ ++

    Returns an iterator function which constinuously applies a function f onto an input value. ++For example, let us go through the powers of two using iterator.

    ++ ++ ++
    ++local function po2(x) return x*2 end
    ++local function iter_po2 = M.iterator(po2, 1)
    ++iter_po2() -- => 2
    ++iter_po2() -- => 4
    ++iter_po2() -- => 8
    ++
    ++ ++ ++

    if n is supplied, it will run at maximum n times.

    ++ ++ ++
    ++local function po2(x) return x*2 end
    ++local function iter_po2 = M.iterator(po2, 1, 3)
    ++iter_po2() -- => 2
    ++iter_po2() -- => 4
    ++iter_po2() -- => 8
    ++iter_po2() -- => nil
    ++
    ++ ++ ++

    skip (iter [, n = 1])

    ++ ++

    Consumes the first n values of a iterator then returns it.

    ++ ++ ++
    ++local w = "hello"
    ++local char = string.gmatch(w,'.')
    ++local iter = M.skip(char, 3)
    ++for w in iter do print(w) end -- => 'l', 'o'
    ++
    ++ ++ ++

    n defaults to 1 when not given.

    ++ ++ ++
    ++local w = "hello"
    ++local char = string.gmatch(w,'.')
    ++local iter = M.skip(char)
    ++for w in iter do print(w) end -- => 'e', 'l', 'l', 'o'
    ++
    ++ ++ ++

    tabulate (...)

    ++ ++

    Iterates a given iterator function and returns its values packed in an array.

    ++ ++ ++
    ++local text = 'letters'
    ++local chars = string.gmatch(text, '.')
    ++M.tabulate(chars) -- => {'l','e','t','t','e','r','s'}
    ++
    ++ ++ ++

    iterlen (...)

    ++ ++

    Returns the length of an iterator.

    ++ ++ ++
    ++local text = 'letters'
    ++local chars = string.gmatch(text, '.')
    ++M.iterlen(chars) -- => 7
    ++
    ++ ++ ++

    It consumes the iterator itself.

    ++ ++ ++
    ++local text = 'lua'
    ++local chars = string.gmatch(text, '.')
    ++M.iterlen(chars) -- => 3
    ++chars() -- => nil
    ++
    ++ ++ ++

    castArray (value)

    ++ ++

    Casts the passed-in value to an array containing the value itself.

    ++ ++ ++
    ++M.castArray(true) -- => {true}
    ++M.castArray(2) -- => {2}
    ++
    ++ ++ ++

    It leaves the given value untouched in case it is already a table.

    ++ ++ ++
    ++local t = {1}
    ++print(M.castArray(t) == t) -- => true
    ++
    ++ ++ ++

    flip (f)

    ++ ++

    Creates a function of f with arguments flipped in reverse order.

    ++ ++ ++
    ++local function f(...) return table.concat({...}) end
    ++local flipped = M.flip(f)
    ++flipped('a','b','c') -- => 'cba'
    ++
    ++ ++ ++

    nthArg (n)

    ++ ++

    Returns a function that gets the nth argument.

    ++ ++ ++
    ++local f = M.nthArg(3)
    ++f('a','b','c') -- => 'c'
    ++
    ++ ++ ++

    If n is negative, the nth argument from the end is returned.

    ++ ++ ++
    ++local f = M.nthArg(-2)
    ++f('a','b','c') -- => 'b'
    ++
    ++ ++ ++

    unary (f)

    ++ ++

    Returns a function which accepts up to one argument. It ignores any additional arguments.

    ++ ++ ++
    ++local f = M.unary(function (...) return ... end)
    ++f('a') - ==> 'a'
    ++f('a','b','c') -- => 'a'
    ++
    ++ ++ ++

    ary (f [, n = 1])

    ++

    *Aliases: nAry*.

    ++ ++

    Returns a function which accepts up to n args. It ignores any additional arguments.

    ++ ++ ++
    ++local f = M.ary(function (...) return ... end, 2)
    ++f(1,2) - ==> 1,2
    ++f(1,2,3,4) -- => 1,2
    ++
    ++ ++ ++

    If n is not given, it defaults to 1.

    ++ ++ ++
    ++local f = M.unary(function (...) return ... end)
    ++f('a','b','c') -- => 'a'
    ++
    ++ ++ ++

    noarg (f)

    ++ ++

    Returns a function with an arity of 0. The new function ignores any arguments passed to it.

    ++ ++ ++
    ++local f = M.noarg(function (x) return x or 'default' end)
    ++f(1) -- => 'default'
    ++f(function() end, 3) -- => 'default'
    ++
    ++ ++ ++

    rearg (f, indexes)

    ++ ++

    Returns a function which runs with arguments arranged according to given indexes.

    ++ ++ ++
    ++local f = M.rearg(function (...) return ... end, {5,4,3,2,1})
    ++f('a','b','c','d','e') -- => 'e','d','c','b','a'
    ++
    ++ ++ ++

    over (...)

    ++ ++

    Creates a function that invokes a set of transforms with the arguments it receives.
    ++One can use use for example to get the tuple of min and max values from a set of values

    ++ ++ ++
    ++local minmax = M.over(math.min, math.max)
    ++minmax(5,10,12,4,3) -- => {3,12}
    ++
    ++ ++ ++

    overEvery (...)

    ++ ++

    Creates a validation function. The returned function checks if all of the given predicates return truthy when invoked with the arguments it receives.

    ++ ++ ++
    ++local function alleven(...)
    ++    for i, v in ipairs({...}) do
    ++        if v%2~=0 then return false end
    ++    end
    ++    return true
    ++end
    ++
    ++local function allpositive(...)
    ++    for i, v in ipairs({...}) do
    ++        if v < 0 then return false end
    ++    end
    ++    return true
    ++end
    ++
    ++local allok = M.overEvery(alleven, allpositive)
    ++
    ++allok(2,4,-1,8) -- => false
    ++allok(10,3,2,6) -- => false
    ++allok(8,4,6,10) -- => true
    ++
    ++ ++ ++

    overSome (...)

    ++ ++

    Creates a validation function. The returned function checks if any of the given predicates return truthy when invoked with the arguments it receives.

    ++ ++ ++
    ++local function alleven(...)
    ++    for i, v in ipairs({...}) do
    ++        if v%2~=0 then return false end
    ++    end
    ++    return true
    ++end
    ++
    ++local function allpositive(...)
    ++    for i, v in ipairs({...}) do
    ++        if v < 0 then return false end
    ++    end
    ++    return true
    ++end
    ++
    ++local anyok = M.overSome(alleven,allpositive)
    ++
    ++anyok(2,4,-1,8) -- => false
    ++anyok(10,3,2,6) -- => true
    ++anyok(-1,-5,-3) -- => false
    ++
    ++ ++ ++

    overArgs (f, ...)

    ++ ++

    Creates a function that invokes f with its arguments transformed

    ++ ++ ++
    ++local function f(x, y) return x, y end
    ++local function triple(x) retun x*3 end
    ++local function square(x) retun x^2 end
    ++local new_f = M.overArgs(f, triple, square)
    ++
    ++new_f(1,2) -- => 3, 4
    ++new_f(10,10) -- => 30, 100
    ++
    ++ ++ ++

    In case the number of arguments is greater than the number of transforms, the remaining args will be left as-is.

    ++ ++ ++
    ++local function f(x, y, z) return x, y, z end
    ++local function triple(x) retun x*3 end
    ++local function square(x) retun x^2 end
    ++local new_f = M.overArgs(f, triple, square)
    ++
    ++new_f(1,2,3) -- => 3, 4, 3
    ++new_f(10,10,10) -- => 30, 100, 10
    ++
    ++ ++ ++

    converge (f, g, h)

    ++ ++

    Converges two functions into one.

    ++ ++ ++
    ++local function pow2(x) return x*x end
    ++local function pow3(x) return x*x*x end
    ++local function sum(a,b) return a+b end
    ++local poly = M.converge(sum, pow2, pow3)
    ++poly(5) -- => 150 (ie. 5*5 + 5*5*5)
    ++
    ++ ++ ++

    partial (f, ...)

    ++ ++

    Partially apply a function by filling in any number of its arguments.

    ++ ++ ++
    ++local function diff(a, b) return a - b end
    ++local diffFrom20 = M.partial(diff, 20) -- arg 'a' will be 20 by default
    ++diffFrom20(5) -- => 15
    ++
    ++ ++ ++

    The string '_' can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time.

    ++ ++ ++
    ++local function diff(a, b) return a - b end
    ++local remove5 = M.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5
    ++remove5(20) -- => 15
    ++
    ++ ++ ++

    partialRight (f, ...)

    ++ ++

    Like M.partial, it partially applies a function by filling in any number of its arguments, but from the right.

    ++ ++ ++
    ++local function concat(...) return table.concat({...},',') end
    ++local concat_right = M.partialRight(concat,'a','b','c')
    ++concat_right('d') -- => d,a,b,c
    ++
    ++concat_right = M.partialRight(concat,'a','b')
    ++concat_right('c','d') -- => c,d,a,b
    ++
    ++concat_right = M.partialRight(concat,'a')
    ++concat_right('b','c','d') -- => b,c,d,a
    ++
    ++ ++ ++

    The string '_', as always, can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time. ++In that case, the first args supplied at runtime will be used to fill the initial list of args while the remaining will be prepended.

    ++ ++ ++
    ++local function concat(...) return table.concat({...},',') end
    ++local concat_right = M.partialRight(concat,'a','_','c')
    ++concat_right('d','b') -- => b,a,d,c
    ++
    ++concat_right = M.partialRight(concat,'a','b','_')
    ++concat_right('c','d') -- => d,a,b,c
    ++
    ++concat_right = M.partialRight(concat,'_','a')
    ++concat_right('b','c','d') -- => c,d,b,a
    ++
    ++ ++ ++

    curry (f [, n_args = 2])

    ++ ++

    Curries a function. If the given function f takes multiple arguments, it returns another version of f that takes a single argument ++(the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result.

    ++ ++ ++
    ++local function sumOf3args(x,y,z) return x + y + z end
    ++local curried_sumOf3args = M.curry(sumOf3args, 3)
    ++sumOf3args(1)(2)(3)) -- => 6
    ++sumOf3args(0)(6)(9)) -- => 15
    ++
    ++ ++ ++

    n_args defaults to 2.

    ++ ++ ++
    ++local function product(x,y) return x * y end
    ++local curried_product = M.curry(product)
    ++curried_product(5)(4) -- => 20
    ++curried_product(3)(-5) -- => -15
    ++curried_product(0)(1) -- => 0
    ++
    ++ ++ ++

    time (f [, ...])

    ++ ++

    Returns the execution time of f (...) in seconds and its results.

    ++ ++ ++
    ++local function wait_count(n)
    ++    local i = 0
    ++    while i < n do i = i + 1 end
    ++    return i
    ++end
    ++
    ++local time, i = M.time(wait_count, 1e6) -- => 0.002 1000000
    ++local time, i = M.time(wait_count, 1e7) -- => 0.018 10000000
    ++
    ++ ++ ++

    [⬆]

    ++ ++

    ++

    Object functions

    ++ ++

    keys (obj)

    ++ ++

    Collects the names of an object attributes.

    ++ ++ ++
    ++M.keys({1,2,3}) -- => "{1,2,3}"
    ++M.keys({x = 0, y = 1}) -- => "{'y','x'}"
    ++
    ++ ++ ++

    values (obj)

    ++ ++

    Collects the values of an object attributes.

    ++ ++ ++
    ++M.values({1,2,3}) -- => "{1,2,3}"
    ++M.values({x = 0, y = 1}) -- => "{1,0}"
    ++
    ++ ++ ++

    path (obj, ...)

    ++ ++

    Returns the value at a given path in an object.

    ++ ++ ++
    ++local entity = {
    ++  pos = {x = 1, y = 2},
    ++  engine = {
    ++    left = {status = 'active', damage = 5},
    ++    right = {status = 'off', damage = 10}
    ++  },
    ++  boost = false
    ++}
    ++
    ++M.path(entity,'pos','x') -- => 1
    ++M.path(entity,'pos','y') -- => 2
    ++M.path(entity,'engine','left','status') -- => 'active'
    ++M.path(entity,'engine','right','damage') -- => 10
    ++M.path(entity,'boost') -- => false
    ++
    ++ ++ ++

    spreadPath (obj, ...)

    ++ ++

    Spreads object under property path onto provided object. It is similar to flattenPath, but removes object under the property path.

    ++ ++ ++
    ++local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}}
    ++M.spreadPath(obj, 'c', 'f')
    ++-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {f = {}}}
    ++
    ++ ++ ++

    flattenPath (obj, ...)

    ++ ++

    Flattens object under property path onto provided object. It is similar to spreadPath, but preserves object under the property path.

    ++ ++ ++
    ++local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}}
    ++M.spreadPath(obj, 'c', 'f')
    ++-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}}
    ++
    ++ ++ ++

    kvpairs (obj)

    ++ ++

    Converts an object to an array-list of key-value pairs.

    ++ ++ ++
    ++local obj = {x = 1, y = 2, z = 3}
    ++M.each(M.kvpairs(obj), function(v,k)
    ++    print(k, table.concat(v,','))
    ++end)
    ++
    ++-- => 1    y,2
    ++-- => 2 x,1
    ++-- => 3 z,3
    ++
    ++ ++ ++

    toObj (kvpairs)

    ++ ++

    Converts an array list of kvpairs to an object where keys are taken from the 1rst column in the kvpairs sequence, associated with values in the 2nd column.

    ++ ++ ++
    ++local list_pairs = {{'x',1},{'y',2},{'z',3}}
    ++obj = M.toObj(list_pairs)
    ++
    ++-- => {x = 1, y = 2, z = 3}
    ++
    ++ ++ ++

    invert (obj)

    ++

    *Aliases: mirror*.

    ++ ++

    Switches key-value pairs:

    ++ ++ ++
    ++M.invert {'a','b','c'} -- => "{a=1, b=2, c=3}"
    ++M.invert {x = 1, y = 2} -- => "{'x','y'}"
    ++
    ++ ++ ++

    property (key)

    ++ ++

    Returns a function that will return the key property of any passed-in object.

    ++ ++ ++
    ++local who = M.property('name')
    ++local people = {name = 'Henry'}
    ++who(people) -- => 'Henry'
    ++
    ++ ++ ++

    propertyOf (obj)

    ++ ++

    Returns a function that will return the key property of any passed-in object.

    ++ ++ ++
    ++local people = {name = 'Henry'}
    ++print(M.propertyOf(people)('name')) -- => 'Henry'
    ++
    ++ ++ ++

    toBoolean (value)

    ++ ++

    Converts a given value to a boolean.

    ++ ++ ++
    ++M.toBoolean(true) -- => true
    ++M.toBoolean(false) -- => false
    ++M.toBoolean(nil) -- => false
    ++M.toBoolean({}) -- => true
    ++M.toBoolean(1) -- => true
    ++
    ++ ++ ++

    extend (destObj, ...)

    ++ ++

    Extends a destination object with the properties of some source objects.

    ++ ++ ++
    ++M.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}"
    ++
    ++ ++ ++

    functions (obj [, recurseMt])

    ++

    *Aliases: methods*.

    ++ ++

    Returns all functions names within an object.

    ++ ++ ++
    ++M.functions(coroutine)
    ++-- => "{'yield','wrap','status','resume','running','create'}"
    ++
    ++ ++ ++

    When given recurseMt, will also include obj metatable's functions.

    ++ ++ ++
    ++local mt = {print = print}
    ++local t = {assert = assert}
    ++setmetatable(t, {__index = mt})
    ++M.functions(t, true) -- => "{'assert','print'}"
    ++
    ++ ++ ++

    clone (obj [, shallow])

    ++ ++

    Clones a given object.

    ++ ++ ++
    ++local obj = {1,2,3}
    ++local obj2 = M.clone(obj)
    ++print(obj2 == obj) -- => false
    ++print(M.isEqual(obj2, obj)) -- => true
    ++
    ++ ++ ++

    tap (obj, f)

    ++ ++

    Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. ++The passed-in interceptor should be prototyped as f(obj,...).

    ++ ++ ++
    ++local v = M.chain({1,2,3,4,5,6,7,8,9,10})
    ++  :filter(function(v) return v%2~=0 end) -- retain odd values
    ++  :tap(function(v) print('Max is', M.max(v) end) -- Tap max value
    ++  :map(function(v) return v^2 end)
    ++  :value() -- =>    Max is 89
    ++
    ++ ++ ++

    has (obj, key)

    ++ ++

    Checks if an object has a given attribute.

    ++ ++ ++
    ++M.has(_,'has') -- => true
    ++M.has(coroutine,'resume') -- => true
    ++M.has(math,'random') -- => true
    ++
    ++ ++ ++

    pick (obj, ...)

    ++

    *Aliases: choose*.

    ++ ++

    Collects whilelisted properties of a given object.

    ++ ++ ++
    ++local object = {a = 1, b = 2, c = 3}
    ++M.pick(object,'a','c') -- => "{a = 1, c = 3}"
    ++
    ++ ++ ++

    omit (obj, ...)

    ++

    *Aliases: drop*.

    ++ ++

    Omits blacklisted properties of a given object.

    ++ ++ ++
    ++local object = {a = 1, b = 2, c = 3}
    ++M.omit(object,'a','c') -- => "{b = 2}"
    ++
    ++ ++ ++

    template (obj [, template])

    ++

    *Aliases: defaults*.

    ++ ++

    Applies a template on an object, preserving existing properties.

    ++ ++ ++
    ++local obj = {a = 0}
    ++M.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}"
    ++
    ++ ++ ++

    isEqual (objA, objB [, useMt])

    ++

    *Aliases: compare, M.matches*.

    ++ ++

    Compares objects:

    ++ ++ ++
    ++M.isEqual(1,1) -- => true
    ++M.isEqual(true,false) -- => false
    ++M.isEqual(3.14,math.pi) -- => false
    ++M.isEqual({3,4,5},{3,4,{5}}) -- => false
    ++
    ++ ++ ++

    result (obj, method)

    ++ ++

    Calls an object method, passing it as a first argument the object itself.

    ++ ++ ++
    ++M.result('abc','len') -- => 3
    ++M.result({'a','b','c'},table.concat) -- => 'abc'
    ++
    ++ ++ ++

    isTable (t)

    ++ ++

    Is the given argument an object (i.e a table) ?

    ++ ++ ++
    ++M.isTable({}) -- => true
    ++M.isTable(math) -- => true
    ++M.isTable(string) -- => true
    ++
    ++ ++ ++

    isCallable (obj)

    ++ ++

    Is the given argument callable ?

    ++ ++ ++
    ++M.isCallable(print) -- => true
    ++M.isCallable(function() end) -- => true
    ++M.isCallable(setmetatable({},{__index = string}).upper) -- => true
    ++M.isCallable(setmetatable({},{__call = function() return end})) -- => true
    ++
    ++ ++ ++

    isArray (obj)

    ++ ++

    Is the given argument an array (i.e. a sequence) ?

    ++ ++ ++
    ++M.isArray({}) -- => true
    ++M.isArray({1,2,3}) -- => true
    ++M.isArray({'a','b','c'}) -- => true
    ++
    ++ ++ ++

    isIterable (obj)

    ++ ++

    Checks if the given object is iterable with pairs.

    ++ ++ ++
    ++M.isIterable({}) -- => true
    ++M.isIterable(function() end) -- => false
    ++M.isIterable(false) -- => false
    ++M.isIterable(1) -- => false
    ++
    ++ ++ ++

    type (obj)

    ++ ++

    Extends Lua's type function. It returns the type of the given object and also recognises 'file' userdata

    ++ ++ ++
    ++M.type('string') -- => 'string'
    ++M.type(table) -- => 'table'
    ++M.type(function() end) -- => 'function'
    ++M.type(io.open('f','w')) -- => 'file'
    ++
    ++ ++ ++

    isEmpty ([obj])

    ++ ++

    Is the given argument empty ?

    ++ ++ ++
    ++M.isEmpty('') -- => true
    ++M.isEmpty({})  -- => true
    ++M.isEmpty({'a','b','c'}) -- => false
    ++
    ++ ++ ++

    isString (obj)

    ++ ++

    Is the given argument a string ?

    ++ ++ ++
    ++M.isString('') -- => true
    ++M.isString('Hello') -- => false
    ++M.isString({}) -- => false
    ++
    ++ ++ ++

    isFunction (obj)

    ++ ++

    Is the given argument a function ?

    ++ ++ ++
    ++M.isFunction(print) -- => true
    ++M.isFunction(function() end) -- => true
    ++M.isFunction({}) -- => false
    ++
    ++ ++ ++

    isNil (obj)

    ++ ++

    Is the given argument nil ?

    ++ ++ ++
    ++M.isNil(nil) -- => true
    ++M.isNil() -- => true
    ++M.isNil({}) -- => false
    ++
    ++ ++ ++

    isNumber (obj)

    ++ ++

    Is the given argument a number ?

    ++ ++ ++
    ++M.isNumber(math.pi) -- => true
    ++M.isNumber(math.huge) -- => true
    ++M.isNumber(0/0) -- => true
    ++M.isNumber() -- => false
    ++
    ++ ++ ++

    isNaN (obj)

    ++ ++

    Is the given argument NaN ?

    ++ ++ ++
    ++M.isNaN(1) -- => false
    ++M.isNaN(0/0) -- => true
    ++
    ++ ++ ++

    isFinite (obj)

    ++ ++

    Is the given argument a finite number ?

    ++ ++ ++
    ++M.isFinite(99e99) -- => true
    ++M.isFinite(math.pi) -- => true
    ++M.isFinite(math.huge) -- => false
    ++M.isFinite(1/0) -- => false
    ++M.isFinite(0/0) -- => false
    ++
    ++ ++ ++

    isBoolean (obj)

    ++ ++

    Is the given argument a boolean ?

    ++ ++ ++
    ++M.isBoolean(true) -- => true
    ++M.isBoolean(false) -- => true
    ++M.isBoolean(1==1) -- => true
    ++M.isBoolean(print) -- => false
    ++
    ++ ++ ++

    isInteger (obj)

    ++ ++

    Is the given argument an integer ?

    ++ ++ ++
    ++M.isInteger(math.pi) -- => false
    ++M.isInteger(1) -- => true
    ++M.isInteger(-1) -- => true
    ++
    ++ ++ ++

    [⬆]

    ++ ++

    ++

    Chaining

    ++ ++

    Method chaining (also known as name parameter idiom), is a technique for invoking consecutively method calls in object-oriented style. ++Each method returns an object, and method calls are chained together. ++Moses offers chaining for your perusal.

    ++ ++

    Let's use chaining to get the count of evey single word in some lyrics (case won't matter here).

    ++ ++ ++ ++
    ++local lyrics = {
    ++  "I am a lumberjack and I am okay",
    ++  "I sleep all night and I work all day",
    ++  "He is a lumberjack and he is okay",
    ++  "He sleeps all night and he works all day"
    ++}
    ++
    ++-- split a text into words
    ++local function words(line)
    ++  local t = {}
    ++  for w in line:gmatch('(%w+)') do t[#t+1] = w end
    ++  return t
    ++end
    ++
    ++local stats = M.chain(lyrics)
    ++  :map(words)
    ++  :flatten()
    ++  :countBy(string.lower)
    ++  :value()
    ++
    ++-- => "{
    ++-- =>    sleep = 1, night = 2, works = 1, am = 2, is = 2,
    ++-- =>    he = 2, and = 4, I = 4, he = 2, day = 2, a = 2,
    ++-- =>    work = 1, all = 4, okay = 2
    ++-- =>  }"
    ++
    ++ ++ ++

    For convenience, you can also use M(value) to start chaining methods, instead of M.chain(value).

    ++ ++

    Note that one can use :value() to unwrap a chained object.

    ++ ++ ++
    ++local t = {1,2,3}
    ++print(_(t):value() == t) -- => true
    ++
    ++ ++ ++

    [⬆]

    ++ ++

    ++

    Import

    ++ ++

    All library functions can be imported in a context using import into a specified context.

    ++ ++ ++
    ++local context = {}
    ++M.import(context)
    ++
    ++context.each({1,2,3},print)
    ++
    ++-- => 1 1
    ++-- => 2 2
    ++-- => 3 3
    ++
    ++ ++ ++

    When no context was provided, it defaults to the current environment, _ENV or _G.

    ++ ++ ++
    ++M.import()
    ++
    ++each({1,2,3},print)
    ++
    ++-- => 1 1
    ++-- => 2 2
    ++-- => 3 3
    ++
    ++ ++ ++

    Passing noConflict argument leaves untouched conflicting keys while importing into the context.

    ++ ++ ++
    ++local context = {each = 1}
    ++M.import(context, true)
    ++
    ++print(context.each) -- => 1
    ++context.eachi({1,2,3},print)
    ++
    ++-- => 1 1
    ++-- => 2 2
    ++-- => 3 3
    ++
    ++ ++ ++

    [⬆] ++ ++

    ++
    ++
    ++generated by LDoc 1.4.6 ++Last updated 2019-04-01 23:55:17 ++
    ++
    ++ ++ +diff --git a/extra/moses/doc/topics/tutorial.md.html b/extra/moses/doc/topics/tutorial.md.html +deleted file mode 100644 +index 1acd09f..0000000 +--- a/extra/moses/doc/topics/tutorial.md.html ++++ /dev/null +@@ -1,2349 +0,0 @@ +- +- +- +- +- Moses documentation +- +- +- +- +-
    +- +-
    +- +-
    +-
    +-
    +- +- +-
    +- +- +- +- +- +- +-
    +- +- Moses: a utility-belt library for functional programming in Lua

    +- +-

    Moses is a Lua utility library which provides support for functional programming. +-It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more.
    +-Moses was deeply inspired by Underscore.js.

    +- +-

    Table of Contents

    +- +- +- +- +-

    Adding Moses to your project

    +- +-

    Drop the file moses.lua into your project and add it to your code with the require function:

    +- +-
    +-local _ = require ("moses")
    +-
    +- +- +-

    Note: Lua purists tend to use “_” to design a “dummy variable”. Here, the usage of this underscore is quite idiomatic and refers to the name Underscore, the JS library from which Moses takes inspiration.

    +- +-

    Moses‘ provides a large set of functions that can be classified into four categories:

    +- +-
      +-
    • Table functions, which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part,
    • +-
    • Array functions, meant for array lists (or sequences),
    • +-
    • Utility functions,
    • +-
    • Object functions.
    • +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Table functions

    +- +-

    clear (t)

    +- +-

    Clears a table. All its values becomes nil. It returns the passed-in table.

    +- +-
    +-local t = _.clear({1,2,'hello',true}) -- => {}
    +-
    +- +- +-

    each (t, f, …)

    +- +-

    Aliases: _.forEach.

    +- +-

    Iterates over each key-value pair in table.

    +- +-
    +-_.each({1,2,3},print)
    +-
    +--- => 1 1
    +--- => 2 2
    +--- => 3 3
    +-
    +- +- +-

    The table can be map-like (array part and hash-part).

    +- +-
    +-_.each({one = 1, two = 2, three = 3},print)
    +-
    +--- => one 1
    +--- => two 2
    +--- => three 3
    +-
    +- +- +-

    Can index and assign in an outer table or in the passed-in table:

    +- +-
    +-t = {'a','b','c'}
    +-_.each(t,function(i,v)
    +-  t[i] = v:rep(2)
    +-  print(t[i])
    +-end)
    +-
    +--- => 1 aa
    +--- => 2 bb
    +--- => 3 cc
    +-
    +- +- +-

    eachi (t, f, …)

    +- +-

    Aliases: _.forEachi.

    +- +-

    Iterates only on integer keys in a sparse array table.

    +- +-
    +-_.eachi({1,2,3},print)
    +-
    +--- => 1 1
    +--- => 2 2
    +--- => 3 3
    +-
    +- +- +-

    The given array can be sparse, or even have a hash-like part.

    +- +-
    +-local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5}
    +-_.eachi(t,function(i,v)
    +-  print(i,v)
    +-end)
    +-
    +--- => -1 6
    +--- => 0 1
    +--- => 1 3
    +--- => 2 5
    +-
    +- +- +-

    at (t, …)

    +- +-

    Collects all values at some specific keys and returns them in an array.

    +- +-
    +-local t = {4,5,6}
    +-_.at(t,1,3) -- => "{4,6}"
    +-
    +-local t = {a = 4, bb = true, ccc = false}
    +-_.at(t,'a', 'ccc') -- => "{4, false}"
    +-
    +- +- +-

    count (t, value)

    +- +-

    Counts the number of occurences of a given value in a table.

    +- +-
    +-_.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2
    +-_.count({1,1,2,3,3,3,2,4,3,2},2) -- => 2
    +-_.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4
    +-_.count({false, false, true},false) -- => 2
    +-_.count({false, false, true},true) -- => 1
    +-
    +- +- +-

    Returns the size of the list in case no value was provided.

    +- +-
    +-_.count({1,1,2,3,3}) -- => 5
    +-
    +- +- +-

    countf (t, f, …)

    +- +-

    Count the number of occurences of all values passing an iterator test.

    +- +-
    +-_.countf({1,2,3,4,5,6}, function(i,v)
    +-  return v%2==0
    +-end) -- => 3
    +-
    +-_.countf({print, pairs, os, assert, ipairs}, function(i,v)
    +-  return type(v)=='function'
    +-end) -- => 4
    +-
    +- +- +-

    cycle (t, n)

    +- +-

    Aliases: _.loop.

    +- +-

    Returns a function which iterates on each key-value pair in a given table (similarly to _.each), except that it restarts iterating again n times. +-If n is not provided, it defaults to 1.

    +- +-
    +-local t = {'a,'b','c'}
    +-for k,v in _.cycle(t, 2) do
    +-  print(k,v)
    +-end
    +-
    +--- => 1 'a'
    +--- => 2 'b'
    +--- => 3 'c'
    +--- => 1 'a'
    +--- => 2 'b'
    +--- => 3 'c'
    +-
    +- +- +-

    Supports array-like tables and map-like tables.

    +- +-
    +-local t = {x = 1, y = 2, z = 3}
    +-for k,v in _.cycle(t) do
    +-  print(k,v)
    +-end
    +-
    +--- => y    2
    +--- => x 1
    +--- => z 3
    +-
    +- +- +-

    map (t, f, …)

    +- +-

    Aliases: _.collect.

    +- +-

    Executes a function on each key-value pairs.

    +- +-
    +-_.map({1,2,3},function(i,v)
    +-  return v+10
    +-end) -- => "{11,12,13}"
    +-
    +- +- +-
    +-_.map({a = 1, b = 2},function(k,v)
    +-  return k..v
    +-end) -- => "{a = 'a1', b = 'b2'}"
    +-
    +- +- +-

    It also maps key-value pairs to key-value pairs

    +- +-
    +-_.map({a = 1, b = 2},function(k,v)
    +-  return k..k, v*2
    +-end) -- => "{aa = 2, bb = 4}"
    +-
    +- +- +-

    reduce (t, f, state)

    +- +-

    Aliases: _.inject, _.foldl.

    +- +-

    Can sums all values in a table.

    +- +-
    +-_.reduce({1,2,3,4},function(memo,v)
    +-  return memo+v
    +-end) -- => 10
    +-
    +- +- +-

    Or concatenates all values.

    +- +-
    +-_.reduce({'a','b','c','d'},function(memo,v)
    +-  return memo..v
    +-end) -- => abcd    
    +-
    +- +- +-

    reduceby (t, f, state, pred, …)

    +- +-

    Reduces a table considering only values matching a predicate. +-For example,let us define a set of values.

    +- +-
    +-local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9}
    +-
    +- +- +-

    We can also define some predicate functions.

    +- +-
    +--- predicate for negative values
    +-local function neg(_, v) return v<=0 end
    +-
    +--- predicate for positive values
    +-local function pos(_, v) return v>=0 end
    +-
    +- +- +-

    Then we can perform reduction considering only negative values :

    +- +-
    +-_.reduceby(val, function(memo,v)
    +-  return memo+v
    +-end, 0, neg) -- => -17
    +-
    +- +- +-

    Or only positive values :

    +- +-
    +-_.reduceby(val, function(memo,v)
    +-  return memo+v
    +-end, 0, pos) -- => 19
    +-
    +- +- +-

    reduceRight (t, f, state)

    +- +-

    Aliases: _.injectr, _.foldr.

    +- +-

    Similar to _.reduce, but performs from right to left.

    +- +-
    +-local initial_state = 256
    +-_.reduceRight({1,2,4,16},function(memo,v)
    +-  return memo/v
    +-end,initial_state) -- => 2
    +-
    +- +- +-

    mapReduce (t, f, state)

    +- +-

    Aliases: _.mapr.

    +- +-

    Reduces while saving intermediate states.

    +- +-
    +-_.mapReduce({'a','b','c'},function(memo,v)
    +-  return memo..v
    +-end) -- => "{'a', 'ab', 'abc'}"
    +-
    +- +- +-

    mapReduceRight (t, f, state)

    +- +-

    Aliases: _.maprr.

    +- +-

    Reduces from right to left, while saving intermediate states.

    +- +-
    +-_.mapReduceRight({'a','b','c'},function(memo,v)
    +-  return memo..v
    +-end) -- => "{'c', 'cb', 'cba'}"
    +-
    +- +- +-

    include (t, value)

    +- +-

    Aliases: _.any, _.some, _.contains.

    +- +-

    Looks for a value in a table.

    +- +-
    +-_.include({6,8,10,16,29},16) -- => true
    +-_.include({6,8,10,16,29},1) -- => false
    +-
    +-local complex_table = {18,{2,{3}}}
    +-local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    +-_.include(collection, complex_table) -- => true
    +-
    +- +- +-

    Handles iterator functions.

    +- +-
    +-local function isUpper(v) return v:upper()== v end
    +-_.include({'a','B','c'},isUpper) -- => true
    +-
    +- +- +-

    detect (t, value)

    +- +-

    Returns the index of a value in a table.

    +- +-
    +-_.detect({6,8,10,16},8) -- => 2
    +-_.detect({nil,true,0,true,true},false) -- => nil
    +-
    +-local complex_table = {18,{2,6}}
    +-local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    +-_.detect(collection, complex_table) -- => 2
    +-
    +- +- +-

    Handles iterator functions.

    +- +-
    +-local function isUpper(v)
    +-  return v:upper()==v
    +-end
    +-_.detect({'a','B','c'},isUpper) -- => 2
    +-
    +- +- +-

    where (t, props)

    +- +-

    Looks through a table and returns all the values that matches all of the key-value pairs listed in props.

    +- +-
    +-local tA = {a = 1, b = 2, c = 0}
    +-local tB = {a = 1, b = 4, c = 1}
    +-local tC = {a = 4, b = 4, c = 3}
    +-local tD = {a = 1, b = 2, c = 3}
    +-local found = _.where({tA, tB, tC, tD}, {a = 1})
    +-
    +--- => found = {tA, tB, tD}
    +-
    +-found = _.where({tA, tB, tC, tD}, {b = 4})
    +-
    +--- => found = {tB, tC}
    +-
    +-found = _.where({tA, tB, tC, tD}, {b = 4, c = 3})
    +-
    +--- => found = {tC}
    +-
    +- +- +-

    findWhere (t, props)

    +- +-

    Looks through a table and returns the first value that matches all of the key-value pairs listed in props.

    +- +-
    +-local a = {a = 1, b = 2, c = 3}
    +-local b = {a = 2, b = 3, d = 4}
    +-local c = {a = 3, b = 4, e = 5}
    +-_.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true
    +-
    +- +- +-

    select (t, f, …)

    +- +-

    Aliases: _.filter.

    +- +-

    Collects values passing a validation test.

    +- +-
    +--- Even values
    +-_.select({1,2,3,4,5,6,7}, function(key,value)
    +-  return (value%2==0)
    +-end) -- => "{2,4,6}"
    +-
    +--- Odd values
    +-_.select({1,2,3,4,5,6,7}, function(key,value)
    +-  return (value%2~=0)
    +-end) -- => "{1,3,5,7}"
    +-
    +- +- +-

    reject (t, f, …)

    +- +-

    Aliases: _.reject.

    +- +-

    Removes all values failing a validation test:

    +- +-
    +-_.reject({1,2,3,4,5,6,7}, function(key,value)
    +-  return (value%2==0)
    +-end) -- => "{1,3,5,7}"
    +-
    +-_.reject({1,2,3,4,5,6,7}, function(key,value)
    +-  return (value%2~=0)
    +-end) -- => "{2,4,6}"
    +-
    +- +- +-

    all (t, f, …)

    +- +-

    Aliases: _.every.

    +- +-

    Checks whether or not all elements pass a validation test.

    +- +-
    +-_.all({2,4,6}, function(key,value)
    +-  return (value%2==0)
    +-end) -- => true
    +-
    +- +- +-

    invoke (t, method, …)

    +- +-

    Invokes a given function on each value in a table

    +- +-
    +-_.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}"
    +-
    +- +- +-

    Can reference the method of the same name in each value.

    +- +-
    +-local a = {}
    +-function a:call() return 'a' end
    +-local b, c, d = {}, {}, {}
    +-b.call, c.call, d.call = a.call, a.call, a.call
    +-
    +-_.invoke({a,b,c,d},'call') -- => "{'a','a','a','a'}"
    +-
    +- +- +-

    pluck (t, property)

    +- +-

    Fetches all values indxed with specific key in a table of objects.

    +- +-
    +-local peoples = {
    +-  {name = 'John', age = 23},{name = 'Peter', age = 17},
    +-  {name = 'Steve', age = 15},{age = 33}}
    +-
    +-_.pluck(peoples,'age') -- => "{23,17,15,33}"
    +-_.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}"
    +-
    +- +- +-

    max (t, transform, …)

    +- +-

    Returns the maximum value in a collection.

    +- +-
    +-_.max {1,2,3} -- => 3
    +-_.max {'a','b','c'} -- => 'c'
    +-
    +- +- +-

    Can take an iterator function to extract a specific property.

    +- +-
    +-local peoples = {
    +-  {name = 'John', age = 23},{name = 'Peter', age = 17},
    +-  {name = 'Steve', age = 15},{age = 33}}
    +-_.max(peoples,function(people) return people.age end) -- => 33
    +-
    +- +- +-

    min (t, transform, …)

    +- +-

    Returns the minimum value in a collection.

    +- +-
    +-_.min {1,2,3} -- => 1
    +-_.min {'a','b','c'} -- => 'a'
    +-
    +- +- +-

    Can take an iterator function to extract a specific property.

    +- +-
    +-local peoples = {
    +-  {name = 'John', age = 23},{name = 'Peter', age = 17},
    +-  {name = 'Steve', age = 15},{age = 33}}
    +-_.min(peoples,function(people) return people.age end) -- => 15
    +-
    +- +- +-

    shuffle (t, seed)

    +- +-

    Shuffles a collection.

    +- +-
    +-local list = _.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}"
    +-_.each(list,print)
    +-
    +- +- +-

    same (a, b)

    +- +-

    Tests whether or not all values in each of the passed-in tables exists in both tables.

    +- +-
    +-local a = {'a','b','c','d'}
    +-local b = {'b','a','d','c'}
    +-_.same(a,b) -- => true
    +-
    +-b[#b+1] = 'e'
    +-_.same(a,b) -- => false
    +-
    +- +- +-

    sort (t, comp)

    +- +-

    Sorts a collection.

    +- +-
    +-_.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}"
    +-
    +- +- +-

    Handles custom comparison functions.

    +- +-
    +-_.sort({'b','a','d','c'}, function(a,b)
    +-  return a:byte() > b:byte()
    +-end) -- => "{'d','c','b','a'}"
    +-
    +- +- +-

    sortBy (t, transform, comp)

    +- +-

    Sorts items in a collection based on the result of running a transform function through every item in the collection.

    +- +-
    +-local r = _.sortBy({1,2,3,4,5}, math.sin)
    +-print(table.concat(r,','))
    +-
    +--- => {5,4,3,1,2}
    +-
    +- +- +-

    The transform function can also be a string name property.

    +- +-
    +-local people ={
    +-    {name = 'albert', age = 40},
    +-    {name = 'louis', age = 55},
    +-    {name = 'steve', age = 35},
    +-    {name = 'henry', age = 19},
    +-}
    +-local r = _.sortBy(people, 'age')
    +-_.each(r, function(__,v) print(v.age, v.name)  end)
    +-
    +--- => 19   henry
    +--- => 35    steve
    +--- => 40    albert
    +--- => 55    louis
    +-
    +- +- +-

    As seen above, the defaut comparison function is the ‘<’ operator. For example, let us supply a different one to sort +-the list of people by decreasing age order :

    +- +-
    +-local people ={
    +-    {name = 'albert', age = 40},
    +-    {name = 'louis', age = 55},
    +-    {name = 'steve', age = 35},
    +-    {name = 'henry', age = 19},
    +-}
    +-local r = _.sortBy(people, 'age', function(a,b) return a > b end)
    +-_.each(r, function(__,v) print(v.age, v.name)  end)
    +-
    +--- => 55   louis
    +--- => 40    albert
    +--- => 35    steve
    +--- => 19    henry
    +-
    +- +- +-

    The transform function defaults to _.indentity and in that case, _.sortBy behaves like _.sort.

    +- +-
    +-local r = _.sortBy({1,2,3,4,5})
    +-print(table.concat(r,','))
    +-
    +--- => {1,2,3,4,5}
    +-
    +- +- +-

    groupBy (t, iter, …)

    +- +-

    Groups values in a collection depending on their return value when passed to a predicate test.

    +- +-
    +-_.groupBy({0,1,2,3,4,5,6},function(i,value)
    +-  return value%2==0 and 'even' or 'odd'
    +-end) -- => "{odd = {1,3,5}, even = {0,2,4,6}}"
    +-
    +-_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value)
    +-  return type(value)
    +-end) -- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}"      
    +-
    +- +- +-

    countBy (t, iter, …)

    +- +-

    Splits a table in subsets and provide the count for each subset.

    +- +-
    +-_.countBy({0,1,2,3,4,5,6},function(i,value)
    +-  return value%2==0 and 'even' or 'odd'
    +-end) -- => "{odd = 3, even = 4}"
    +-
    +- +- +-

    size (…)

    +- +-

    When given a table, provides the count for the very number of values in that table.

    +- +-
    +-_.size {1,2,3} -- => 3
    +-_.size {one = 1, two = 2} -- => 2
    +-
    +- +- +-

    When given a vararg list of argument, returns the count of these arguments.

    +- +-
    +-_.size(1,2,3) -- => 3
    +-_.size('a','b',{}, function() end) -- => 4
    +-
    +- +- +-

    containsKeys (t, other)

    +- +-

    Checks whether a table has all the keys existing in another table.

    +- +-
    +-_.contains({1,2,3,4},{1,2,3}) -- => true
    +-_.contains({1,2,'d','b'},{1,2,3,5}) -- => true
    +-_.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true
    +-
    +- +- +-

    sameKeys (tA, tB)

    +- +-

    Checks whether both tables features the same keys:

    +- +-
    +-_.sameKeys({1,2,3,4},{1,2,3}) -- => false
    +-_.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true
    +-_.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false
    +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Array functions

    +- +-

    sample (array, n, seed)

    +- +-

    Samples n values from array.

    +- +-
    +-local array = _.range(1,20)
    +-local sample = _.sample(array, 3)
    +-print(table.concat(sample,','))
    +-
    +--- => {12,11,15}
    +-
    +- +- +-

    n defaults to 1. In that case, a single value will be returned.

    +- +-
    +-local array = _.range(1,20)
    +-local sample = _.sample(array)
    +-print(sample)
    +-
    +--- => 12
    +-
    +- +- +-

    An optional 3rd argument seed can be passed for deterministic random sampling.

    +- +-

    sampleProb (array, prob, seed)

    +- +-

    Returns an array of values randomly selected from a given array. +-In case seed is provided, it is used for deterministic sampling.

    +- +-
    +-local array = _.range(1,20)
    +-local sample = _.sampleProb(array, 0.2)
    +-print(table.concat(sample,','))
    +-
    +--- => 5,11,12,15
    +-
    +-sample = _.sampleProb(array, 0.2, os.time())
    +-print(table.concat(sample,','))
    +-
    +--- => 1,6,10,12,15,20 (or similar)
    +-
    +- +- +-

    toArray (…)

    +- +-

    Converts a vararg list of arguments to an array.

    +- +-
    +-_.toArray(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}"
    +-
    +- +- +-

    find (array, value, from)

    +- +-

    Looks for a value in a given array and returns the position of the first occurence.

    +- +-
    +-_.find({{4},{3},{2},{1}},{3}) -- => 2
    +-
    +- +- +-

    It can also start the search at a specific position in the array:

    +- +-
    +--- search value 4 starting from index 3
    +-_.find({1,4,2,3,4,5},4,3) -- => 5
    +-
    +- +- +-

    reverse (array)

    +- +-

    Reverses an array.

    +- +-
    +-_.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}"
    +-
    +- +- +-

    fill (array, value, i, j)

    +- +-

    Replaces all elements in a given array with a given value.

    +- +-
    +-local array = _.range(1,5)
    +-_.fill(array, 0) -- => {0,0,0,0,0}
    +-
    +- +- +-

    It can start replacing value at a specific index.

    +- +-
    +-local array = _.range(1,5)
    +-_.fill(array,0,3) -- => {1,2,0,0,0}
    +-
    +- +- +-

    It can replace only values within a specific range.

    +- +-
    +-local array = _.range(1,5)
    +-_.fill(array,0,2,4) -- => {1,0,0,0,5}
    +-
    +- +- +-

    In case the upper bound index i greather than the array size, it will enlarge the array.

    +- +-
    +-local array = _.range(1,5)
    +-_.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0}
    +-
    +- +- +-

    selectWhile (array, f, …

    +- +-

    Aliases: _.takeWhile.

    +- +-

    Collects values as long as they pass a given test. Stops on the first non-passing test.

    +- +-
    +-_.selectWhile({2,4,5,8}, function(i,v)
    +-  return v%2==0
    +-end) -- => "{2,4}"
    +-
    +- +- +-

    dropWhile (array, f, …

    +- +-

    Aliases: _.rejectWhile.

    +- +-

    Removes values as long as they pass a given test. Stops on the first non-passing test.

    +- +-
    +-_.dropWhile({2,4,5,8}, function(i,v)
    +-  return v%2==0
    +-end) -- => "{5,8}"
    +-
    +- +- +-

    sortedIndex (array, value, comp, sort)

    +- +-

    Returns the index at which a value should be inserted to preserve order.

    +- +-
    +-_.sortedIndex({1,2,3},4) -- => 4
    +-
    +- +- +-

    Can take a custom comparison functions.

    +- +-
    +-local comp = function(a,b) return a<b end
    +-_.sortedIndex({-5,0,4,4},3,comp) -- => 3
    +-
    +- +- +-

    indexOf (array, value)

    +- +-

    Returns the index of a value in an array.

    +- +-
    +-_.indexOf({1,2,3},2) -- => 2
    +-
    +- +- +-

    lastIndexOf (array, value)

    +- +-

    Returns the index of the last occurence of a given value in an array.

    +- +-
    +-_.lastIndexOf({1,2,2,3},2) -- => 3
    +-
    +- +- +-

    findIndex (array, predicate, …)

    +- +-

    Returns the first index at which a predicate passes a truth test.

    +- +-
    +-local array = {1,2,3,4,5,6}
    +-local function multipleOf3(__,v) return v%3==0 end
    +-_.findIndex(array, multipleOf3) -- => 3
    +-
    +- +- +-

    findLastIndex (array, predicate, …)

    +- +-

    Returns the last index at which a predicate passes a truth test.

    +- +-
    +-local array = {1,2,3,4,5,6}
    +-local function multipleOf3(__,v) return v%3==0 end
    +-_.findLastIndex(array, multipleOf3) -- => 6
    +-
    +- +- +-

    addTop (array, …)

    +- +-

    Adds given values at the top of an array. The latter values bubbles at the top.

    +- +-
    +-local array = {1}
    +-_.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}"
    +-
    +- +- +-

    push (array, …)

    +- +-

    Adds given values at the end of an array.

    +- +-
    +-local array = {1}
    +-_.push(array,1,2,3,4) -- => "{1,1,2,3,4}"
    +-
    +- +- +-

    pop (array, n)

    +- +-

    Aliases: _.shift.

    +- +-

    Removes and returns the first value in an array.

    +- +-
    +-local array = {1,2,3}
    +-local pop = _.pop(array) -- => "pop = 1", "array = {2,3}"
    +-
    +- +- +-

    unshift (array, n)

    +- +-

    Removes and returns the last value in an array.

    +- +-
    +-local array = {1,2,3}
    +-local value = _.unshift(array) -- => "value = 3", "array = {1,2}"
    +-
    +- +- +-

    pull (array, …)

    +- +-

    Aliases: _.remove.

    +- +-

    Removes all provided values from a given array.

    +- +-
    +-_.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}"
    +-
    +- +- +-

    removeRange (array, start, finish)

    +- +-

    Aliases: _.rmRange, _.chop.

    +- +-

    Trims out all values index within a range.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.removeRange(array, 3,8) -- => "{1,2,9}"
    +-
    +- +- +-

    chunk (array, f, …)

    +- +-

    Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return +-value of f(key,value,…). Consecutive elements which return the same value are aggregated together.

    +- +-
    +-local t = {1,1,2,3,3,4}
    +-_.chunk(t, function(k,v) return v%2==0 end) -- => "{{1,1},{2},{3,3},{4}}"
    +-
    +- +- +-

    slice (array, start, finish)

    +- +-

    Aliases: _.sub.

    +- +-

    Slices and returns a part of an array.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.slice(array, 3,6) -- => "{3,4,5,6}"
    +-
    +- +- +-

    first (array, n)

    +- +-

    Aliases: _.head, _.take.

    +- +-

    Returns the first N elements in an array.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.first(array,3) -- => "{1,2,3}"
    +-
    +- +- +-

    initial (array, n)

    +- +-

    Excludes the last N elements in an array.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.initial(array,5) -- => "{1,2,3,4}"
    +-
    +- +- +-

    last (array, n)

    +- +-

    Aliases: _.skip.

    +- +-

    Returns the last N elements in an array.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.last(array,3) -- => "{7,8,9}"
    +-
    +- +- +-

    rest (array, index)

    +- +-

    Aliases: _.tail.

    +- +-

    Trims out all values indexed before index.

    +- +-
    +-local array = {1,2,3,4,5,6,7,8,9}
    +-_.rest(array,6) -- => "{6,7,8,9}"
    +-
    +- +- +-

    nth (array, index)

    +- +-

    Returns the value at index.

    +- +-
    +-local array = {1,2,3,4,5,6}
    +-_.nth(array,3) -- => "3"
    +-
    +- +- +-

    compact (array)

    +- +-

    Trims out all falsy values.

    +- +-
    +-_.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}"
    +-
    +- +- +-

    flatten (array, shallow)

    +- +-

    Flattens a nested array.

    +- +-
    +-_.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}"
    +-
    +- +- +-

    When given arg “shallow”, flatten only at the first level.

    +- +-
    +-_.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}"
    +-
    +- +- +-

    difference (array, array2)

    +- +-

    Aliases: _.without, _.diff.

    +- +-

    Returns values in the given array not present in a second array.

    +- +-
    +-local array = {1,2,'a',4,5}
    +-_.difference(array,{1,'a'}) -- => "{2,4,5}"
    +-
    +- +- +-

    union (…)

    +- +-

    Produces a duplicate-free union of all passed-in arrays.

    +- +-
    +-local A = {'a'}
    +-local B = {'a',1,2,3}
    +-local C = {2,10}
    +-_.union(A,B,C) -- => "{'a',1,2,3,10}"
    +-
    +- +- +-

    intersection (array, …)

    +- +-

    Returns the intersection (common-part) of all passed-in arrays:

    +- +-
    +-local A = {'a'}
    +-local B = {'a',1,2,3}
    +-local C = {2,10,1,'a'}
    +-_.intersection(A,B,C) -- => "{'a',2,1}"
    +-
    +- +- +-

    symmetricDifference (array, array2)

    +- +-

    Aliases: _.symdiff,_.xor.

    +- +-

    Returns values in the first array not present in the second and also values in the second array not present in the first one.

    +- +-
    +-local array = {1,2,3}
    +-local array2 = {1,4,5}
    +-_.symmetricDifference(array, array2) -- => "{2,3,4,5}"
    +-
    +- +- +-

    unique (array)

    +- +-

    Aliases: _.uniq.

    +- +-

    Makes an array duplicate-free.

    +- +-
    +-_.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}"
    +-
    +- +- +-

    isunique (array)

    +- +-

    Aliases: _.isuniq.

    +- +-

    Checks if a given array contains no duplicate value.

    +- +-
    +-_.isunique({1,2,3,4,5}) -- => true
    +-_.isunique({1,2,3,4,4}) -- => false
    +-
    +- +- +-

    zip (…)

    +- +-

    Aliases: _.transpose.

    +- +-

    Zips values from different arrays, on the basis on their common keys.

    +- +-
    +-local names = {'Bob','Alice','James'}
    +-local ages = {22, 23}
    +-_.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}"
    +-
    +- +- +-

    append (array, other)

    +- +-

    Appends two arrays.

    +- +-
    +-_.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}"
    +-
    +- +- +-

    interleave (…)

    +- +-

    Interleaves values from passed-in arrays.

    +- +-
    +-t1 = {1, 2, 3}
    +-t2 = {'a', 'b', 'c'}
    +-_.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}"
    +-
    +- +- +-

    interpose (value, array)

    +- +-

    Interposes a value between consecutive values in an arrays.

    +- +-
    +-_.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}"
    +-
    +- +- +-

    range (…)

    +- +-

    Generates an arithmetic sequence.

    +- +-
    +-_.range(1,4) -- => "{1,2,3,4}"
    +-
    +- +- +-

    In case a single value is provided, it generates a sequence from 0 to that value.

    +- +-
    +-_.range(3) -- => "{0,1,2,3}"
    +-
    +- +- +-

    The incremental step can also be provided as third argument.

    +- +-
    +-_.range(0,2,0.7) -- => "{0,0.7,1.4}"
    +-
    +- +- +-

    rep (value, n)

    +- +-

    Generates a list of n repetitions of a value.

    +- +-
    +-_.rep(4,3) -- => "{4,4,4}"
    +-
    +- +- +-

    partition (array, n, pad)

    +- +-

    Aliases: _.part.

    +- +-

    Returns an iterator function for partitions of a given array.

    +- +-
    +-local t = {1,2,3,4,5,6}
    +-for p in _.partition(t,2) do
    +-  print(table.concat(p, ','))
    +-end
    +-
    +--- => 1,2
    +--- => 3,4
    +--- => 5,6
    +-
    +-local t = {1,2,3,4,5,6}
    +-for p in _.partition(t,4) do
    +-  print(table.concat(p, ','))
    +-end
    +-
    +--- => 1,2,3,4
    +--- => 5,6
    +-
    +- +- +-

    In case the last partition has less elements than desired, a 3rd argument can be supplied to adjust the partition size.

    +- +-
    +-local t = {1,2,3,4,5,6}
    +-for p in _.partition(t,4,0) do
    +-  print(table.concat(p, ','))
    +-end
    +-
    +--- => 1,2,3,4
    +--- => 5,6,0,0
    +-
    +- +- +-

    sliding (array, n, pad)

    +- +-

    Returns an iterator function which provides overlapping subsequences of a given array.

    +- +-
    +-local t = {1,2,3,4,5,6,7}
    +-for p in _.sliding(t,3) do
    +-    print(table.concat(p,','))
    +-end
    +-
    +--- => 1,2,3
    +--- => 3,4,5
    +--- => 5,6,7
    +-
    +-for p in _.sliding(t,4) do
    +-    print(table.concat(p,','))
    +-end
    +-
    +--- => 1,2,3,4
    +--- => 4,5,6,7
    +-
    +-for p in _.sliding(t,5) do
    +-    print(table.concat(p,','))
    +-end
    +-
    +--- => 1,2,3,4,5
    +--- => 5,6,7
    +-
    +- +- +-

    In case the last subsequence wil not match the exact desired length, it can be adjusted with a 3rd argument pad.

    +- +-
    +-local t = {1,2,3,4,5,6,7}
    +-for p in _.sliding(t,5,0) do
    +-    print(table.concat(p,','))
    +-end
    +-
    +--- => 1,2,3,4,5
    +--- => 5,6,7,0,0
    +-
    +- +- +-

    permutation (array)

    +- +-

    Aliases: _.perm.

    +- +-

    Returns an iterator function for permutations of a given array.

    +- +-
    +-t = {'a','b','c'}
    +-for p in _.permutation(t) do
    +-  print(table.concat(p))
    +-end
    +-
    +--- => 'bca'
    +--- => 'cba'
    +--- => 'cab'
    +--- => 'acb'
    +--- => 'bac'
    +--- => 'abc'
    +-
    +- +- +-

    invert (array)

    +- +-

    Aliases: _.mirror.

    +- +-

    Switches key-value pairs:

    +- +-
    +-_.invert {'a','b','c'} -- => "{a=1, b=2, c=3}"
    +-
    +- +- +-

    concat (array, sep, i, j)

    +- +-

    Aliases: _.join.

    +- +-

    Concatenates a given array values:

    +- +-
    +-_.concat({'a',1,0,1,'b'}) -- => 'a101b'
    +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Utility functions

    +- +-

    noop ()

    +- +-

    The no-operation function. Takes nothing, returns nothing. It is being used internally.

    +- +-
    +-_.noop() -- => nil
    +-
    +- +- +-

    identity (value)

    +- +-

    Returns the passed-in value.
    +-This function is internally used as a default transformation function.

    +- +-
    +-_.identity(1)-- => 1
    +-_.identity(false) -- => false
    +-_.identity('hello!') -- => 'hello!'
    +-
    +- +- +-

    constant (value)

    +- +-

    Creates a constant function. This function will constinuously yield the same output.

    +- +-
    +-local pi = _.constant(math.pi)
    +-pi(1) -- => 3.1415926535898
    +-pi(2) -- => 3.1415926535898
    +-pi(math.pi) -- => 3.1415926535898
    +-
    +- +- +-

    memoize (f, hash)

    +- +-

    Aliases: _.cache.

    +- +-

    Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body.

    +- +-
    +-local function fibonacci(n)
    +-  return n < 2 and n or fibonacci(n-1)+fibonacci(n-2)
    +-end
    +-local mem_fibonacci = _.memoize(fibonacci)
    +-fibonacci(20) -- => 6765 (but takes some time)
    +-mem_fibonacci(20) -- => 6765 (takes less time)
    +-
    +- +- +-

    once (f)

    +- +-

    Produces a function that runs only once. Successive calls to this function will still yield the same input.

    +- +-
    +-local sq = _.once(function(a) return a*a end)
    +-sq(1) -- => 1
    +-sq(2) -- => 1
    +-sq(3) -- => 1
    +-sq(4) -- => 1
    +-sq(5) -- => 1
    +-
    +- +- +-

    before (f, count)

    +- +-

    Returns a version of f that will run no more than count times. Next calls will keep yielding the results of the (n-th)-1 call.

    +- +-
    +-local function greet(someone) return 'hello '..someone end
    +-local greetOnly3people = _.before(greet, 3)
    +-greetOnly3people('John') -- => 'hello John'
    +-greetOnly3people('Moe') -- => 'hello Moe'
    +-greetOnly3people('James') -- => 'hello James'
    +-greetOnly3people('Joseph') -- => 'hello James'
    +-greetOnly3people('Allan') -- => 'hello James'
    +-
    +- +- +-

    after (f, count)

    +- +-

    Produces a function that will respond only after a given number of calls.

    +- +-
    +-local f = _.after(_.identity,3)
    +-f(1) -- => nil
    +-f(2) -- => nil
    +-f(3) -- => 3
    +-f(4) -- => 4
    +-
    +- +- +-

    compose (…)

    +- +-

    Composes functions. Each function consumes the return value of the one that follows.

    +- +-
    +-local function f(x) return x^2 end
    +-local function g(x) return x+1 end
    +-local function h(x) return x/2 end
    +-local compositae = _.compose(f,g,h)
    +-compositae(10) -- => 36
    +-compositae(20) -- => 121
    +-
    +- +- +-

    pipe (value, …)

    +- +-

    Pipes a value through a series of functions.

    +- +-
    +-local function f(x) return x^2 end
    +-local function g(x) return x+1 end
    +-local function h(x) return x/2 end
    +-_.pipe(10,f,g,h) -- => 36
    +-_.pipe(20,f,g,h) -- => 121
    +-
    +- +- +-

    complement (f)

    +- +-

    Returns a function which returns the logical complement of a given function.

    +- +-
    +-_.complement(function() return true end)() -- => false
    +-
    +- +- +-

    juxtapose (value, …)

    +- +-

    Aliases: _.juxt.

    +- +-

    Calls a sequence of functions with the same input.

    +- +-
    +-local function f(x) return x^2 end
    +-local function g(x) return x+1 end
    +-local function h(x) return x/2 end
    +-_.juxtapose(10, f, g, h) -- => 100, 11, 5
    +-
    +- +- +-

    wrap (f, wrapper)

    +- +-

    Wraps a function inside a wrapper. Allows the wrapper to execute code before and after function run.

    +- +-
    +-local greet = function(name) return "hi: " .. name end
    +-local greet_backwards = _.wrap(greet, function(f,arg)
    +-  return f(arg) ..'\nhi: ' .. arg:reverse()
    +-end)
    +-greet_backwards('John')
    +-
    +--- => hi: John
    +--- => hi: nhoJ
    +-
    +- +- +-

    times (n, iter, …)

    +- +-

    Calls a given function n times.

    +- +-
    +-local f = ('Lua programming'):gmatch('.')
    +-_.times(3,f) -- => {'L','u','a'}
    +-
    +- +- +-

    bind (f, v)

    +- +-

    Binds a value to be the first argument to a function.

    +- +-
    +-local sqrt2 = _.bind(math.sqrt,2)
    +-sqrt2() -- => 1.4142135623731
    +-
    +- +- +-

    bind2 (f, v)

    +- +-

    Binds a value to be the second argument to a function.

    +- +-
    +-local last2 = _.bind(_.last,2)
    +-last2({1,2,3,4,5,6}) -- => {5,6}
    +-
    +- +- +-

    bindn (f, …)

    +- +-

    Binds a variable number of values to be the first arguments to a function.

    +- +-
    +-local function out(...) return table.concat {...} end
    +-local out = _.bindn(out,'OutPut',':',' ')
    +-out(1,2,3) -- => OutPut: 123
    +-out('a','b','c','d') -- => OutPut: abcd
    +-
    +- +- +-

    bindAll (obj, …)

    +- +-

    Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument.

    +- +-
    +-local window = {
    +-    setPos = function(w,x,y) w.x, w.y = x, y end,
    +-    setName = function(w,name) w.name = name end,
    +-    getName = function(w) return w.name end,
    +-}
    +-window = _.bindAll(window, 'setPos', 'setName', 'getName')
    +-window.setPos(10,15)
    +-print(window.x, window.y) -- => 10,15
    +-
    +-window.setName('fooApp')
    +-print(window.name) -- => 'fooApp'
    +-
    +-print(window.getName()) -- => 'fooApp'
    +-
    +- +- +-

    uniqueId (template, …)

    +- +-

    Aliases: _.uid.

    +- +-

    Returns an unique integer ID.

    +- +-
    +-_.uniqueId() -- => 1
    +-
    +- +- +-

    Can handle string templates for formatted output.

    +- +-
    +-_.uniqueId('ID%s') -- => 'ID2'
    +-
    +- +- +-

    Or a function, for the same purpose.

    +- +-
    +-local formatter = function(ID) return '$'..ID..'$' end
    +-_.uniqueId(formatter) -- => '$ID1$'
    +-
    +- +- +-

    iterator(f, x)

    +- +-

    Aliases: _.iter.

    +- +-

    Returns an iterator function which constinuously applies a function f onto an input x. +-For example, let us go through the powers of two.

    +- +-
    +-local function po2(x) return x*2 end
    +-local function iter_po2 = _.iterator(po2, 1)
    +-iter_po2() -- => 2
    +-iter_po2() -- => 4
    +-iter_po2() -- => 8
    +-
    +- +- +-

    array (…)

    +- +-

    Iterates a given iterator function and returns its values packed in an array.

    +- +-
    +-local text = 'letters'
    +-local chars = string.gmatch(text, '.')
    +-local letters = _.array(chars) -- => {'l','e','t','t','e','r','s'}
    +-
    +- +- +-

    flip (f)

    +- +-

    Creates a function of f with arguments flipped in reverse order.

    +- +-
    +-local function f(...) return table.concat({...}) end
    +-local flipped = _.flip(f)
    +-flipped('a','b','c') -- => 'cba'
    +-
    +- +- +-

    over (…)

    +- +-

    Creates a function that invokes a set of transforms with the arguments it receives.
    +-One can use use for example to get the tuple of min and max values from a set of values

    +- +-
    +-local minmax = _.over(math.min, math.max)
    +-minmax(5,10,12,4,3) -- => {3,12}
    +-
    +- +- +-

    overEvery (…)

    +- +-

    Creates a validation function. The returned function checks if all of the given predicates return truthy when invoked with the arguments it receives.

    +- +-
    +-local function alleven(...)
    +-    for i, v in ipairs({...}) do
    +-        if v%2~=0 then return false end
    +-    end
    +-    return true
    +-end
    +-
    +-local function allpositive(...)
    +-    for i, v in ipairs({...}) do
    +-        if v < 0 then return false end
    +-    end
    +-    return true
    +-end
    +-
    +-local allok = _.overEvery(alleven, allpositive)
    +-
    +-allok(2,4,-1,8) -- => false
    +-allok(10,3,2,6) -- => false
    +-allok(8,4,6,10) -- => true
    +-
    +- +- +-

    overSome (…)

    +- +-

    Creates a validation function. The returned function checks if any of the given predicates return truthy when invoked with the arguments it receives.

    +- +-
    +-local function alleven(...)
    +-    for i, v in ipairs({...}) do
    +-        if v%2~=0 then return false end
    +-    end
    +-    return true
    +-end
    +-
    +-local function allpositive(...)
    +-    for i, v in ipairs({...}) do
    +-        if v < 0 then return false end
    +-    end
    +-    return true
    +-end
    +-
    +-local anyok = _.overSome(alleven,allpositive)
    +-
    +-anyok(2,4,-1,8) -- => false
    +-anyok(10,3,2,6) -- => true
    +-anyok(-1,-5,-3) -- => false
    +-
    +- +- +-

    overArgs (f, …)

    +- +-

    Creates a function that invokes f with its arguments transformed

    +- +-
    +-local function f(x, y) return x, y end
    +-local function triple(x) retun x*3 end
    +-local function square(x) retun x^2 end
    +-local new_f = _.overArgs(f, triple, square)
    +-
    +-new_f(1,2) -- => 3, 4
    +-new_f(10,10) -- => 30, 100
    +-
    +- +- +-

    In case the number of arguments is greater than the number of transforms, the remaining args will be left as-is.

    +- +-
    +-local function f(x, y, z) return x, y, z end
    +-local function triple(x) retun x*3 end
    +-local function square(x) retun x^2 end
    +-local new_f = _.overArgs(f, triple, square)
    +-
    +-new_f(1,2,3) -- => 3, 4, 3
    +-new_f(10,10,10) -- => 30, 100, 10
    +-
    +- +- +-

    partial (f, …)

    +- +-

    Partially apply a function by filling in any number of its arguments.

    +- +-
    +-local function diff(a, b) return a - b end
    +-local diffFrom20 = _.partial(diff, 20) -- arg 'a' will be 20 by default
    +-diffFrom20(5) -- => 15
    +-
    +- +- +-

    The string '_' can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time.

    +- +-
    +-local function diff(a, b) return a - b end
    +-local remove5 = _.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5
    +-remove5(20) -- => 15
    +-
    +- +- +-

    partialRight (f, …)

    +- +-

    Like _.partial, it partially applies a function by filling in any number of its arguments, but from the right.

    +- +-
    +-local function concat(...) return table.concat({...},',') end
    +-local concat_right = _.partialRight(concat,'a','b','c')
    +-concat_right('d') -- => d,a,b,c
    +-
    +-concat_right = _.partialRight(concat,'a','b')
    +-concat_right('c','d') -- => c,d,a,b
    +-
    +-concat_right = _.partialRight(concat,'a')
    +-concat_right('b','c','d') -- => b,c,d,a
    +-
    +- +- +-

    The string '_', as always, can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time. +-In that case, the first args supplied at runtime will be used to fill the initial list of args while the remaining will be prepended.

    +- +-
    +-local function concat(...) return table.concat({...},',') end
    +-local concat_right = _.partialRight(concat,'a','_','c')
    +-concat_right('d','b') -- => b,a,d,c
    +-
    +-concat_right = _.partialRight(concat,'a','b','_')
    +-concat_right('c','d') -- => d,a,b,c
    +-
    +-concat_right = _.partialRight(concat,'_','a')
    +-concat_right('b','c','d') -- => c,d,b,a
    +-
    +- +- +-

    curry (f, n_args)

    +- +-

    Curries a function. If the given function f takes multiple arguments, it returns another version of f that takes a single argument +-(the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result.

    +- +-
    +-local function sumOf3args(x,y,z) return x + y + z end
    +-local curried_sumOf3args = _.curry(sumOf3args, 3)
    +-sumOf3args(1)(2)(3)) -- => 6
    +-sumOf3args(0)(6)(9)) -- => 15
    +-
    +- +- +-

    n_args defaults to 2.

    +- +-
    +-local function product(x,y) return x * y end
    +-local curried_product = _.curry(product)
    +-curried_product(5)(4) -- => 20
    +-curried_product(3)(-5) -- => -15
    +-curried_product(0)(1) -- => 0
    +-
    +- +- +-

    time (f, …)

    +- +-

    Returns the execution time of f (…) in seconds and its results.

    +- +-
    +-local function wait_count(n)
    +-    local i = 0
    +-    while i < n do i = i + 1 end
    +-    return i
    +-end
    +-
    +-local time, i = _.time(wait_count, 1e6) -- => 0.002 1000000
    +-local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000
    +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Object functions

    +- +-

    keys (obj)

    +- +-

    Collects the names of an object attributes.

    +- +-
    +-_.keys({1,2,3}) -- => "{1,2,3}"
    +-_.keys({x = 0, y = 1}) -- => "{'y','x'}"
    +-
    +- +- +-

    values (obj)

    +- +-

    Collects the values of an object attributes.

    +- +-
    +-_.values({1,2,3}) -- => "{1,2,3}"
    +-_.values({x = 0, y = 1}) -- => "{1,0}"
    +-
    +- +- +-

    kvpairs (obj)

    +- +-

    Converts an object to an array-list of key-value pairs.

    +- +-
    +-local obj = {x = 1, y = 2, z = 3}
    +-_.each(_.kvpairs(obj), function(k,v)
    +-    print(k, table.concat(v,','))
    +-end)
    +-
    +--- => 1    y,2
    +--- => 2 x,1
    +--- => 3 z,3
    +-
    +- +- +-

    toObj

    +- +-

    Converts an array list of kvpairs to an object where keys are taken from the 1rst column in the kvpairs sequence, associated with values in the 2nd column.

    +- +-
    +-local list_pairs = {{'x',1},{'y',2},{'z',3}}
    +-obj = _.toObj(list_pairs)
    +-
    +--- => {x = 1, y = 2, z = 3}
    +-
    +- +- +-

    property (key)

    +- +-

    Returns a function that will return the key property of any passed-in object.

    +- +-
    +-local who = _.property('name')
    +-local people = {name = 'Henry'}
    +-who(people) -- => 'Henry'
    +-
    +- +- +-

    propertyOf (obj)

    +- +-

    Returns a function that will return the key property of any passed-in object.

    +- +-
    +-local people = {name = 'Henry'}
    +-print(_.propertyOf(people)('name')) -- => 'Henry'
    +-
    +- +- +-

    toBoolean (value)

    +- +-

    Converts a given value to a boolean.

    +- +-
    +-_.toBoolean(true) -- => true
    +-_.toBoolean(false) -- => false
    +-_.toBoolean(nil) -- => false
    +-_.toBoolean({}) -- => true
    +-_.toBoolean(1) -- => true
    +-
    +- +- +-

    extend (destObj, …)

    +- +-

    Extends a destination object with the properties of some source objects.

    +- +-
    +-_.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}"
    +-
    +- +- +-

    functions (obj, recurseMt)

    +- +-

    Aliases: _.methods.

    +- +-

    Returns all functions names within an object.

    +- +-
    +-_.functions(coroutine) -- => "{'create','resume','running','status','wrap','yield'}"
    +-
    +- +- +-

    clone (obj, shallow)

    +- +-

    Clones a given object.

    +- +-
    +-local obj = {1,2,3}
    +-local obj2 = _.clone(obj)
    +-print(obj2 == obj) -- => false
    +-print(_.isEqual(obj2, obj)) -- => true
    +-
    +- +- +-

    tap (obj, f, …)

    +- +-

    Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. +-The pased-interceptor is prototyped as f(obj,…).

    +- +-
    +-local v = _.chain({1,2,3,4,5,6,7,8,9,10)
    +-  :filter(function(k,v) return v%2~=0 end) -- filters even values
    +-  :tap(function(v) print('Max is', _.max(v) end) -- Tap max values
    +-  :map(function(k,v) return k^2)
    +-  :value() -- =>    Max is 9
    +-
    +- +- +-

    has (obj, key)

    +- +-

    Checks if an object has a given attribute.

    +- +-
    +-_.has(_,'has') -- => true
    +-_.has(coroutine,'resume') -- => true
    +-_.has(math,'random') -- => true
    +-
    +- +- +-

    pick (obj, …)

    +- +-

    Aliases: _.choose.

    +- +-

    Collects whilelisted properties of a given object.

    +- +-
    +-local object = {a = 1, b = 2, c = 3}
    +-_.pick(object,'a','c') -- => "{a = 1, c = 3}"
    +-
    +- +- +-

    omit (obj, …)

    +- +-

    Aliases: _.drop.

    +- +-

    Omits blacklisted properties of a given object.

    +- +-
    +-local object = {a = 1, b = 2, c = 3}
    +-_.omit(object,'a','c') -- => "{b = 2}"
    +-
    +- +- +-

    template (obj, template)

    +- +-

    Aliases: _.defaults.

    +- +-

    Applies a template on an object, preserving existing properties.

    +- +-
    +-local obj = {a = 0}
    +-_.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}"
    +-
    +- +- +-

    isEqual (objA, objB, useMt)

    +- +-

    Aliases: _.compare.

    +- +-

    Compares objects:

    +- +-
    +-_.isEqual(1,1) -- => true
    +-_.isEqual(true,false) -- => false
    +-_.isEqual(3.14,math.pi) -- => false
    +-_.isEqual({3,4,5},{3,4,{5}}) -- => false
    +-
    +- +- +-

    result (obj, method, …)

    +- +-

    Calls an object method, passing it as a first argument the object itself.

    +- +-
    +-_.result('abc','len') -- => 3
    +-_.result({'a','b','c'},table.concat) -- => 'abc'
    +-
    +- +- +-

    isTable (t)

    +- +-

    Is the given argument an object (i.e a table) ?

    +- +-
    +-_.isTable({}) -- => true
    +-_.isTable(math) -- => true
    +-_.isTable(string) -- => true
    +-
    +- +- +-

    isCallable (obj)

    +- +-

    Is the given argument callable ?

    +- +-
    +-_.isCallable(print) -- => true
    +-_.isCallable(function() end) -- => true
    +-_.isCallable(setmetatable({},{__index = string}).upper) -- => true
    +-_.isCallable(setmetatable({},{__call = function() return end})) -- => true
    +-
    +- +- +-

    isArray (obj)

    +- +-

    Is the given argument an array (i.e. a sequence) ?

    +- +-
    +-_.isArray({}) -- => true
    +-_.isArray({1,2,3}) -- => true
    +-_.isArray({'a','b','c'}) -- => true
    +-
    +- +- +-

    isIterable (obj)

    +- +-

    Checks if the given object is iterable with pairs.

    +- +-
    +-_.isIterable({}) -- => true
    +-_.isIterable(function() end) -- => false
    +-_.isIterable(false) -- => false
    +-_.isIterable(1) -- => false
    +-
    +- +- +-

    isEmpty (obj)

    +- +-

    Is the given argument empty ?

    +- +-
    +-_.isEmpty('') -- => true
    +-_.isEmpty({})  -- => true
    +-_.isEmpty({'a','b','c'}) -- => false
    +-
    +- +- +-

    isString (obj)

    +- +-

    Is the given argument a string ?

    +- +-
    +-_.isString('') -- => true
    +-_.isString('Hello') -- => false
    +-_.isString({}) -- => false
    +-
    +- +- +-

    isFunction (obj)

    +- +-

    Is the given argument a function ?

    +- +-
    +-_.isFunction(print) -- => true
    +-_.isFunction(function() end) -- => true
    +-_.isFunction({}) -- => false
    +-
    +- +- +-

    isNil (obj)

    +- +-

    Is the given argument nil ?

    +- +-
    +-_.isNil(nil) -- => true
    +-_.isNil() -- => true
    +-_.isNil({}) -- => false
    +-
    +- +- +-

    isNumber (obj)

    +- +-

    Is the given argument a number ?

    +- +-
    +-_.isNumber(math.pi) -- => true
    +-_.isNumber(math.huge) -- => true
    +-_.isNumber(0/0) -- => true
    +-_.isNumber() -- => false
    +-
    +- +- +-

    isNaN (obj)

    +- +-

    Is the given argument NaN ?

    +- +-
    +-_.isNaN(1) -- => false
    +-_.isNaN(0/0) -- => true
    +-
    +- +- +-

    isFinite (obj)

    +- +-

    Is the given argument a finite number ?

    +- +-
    +-_.isFinite(99e99) -- => true
    +-_.isFinite(math.pi) -- => true
    +-_.isFinite(math.huge) -- => false
    +-_.isFinite(1/0) -- => false
    +-_.isFinite(0/0) -- => false
    +-
    +- +- +-

    isBoolean (obj)

    +- +-

    Is the given argument a boolean ?

    +- +-
    +-_.isBoolean(true) -- => true
    +-_.isBoolean(false) -- => true
    +-_.isBoolean(1==1) -- => true
    +-_.isBoolean(print) -- => false
    +-
    +- +- +-

    isInteger (obj)

    +- +-

    Is the given argument an integer ?

    +- +-
    +-_.isInteger(math.pi) -- => false
    +-_.isInteger(1) -- => true
    +-_.isInteger(-1) -- => true
    +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Chaining

    +- +-

    Method chaining (also known as name parameter idiom), is a technique for invoking consecutively method calls in object-oriented style. +-Each method returns an object, and methods calls are chained together. +-Moses offers chaining for your perusal.
    +-Let’s use chaining to get the count of evey single word in some lyrics (case won’t matter here).

    +- +-
    +-local lyrics = {
    +-  "I am a lumberjack and I am okay",
    +-  "I sleep all night and I work all day",
    +-  "He is a lumberjack and he is okay",
    +-  "He sleeps all night and he works all day"
    +-}
    +-
    +-local stats = _.chain(lyrics)
    +-  :map(function(k,line)
    +-    local t = {}
    +-    for w in line:gmatch('(%w+)') do
    +-      t[#t+1] = w
    +-    end
    +-    return t
    +-  end)
    +-  :flatten()
    +-  :countBy(function(i,v) return v:lower() end)
    +-  :value()
    +-
    +--- => "{
    +--- =>    sleep = 1, night = 2, works = 1, am = 2, is = 2,
    +--- =>    he = 2, and = 4, I = 4, he = 2, day = 2, a = 2,
    +--- =>    work = 1, all = 4, okay = 2
    +--- =>  }"
    +-
    +- +- +-

    For convenience, you can also use _(value) to start chaining methods, instead of _.chain(value).

    +- +-

    Note that one can use :value() to unwrap a chained object.

    +- +-
    +-local t = {1,2,3}
    +-print(_(t):value() == t) -- => true
    +-
    +- +- +-

    [⬆]

    +- +-

    +- +-

    Import

    +- +-

    All library functions can be imported in a context using import into a specified context.

    +- +-
    +-local context = {}
    +-_.import(context)
    +-
    +-context.each({1,2,3},print)
    +-
    +--- => 1 1
    +--- => 2 2
    +--- => 3 3
    +-
    +- +- +-

    When no context was provided, it defaults to the global environment _G.

    +- +-
    +-_.import()
    +-
    +-each({1,2,3},print)
    +-
    +--- => 1 1
    +--- => 2 2
    +--- => 3 3
    +-
    +- +- +-

    Passing noConflict argument leaves untouched conflicting keys while importing into the context.

    +- +-
    +-local context = {each = 1}
    +-_.import(context, true)
    +-
    +-print(context.each) -- => 1
    +-context.eachi({1,2,3},print)
    +-
    +--- => 1 1
    +--- => 2 2
    +--- => 3 3
    +-
    +- +- +-

    [⬆] +- +-

    +-
    +-
    +-generated by LDoc 1.4.6 +-Last updated 2017-04-27 15:26:55 +-
    +-
    +- +- +diff --git a/extra/moses/doc/tutorial.md b/extra/moses/doc/tutorial.md +index feff3e9..035a77c 100644 +--- a/extra/moses/doc/tutorial.md ++++ b/extra/moses/doc/tutorial.md +@@ -1,10 +1,11 @@ + *Moses: a utility-belt library for functional programming in Lua* + + __Moses__ is a Lua utility library which provides support for functional programming. +-It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more.
    +-__Moses__ was deeply inspired by [Underscore.js](http://documentcloud.github.com/underscore/). ++It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more. ++
    ++
    + +-# Table of Contents ++# Sections + + + * [Adding *Moses* to your project](#adding) +@@ -14,23 +15,21 @@ __Moses__ was deeply inspired by [Underscore.js](http://documentcloud.github.com + * [Object functions](#object) + * [Chaining](#chaining) + * [Import](#import) +- ++

    + # Adding *Moses* to your project + + Drop the file [moses.lua](http://github.com/Yonaba/Moses/blob/master/moses.lua) into your project and add it to your code with the *require* function: + + ```lua +-local _ = require ("moses") ++local M = require ("moses") + ```` + +-*Note: Lua purists tend to use "\_" to design a "dummy variable". Here, the usage of this underscore is quite idiomatic and refers to the name [Underscore](http://documentcloud.github.com/underscore/), the JS library from which *Moses* takes inspiration*. +- +-*Moses*' provides a large set of functions that can be classified into four categories: ++*Moses* provides a large set of functions that can be classified into four categories: + +-* __Table functions__, which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part, +-* __Array functions__, meant for array lists (or sequences), +-* __Utility functions__, +-* __Object functions__. ++* [__Table functions__](#table), which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part, ++* [__Array functions__](#array), meant for array lists (or sequences), ++* [__Utility functions__](#utility), ++* [__Object functions__](#object). + + **[[⬆]](#TOC)** + +@@ -38,198 +37,271 @@ local _ = require ("moses") + + ### clear (t) + +-Clears a table. All its values becomes nil. It returns the passed-in table. ++Clears a table. All its values becomes nil. Returns the passed-in table. + + ```lua +-local t = _.clear({1,2,'hello',true}) -- => {} ++M.clear({1,2,'hello',true}) -- => {} + ```` + +-### each (t, f, ...) +-*Aliases: `_.forEach`*. ++### each (t, f) ++*Aliases: `forEach`*. + +-Iterates over each key-value pair in table. ++Iterates over each value-key pair in the passed-in table. + + ```lua +-_.each({1,2,3},print) ++M.each({4,2,1},print) + +--- => 1 1 ++-- => 4 1 + -- => 2 2 +--- => 3 3 ++-- => 1 3 + ```` + +-The table can be map-like (array part and hash-part). ++The table can be map-like (both array part and hash part). + + ```lua +-_.each({one = 1, two = 2, three = 3},print) ++M.each({one = 1, two = 2, three = 3},print) + +--- => one 1 +--- => two 2 +--- => three 3 ++-- => 1 one ++-- => 2 two ++-- => 3 three + ```` + + Can index and assign in an outer table or in the passed-in table: + + ```lua + t = {'a','b','c'} +-_.each(t,function(i,v) ++M.each(t,function(v,i) + t[i] = v:rep(2) + print(t[i]) + end) + +--- => 1 aa +--- => 2 bb +--- => 3 cc ++-- => aa ++-- => bb ++-- => cc + ```` + +-### eachi (t, f, ...) +-*Aliases: `_.forEachi`*. ++### eachi (t, f) ++*Aliases: `forEachi`*. + +-Iterates only on integer keys in a sparse array table. ++Iterates only on integer keys in an array table. It returns value-key pairs. + + ```lua +-_.eachi({1,2,3},print) ++M.eachi({4,2,1},print) + +--- => 1 1 ++-- => 4 1 + -- => 2 2 +--- => 3 3 ++-- => 1 3 + ```` + + The given array can be sparse, or even have a hash-like part. + + ```lua + local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5} +-_.eachi(t,function(i,v) +- print(i,v) +-end) ++M.eachi(t,print) + +--- => -1 6 +--- => 0 1 +--- => 1 3 +--- => 2 5 ++-- => 6 -1 ++-- => 1 0 ++-- => 3 1 ++-- => 5 2 + ```` + + ### at (t, ...) + +-Collects all values at some specific keys and returns them in an array. ++Collects values at given keys and returns them in an array. + + ```lua + local t = {4,5,6} +-_.at(t,1,3) -- => "{4,6}" ++M.at(t,1,3) -- => "{4,6}" + + local t = {a = 4, bb = true, ccc = false} +-_.at(t,'a', 'ccc') -- => "{4, false}" ++M.at(t,'a', 'ccc') -- => "{4, false}" + ```` + +-### count (t, value) ++### adjust (t, key, f) ++ ++Adjusts the value at a given key using a function or a value. In case `f` is a function, it should be prototyped `f(v)`. ++It does not mutate the given table, but rather returns a new array. ++ ++```lua ++local t = {1,2,3} ++M.adjust(t, 2, math.sin) -- => {1, 0.90929, 3} ++ ++local v = {x = 1} ++ M.adjust(t, 'x', 4) -- => {x = 4} ++```` ++ ++In case the given `key` does not exist in `t`, it throws an error. ++ ++### count (t [, val]) + + Counts the number of occurences of a given value in a table. + + ```lua +-_.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2 +-_.count({1,1,2,3,3,3,2,4,3,2},2) -- => 2 +-_.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4 +-_.count({false, false, true},false) -- => 2 +-_.count({false, false, true},true) -- => 1 ++M.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2 ++M.count({1,1,2,3,3,3,2,4,3,2},2) -- => 3 ++M.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4 ++M.count({false, false, true},false) -- => 2 ++M.count({false, false, true},true) -- => 1 + ```` + + Returns the size of the list in case no value was provided. + + ```lua +-_.count({1,1,2,3,3}) -- => 5 ++M.count({1,1,2,3,3}) -- => 5 + ```` + +-### countf (t, f, ...) ++### countf (t, f) + +-Count the number of occurences of all values passing an iterator test. ++Counts the number of values passing an iterator test. + + ```lua +-_.countf({1,2,3,4,5,6}, function(i,v) ++M.countf({1,2,3,4,5,6}, function(v) + return v%2==0 + end) -- => 3 + +-_.countf({print, pairs, os, assert, ipairs}, function(i,v) ++M.countf({print, pairs, os, assert, ipairs}, function(v) + return type(v)=='function' + end) -- => 4 + ```` + +-### cycle (t, n) +-*Aliases: `_.loop`*. ++### allEqual (t [, comp]) ++*Aliases: `alleq`*. ++ ++Checks if all values in a collection are equal. Uses `M.isEqual` by default to compare values. ++ ++```lua ++M.allEqual({1,1,1,1,1}, comp) -- => true ++M.allEqual({1,1,2,1,1}, comp) -- => false ++ ++local t1 = {1, 2, {3}} ++local t2 = {1, 2, {3}} ++M.allEqual({t1, t2}) -- => true ++```` ++ ++Can take an optional `comp` function which will be used to compare values. ++ ++```lua ++local t1 = {x = 1, y = 0} ++local t2 = {x = 1, y = 0} ++local t3 = {x = 1, y = 2} ++local t4 = {x = 1, y = 2} ++local function compx(a, b) return a.x == b.x end ++local function compy(a, b) return a.y == b.y end ++ ++M.allEqual({t1, t2}, compx) -- => true ++M.allEqual({t1, t2}, compy) -- => true ++M.allEqual({t3, t4}, compx) -- => true ++M.allEqual({t3, t4}, compy) -- => true ++M.allEqual({t1, t2, t3, t4}, compx) -- => true ++M.allEqual({t1, t2, t3, t4}, compy) -- => false ++```` ++ ++### cycle (t [, n = 1]) ++*Aliases: `loop`*. + +-Returns a function which iterates on each key-value pair in a given table (similarly to `_.each`), except that it restarts iterating again `n` times. ++Returns a function which iterates on each value-key pair in a given table (similarly to `M.each`), except that it restarts iterating again `n` times. + If `n` is not provided, it defaults to 1. + + ```lua +-local t = {'a,'b','c'} +-for k,v in _.cycle(t, 2) do +- print(k,v) ++local t = {'a','b','c'} ++for v in M.cycle(t, 2) do ++ print(v) + end + +--- => 1 'a' +--- => 2 'b' +--- => 3 'c' +--- => 1 'a' +--- => 2 'b' +--- => 3 'c' ++-- => 'a' ++-- => 'b' ++-- => 'c' ++-- => 'a' ++-- => 'b' ++-- => 'c' + ```` + + Supports array-like tables and map-like tables. + + ```lua + local t = {x = 1, y = 2, z = 3} +-for k,v in _.cycle(t) do +- print(k,v) ++for v in M.cycle(t) do ++ print(v) + end + +--- => y 2 +--- => x 1 +--- => z 3 ++-- => 2 ++-- => 1 ++-- => 3 + ```` + +-### map (t, f, ...) +-*Aliases: `_.collect`*. ++### map (t, f) ++*Aliases: `collect`*. + +-Executes a function on each key-value pairs. ++Executes a function on each value in a given array. + + ```lua +-_.map({1,2,3},function(i,v) ++M.map({1,2,3},function(v) + return v+10 + end) -- => "{11,12,13}" + ```` + + ```lua +-_.map({a = 1, b = 2},function(k,v) ++M.map({a = 1, b = 2},function(v, k) + return k..v + end) -- => "{a = 'a1', b = 'b2'}" + ```` + +-It also maps key-value pairs to key-value pairs ++It also maps both keys and values. + + ```lua +-_.map({a = 1, b = 2},function(k,v) ++M.map({a = 1, b = 2},function(v, k) + return k..k, v*2 + end) -- => "{aa = 2, bb = 4}" + ```` + +-### reduce (t, f, state) +-*Aliases: `_.inject`, `_.foldl`*. ++### mapi (t, f) ++ ++Executes a function on each value in a given array. ++ ++```lua ++M.mapi({1,2,3},function(v) ++ return v+10 ++end) -- => "{11,12,13}" ++```` + +-Can sums all values in a table. ++It only works for the array-part of the given table. + + ```lua +-_.reduce({1,2,3,4},function(memo,v) +- return memo+v +-end) -- => 10 ++M.map({a = 1, 2, 3, 4, 5},function(v, k) ++ return k..v ++end) -- => "{'12','23','34','45'}" ++```` ++ ++### reduce (t, f [, state = next(t)]) ++*Aliases: `inject`, `foldl`*. ++ ++Can sum all values in a table. In case `state` is not provided, it defaults to the first value in the given table `t`. ++ ++```lua ++local function add(a,b) return a+b end ++M.reduce({1,2,3,4},add) -- => 10 + ```` + + Or concatenates all values. + +-```lua +-_.reduce({'a','b','c','d'},function(memo,v) +- return memo..v +-end) -- => abcd ++```lua ++local function concat(a,b) return a..b end ++M.reduce({'a','b','c','d'},concat) -- => abcd + ```` + +-### reduceby (t, f, state, pred, ...) ++### best (t, f) ++ ++Returns the best value passing a selector function. Acts as a special case of `reduce`, using the first value in `t` as ++an initial state. It thens folds the given table, testing each of its values `v` and selecting the value passing the ++call `f(state,v)` every time. ++ ++```lua ++local words = {'Lua', 'Programming', 'Language'} ++M.best(words, function(a,b) return #a > #b end) -- => 'Programming' ++M.best(words, function(a,b) return #a < #b end) -- => 'Lua' ++```` ++ ++### reduceBy (t, f, pred [, state = next(t)]) + + Reduces a table considering only values matching a predicate. + For example,let us define a set of values. +@@ -237,85 +309,87 @@ For example,let us define a set of values. + ```lua + local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9} + ```` ++ ++And a reduction function which will add up values. ++ ++```lua ++local function add(a,b) return a+b end ++```` ++ + We can also define some predicate functions. + + ```lua + -- predicate for negative values +-local function neg(_, v) return v<=0 end ++local function neg(v) return v<=0 end + + -- predicate for positive values +-local function pos(_, v) return v>=0 end ++local function pos(v) return v>=0 end + ```` + +-Then we can perform reduction considering only negative values : ++Then we can perform reduction considering only negative or positive values : + + ```lua +-_.reduceby(val, function(memo,v) +- return memo+v +-end, 0, neg) -- => -17 ++M.reduceBy(val, add, neg) -- => -17 ++M.reduceBy(val, add, pos) -- => 19 + ```` + +-Or only positive values : ++An initial state can be passed in. + + ```lua +-_.reduceby(val, function(memo,v) +- return memo+v +-end, 0, pos) -- => 19 ++M.reduceBy(val, add, neg, 17) -- => 0 ++M.reduceBy(val, add, pos, -19) -- => 0 + ```` + +-### reduceRight (t, f, state) +-*Aliases: `_.injectr`, `_.foldr`*. ++### reduceRight (t, f [, state = next(t)]) ++*Aliases: `injectr`, `foldr`*. + +-Similar to `_.reduce`, but performs from right to left. ++Similar to `M.reduce`, but performs from right to left. + + ```lua + local initial_state = 256 +-_.reduceRight({1,2,4,16},function(memo,v) +- return memo/v +-end,initial_state) -- => 2 ++local function div(a,b) return a/b end ++M.reduceRight({1,2,4,16},div,initial_state) -- => 2 + ```` + +-### mapReduce (t, f, state) +-*Aliases: `_.mapr`*. ++### mapReduce (t, f [, state = next(t)]) ++*Aliases: `mapr`*. + + Reduces while saving intermediate states. + + ```lua +-_.mapReduce({'a','b','c'},function(memo,v) +- return memo..v +-end) -- => "{'a', 'ab', 'abc'}" ++local function concat(a,b) return a..b end ++M.mapReduce({'a','b','c'},concat) -- => "{'a', 'ab', 'abc'}" + ```` + +-### mapReduceRight (t, f, state) +-*Aliases: `_.maprr`*. ++### mapReduceRight (t, f [, state = next(t)]) ++*Aliases: `maprr`*. + + Reduces from right to left, while saving intermediate states. + + ```lua +-_.mapReduceRight({'a','b','c'},function(memo,v) +- return memo..v +-end) -- => "{'c', 'cb', 'cba'}" ++local function concat(a,b) return a..b end ++M.mapReduceRight({'a','b','c'},concat) -- => "{'c', 'cb', 'cba'}" + ```` + + ### include (t, value) +-*Aliases: `_.any`, `_.some`, `_.contains`*. ++*Aliases: `any`, `some`, `contains`*. + + Looks for a value in a table. + + ```lua +-_.include({6,8,10,16,29},16) -- => true +-_.include({6,8,10,16,29},1) -- => false ++M.include({6,8,10,16,29},16) -- => true ++M.include({6,8,10,16,29},1) -- => false + + local complex_table = {18,{2,{3}}} + local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29} +-_.include(collection, complex_table) -- => true ++M.include(collection, complex_table) -- => true + ```` + + Handles iterator functions. + + ```lua + local function isUpper(v) return v:upper()== v end +-_.include({'a','B','c'},isUpper) -- => true ++M.include({'a','B','c'},isUpper) -- => true + ```` + + ### detect (t, value) +@@ -323,12 +397,12 @@ _.include({'a','B','c'},isUpper) -- => true + Returns the index of a value in a table. + + ```lua +-_.detect({6,8,10,16},8) -- => 2 +-_.detect({nil,true,0,true,true},false) -- => nil ++M.detect({6,8,10,16},8) -- => 2 ++M.detect({nil,true,0,true,true},false) -- => nil + + local complex_table = {18,{2,6}} + local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29} +-_.detect(collection, complex_table) -- => 2 ++M.detect(collection, complex_table) -- => 2 + ```` + + Handles iterator functions. +@@ -337,7 +411,7 @@ Handles iterator functions. + local function isUpper(v) + return v:upper()==v + end +-_.detect({'a','B','c'},isUpper) -- => 2 ++M.detect({'a','B','c'},isUpper) -- => 2 + ```` + + ### where (t, props) +@@ -345,94 +419,79 @@ _.detect({'a','B','c'},isUpper) -- => 2 + Looks through a table and returns all the values that matches all of the key-value pairs listed in `props`. + + ```lua +-local tA = {a = 1, b = 2, c = 0} +-local tB = {a = 1, b = 4, c = 1} +-local tC = {a = 4, b = 4, c = 3} +-local tD = {a = 1, b = 2, c = 3} +-local found = _.where({tA, tB, tC, tD}, {a = 1}) +- +--- => found = {tA, tB, tD} +- +-found = _.where({tA, tB, tC, tD}, {b = 4}) +- +--- => found = {tB, tC} +- +-found = _.where({tA, tB, tC, tD}, {b = 4, c = 3}) +- +--- => found = {tC} ++local items = { ++ {height = 10, weight = 8, price = 500}, ++ {height = 10, weight = 15, price = 700}, ++ {height = 15, weight = 15, price = 3000}, ++ {height = 10, weight = 8, price = 3000}, ++} ++M.where(items, {height = 10}) -- => {items[1], items[2], items[4]} ++M.where(items, {weight = 15}) -- => {items[2], items[3]} ++M.where(items, {prince = 3000}) -- => {items[3], items[4]} ++M.where(items, {height = 10, weight = 15, prince = 700}) -- => {items[2]} + ```` + + ### findWhere (t, props) + +-Looks through a table and returns the first value that matches all of the key-value pairs listed in `props`. ++Looks through a table and returns the first value found that matches all of the key-value pairs listed in `props`. + + ```lua + local a = {a = 1, b = 2, c = 3} + local b = {a = 2, b = 3, d = 4} + local c = {a = 3, b = 4, e = 5} +-_.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true ++M.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true + ```` + +-### select (t, f, ...) +-*Aliases: `_.filter`*. ++### select (t, f) ++*Aliases: `filter`*. + + Collects values passing a validation test. + + ```lua +--- Even values +-_.select({1,2,3,4,5,6,7}, function(key,value) +- return (value%2==0) +-end) -- => "{2,4,6}" ++local function isEven(v) return v%2==0 end ++local function isOdd(v) return v%2~=0 end + +--- Odd values +-_.select({1,2,3,4,5,6,7}, function(key,value) +- return (value%2~=0) +-end) -- => "{1,3,5,7}" ++M.select({1,2,3,4,5,6,7}, isEven) -- => "{2,4,6}" ++M.select({1,2,3,4,5,6,7}, isOdd) -- => "{1,3,5,7}" + ```` + +-### reject (t, f, ...) +-*Aliases: `_.reject`*. ++### reject (t, f) ++*Aliases: `reject`*. + +-Removes all values failing a validation test: ++Removes all values failing (returning false or nil) a validation test: + + ```lua +-_.reject({1,2,3,4,5,6,7}, function(key,value) +- return (value%2==0) +-end) -- => "{1,3,5,7}" ++local function isEven(v) return v%2==0 end ++local function isOdd(v) return v%2~=0 end + +-_.reject({1,2,3,4,5,6,7}, function(key,value) +- return (value%2~=0) +-end) -- => "{2,4,6}" ++M.reject({1,2,3,4,5,6,7}, isEven) -- => "{1,3,5,7}" ++M.reject({1,2,3,4,5,6,7}, isOdd) -- => "{2,4,6}" + ```` + +-### all (t, f, ...) +-*Aliases: `_.every`*. ++### all (t, f) ++*Aliases: `every`*. + + Checks whether or not all elements pass a validation test. + + ```lua +-_.all({2,4,6}, function(key,value) +- return (value%2==0) +-end) -- => true ++local function isEven(v) return v%2==0 end ++M.all({2,4,6}, isEven) -- => true + ```` + +-### invoke (t, method, ...) ++### invoke (t, method) + +-Invokes a given function on each value in a table ++Invokes a given function on each value in a table. + + ```lua +-_.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}" ++M.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}" + ```` + + Can reference the method of the same name in each value. + + ```lua +-local a = {} +-function a:call() return 'a' end +-local b, c, d = {}, {}, {} +-b.call, c.call, d.call = a.call, a.call, a.call +- +-_.invoke({a,b,c,d},'call') -- => "{'a','a','a','a'}" ++local a, b, c, d = {id = 'a'}, {id = 'b'}, {id = 'c'}, {id = 'd'} ++local function call(self) return self.id end ++M.invoke({a,b,c,d},call) -- => "{'a','b','c','d'}" + ```` + + ### pluck (t, property) +@@ -444,17 +503,17 @@ local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} + +-_.pluck(peoples,'age') -- => "{23,17,15,33}" +-_.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}" ++M.pluck(peoples,'age') -- => "{23,17,15,33}" ++M.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}" + ```` + +-### max (t, transform, ...) ++### max (t [, transform]) + + Returns the maximum value in a collection. + + ```lua +-_.max {1,2,3} -- => 3 +-_.max {'a','b','c'} -- => 'c' ++M.max {1,2,3} -- => 3 ++M.max {'a','b','c'} -- => 'c' + ```` + + Can take an iterator function to extract a specific property. +@@ -463,16 +522,16 @@ Can take an iterator function to extract a specific property. + local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} +-_.max(peoples,function(people) return people.age end) -- => 33 ++M.max(peoples,function(people) return people.age end) -- => 33 + ```` + +-### min (t, transform, ...) ++### min (t [, transform]) + + Returns the minimum value in a collection. + + ```lua +-_.min {1,2,3} -- => 1 +-_.min {'a','b','c'} -- => 'a' ++M.min {1,2,3} -- => 1 ++M.min {'a','b','c'} -- => 'a' + ```` + + Can take an iterator function to extract a specific property. +@@ -481,16 +540,7 @@ Can take an iterator function to extract a specific property. + local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} +-_.min(peoples,function(people) return people.age end) -- => 15 +-```` +- +-### shuffle (t, seed) +- +-Shuffles a collection. +- +-```lua +-local list = _.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}" +-_.each(list,print) ++M.min(peoples,function(people) return people.age end) -- => 15 + ```` + + ### same (a, b) +@@ -500,34 +550,84 @@ Tests whether or not all values in each of the passed-in tables exists in both t + ```lua + local a = {'a','b','c','d'} + local b = {'b','a','d','c'} +-_.same(a,b) -- => true ++M.same(a,b) -- => true + + b[#b+1] = 'e' +-_.same(a,b) -- => false ++M.same(a,b) -- => false + ```` + +-### sort (t, comp) ++### sort (t [, comp = math.min]) + + Sorts a collection. + + ```lua +-_.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}" ++M.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}" + ```` + + Handles custom comparison functions. + + ```lua +-_.sort({'b','a','d','c'}, function(a,b) ++M.sort({'b','a','d','c'}, function(a,b) + return a:byte() > b:byte() + end) -- => "{'d','c','b','a'}" + ```` + +-### sortBy (t, transform, comp) ++### sortedk (t [, comp]) ++ ++Iterates on values with respect to key order. Keys are sorted using `comp` function which defaults to `math.min`. ++It returns upon each call a `key, value` pair. ++ ++```lua ++local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++for k, v in M.sortedk(tbl) do print(k, v) end ++ ++-- => 1 12 ++-- => 2 6 ++-- => 3 5 ++-- => 4 10 ++-- => 5 8 ++ ++local function comp(a,b) return a > b end ++for k, v in M.sortedk(tbl, comp) do print(k, v) end ++ ++-- => 5 8 ++-- => 4 10 ++-- => 3 5 ++-- => 2 6 ++-- => 1 12 ++```` ++ ++### sortedv (t [, comp]) ++ ++Iterates on values with respect to key order. Keys are sorted using `comp` function which defaults to `math.min`. ++It returns upon each call a `key, value` pair. ++ ++```lua ++local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++for k, v in M.sortedv(tbl) do print(k, v) end ++ ++-- => 3 5 ++-- => 2 6 ++-- => 5 8 ++-- => 4 10 ++-- => 1 12 ++ ++local function comp(a,b) return a > b end ++for k, v in M.sortedv(tbl, comp) do print(k, v) end ++ ++-- => 1 12 ++-- => 4 10 ++-- => 5 8 ++-- => 2 6 ++-- => 3 5 ++```` ++ ++### sortBy (t [, transform [, comp = math.min]]) + + Sorts items in a collection based on the result of running a transform function through every item in the collection. + + ```lua +-local r = _.sortBy({1,2,3,4,5}, math.sin) ++local r = M.sortBy({1,2,3,4,5}, math.sin) + print(table.concat(r,',')) + + -- => {5,4,3,1,2} +@@ -536,14 +636,14 @@ print(table.concat(r,',')) + The transform function can also be a string name property. + + ```lua +-local people ={ ++local people = { + {name = 'albert', age = 40}, + {name = 'louis', age = 55}, + {name = 'steve', age = 35}, + {name = 'henry', age = 19}, + } +-local r = _.sortBy(people, 'age') +-_.each(r, function(__,v) print(v.age, v.name) end) ++local r = M.sortBy(people, 'age') ++M.each(r, function(v) print(v.age, v.name) end) + + -- => 19 henry + -- => 35 steve +@@ -551,18 +651,17 @@ _.each(r, function(__,v) print(v.age, v.name) end) + -- => 55 louis + ```` + +-As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort +-the list of people by decreasing age order : ++As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort the list of people by decreasing age order : + + ```lua +-local people ={ ++local people = { + {name = 'albert', age = 40}, + {name = 'louis', age = 55}, + {name = 'steve', age = 35}, + {name = 'henry', age = 19}, + } +-local r = _.sortBy(people, 'age', function(a,b) return a > b end) +-_.each(r, function(__,v) print(v.age, v.name) end) ++local r = M.sortBy(people, 'age', function(a,b) return a > b end) ++M.each(r, function(v) print(v.age, v.name) end) + + -- => 55 louis + -- => 40 albert +@@ -570,36 +669,36 @@ _.each(r, function(__,v) print(v.age, v.name) end) + -- => 19 henry + ```` + +-The `transform` function defaults to `_.indentity` and in that case, `_.sortBy` behaves like `_.sort`. ++The `transform` function defaults to `M.indentity` and in that case, `M.sortBy` behaves like `M.sort`. + + ```lua +-local r = _.sortBy({1,2,3,4,5}) ++local r = M.sortBy({1,2,3,4,5}) + print(table.concat(r,',')) + + -- => {1,2,3,4,5} + ```` + +-### groupBy (t, iter, ...) ++### groupBy (t, iter) + + Groups values in a collection depending on their return value when passed to a predicate test. + + ```lua +-_.groupBy({0,1,2,3,4,5,6},function(i,value) +- return value%2==0 and 'even' or 'odd' +-end) -- => "{odd = {1,3,5}, even = {0,2,4,6}}" ++M.groupBy({0,1,2,3,4,5,6},function(v) ++ return v%2==0 and 'even' or 'odd' ++end) ++-- => "{odd = {1,3,5}, even = {0,2,4,6}}" + +-_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value) +- return type(value) +-end) -- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}" ++M.groupBy({0,'a',true, false,nil,b,0.5},type) ++-- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}" + ```` + +-### countBy (t, iter, ...) ++### countBy (t, iter) + + Splits a table in subsets and provide the count for each subset. + + ```lua +-_.countBy({0,1,2,3,4,5,6},function(i,value) +- return value%2==0 and 'even' or 'odd' ++M.countBy({0,1,2,3,4,5,6},function(v) ++ return v%2==0 and 'even' or 'odd' + end) -- => "{odd = 3, even = 4}" + ```` + +@@ -608,15 +707,15 @@ end) -- => "{odd = 3, even = 4}" + When given a table, provides the count for the very number of values in that table. + + ```lua +-_.size {1,2,3} -- => 3 +-_.size {one = 1, two = 2} -- => 2 ++M.size {1,2,3} -- => 3 ++M.size {one = 1, two = 2} -- => 2 + ```` + +-When given a vararg list of argument, returns the count of these arguments. ++When given a vararg list of arguments, returns the count of these arguments. + + ```lua +-_.size(1,2,3) -- => 3 +-_.size('a','b',{}, function() end) -- => 4 ++M.size(1,2,3) -- => 3 ++M.size('a','b',{}, function() end) -- => 4 + ```` + + ### containsKeys (t, other) +@@ -624,9 +723,9 @@ _.size('a','b',{}, function() end) -- => 4 + Checks whether a table has all the keys existing in another table. + + ```lua +-_.contains({1,2,3,4},{1,2,3}) -- => true +-_.contains({1,2,'d','b'},{1,2,3,5}) -- => true +-_.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true ++M.contains({1,2,3,4},{1,2,3}) -- => true ++M.contains({1,2,'d','b'},{1,2,3,5}) -- => true ++M.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true + ```` + + ### sameKeys (tA, tB) +@@ -634,22 +733,22 @@ _.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true + Checks whether both tables features the same keys: + + ```lua +-_.sameKeys({1,2,3,4},{1,2,3}) -- => false +-_.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true +-_.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false ++M.sameKeys({1,2,3,4},{1,2,3}) -- => false ++M.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true ++M.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false + ```` + + **[[⬆]](#TOC)** + + ## Array functions + +-### sample (array, n, seed) ++### sample (array [, n = 1 [, seed]]) + + Samples `n` values from array. + + ```lua +-local array = _.range(1,20) +-local sample = _.sample(array, 3) ++local array = M.range(1,20) ++local sample = M.sample(array, 3) + print(table.concat(sample,',')) + + -- => {12,11,15} +@@ -658,8 +757,8 @@ print(table.concat(sample,',')) + `n` defaults to 1. In that case, a single value will be returned. + + ```lua +-local array = _.range(1,20) +-local sample = _.sample(array) ++local array = M.range(1,20) ++local sample = M.sample(array) + print(sample) + + -- => 12 +@@ -667,45 +766,72 @@ print(sample) + + An optional 3rd argument `seed` can be passed for deterministic random sampling. + +-### sampleProb (array, prob, seed) ++### sampleProb (array, prob [, seed]) + + Returns an array of values randomly selected from a given array. + In case `seed` is provided, it is used for deterministic sampling. + + ```lua +-local array = _.range(1,20) +-local sample = _.sampleProb(array, 0.2) ++local array = M.range(1,20) ++local sample = M.sampleProb(array, 0.2) + print(table.concat(sample,',')) + + -- => 5,11,12,15 + +-sample = _.sampleProb(array, 0.2, os.time()) ++sample = M.sampleProb(array, 0.2, os.time()) + print(table.concat(sample,',')) + + -- => 1,6,10,12,15,20 (or similar) + ```` + +-### toArray (...) ++### nsorted (array [, n = 1[, comp]]) ++ ++Returns the n-top values satisfying a predicate. It takes a comparison function `comp` used to sort array values, ++and then picks the top n-values. It leaves the original array untouched. ++ ++```lua ++local function comp(a,b) return a > b end ++M.nsorted(array,5, comp) -- => {5,4,3,2,1} ++```` ++ ++`n` defaults to 1 and `comp` defaults to the `<` operator. ++ ++```lua ++local array = M.range(1,20) ++M.nsorted(array) -- => {1} ++```` ++ ++### shuffle (array [, seed]) ++ ++Shuffles a given array. ++ ++```lua ++local list = M.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}" ++M.each(list,print) ++```` ++ ++### pack (...) + + Converts a vararg list of arguments to an array. + + ```lua +-_.toArray(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}" ++M.pack(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}" + ```` + +-### find (array, value, from) ++### find (array, value [, from = 1]) + + Looks for a value in a given array and returns the position of the first occurence. + + ```lua +-_.find({{4},{3},{2},{1}},{3}) -- => 2 ++local value = {3} ++M.find({{4},{3},{2},{1}},value) -- => 2 + ```` + + It can also start the search at a specific position in the array: + + ```lua + -- search value 4 starting from index 3 +-_.find({1,4,2,3,4,5},4,3) -- => 5 ++M.find({1,4,2,3,4,5},4,3) -- => 5 + ```` + + ### reverse (array) +@@ -713,74 +839,98 @@ _.find({1,4,2,3,4,5},4,3) -- => 5 + Reverses an array. + + ```lua +-_.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}" ++M.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}" + ```` + +-### fill (array, value, i, j) ++### fill (array, value [, i = 1 [, j = #array]]) + + Replaces all elements in a given array with a given value. + + ```lua +-local array = _.range(1,5) +-_.fill(array, 0) -- => {0,0,0,0,0} ++local array = M.range(1,5) ++M.fill(array, 0) -- => {0,0,0,0,0} + ```` + + It can start replacing value at a specific index. + + ```lua +-local array = _.range(1,5) +-_.fill(array,0,3) -- => {1,2,0,0,0} ++local array = M.range(1,5) ++M.fill(array,0,3) -- => {1,2,0,0,0} + ```` + + It can replace only values within a specific range. + + ```lua +-local array = _.range(1,5) +-_.fill(array,0,2,4) -- => {1,0,0,0,5} ++local array = M.range(1,5) ++M.fill(array,0,2,4) -- => {1,0,0,0,5} + ```` + + In case the upper bound index i greather than the array size, it will enlarge the array. + + ```lua +-local array = _.range(1,5) +-_.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0} ++local array = M.range(1,5) ++M.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0} + ```` + +-### selectWhile (array, f, ... +-*Aliases: `_.takeWhile`*. ++### zeros (n) ++ ++Returns an array of `n` zeros. ++ ++```lua ++M.zeros(4) -- => {0,0,0,0} ++```` ++ ++### ones (n) ++ ++Returns an array of `n` 1's. ++ ++```lua ++M.ones(3) -- => {1,1,1} ++```` ++ ++### vector (value, n) ++ ++Returns an array of `n` times a given value. ++ ++```lua ++M.vector(10, 4) -- => {10,10,10,10} ++```` ++ ++### selectWhile (array, f [, ...]) ++*Aliases: `takeWhile`*. + + Collects values as long as they pass a given test. Stops on the first non-passing test. + + ```lua +-_.selectWhile({2,4,5,8}, function(i,v) ++M.selectWhile({2,4,5,8}, function(v) + return v%2==0 + end) -- => "{2,4}" + ```` + +-### dropWhile (array, f, ... +-*Aliases: `_.rejectWhile`*. ++### dropWhile (array, f [, ...]) ++*Aliases: `rejectWhile`*. + + Removes values as long as they pass a given test. Stops on the first non-passing test. + + ```lua +-_.dropWhile({2,4,5,8}, function(i,v) ++M.dropWhile({2,4,5,8}, function(v) + return v%2==0 + end) -- => "{5,8}" + ```` + +-### sortedIndex (array, value, comp, sort) ++### sortedIndex (array, value [, comp = math.min [, sort = nil]]) + + Returns the index at which a value should be inserted to preserve order. + + ```lua +-_.sortedIndex({1,2,3},4) -- => 4 ++M.sortedIndex({1,2,3},4) -- => 4 + ```` + + Can take a custom comparison functions. + + ```lua + local comp = function(a,b) return a 3 ++M.sortedIndex({-5,0,4,4},3,comp) -- => 3 + ```` + + ### indexOf (array, value) +@@ -788,7 +938,7 @@ _.sortedIndex({-5,0,4,4},3,comp) -- => 3 + Returns the index of a value in an array. + + ```lua +-_.indexOf({1,2,3},2) -- => 2 ++M.indexOf({1,2,3},2) -- => 2 + ```` + + ### lastIndexOf (array, value) +@@ -796,27 +946,27 @@ _.indexOf({1,2,3},2) -- => 2 + Returns the index of the last occurence of a given value in an array. + + ```lua +-_.lastIndexOf({1,2,2,3},2) -- => 3 ++M.lastIndexOf({1,2,2,3},2) -- => 3 + ```` + +-### findIndex (array, predicate, ...) ++### findIndex (array, pred) + + Returns the first index at which a predicate passes a truth test. + + ```lua + local array = {1,2,3,4,5,6} +-local function multipleOf3(__,v) return v%3==0 end +-_.findIndex(array, multipleOf3) -- => 3 ++local function multipleOf3(v) return v%3==0 end ++M.findIndex(array, multipleOf3) -- => 3 + ```` + +-### findLastIndex (array, predicate, ...) ++### findLastIndex (array, pred) + +-Returns the last index at which a predicate passes a truth test. ++Returns the last index at which a predicate passes a truthy test. + + ```lua + local array = {1,2,3,4,5,6} +-local function multipleOf3(__,v) return v%3==0 end +-_.findLastIndex(array, multipleOf3) -- => 6 ++local function multipleOf3(v) return v%3==0 end ++M.findLastIndex(array, multipleOf3) -- => 6 + ```` + + ### addTop (array, ...) +@@ -825,7 +975,16 @@ Adds given values at the top of an array. The latter values bubbles at the top. + + ```lua + local array = {1} +-_.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}" ++M.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}" ++```` ++ ++### prepend (array, ...) ++ ++Adds given values at the top of an array, preserving the order at which elements are passed-in. ++ ++```lua ++local array = {'old_val'} ++M.prepend(array,1,2,3,4) -- => "{1,2,3,4,'old_val'}" + ```` + + ### push (array, ...) +@@ -834,104 +993,115 @@ Adds given values at the end of an array. + + ```lua + local array = {1} +-_.push(array,1,2,3,4) -- => "{1,1,2,3,4}" ++M.push(array,1,2,3,4) -- => "{1,1,2,3,4}" + ```` + +-### pop (array, n) +-*Aliases: `_.shift`*. ++### shift (array [, n = 1]) ++*Aliases: `pop`*. + + Removes and returns the first value in an array. + + ```lua + local array = {1,2,3} +-local pop = _.pop(array) -- => "pop = 1", "array = {2,3}" ++local shift = M.shift(array) -- => "shift = 1", "array = {2,3}" ++```` ++If `n` is supplied, returns `n` values. ++ ++```lua ++local array = {1,2,3,4,5} ++local a, b = M.shift(array, 2) -- => "a = 1, b = 2", "array = {3,4,5}" + ```` + +-### unshift (array, n) ++### unshift (array [, n = 1]) + + Removes and returns the last value in an array. + + ```lua + local array = {1,2,3} +-local value = _.unshift(array) -- => "value = 3", "array = {1,2}" ++local value = M.unshift(array) -- => "value = 3", "array = {1,2}" + ```` + + ### pull (array, ...) +-*Aliases: `_.remove`*. ++*Aliases: `remove`*. + + Removes all provided values from a given array. + + ```lua +-_.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}" ++M.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}" + ```` + +-### removeRange (array, start, finish) +-*Aliases: `_.rmRange`, `_.chop`*. ++### removeRange (array [, start = 1 [, finish = #array]]) ++*Aliases: `rmRange`, `M.chop`*. + + Trims out all values index within a range. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.removeRange(array, 3,8) -- => "{1,2,9}" ++M.removeRange(array, 3,8) -- => "{1,2,9}" + ```` + +-### chunk (array, f, ...) ++### chunk (array [, f]) + +-Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return +-value of `f(key,value,...)`. Consecutive elements which return the same value are aggregated together. ++Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return value of `f(v, k, ...)`. Consecutive elements which return the same value are chunked together. + + ```lua +-local t = {1,1,2,3,3,4} +-_.chunk(t, function(k,v) return v%2==0 end) -- => "{{1,1},{2},{3,3},{4}}" ++local t = {1,5,2,4,3,3,4} ++M.chunk(t, function(v) return v%2==0 end) -- => "{{1,5},{2,4},{3,3},{4}}" + ```` + +-### slice (array, start, finish) +-*Aliases: `_.sub`*. ++If not given, `f` defaults to `identity`. ++ ++```lua ++local t = {1,5,2,4,3,3,4} ++M.chunk(t) -- => "{{1},{5},{2},{4},{3,3},{4}}" ++```` ++ ++### slice (array [, start = 1 [, finish = #array]]) ++*Aliases: `sub`*. + + Slices and returns a part of an array. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.slice(array, 3,6) -- => "{3,4,5,6}" ++M.slice(array, 3,6) -- => "{3,4,5,6}" + ```` + +-### first (array, n) +-*Aliases: `_.head`, `_.take`*. ++### first (array [, n = 1]) ++*Aliases: `head`, `M.take`*. + + Returns the first N elements in an array. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.first(array,3) -- => "{1,2,3}" ++M.first(array,3) -- => "{1,2,3}" + ```` + +-### initial (array, n) ++### initial (array [, n = #array]) + + Excludes the last N elements in an array. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.initial(array,5) -- => "{1,2,3,4}" ++M.initial(array,5) -- => "{1,2,3,4}" + ```` + +-### last (array, n) +-*Aliases: `_.skip`*. ++### last (array [, n = #array]) + + Returns the last N elements in an array. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.last(array,3) -- => "{7,8,9}" ++M.last(array,3) -- => "{7,8,9}" + ```` + +-### rest (array, index) +-*Aliases: `_.tail`*. ++### rest (array [, index = 1]) ++*Aliases: `tail`*. + +-Trims out all values indexed before *index*. ++Returns all values after *index*, including the given *index* itself. + + ```lua + local array = {1,2,3,4,5,6,7,8,9} +-_.rest(array,6) -- => "{6,7,8,9}" ++M.rest(array,6) -- => "{6,7,8,9}" + ```` + + ### nth (array, index) +@@ -940,7 +1110,7 @@ Returns the value at *index*. + + ```lua + local array = {1,2,3,4,5,6} +-_.nth(array,3) -- => "3" ++M.nth(array,3) -- => "3" + ```` + + ### compact (array) +@@ -948,31 +1118,31 @@ _.nth(array,3) -- => "3" + Trims out all falsy values. + + ```lua +-_.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}" ++M.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}" + ```` + +-### flatten (array, shallow) ++### flatten (array [, shallow = false]) + + Flattens a nested array. + + ```lua +-_.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}" ++M.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}" + ```` + +-When given arg "shallow", flatten only at the first level. ++When given arg `shallow`, flatten only at the first level. + + ```lua +-_.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}" ++M.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}" + ```` + + ### difference (array, array2) +-*Aliases: `_.without`, `_.diff`*. ++*Aliases: `without`, `diff`*. + + Returns values in the given array not present in a second array. + + ```lua + local array = {1,2,'a',4,5} +-_.difference(array,{1,'a'}) -- => "{2,4,5}" ++M.difference(array,{1,'a'}) -- => "{2,4,5}" + ```` + + ### union (...) +@@ -983,10 +1153,10 @@ Produces a duplicate-free union of all passed-in arrays. + local A = {'a'} + local B = {'a',1,2,3} + local C = {2,10} +-_.union(A,B,C) -- => "{'a',1,2,3,10}" ++M.union(A,B,C) -- => "{'a',1,2,3,10}" + ```` + +-### intersection (array, ...) ++### intersection (...) + + Returns the intersection (common-part) of all passed-in arrays: + +@@ -994,48 +1164,87 @@ Returns the intersection (common-part) of all passed-in arrays: + local A = {'a'} + local B = {'a',1,2,3} + local C = {2,10,1,'a'} +-_.intersection(A,B,C) -- => "{'a',2,1}" ++M.intersection(A,B,C) -- => "{'a'}" ++```` ++ ++### disjoint (...) ++ ++Checks if all passed in arrays are disjoint. ++ ++```lua ++local A = {'a'} ++local B = {'a',1,3} ++local C = {3,10,2} ++ ++M.disjoint(A,B) -- => false ++M.disjoint(A,C) -- => true ++M.disjoint(B,C) -- => false + ```` + + ### symmetricDifference (array, array2) +-*Aliases: `_.symdiff`,`_.xor`*. ++*Aliases: `symdiff`,`xor`*. + + Returns values in the first array not present in the second and also values in the second array not present in the first one. + + ```lua + local array = {1,2,3} + local array2 = {1,4,5} +-_.symmetricDifference(array, array2) -- => "{2,3,4,5}" ++M.symmetricDifference(array, array2) -- => "{2,3,4,5}" + ```` + + ### unique (array) +-*Aliases: `_.uniq`*. ++*Aliases: `uniq`*. + + Makes an array duplicate-free. + + ```lua +-_.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}" ++M.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}" + ```` + + ### isunique (array) +-*Aliases: `_.isuniq`*. ++*Aliases: `isuniq`*. + + Checks if a given array contains no duplicate value. + + ```lua +-_.isunique({1,2,3,4,5}) -- => true +-_.isunique({1,2,3,4,4}) -- => false ++M.isunique({1,2,3,4,5}) -- => true ++M.isunique({1,2,3,4,4}) -- => false ++```` ++ ++### duplicates (array) ++ ++Returns an array list of all duplicates in array. ++ ++```lua ++M.duplicates({1,2,3,3,8,8,3,2,4}) -- => {2,3,8} + ```` + + ### zip (...) +-*Aliases: `_.transpose`*. ++*Aliases: `transpose`*. + + Zips values from different arrays, on the basis on their common keys. + + ```lua + local names = {'Bob','Alice','James'} + local ages = {22, 23} +-_.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" ++M.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" ++```` ++ ++### zipWith (f, ...) ++*Aliases: `transposeWith`*. ++ ++Merges values using a given function. Only values indexed with the same key in the given arrays are merged in the same subset. ++Function `f` is used to combine values. ++ ++```lua ++local names = {'Bob','Alice','James'}; local ages = {22, 23, 25} ++local function introduce(name, age) return 'I am '..name..' and I am '..age..' years old.' end ++local t = M.zipWith(introduce,names,ages) ++-- => { ++-- => 'I am Bob and I am 22 years old.' ++-- => 'I am Alice and I am 23 years old.' ++-- => 'I am James and I am 25 years old.' ++-- => } + ```` + + ### append (array, other) +@@ -1043,7 +1252,7 @@ _.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" + Appends two arrays. + + ```lua +-_.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}" ++M.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}" + ```` + + ### interleave (...) +@@ -1053,35 +1262,43 @@ Interleaves values from passed-in arrays. + ```lua + t1 = {1, 2, 3} + t2 = {'a', 'b', 'c'} +-_.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}" ++M.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}" + ```` + +-### interpose (value, array) ++### interpose (array, value) ++*Aliases: `intersperce`*. + + Interposes a value between consecutive values in an arrays. + + ```lua +-_.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}" ++M.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}" + ```` + +-### range (...) ++### range ([from [, to [, step]]]) + + Generates an arithmetic sequence. + + ```lua +-_.range(1,4) -- => "{1,2,3,4}" ++M.range(1,4) -- => "{1,2,3,4}" + ```` + +-In case a single value is provided, it generates a sequence from 0 to that value. ++In case a single value is provided, it generates a sequence from 1 to that value. + + ```` +-_.range(3) -- => "{0,1,2,3}" ++M.range(3) -- => "{1,2,3}" + ```` + + The incremental step can also be provided as third argument. + + ```lua +-_.range(0,2,0.7) -- => "{0,0.7,1.4}" ++M.range(0,2,0.7) -- => "{0,0.7,1.4}" ++```` ++ ++It also handles negative progressions. ++ ++```lua ++M.range(-5) -- => "{-1,-2,-3,-4,-5}" ++M.range(5,1) -- => "{5,4,3,2,1}" + ```` + + ### rep (value, n) +@@ -1089,17 +1306,25 @@ _.range(0,2,0.7) -- => "{0,0.7,1.4}" + Generates a list of n repetitions of a value. + + ```lua +-_.rep(4,3) -- => "{4,4,4}" ++M.rep(4,3) -- => "{4,4,4}" ++```` ++ ++### powerset (array) ++ ++Returns the powerset of an array. ++ ++```lua ++M.powerset {1,2,3} -- => "{{1},{2},{3},{1,2},{2,3},{1,2,3}}" + ```` + +-### partition (array, n, pad) +-*Aliases: `_.part`*. ++### partition (array [, n = 1 [, pad]]) ++*Aliases: `part`*. + + Returns an iterator function for partitions of a given array. + + ```lua + local t = {1,2,3,4,5,6} +-for p in _.partition(t,2) do ++for p in M.partition(t,2) do + print(table.concat(p, ',')) + end + +@@ -1108,7 +1333,7 @@ end + -- => 5,6 + + local t = {1,2,3,4,5,6} +-for p in _.partition(t,4) do ++for p in M.partition(t,4) do + print(table.concat(p, ',')) + end + +@@ -1120,7 +1345,7 @@ In case the last partition has less elements than desired, a 3rd argument can be + + ```lua + local t = {1,2,3,4,5,6} +-for p in _.partition(t,4,0) do ++for p in M.partition(t,4,0) do + print(table.concat(p, ',')) + end + +@@ -1128,13 +1353,13 @@ end + -- => 5,6,0,0 + ```` + +-### sliding (array, n, pad) ++### overlapping (array [, n = 2 [, pad]]) + + Returns an iterator function which provides overlapping subsequences of a given array. + + ```lua + local t = {1,2,3,4,5,6,7} +-for p in _.sliding(t,3) do ++for p in M.overlapping(t,3) do + print(table.concat(p,',')) + end + +@@ -1142,14 +1367,14 @@ end + -- => 3,4,5 + -- => 5,6,7 + +-for p in _.sliding(t,4) do ++for p in M.overlapping(t,4) do + print(table.concat(p,',')) + end + + -- => 1,2,3,4 + -- => 4,5,6,7 + +-for p in _.sliding(t,5) do ++for p in M.overlapping(t,5) do + print(table.concat(p,',')) + end + +@@ -1161,7 +1386,7 @@ In case the last subsequence wil not match the exact desired length, it can be a + + ```lua + local t = {1,2,3,4,5,6,7} +-for p in _.sliding(t,5,0) do ++for p in M.overlapping(t,5,0) do + print(table.concat(p,',')) + end + +@@ -1169,14 +1394,53 @@ end + -- => 5,6,7,0,0 + ```` + ++### aperture (array [, n = 2]) ++*Aliases: `sliding`*. ++ ++Returns an iterator function which provides sliding partitions of a given array. ++ ++```lua ++local t = {1,2,3,4,5} ++for p in M.aperture(t,4) do ++ print(table.concat(p,',')) ++end ++ ++-- => 1,2,3,4 ++-- => 2,3,4,5 ++ ++for p in M.aperture(t,3) do ++ print(table.concat(p,',')) ++end ++ ++-- => 1,2,3 ++-- => 2,3,4 ++-- => 3,4,5 ++```` ++ ++### pairwise (array) ++ ++Iterator returning sliding pairs of an array. ++ ++```lua ++local t = M.range(5) ++for p in pairwise(t) do ++ print(table.concat(p,',')) ++end ++ ++-- => 1,2 ++-- => 2,3 ++-- => 3,4 ++-- => 4,5 ++```` ++ + ### permutation (array) +-*Aliases: `_.perm`*. ++*Aliases: `perm`*. + + Returns an iterator function for permutations of a given array. + + ```lua + t = {'a','b','c'} +-for p in _.permutation(t) do ++for p in M.permutation(t) do + print(table.concat(p)) + end + +@@ -1188,22 +1452,73 @@ end + -- => 'abc' + ```` + +-### invert (array) +-*Aliases: `_.mirror`*. ++### concat (array [, sep = '' [, i = 1 [, j = #array]]]) ++*Aliases: `join`*. + +-Switches key-value pairs: ++Concatenates a given array values: + + ```lua +-_.invert {'a','b','c'} -- => "{a=1, b=2, c=3}" ++M.concat({'a',1,0,1,'b'}) -- => 'a101b' + ```` + +-### concat (array, sep, i, j) +-*Aliases: `_.join`*. ++### xprod (array, array2) + +-Concatenates a given array values: ++Returns all possible pairs built from given arrays. ++ ++```lua ++local t = M.xprod({1,2},{'a','b'}) ++-- => {{1,'a'},{1,'b'},{2,'a'},{2,'b'}} ++```` ++ ++### xpairs (value, array) ++ ++Creates pairs from value and array. Value is always prepended to the pair. ++ ++```lua ++local t = M.xpairs(1, {1, 2, 3}) ++-- => {{1,1},{1,2},{1,3}} ++```` ++ ++### xpairsRight (value, array) ++ ++Creates pairs from value and array. Value is always appended as the last item to the pair. + + ```lua +-_.concat({'a',1,0,1,'b'}) -- => 'a101b' ++local t = M.xpairsRight(1, {1, 2, 3}) ++-- => {{1,1},{2,1},{3,1}} ++```` ++ ++### sum (array) ++ ++Returns the sum of array values. ++ ++```lua ++M.sum({1,2,3,4,5}) -- => 15 ++```` ++ ++### product (array) ++ ++Returns the product of array values. ++ ++```lua ++M.product({1,2,3,4,5}) -- => 120 ++```` ++ ++### mean (array) ++ ++Returns the mean of array values. ++ ++```lua ++M.mean({1,2,3,4,5}) -- => 3 ++```` ++ ++### median (array) ++ ++Returns the median of array values. ++ ++```lua ++M.median({1,2,3,4,5}) -- => 3 ++M.median({1,2,3,4}) -- => 2.5 + ```` + + **[[⬆]](#TOC)** +@@ -1215,7 +1530,7 @@ _.concat({'a',1,0,1,'b'}) -- => 'a101b' + The no-operation function. Takes nothing, returns nothing. It is being used internally. + + ```lua +-_.noop() -- => nil ++M.noop() -- => nil + ```` + + ### identity (value) +@@ -1224,9 +1539,19 @@ Returns the passed-in value.
    + This function is internally used as a default transformation function. + + ```lua +-_.identity(1)-- => 1 +-_.identity(false) -- => false +-_.identity('hello!') -- => 'hello!' ++M.identity(1)-- => 1 ++M.identity(false) -- => false ++M.identity('hello!') -- => 'hello!' ++```` ++ ++### call (f [, ...]) ++ ++Calls `f` with the supplied arguments. Returns the results of `f(...)`. ++ ++```lua ++M.call(math.pow, 2, 3) -- => 8 ++M.call(string.len, 'hello' ) -- => 5 ++M.call(table.concat, {1,2,3,4,5}, ',', 2, 4) -- => {2,3,4} + ```` + + ### constant (value) +@@ -1234,14 +1559,82 @@ _.identity('hello!') -- => 'hello!' + Creates a constant function. This function will continuously yield the same output. + + ```lua +-local pi = _.constant(math.pi) ++local pi = M.constant(math.pi) + pi(1) -- => 3.1415926535898 + pi(2) -- => 3.1415926535898 + pi(math.pi) -- => 3.1415926535898 + ```` + +-### memoize (f, hash) +-*Aliases: `_.cache`*. ++### applySpec (specs) ++ ++Returns a function which applies `specs` on args. This function will produce an object having the same structure than `specs` ++by mapping each property to the result of calling its associated function with the supplied arguments. ++ ++```lua ++local stats = M.applySpec({ ++ min = function(...) return math.min(...) end, ++ max = function(...) return math.max(...) end, ++}) ++ ++stats(5,4,10,1,8) -- => {min = 1, max = 10} ++```` ++ ++### thread (value [, ...]) ++ ++Threads `value` through a series of functions. ++ ++```lua ++local function inc(x) return x + 1 end ++local function double(x) return 2 * x end ++local function square(x) return x * x end ++M.thread(2, inc, double, square) -- => 36 ++M.thread(3, double, inc, square) -- => 49 ++M.thread(4, square, double, inc) -- => 33 ++M.thread(5, square, inc, double) -- => 52 ++```` ++ ++If a function expects more than one args, it can be specified using an array list, ++where the first item is the function and the following are the remaining args neeeded. ++ ++```lua ++local function inc(x) return x + 1 end ++local function add(x, y) return x * y end ++local function pow(x, y) return x ^ y end ++M.thread(2, inc, {add, 3}, {pow, 2}) -- => 36 ++M.thread(2, {add, 4}, inc, {pow, 2}) -- => 49 ++```` ++ ++### threadRight (value [, ...]) ++ ++Threads `value` through a series of functions. If a function expects more than one args, ++it can be specified using an array list, where the first item is the function and the following are ++the remaining args neeeded. The value is used as the last input. ++ ++```lua ++local function inc(x) return x + 1 end ++local function add(x, y) return x * y end ++local function pow(x, y) return x ^ y end ++M.threadRight(2, inc, {add, 3}, {pow, 2}) -- => 64 ++M.threadRight(2, {add, 4}, inc, {pow, 2}) -- => 128 ++```` ++ ++### dispatch (...) ++ ++Returns a dispatching function. When called with arguments, this function invokes each of its functions ++in the passed-in order and returns the results of the first non-nil evaluation. ++ ++```lua ++local f = M.dispatch( ++ function() return nil end, ++ function (v) return v+1 end, ++ function (v) return 2*v end ++) ++f(5) -- => 6 ++f(7) -- => 8 ++```` ++ ++### memoize (f) ++*Aliases: `cache`*. + + Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body. + +@@ -1249,17 +1642,28 @@ Memoizes a slow-running function. It caches the result for a specific input, so + local function fibonacci(n) + return n < 2 and n or fibonacci(n-1)+fibonacci(n-2) + end +-local mem_fibonacci = _.memoize(fibonacci) ++local mem_fibonacci = M.memoize(fibonacci) + fibonacci(20) -- => 6765 (but takes some time) + mem_fibonacci(20) -- => 6765 (takes less time) + ```` + ++### unfold (f, seed) ++ ++Builds a list from a seed value. Accepts an iterator function, which returns either nil to stop iteration or two values : the value to add to the list of results and the seed to be used in the next call to the iterator function. ++ ++```lua ++local function f(v) ++ if v < 100 then return v, v * 2 end ++end ++local t = M.unfold(f, 10) -- => {10,20,40,80} ++```` ++ + ### once (f) + + Produces a function that runs only once. Successive calls to this function will still yield the same input. + + ```lua +-local sq = _.once(function(a) return a*a end) ++local sq = M.once(function(a) return a*a end) + sq(1) -- => 1 + sq(2) -- => 1 + sq(3) -- => 1 +@@ -1273,7 +1677,7 @@ Returns a version of `f` that will run no more than `count` times. Next calls wi + + ```lua + local function greet(someone) return 'hello '..someone end +-local greetOnly3people = _.before(greet, 3) ++local greetOnly3people = M.before(greet, 3) + greetOnly3people('John') -- => 'hello John' + greetOnly3people('Moe') -- => 'hello Moe' + greetOnly3people('James') -- => 'hello James' +@@ -1286,7 +1690,7 @@ greetOnly3people('Allan') -- => 'hello James' + Produces a function that will respond only after a given number of calls. + + ```lua +-local f = _.after(_.identity,3) ++local f = M.after(M.identity,3) + f(1) -- => nil + f(2) -- => nil + f(3) -- => 3 +@@ -1301,7 +1705,7 @@ Composes functions. Each function consumes the return value of the one that foll + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +-local compositae = _.compose(f,g,h) ++local compositae = M.compose(f,g,h) + compositae(10) -- => 36 + compositae(20) -- => 121 + ```` +@@ -1314,8 +1718,8 @@ Pipes a value through a series of functions. + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +-_.pipe(10,f,g,h) -- => 36 +-_.pipe(20,f,g,h) -- => 121 ++M.pipe(10,f,g,h) -- => 36 ++M.pipe(20,f,g,h) -- => 121 + ```` + + ### complement (f) +@@ -1323,11 +1727,11 @@ _.pipe(20,f,g,h) -- => 121 + Returns a function which returns the logical complement of a given function. + + ```lua +-_.complement(function() return true end)() -- => false ++M.complement(function() return true end)() -- => false + ```` + + ### juxtapose (value, ...) +-*Aliases: `_.juxt`*. ++*Aliases: `juxt`*. + + Calls a sequence of functions with the same input. + +@@ -1335,7 +1739,7 @@ Calls a sequence of functions with the same input. + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +-_.juxtapose(10, f, g, h) -- => 100, 11, 5 ++M.juxtapose(10, f, g, h) -- => 100, 11, 5 + ```` + + ### wrap (f, wrapper) +@@ -1344,7 +1748,7 @@ Wraps a function inside a wrapper. Allows the wrapper to execute code before and + + ```lua + local greet = function(name) return "hi: " .. name end +-local greet_backwards = _.wrap(greet, function(f,arg) ++local greet_backwards = M.wrap(greet, function(f,arg) + return f(arg) ..'\nhi: ' .. arg:reverse() + end) + greet_backwards('John') +@@ -1353,13 +1757,13 @@ greet_backwards('John') + -- => hi: nhoJ + ```` + +-### times (n, iter, ...) ++### times (iter [, n]) + + Calls a given function `n` times. + + ```lua + local f = ('Lua programming'):gmatch('.') +-_.times(3,f) -- => {'L','u','a'} ++M.times(f, 3) -- => {'L','u','a'} + ```` + + ### bind (f, v) +@@ -1367,7 +1771,7 @@ _.times(3,f) -- => {'L','u','a'} + Binds a value to be the first argument to a function. + + ```lua +-local sqrt2 = _.bind(math.sqrt,2) ++local sqrt2 = M.bind(math.sqrt,2) + sqrt2() -- => 1.4142135623731 + ```` + +@@ -1376,7 +1780,7 @@ sqrt2() -- => 1.4142135623731 + Binds a value to be the second argument to a function. + + ```lua +-local last2 = _.bind(_.last,2) ++local last2 = M.bind(M.last,2) + last2({1,2,3,4,5,6}) -- => {5,6} + ```` + +@@ -1386,12 +1790,12 @@ Binds a variable number of values to be the first arguments to a function. + + ```lua + local function out(...) return table.concat {...} end +-local out = _.bindn(out,'OutPut',':',' ') ++local out = M.bindn(out,'OutPut',':',' ') + out(1,2,3) -- => OutPut: 123 + out('a','b','c','d') -- => OutPut: abcd + ```` + +-### bindAll (obj, ...) ++### bindall (obj, ...) + + Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument. + +@@ -1401,7 +1805,7 @@ local window = { + setName = function(w,name) w.name = name end, + getName = function(w) return w.name end, + } +-window = _.bindAll(window, 'setPos', 'setName', 'getName') ++window = M.bindall(window, 'setPos', 'setName', 'getName') + window.setPos(10,15) + print(window.x, window.y) -- => 10,15 + +@@ -1411,50 +1815,184 @@ print(window.name) -- => 'fooApp' + print(window.getName()) -- => 'fooApp' + ```` + +-### uniqueId (template, ...) +-*Aliases: `_.uid`*. ++### cond (conds) ++ ++Returns a function which iterate over an array list of conditions. It invokes each predicate, passing it given values. It returns the value of the corresponding function of the first predicate to return a non-nil value ++ ++```lua ++local multipleOf = M.cond({ ++ {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end}, ++ {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end}, ++ {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end}, ++ {function() return true end, function(v) return 'could not find an answer for '..v end} ++}) ++for i = 15, 20 do ++ print(multipleOf(i)) ++end ++ ++-- => 15 is multiple of 3 ++-- => 16 is multiple of 2 ++-- => could not find an answer for 17 ++-- => 18 is multiple of 2 ++-- => could not find an answer for 19 ++-- => 20 is multiple of 2 ++```` ++ ++### both (...) ++ ++Returns a validation function. Given a set of functions, the validation function ++evaluates to `true` only when all its funcs returns `true`. ++ ++```lua ++local f = M.both( ++ function(x) return x > 0 end, ++ function(x) return x < 10 end, ++ function(x) return x % 2 == 0 end ++) ++f(2) -- => true ++f(8) -- => true ++f(9) -- => false ++```` ++ ++### either (...) ++ ++Returns a validation function. Given a set of functions, the validation function ++evaluates to `true` when one of its funcs returns `true`. ++ ++```lua ++local f = M.either( ++ function(x) return x > 0 end, ++ function(x) return x % 2 == 0 end ++) ++f(0) -- => true ++f(-3) -- => false ++```` ++ ++### neither (...) ++ ++Returns a validation function. Given a set of functions, the validation function ++evaluates to `true` when neither of its funcs returns `true`. ++ ++```lua ++local f = M.neither( ++ function(x) return x > 10 end, ++ function(x) return x % 2 == 0 end ++) ++f(12) -- => false ++f(8) -- => false ++f(7) -- => true ++```` ++ ++### uniqueId ([template]) ++*Aliases: `uid`*. + + Returns an unique integer ID. + + ```lua +-_.uniqueId() -- => 1 ++M.uniqueId() -- => 1 + ```` + + Can handle string templates for formatted output. + + ```lua +-_.uniqueId('ID%s') -- => 'ID2' ++M.uniqueId('ID%s') -- => 'ID2' + ```` + + Or a function, for the same purpose. + + ```lua + local formatter = function(ID) return '$'..ID..'$' end +-_.uniqueId(formatter) -- => '$ID1$' ++M.uniqueId(formatter) -- => '$ID1$' + ```` + +-### iterator(f, x) +-*Aliases: `_.iter`*. ++### iterator (f, value [, n]) ++*Aliases: `iter`*. + +-Returns an iterator function which constinuously applies a function `f` onto an input `x`. +-For example, let us go through the powers of two. ++Returns an iterator function which constinuously applies a function `f` onto an input `value`. ++For example, let us go through the powers of two using `iterator`. + + ```lua + local function po2(x) return x*2 end +-local function iter_po2 = _.iterator(po2, 1) ++local function iter_po2 = M.iterator(po2, 1) + iter_po2() -- => 2 + iter_po2() -- => 4 + iter_po2() -- => 8 + ```` + +-### array (...) ++if `n` is supplied, it will run at maximum `n` times. ++ ++```lua ++local function po2(x) return x*2 end ++local function iter_po2 = M.iterator(po2, 1, 3) ++iter_po2() -- => 2 ++iter_po2() -- => 4 ++iter_po2() -- => 8 ++iter_po2() -- => nil ++```` ++ ++### skip (iter [, n = 1]) ++ ++Consumes the first `n` values of a iterator then returns it. ++ ++```lua ++local w = "hello" ++local char = string.gmatch(w,'.') ++local iter = M.skip(char, 3) ++for w in iter do print(w) end -- => 'l', 'o' ++```` ++ ++`n` defaults to 1 when not given. ++ ++```lua ++local w = "hello" ++local char = string.gmatch(w,'.') ++local iter = M.skip(char) ++for w in iter do print(w) end -- => 'e', 'l', 'l', 'o' ++```` ++ ++### tabulate (...) + + Iterates a given iterator function and returns its values packed in an array. + + ```lua + local text = 'letters' + local chars = string.gmatch(text, '.') +-local letters = _.array(chars) -- => {'l','e','t','t','e','r','s'} ++M.tabulate(chars) -- => {'l','e','t','t','e','r','s'} ++```` ++ ++### iterlen (...) ++ ++Returns the length of an iterator. ++ ++```lua ++local text = 'letters' ++local chars = string.gmatch(text, '.') ++M.iterlen(chars) -- => 7 ++```` ++ ++It consumes the iterator itself. ++ ++```lua ++local text = 'lua' ++local chars = string.gmatch(text, '.') ++M.iterlen(chars) -- => 3 ++chars() -- => nil ++```` ++ ++### castArray (value) ++ ++Casts the passed-in value to an array containing the value itself. ++ ++```lua ++M.castArray(true) -- => {true} ++M.castArray(2) -- => {2} ++```` ++ ++It leaves the given value untouched in case it is already a table. ++ ++```lua ++local t = {1} ++print(M.castArray(t) == t) -- => true + ```` + + ### flip (f) +@@ -1463,17 +2001,80 @@ Creates a function of `f` with arguments flipped in reverse order. + + ```lua + local function f(...) return table.concat({...}) end +-local flipped = _.flip(f) ++local flipped = M.flip(f) + flipped('a','b','c') -- => 'cba' + ```` + ++### nthArg (n) ++ ++Returns a function that gets the nth argument. ++ ++```lua ++local f = M.nthArg(3) ++f('a','b','c') -- => 'c' ++```` ++ ++If n is negative, the nth argument from the end is returned. ++ ++```lua ++local f = M.nthArg(-2) ++f('a','b','c') -- => 'b' ++```` ++ ++### unary (f) ++ ++Returns a function which accepts up to one argument. It ignores any additional arguments. ++ ++```lua ++local f = M.unary(function (...) return ... end) ++f('a') - ==> 'a' ++f('a','b','c') -- => 'a' ++```` ++ ++### ary (f [, n = 1]) ++*Aliases: `nAry`*. ++ ++Returns a function which accepts up to `n` args. It ignores any additional arguments. ++ ++```lua ++local f = M.ary(function (...) return ... end, 2) ++f(1,2) - ==> 1,2 ++f(1,2,3,4) -- => 1,2 ++```` ++ ++If `n` is not given, it defaults to `1`. ++ ++```lua ++local f = M.unary(function (...) return ... end) ++f('a','b','c') -- => 'a' ++```` ++ ++### noarg (f) ++ ++Returns a function with an arity of 0. The new function ignores any arguments passed to it. ++ ++```lua ++local f = M.noarg(function (x) return x or 'default' end) ++f(1) -- => 'default' ++f(function() end, 3) -- => 'default' ++```` ++ ++### rearg (f, indexes) ++ ++Returns a function which runs with arguments arranged according to given `indexes`. ++ ++```lua ++local f = M.rearg(function (...) return ... end, {5,4,3,2,1}) ++f('a','b','c','d','e') -- => 'e','d','c','b','a' ++```` ++ + ### over (...) + + Creates a function that invokes a set of transforms with the arguments it receives.
    + One can use use for example to get the tuple of min and max values from a set of values + + ```lua +-local minmax = _.over(math.min, math.max) ++local minmax = M.over(math.min, math.max) + minmax(5,10,12,4,3) -- => {3,12} + ```` + +@@ -1496,7 +2097,7 @@ local function allpositive(...) + return true + end + +-local allok = _.overEvery(alleven, allpositive) ++local allok = M.overEvery(alleven, allpositive) + + allok(2,4,-1,8) -- => false + allok(10,3,2,6) -- => false +@@ -1522,7 +2123,7 @@ local function allpositive(...) + return true + end + +-local anyok = _.overSome(alleven,allpositive) ++local anyok = M.overSome(alleven,allpositive) + + anyok(2,4,-1,8) -- => false + anyok(10,3,2,6) -- => true +@@ -1537,7 +2138,7 @@ Creates a function that invokes `f` with its arguments transformed + local function f(x, y) return x, y end + local function triple(x) retun x*3 end + local function square(x) retun x^2 end +-local new_f = _.overArgs(f, triple, square) ++local new_f = M.overArgs(f, triple, square) + + new_f(1,2) -- => 3, 4 + new_f(10,10) -- => 30, 100 +@@ -1549,19 +2150,31 @@ In case the number of arguments is greater than the number of transforms, the re + local function f(x, y, z) return x, y, z end + local function triple(x) retun x*3 end + local function square(x) retun x^2 end +-local new_f = _.overArgs(f, triple, square) ++local new_f = M.overArgs(f, triple, square) + + new_f(1,2,3) -- => 3, 4, 3 + new_f(10,10,10) -- => 30, 100, 10 + ```` + ++### converge (f, g, h) ++ ++Converges two functions into one. ++ ++```lua ++local function pow2(x) return x*x end ++local function pow3(x) return x*x*x end ++local function sum(a,b) return a+b end ++local poly = M.converge(sum, pow2, pow3) ++poly(5) -- => 150 (ie. 5*5 + 5*5*5) ++```` ++ + ### partial (f, ...) + + Partially apply a function by filling in any number of its arguments. + + ```lua + local function diff(a, b) return a - b end +-local diffFrom20 = _.partial(diff, 20) -- arg 'a' will be 20 by default ++local diffFrom20 = M.partial(diff, 20) -- arg 'a' will be 20 by default + diffFrom20(5) -- => 15 + ```` + +@@ -1569,23 +2182,23 @@ The string `'_'` can be used as a placeholder in the list of arguments to specif + + ```lua + local function diff(a, b) return a - b end +-local remove5 = _.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5 ++local remove5 = M.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5 + remove5(20) -- => 15 + ```` + + ### partialRight (f, ...) + +-Like `_.partial`, it partially applies a function by filling in any number of its arguments, but from the right. ++Like `M.partial`, it partially applies a function by filling in any number of its arguments, but from the right. + + ```lua + local function concat(...) return table.concat({...},',') end +-local concat_right = _.partialRight(concat,'a','b','c') ++local concat_right = M.partialRight(concat,'a','b','c') + concat_right('d') -- => d,a,b,c + +-concat_right = _.partialRight(concat,'a','b') ++concat_right = M.partialRight(concat,'a','b') + concat_right('c','d') -- => c,d,a,b + +-concat_right = _.partialRight(concat,'a') ++concat_right = M.partialRight(concat,'a') + concat_right('b','c','d') -- => b,c,d,a + ``` + +@@ -1594,24 +2207,24 @@ In that case, the first args supplied at runtime will be used to fill the initia + + ```lua + local function concat(...) return table.concat({...},',') end +-local concat_right = _.partialRight(concat,'a','_','c') ++local concat_right = M.partialRight(concat,'a','_','c') + concat_right('d','b') -- => b,a,d,c + +-concat_right = _.partialRight(concat,'a','b','_') ++concat_right = M.partialRight(concat,'a','b','_') + concat_right('c','d') -- => d,a,b,c + +-concat_right = _.partialRight(concat,'_','a') ++concat_right = M.partialRight(concat,'_','a') + concat_right('b','c','d') -- => c,d,b,a + ```` + +-### curry (f, n_args) ++### curry (f [, n_args = 2]) + + Curries a function. If the given function `f` takes multiple arguments, it returns another version of `f` that takes a single argument + (the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result. + + ```lua + local function sumOf3args(x,y,z) return x + y + z end +-local curried_sumOf3args = _.curry(sumOf3args, 3) ++local curried_sumOf3args = M.curry(sumOf3args, 3) + sumOf3args(1)(2)(3)) -- => 6 + sumOf3args(0)(6)(9)) -- => 15 + ```` +@@ -1620,13 +2233,13 @@ sumOf3args(0)(6)(9)) -- => 15 + + ```lua + local function product(x,y) return x * y end +-local curried_product = _.curry(product) ++local curried_product = M.curry(product) + curried_product(5)(4) -- => 20 + curried_product(3)(-5) -- => -15 + curried_product(0)(1) -- => 0 + ```` + +-### time (f, ...) ++### time (f [, ...]) + + Returns the execution time of `f (...)` in seconds and its results. + +@@ -1637,8 +2250,8 @@ local function wait_count(n) + return i + end + +-local time, i = _.time(wait_count, 1e6) -- => 0.002 1000000 +-local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000 ++local time, i = M.time(wait_count, 1e6) -- => 0.002 1000000 ++local time, i = M.time(wait_count, 1e7) -- => 0.018 10000000 + ```` + + **[[⬆]](#TOC)** +@@ -1650,8 +2263,8 @@ local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000 + Collects the names of an object attributes. + + ```lua +-_.keys({1,2,3}) -- => "{1,2,3}" +-_.keys({x = 0, y = 1}) -- => "{'y','x'}" ++M.keys({1,2,3}) -- => "{1,2,3}" ++M.keys({x = 0, y = 1}) -- => "{'y','x'}" + ```` + + ### values (obj) +@@ -1659,8 +2272,49 @@ _.keys({x = 0, y = 1}) -- => "{'y','x'}" + Collects the values of an object attributes. + + ```lua +-_.values({1,2,3}) -- => "{1,2,3}" +-_.values({x = 0, y = 1}) -- => "{1,0}" ++M.values({1,2,3}) -- => "{1,2,3}" ++M.values({x = 0, y = 1}) -- => "{1,0}" ++```` ++ ++### path (obj, ...) ++ ++Returns the value at a given path in an object. ++ ++```lua ++local entity = { ++ pos = {x = 1, y = 2}, ++ engine = { ++ left = {status = 'active', damage = 5}, ++ right = {status = 'off', damage = 10} ++ }, ++ boost = false ++} ++ ++M.path(entity,'pos','x') -- => 1 ++M.path(entity,'pos','y') -- => 2 ++M.path(entity,'engine','left','status') -- => 'active' ++M.path(entity,'engine','right','damage') -- => 10 ++M.path(entity,'boost') -- => false ++```` ++ ++### spreadPath (obj, ...) ++ ++Spreads object under property path onto provided object. It is similar to `flattenPath`, but removes object under the property path. ++ ++```lua ++local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} ++M.spreadPath(obj, 'c', 'f') ++-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {f = {}}} ++```` ++ ++### flattenPath (obj, ...) ++ ++Flattens object under property path onto provided object. It is similar to `spreadPath`, but preserves object under the property path. ++ ++```lua ++local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} ++M.spreadPath(obj, 'c', 'f') ++-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}} + ```` + + ### kvpairs (obj) +@@ -1669,7 +2323,7 @@ Converts an object to an array-list of key-value pairs. + + ```lua + local obj = {x = 1, y = 2, z = 3} +-_.each(_.kvpairs(obj), function(k,v) ++M.each(M.kvpairs(obj), function(v,k) + print(k, table.concat(v,',')) + end) + +@@ -1678,23 +2332,33 @@ end) + -- => 3 z,3 + ```` + +-### toObj ++### toObj (kvpairs) + + Converts an array list of `kvpairs` to an object where keys are taken from the 1rst column in the `kvpairs` sequence, associated with values in the 2nd column. + + ```lua + local list_pairs = {{'x',1},{'y',2},{'z',3}} +-obj = _.toObj(list_pairs) ++obj = M.toObj(list_pairs) + + -- => {x = 1, y = 2, z = 3} + ```` + ++### invert (obj) ++*Aliases: `mirror`*. ++ ++Switches key-value pairs: ++ ++```lua ++M.invert {'a','b','c'} -- => "{a=1, b=2, c=3}" ++M.invert {x = 1, y = 2} -- => "{'x','y'}" ++```` ++ + ### property (key) + + Returns a function that will return the key property of any passed-in object. + + ```lua +-local who = _.property('name') ++local who = M.property('name') + local people = {name = 'Henry'} + who(people) -- => 'Henry' + ```` +@@ -1705,7 +2369,7 @@ Returns a function that will return the key property of any passed-in object. + + ```lua + local people = {name = 'Henry'} +-print(_.propertyOf(people)('name')) -- => 'Henry' ++print(M.propertyOf(people)('name')) -- => 'Henry' + ```` + + ### toBoolean (value) +@@ -1713,11 +2377,11 @@ print(_.propertyOf(people)('name')) -- => 'Henry' + Converts a given value to a boolean. + + ```lua +-_.toBoolean(true) -- => true +-_.toBoolean(false) -- => false +-_.toBoolean(nil) -- => false +-_.toBoolean({}) -- => true +-_.toBoolean(1) -- => true ++M.toBoolean(true) -- => true ++M.toBoolean(false) -- => false ++M.toBoolean(nil) -- => false ++M.toBoolean({}) -- => true ++M.toBoolean(1) -- => true + ```` + + ### extend (destObj, ...) +@@ -1725,40 +2389,50 @@ _.toBoolean(1) -- => true + Extends a destination object with the properties of some source objects. + + ```lua +-_.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}" ++M.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}" + ```` + +-### functions (obj, recurseMt) +-*Aliases: `_.methods`*. ++### functions (obj [, recurseMt]) ++*Aliases: `methods`*. + + Returns all functions names within an object. + + ```lua +-_.functions(coroutine) -- => "{'create','resume','running','status','wrap','yield'}" ++M.functions(coroutine) ++-- => "{'yield','wrap','status','resume','running','create'}" + ```` + +-### clone (obj, shallow) ++When given `recurseMt`, will also include `obj` metatable's functions. ++ ++````lua ++local mt = {print = print} ++local t = {assert = assert} ++setmetatable(t, {__index = mt}) ++M.functions(t, true) -- => "{'assert','print'}" ++```` ++ ++### clone (obj [, shallow]) + + Clones a given object. + + ```lua + local obj = {1,2,3} +-local obj2 = _.clone(obj) ++local obj2 = M.clone(obj) + print(obj2 == obj) -- => false +-print(_.isEqual(obj2, obj)) -- => true ++print(M.isEqual(obj2, obj)) -- => true + ```` + +-### tap (obj, f, ...) ++### tap (obj, f) + + Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. +-The pased-interceptor is prototyped as `f(obj,...)`. ++The passed-in interceptor should be prototyped as `f(obj,...)`. + + ```lua +-local v = _.chain({1,2,3,4,5,6,7,8,9,10) +- :filter(function(k,v) return v%2~=0 end) -- filters even values +- :tap(function(v) print('Max is', _.max(v) end) -- Tap max values +- :map(function(k,v) return k^2) +- :value() -- => Max is 9 ++local v = M.chain({1,2,3,4,5,6,7,8,9,10}) ++ :filter(function(v) return v%2~=0 end) -- retain odd values ++ :tap(function(v) print('Max is', M.max(v) end) -- Tap max value ++ :map(function(v) return v^2 end) ++ :value() -- => Max is 89 + ```` + + ### has (obj, key) +@@ -1766,60 +2440,60 @@ local v = _.chain({1,2,3,4,5,6,7,8,9,10) + Checks if an object has a given attribute. + + ```lua +-_.has(_,'has') -- => true +-_.has(coroutine,'resume') -- => true +-_.has(math,'random') -- => true ++M.has(_,'has') -- => true ++M.has(coroutine,'resume') -- => true ++M.has(math,'random') -- => true + ```` + + ### pick (obj, ...) +-*Aliases: `_.choose`*. ++*Aliases: `choose`*. + + Collects whilelisted properties of a given object. + + ```lua + local object = {a = 1, b = 2, c = 3} +-_.pick(object,'a','c') -- => "{a = 1, c = 3}" ++M.pick(object,'a','c') -- => "{a = 1, c = 3}" + ```` + + ### omit (obj, ...) +-*Aliases: `_.drop`*. ++*Aliases: `drop`*. + + Omits blacklisted properties of a given object. + + ```lua + local object = {a = 1, b = 2, c = 3} +-_.omit(object,'a','c') -- => "{b = 2}" ++M.omit(object,'a','c') -- => "{b = 2}" + ```` + +-### template (obj, template) +-*Aliases: `_.defaults`*. ++### template (obj [, template]) ++*Aliases: `defaults`*. + + Applies a template on an object, preserving existing properties. + + ```lua + local obj = {a = 0} +-_.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}" ++M.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}" + ```` + +-### isEqual (objA, objB, useMt) +-*Aliases: `_.compare`*. ++### isEqual (objA, objB [, useMt]) ++*Aliases: `compare`, `M.matches`*. + + Compares objects: + + ```lua +-_.isEqual(1,1) -- => true +-_.isEqual(true,false) -- => false +-_.isEqual(3.14,math.pi) -- => false +-_.isEqual({3,4,5},{3,4,{5}}) -- => false ++M.isEqual(1,1) -- => true ++M.isEqual(true,false) -- => false ++M.isEqual(3.14,math.pi) -- => false ++M.isEqual({3,4,5},{3,4,{5}}) -- => false + ```` + +-### result (obj, method, ...) ++### result (obj, method) + + Calls an object method, passing it as a first argument the object itself. + + ```lua +-_.result('abc','len') -- => 3 +-_.result({'a','b','c'},table.concat) -- => 'abc' ++M.result('abc','len') -- => 3 ++M.result({'a','b','c'},table.concat) -- => 'abc' + ```` + + ### isTable (t) +@@ -1827,9 +2501,9 @@ _.result({'a','b','c'},table.concat) -- => 'abc' + Is the given argument an object (i.e a table) ? + + ```lua +-_.isTable({}) -- => true +-_.isTable(math) -- => true +-_.isTable(string) -- => true ++M.isTable({}) -- => true ++M.isTable(math) -- => true ++M.isTable(string) -- => true + ```` + + ### isCallable (obj) +@@ -1837,10 +2511,10 @@ _.isTable(string) -- => true + Is the given argument callable ? + + ```lua +-_.isCallable(print) -- => true +-_.isCallable(function() end) -- => true +-_.isCallable(setmetatable({},{__index = string}).upper) -- => true +-_.isCallable(setmetatable({},{__call = function() return end})) -- => true ++M.isCallable(print) -- => true ++M.isCallable(function() end) -- => true ++M.isCallable(setmetatable({},{__index = string}).upper) -- => true ++M.isCallable(setmetatable({},{__call = function() return end})) -- => true + ```` + + ### isArray (obj) +@@ -1848,9 +2522,9 @@ _.isCallable(setmetatable({},{__call = function() return end})) -- => true + Is the given argument an array (i.e. a sequence) ? + + ```lua +-_.isArray({}) -- => true +-_.isArray({1,2,3}) -- => true +-_.isArray({'a','b','c'}) -- => true ++M.isArray({}) -- => true ++M.isArray({1,2,3}) -- => true ++M.isArray({'a','b','c'}) -- => true + ```` + + ### isIterable (obj) +@@ -1858,20 +2532,31 @@ _.isArray({'a','b','c'}) -- => true + Checks if the given object is iterable with `pairs`. + + ```lua +-_.isIterable({}) -- => true +-_.isIterable(function() end) -- => false +-_.isIterable(false) -- => false +-_.isIterable(1) -- => false ++M.isIterable({}) -- => true ++M.isIterable(function() end) -- => false ++M.isIterable(false) -- => false ++M.isIterable(1) -- => false + ```` + +-### isEmpty (obj) ++### type (obj) ++ ++Extends Lua's `type` function. It returns the type of the given object and also recognises 'file' userdata ++ ++```lua ++M.type('string') -- => 'string' ++M.type(table) -- => 'table' ++M.type(function() end) -- => 'function' ++M.type(io.open('f','w')) -- => 'file' ++```` ++ ++### isEmpty ([obj]) + + Is the given argument empty ? + + ```lua +-_.isEmpty('') -- => true +-_.isEmpty({}) -- => true +-_.isEmpty({'a','b','c'}) -- => false ++M.isEmpty('') -- => true ++M.isEmpty({}) -- => true ++M.isEmpty({'a','b','c'}) -- => false + ```` + + ### isString (obj) +@@ -1879,9 +2564,9 @@ _.isEmpty({'a','b','c'}) -- => false + Is the given argument a string ? + + ```lua +-_.isString('') -- => true +-_.isString('Hello') -- => false +-_.isString({}) -- => false ++M.isString('') -- => true ++M.isString('Hello') -- => false ++M.isString({}) -- => false + ```` + + ### isFunction (obj) +@@ -1889,9 +2574,9 @@ _.isString({}) -- => false + Is the given argument a function ? + + ```lua +-_.isFunction(print) -- => true +-_.isFunction(function() end) -- => true +-_.isFunction({}) -- => false ++M.isFunction(print) -- => true ++M.isFunction(function() end) -- => true ++M.isFunction({}) -- => false + ```` + + ### isNil (obj) +@@ -1899,9 +2584,9 @@ _.isFunction({}) -- => false + Is the given argument nil ? + + ```lua +-_.isNil(nil) -- => true +-_.isNil() -- => true +-_.isNil({}) -- => false ++M.isNil(nil) -- => true ++M.isNil() -- => true ++M.isNil({}) -- => false + ```` + + ### isNumber (obj) +@@ -1909,10 +2594,10 @@ _.isNil({}) -- => false + Is the given argument a number ? + + ```lua +-_.isNumber(math.pi) -- => true +-_.isNumber(math.huge) -- => true +-_.isNumber(0/0) -- => true +-_.isNumber() -- => false ++M.isNumber(math.pi) -- => true ++M.isNumber(math.huge) -- => true ++M.isNumber(0/0) -- => true ++M.isNumber() -- => false + ```` + + ### isNaN (obj) +@@ -1920,8 +2605,8 @@ _.isNumber() -- => false + Is the given argument NaN ? + + ```lua +-_.isNaN(1) -- => false +-_.isNaN(0/0) -- => true ++M.isNaN(1) -- => false ++M.isNaN(0/0) -- => true + ```` + + ### isFinite (obj) +@@ -1929,11 +2614,11 @@ _.isNaN(0/0) -- => true + Is the given argument a finite number ? + + ```lua +-_.isFinite(99e99) -- => true +-_.isFinite(math.pi) -- => true +-_.isFinite(math.huge) -- => false +-_.isFinite(1/0) -- => false +-_.isFinite(0/0) -- => false ++M.isFinite(99e99) -- => true ++M.isFinite(math.pi) -- => true ++M.isFinite(math.huge) -- => false ++M.isFinite(1/0) -- => false ++M.isFinite(0/0) -- => false + ```` + + ### isBoolean (obj) +@@ -1941,10 +2626,10 @@ _.isFinite(0/0) -- => false + Is the given argument a boolean ? + + ```lua +-_.isBoolean(true) -- => true +-_.isBoolean(false) -- => true +-_.isBoolean(1==1) -- => true +-_.isBoolean(print) -- => false ++M.isBoolean(true) -- => true ++M.isBoolean(false) -- => true ++M.isBoolean(1==1) -- => true ++M.isBoolean(print) -- => false + ```` + + ### isInteger (obj) +@@ -1952,9 +2637,9 @@ _.isBoolean(print) -- => false + Is the given argument an integer ? + + ```lua +-_.isInteger(math.pi) -- => false +-_.isInteger(1) -- => true +-_.isInteger(-1) -- => true ++M.isInteger(math.pi) -- => false ++M.isInteger(1) -- => true ++M.isInteger(-1) -- => true + ```` + + **[[⬆]](#TOC)** +@@ -1962,8 +2647,9 @@ _.isInteger(-1) -- => true + ## Chaining + + *Method chaining* (also known as *name parameter idiom*), is a technique for invoking consecutively method calls in object-oriented style. +-Each method returns an object, and methods calls are chained together. ++Each method returns an object, and method calls are chained together. + Moses offers chaining for your perusal.
    ++ + Let's use chaining to get the count of evey single word in some lyrics (case won't matter here). + + +@@ -1975,16 +2661,17 @@ local lyrics = { + "He sleeps all night and he works all day" + } + +-local stats = _.chain(lyrics) +- :map(function(k,line) +- local t = {} +- for w in line:gmatch('(%w+)') do +- t[#t+1] = w +- end +- return t +- end) ++-- split a text into words ++local function words(line) ++ local t = {} ++ for w in line:gmatch('(%w+)') do t[#t+1] = w end ++ return t ++end ++ ++local stats = M.chain(lyrics) ++ :map(words) + :flatten() +- :countBy(function(i,v) return v:lower() end) ++ :countBy(string.lower) + :value() + + -- => "{ +@@ -1994,7 +2681,7 @@ local stats = _.chain(lyrics) + -- => }" + ```` + +-For convenience, you can also use `_(value)` to start chaining methods, instead of `_.chain(value)`. ++For convenience, you can also use `M(value)` to start chaining methods, instead of `M.chain(value)`. + + Note that one can use `:value()` to unwrap a chained object. + +@@ -2011,7 +2698,7 @@ All library functions can be imported in a context using `import` into a specifi + + ```lua + local context = {} +-_.import(context) ++M.import(context) + + context.each({1,2,3},print) + +@@ -2020,10 +2707,10 @@ context.each({1,2,3},print) + -- => 3 3 + ```` + +-When no `context` was provided, it defaults to the global environment `_G`. ++When no `context` was provided, it defaults to the current environment, `_ENV` or `_G`. + + ```lua +-_.import() ++M.import() + + each({1,2,3},print) + +@@ -2036,7 +2723,7 @@ Passing `noConflict` argument leaves untouched conflicting keys while importing + + ```lua + local context = {each = 1} +-_.import(context, true) ++M.import(context, true) + + print(context.each) -- => 1 + context.eachi({1,2,3},print) +diff --git a/extra/moses/moses.lua b/extra/moses/moses.lua +index f3d484a..ccf628e 100644 +--- a/extra/moses/moses.lua ++++ b/extra/moses/moses.lua +@@ -1,33 +1,34 @@ + --- Utility-belt library for functional programming in Lua ([source](http://github.com/Yonaba/Moses)) + -- @author [Roland Yonaba](http://github.com/Yonaba) +--- @copyright 2012-2017 ++-- @copyright 2012-2018 + -- @license [MIT](http://www.opensource.org/licenses/mit-license.php) +--- @release 1.6.1 ++-- @release 2.1.0 + -- @module moses ++-- @set sort=true + +-local _MODULEVERSION = '1.6.1' ++local _MODULEVERSION = '2.1.0' + + -- Internalisation +-local next, type, select, pcall = next, type, select, pcall ++local next, type, pcall = next, type, pcall + local setmetatable, getmetatable = setmetatable, getmetatable + local t_insert, t_sort = table.insert, table.sort + local t_remove,t_concat = table.remove, table.concat + local randomseed, random, huge = math.randomseed, math.random, math.huge +-local floor, max, min = math.floor, math.max, math.min ++local floor, max, min, ceil = math.floor, math.max, math.min, math.ceil ++local wrap = coroutine.wrap ++local yield = coroutine.yield + local rawget = rawget + local unpack = table.unpack or unpack + local pairs,ipairs = pairs,ipairs +-local clock = os.clock +-local _ = {} ++local error = error ++local clock = os and os.clock or nil ++local M = {} + + + -- ======== Private helpers + + local function f_max(a,b) return a>b end + local function f_min(a,b) return ab and b or var) end +-local function isTrue(_,value) return value and true end +-local function iNot(value) return not value end + + local function count(t) -- raw count of items in an map-table + local i = 0 +@@ -36,13 +37,13 @@ local function count(t) -- raw count of items in an map-table + end + + local function extract(list,comp,transform,...) -- extracts value from a list +- local _ans +- local transform = transform or _.identity +- for index,value in pairs(list) do +- if not _ans then _ans = transform(value,...) ++ transform = transform or M.identity ++ local _ans ++ for k,v in pairs(list) do ++ if not _ans then _ans = transform(v,...) + else +- local value = transform(value,...) +- _ans = comp(_ans,value) and _ans or value ++ local val = transform(v,...) ++ _ans = comp(_ans,val) and _ans or val + end + end + return _ans +@@ -50,7 +51,7 @@ end + + local function partgen(t, n, f, pad) -- generates array partitions + for i = 0, #t, n do +- local s = _.slice(t, i+1, i+n) ++ local s = M.slice(t, i+1, i+n) + if #s>0 then + while (#s < n and pad) do s[#s+1] = pad end + f(s) +@@ -58,9 +59,9 @@ local function partgen(t, n, f, pad) -- generates array partitions + end + end + +-local function partgen2(t, n, f, pad) -- generates sliding array partitions ++local function partgen2(t, n, f, pad) -- generates overlapping array partitions + for i = 0, #t, n-1 do +- local s = _.slice(t, i+1, i+n) ++ local s = M.slice(t, i+1, i+n) + if #s>0 and i+1<#t then + while (#s < n and pad) do s[#s+1] = pad end + f(s) +@@ -68,6 +69,16 @@ local function partgen2(t, n, f, pad) -- generates sliding array partitions + end + end + ++local function partgen3(t, n, f, pad) -- generates sliding array partitions ++ for i = 0, #t, 1 do ++ local s = M.slice(t, i+1, i+n) ++ if #s>0 and i+n<=#t then ++ while (#s < n and pad) do s[#s+1] = pad end ++ f(s) ++ end ++ end ++end ++ + local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.html + if n == 0 then f(t) end + for i = 1,n do +@@ -77,9 +88,158 @@ local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.ht + end + end + ++local function signum(a) return a>=0 and 1 or -1 end ++ + -- Internal counter for unique ids generation + local unique_id_counter = -1 + ++--- Operator functions ++-- @section Operator functions ++ ++M.operator = {} ++--- Returns a + b. Aliased as `op.add`. ++-- @name operator.add ++-- @param a a value ++-- @param b a value ++-- @return a + b ++M.operator.add = function(a,b) return a + b end ++ ++--- Returns a - b. Aliased as `op.sub`. ++-- @name operator.sub ++-- @param a a value ++-- @param b a value ++-- @return a - b ++M.operator.sub = function(a,b) return a - b end ++ ++--- Returns a * b. Aliased as `op.mul`. ++-- @name operator.mul ++-- @param a a value ++-- @param b a value ++-- @return a * b ++M.operator.mul = function(a,b) return a * b end ++ ++--- Returns a / b. Aliased as `op.div`. ++-- @name operator.div ++-- @param a a value ++-- @param b a value ++-- @return a / b ++M.operator.div = function(a,b) return a / b end ++ ++--- Returns a % b. Aliased as `op.mod`. ++-- @name operator.mod ++-- @param a a value ++-- @param b a value ++-- @return a % b ++M.operator.mod = function(a,b) return a % b end ++ ++--- Returns a ^ b. Aliased as `op.exp`, `op.pow`. ++-- @name operator.exp ++-- @param a a value ++-- @param b a value ++-- @return a ^ b ++M.operator.exp = function(a,b) return a ^ b end ++M.operator.pow = M.operator.exp ++ ++--- Returns -a. Aliased as `op.unm`, `op.neg`. ++-- @name operator.unm ++-- @param a a value ++-- @return -a ++M.operator.unm = function(a) return -a end ++M.operator.neg = M.operator.unm ++ ++--- Performs floor division (//) between `a` and `b`. It rounds the quotient towards minus infinity. ++-- Aliased as `op.floordiv`. ++-- @name operator.floordiv ++-- @param a a value ++-- @param b a value ++-- @return a // b ++M.operator.floordiv = function(a, b) return floor(a/b) end ++ ++--- Performs integer division between `a` and `b`. Aliased as `op.intdiv`. ++-- @name operator.intdiv ++-- @param a a value ++-- @param b a value ++-- @return a / b ++M.operator.intdiv = function(a,b) ++ return a>=0 and floor(a/b) or ceil(a/b) ++end ++ ++--- Checks if a equals b. Aliased as `op.eq`. ++-- @name operator.eq ++-- @param a a value ++-- @param b a value ++-- @return a == b ++M.operator.eq = function(a,b) return a == b end ++ ++--- Checks if a not equals b. Aliased as `op.neq`. ++-- @name operator.neq ++-- @param a a value ++-- @param b a value ++-- @return a ~= b ++M.operator.neq = function(a,b) return a ~= b end ++ ++--- Checks if a is strictly less than b. Aliased as `op.lt`. ++-- @name operator.lt ++-- @param a a value ++-- @param b a value ++-- @return a < b ++M.operator.lt = function(a,b) return a < b end ++ ++--- Checks if a is strictly greater than b. Aliased as `op.gt`. ++-- @name operator.gt ++-- @param a a value ++-- @param b a value ++-- @return a > b ++M.operator.gt = function(a,b) return a > b end ++ ++--- Checks if a is less or equal to b. Aliased as `op.le`. ++-- @name operator.le ++-- @param a a value ++-- @param b a value ++-- @return a <= b ++M.operator.le = function(a,b) return a <= b end ++ ++--- Checks if a is greater or equal to b. Aliased as `op.ge`. ++-- @name operator.ge ++-- @param a a value ++-- @param b a value ++-- @return a >= b ++M.operator.ge = function(a,b) return a >= b end ++ ++--- Returns logical a and b. Aliased as `op.land`. ++-- @name operator.land ++-- @param a a value ++-- @param b a value ++-- @return a and b ++M.operator.land = function(a,b) return a and b end ++ ++--- Returns logical a or b. Aliased as `op.lor`. ++-- @name operator.lor ++-- @param a a value ++-- @param b a value ++-- @return a or b ++M.operator.lor = function(a,b) return a or b end ++ ++--- Returns logical not a. Aliased as `op.lnot`. ++-- @name operator.lnot ++-- @param a a value ++-- @return not a ++M.operator.lnot = function(a) return not a end ++ ++--- Returns concatenation of a and b. Aliased as `op.concat`. ++-- @name operator.concat ++-- @param a a value ++-- @param b a value ++-- @return a .. b ++M.operator.concat = function(a,b) return a..b end ++ ++--- Returns the length of a. Aliased as `op.len`. ++-- @name operator.length ++-- @param a a value ++-- @return #a ++M.operator.length = function(a) return #a end ++M.operator.len = M.operator.length ++ + --- Table functions + -- @section Table functions + +@@ -87,39 +247,37 @@ local unique_id_counter = -1 + -- @name clear + -- @param t a table + -- @return the given table, cleared. +-function _.clear(t) ++function M.clear(t) + for k in pairs(t) do t[k] = nil end + return t + end + +---- Iterates on key-value pairs, calling `f (k, v)` at every step. ++ ++ ++--- Iterates on key-value pairs, calling `f (v, k)` at every step. + --
    Aliased as `forEach`. + -- @name each + -- @param t a table +--- @param f a function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f a function, prototyped as `f (v, k)` + -- @see eachi +-function _.each(t, f, ...) ++function M.each(t, f) + for index,value in pairs(t) do +- f(index,value,...) ++ f(value, index) + end + end + +---- Iterates on integer key-value pairs, calling `f(k, v)` every step. ++--- Iterates on integer key-value pairs, calling `f(v, k)` every step. + -- Only applies to values located at integer keys. The table can be a sparse array. + -- Iteration will start from the lowest integer key found to the highest one. + --
    Aliased as `forEachi`. + -- @name eachi + -- @param t a table +--- @param f a function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f a function, prototyped as `f (v, k)` + -- @see each +-function _.eachi(t, f, ...) +- local lkeys = _.sort(_.select(_.keys(t), function(k,v) +- return _.isInteger(v) +- end)) ++function M.eachi(t, f) ++ local lkeys = M.sort(M.select(M.keys(t), M.isInteger)) + for k, key in ipairs(lkeys) do +- f(key, t[key],...) ++ f(t[key], key) + end + end + +@@ -128,41 +286,75 @@ end + -- @param t a table + -- @param ... A variable number of keys to collect values + -- @return an array-list of values +-function _.at(t, ...) ++function M.at(t, ...) + local values = {} +- for i, key in ipairs({...}) do +- if _.has(t, key) then values[#values+1] = t[key] end +- end ++ for i, key in ipairs({...}) do values[#values+1] = t[key] end + return values + end + ++--- Adjusts the value at a given key using a function or a value. In case `f` is a function, ++-- it should be prototyped `f(v)`. It does not mutate the given table, but rather ++-- returns a new array. In case the given `key` does not exist in `t`, it throws an error. ++-- @param t a table ++-- @param key a key ++-- @param f a function, prototyped as `f(v)` or a value ++function M.adjust(t, key, f) ++ if (t[key] == nil) then error("key not existing in table") end ++ local _t = M.clone(t) ++ _t[key] = type(f) == 'function' and f(_t[key]) or f ++ return _t ++end ++ + --- Counts occurrences of a given value in a table. Uses @{isEqual} to compare values. + -- @name count + -- @param t a table +--- @param[opt] value a value to be searched in the table. If not given, the @{size} of the table will be returned ++-- @param[opt] val a value to be searched in the table. If not given, the @{size} of the table will be returned + -- @return the count of occurrences of the given value + -- @see countf + -- @see size +-function _.count(t, value) +- if _.isNil(value) then return _.size(t) end ++function M.count(t, val) ++ if val == nil then return M.size(t) end + local count = 0 +- _.each(t, function(k,v) +- if _.isEqual(v, value) then count = count + 1 end +- end) ++ for k, v in pairs(t) do ++ if M.isEqual(v, val) then count = count + 1 end ++ end + return count + end + +---- Counts occurrences validating a predicate. Same as @{count}, but uses an iterator. +--- Returns the count for values passing the test `f (k, v, ...)` ++--- Counts the number of values passing a predicate test. Same as @{count}, but uses an iterator. ++-- Returns the count for values passing the test `f (v, k)` + -- @name countf + -- @param t a table +--- @param f an iterator function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function, prototyped as `f (v, k)` + -- @return the count of values validating the predicate + -- @see count + -- @see size +-function _.countf(t, f, ...) +- return _.count(_.map(t, f, ...), true) ++function M.countf(t, f) ++ local count = 0 ++ for k, v in pairs(t) do ++ if f(v, k) then count = count + 1 end ++ end ++ return count ++end ++ ++--- Checks if all values in a collection are equal. Uses an optional `comp` function which is used ++-- to compare values and defaults to @{isEqual} when not given. ++--
    Aliased as `alleq`. ++-- @name allEqual ++-- @param t a table ++-- @param[opt] comp a comparison function. Defaults to `isEqual` ++-- @return `true` when all values in `t` are equal, `false` otherwise. ++-- @see isEqual ++function M.allEqual(t, comp) ++ local k, pivot = next(t) ++ for k, v in pairs(t) do ++ if comp then ++ if not comp(pivot, v) then return false end ++ else ++ if not M.isEqual(pivot, v) then return false end ++ end ++ end ++ return true + end + + --- Loops `n` times through a table. In case `n` is omitted, it will loop forever. +@@ -170,11 +362,11 @@ end + --
    Aliased as `loop`. + -- @name cycle + -- @param t a table +--- @param n the number of loops +--- @return an iterator function yielding key-value pairs from the passed-in table. +-function _.cycle(t, n) ++-- @param[opt] n the number of loops ++-- @return an iterator function yielding value-key pairs from the passed-in table. ++function M.cycle(t, n) + n = n or 1 +- if n<=0 then return _.noop end ++ if n<=0 then return M.noop end + local k, fk + local i = 0 + while true do +@@ -187,22 +379,39 @@ function _.cycle(t, n) + return + end + end +- return k, t[k] ++ return t[k], k + end + end + end + +---- Maps `f (k, v)` on key-value pairs, collects and returns the results. ++--- Maps `f (v, k)` on value-key pairs, collects and returns the results. ++-- Uses `pairs` to iterate over elements in `t`. + --
    Aliased as `collect`. + -- @name map + -- @param t a table +--- @param f an iterator function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function, prototyped as `f (v, k)` + -- @return a table of results +-function _.map(t, f, ...) ++-- @see mapi ++function M.map(t, f) + local _t = {} + for index,value in pairs(t) do +- local k, kv, v = index, f(index,value,...) ++ local k, kv, v = index, f(value, index) ++ _t[v and kv or k] = v or kv ++ end ++ return _t ++end ++ ++--- Maps `f (v, k)` on value-key pairs, collects and returns the results. ++-- Uses `ipairs` to iterate over elements in `t`. ++-- @name mapi ++-- @param t a table ++-- @param f an iterator function, prototyped as `f (v, k)` ++-- @return a table of results ++-- @see map ++function M.mapi(t, f) ++ local _t = {} ++ for index,value in ipairs(t) do ++ local k, kv, v = index, f(value, index) + _t[v and kv or k] = v or kv + end + return _t +@@ -217,10 +426,11 @@ end + -- @param f an iterator function, prototyped as `f (state, value)` + -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. + -- @return the final state of reduction ++-- @see best + -- @see reduceRight +--- @see reduceby +-function _.reduce(t, f, state) +- for __,value in pairs(t) do ++-- @see reduceBy ++function M.reduce(t, f, state) ++ for k,value in pairs(t) do + if state == nil then state = value + else state = f(state,value) + end +@@ -228,18 +438,40 @@ function _.reduce(t, f, state) + return state + end + ++--- Returns the best value passing a selector function. Acts as a special case of ++-- @{reduce}, using the first value in `t` as an initial state. It thens folds the given table, ++-- testing each of its values `v` and selecting the value passing the call `f(state,v)` every time. ++-- @name best ++-- @param t a table ++-- @param f an iterator function, prototyped as `f (state, value)` ++-- @return the final state of reduction ++-- @see reduce ++-- @see reduceRight ++-- @see reduceBy ++function M.best(t, f) ++ local _, state = next(t) ++ for k,value in pairs(t) do ++ if state == nil then state = value ++ else state = f(state,value) and state or value ++ end ++ end ++ return state ++end ++ + --- Reduces values in a table passing a given predicate. Folds the table left-to-right, considering + -- only values validating a given predicate. +--- @name reduceby ++-- @name reduceBy + -- @param t a table + -- @param f an iterator function, prototyped as `f (state, value)` +--- @param state an initial state of reduction. +--- @param pred a predicate function `pred (k, v, ...)` to select values to be considered for reduction +--- @param[opt] ... optional args to be passed to `pred` ++-- @param pred a predicate function `pred (v, k)` to select values to be considered for reduction ++-- @param[opt] state an initial state of reduction. Defaults to the first value in the table of selected values. ++-- @param[optchain] ... optional args to be passed to `pred` + -- @return the final state of reduction + -- @see reduce +-function _.reduceby(t, f, state, pred, ...) +- return _.reduce(_.select(t, pred, ...), f, state) ++-- @see best ++-- @see reduceRight ++function M.reduceBy(t, f, pred, state) ++ return M.reduce(M.select(t, pred), f, state) + end + + --- Reduces a table, right-to-left. Folds the table from the last element to the first element +@@ -252,8 +484,10 @@ end + -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. + -- @return the final state of reduction + -- @see reduce +-function _.reduceRight(t, f, state) +- return _.reduce(_.reverse(t),f,state) ++-- @see best ++-- @see reduceBy ++function M.reduceRight(t, f, state) ++ return M.reduce(M.reverse(t),f,state) + end + + --- Reduces a table while saving intermediate states. Folds the table left-to-right +@@ -266,7 +500,7 @@ end + -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. + -- @return an array of states + -- @see mapReduceRight +-function _.mapReduce(t, f, state) ++function M.mapReduce(t, f, state) + local _t = {} + for i,value in pairs(t) do + _t[i] = not state and value or f(state,value) +@@ -285,8 +519,8 @@ end + -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. + -- @return an array of states + -- @see mapReduce +-function _.mapReduceRight(t, f, state) +- return _.mapReduce(_.reverse(t),f,state) ++function M.mapReduceRight(t, f, state) ++ return M.mapReduce(M.reverse(t),f,state) + end + + --- Performs a linear search for a value in a table. It does not work for nested tables. +@@ -298,9 +532,9 @@ end + -- @param value a value to search for + -- @return a boolean : `true` when found, `false` otherwise + -- @see detect +-function _.include(t, value) +- local _iter = _.isFunction(value) and value or _.isEqual +- for __,v in pairs(t) do ++function M.include(t, value) ++ local _iter = (type(value) == 'function') and value or M.isEqual ++ for k,v in pairs(t) do + if _iter(v,value) then return true end + end + return false +@@ -308,14 +542,16 @@ end + + --- Performs a linear search for a value in a table. Returns the key of the value if found. + -- The given value can be a function prototyped as `f (v, value)` which should return true when +--- any v in the table equals the value being searched. ++-- any v in the table equals the value being searched. This function is similar to @{find}, ++-- which is mostly meant to work with array. + -- @name detect + -- @param t a table + -- @param value a value to search for + -- @return the key of the value when found or __nil__ + -- @see include +-function _.detect(t, value) +- local _iter = _.isFunction(value) and value or _.isEqual ++-- @see find ++function M.detect(t, value) ++ local _iter = (type(value) == 'function') and value or M.isEqual + for key,arg in pairs(t) do + if _iter(arg,value) then return key end + end +@@ -327,8 +563,8 @@ end + -- @param props a set of keys + -- @return an array of values from the passed-in table + -- @see findWhere +-function _.where(t, props) +- local r = _.select(t, function(__,v) ++function M.where(t, props) ++ local r = M.select(t, function(v) + for key in pairs(props) do + if v[key] ~= props[key] then return false end + end +@@ -343,8 +579,8 @@ end + -- @param props a set of keys + -- @return a value from the passed-in table + -- @see where +-function _.findWhere(t, props) +- local index = _.detect(t, function(v) ++function M.findWhere(t, props) ++ local index = M.detect(t, function(v) + for key in pairs(props) do + if props[key] ~= v[key] then return false end + end +@@ -357,14 +593,13 @@ end + --
    Aliased as `filter`. + -- @name select + -- @param t a table +--- @param f an iterator function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function, prototyped as `f (v, k)` + -- @return the selected values + -- @see reject +-function _.select(t, f, ...) ++function M.select(t, f) + local _t = {} + for index,value in pairs(t) do +- if f(index, value,...) then _t[#_t+1] = value end ++ if f(value,index) then _t[#_t+1] = value end + end + return _t + end +@@ -373,15 +608,13 @@ end + --
    Aliased as `discard` + -- @name reject + -- @param t a table +--- @param f an iterator function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function, prototyped as `f (v, k)` + -- @return the remaining values + -- @see select +-function _.reject(t, f, ...) +- local _mapped = _.map(t,f,...) ++function M.reject(t, f) + local _t = {} +- for index,value in pairs (_mapped) do +- if not value then _t[#_t+1] = t[index] end ++ for index,value in pairs (t) do ++ if not f(value,index) then _t[#_t+1] = value end + end + return _t + end +@@ -390,37 +623,37 @@ end + --
    Aliased as `every` + -- @name all + -- @param t a table +--- @param f an iterator function, prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function, prototyped as `f (v, k)` + -- @return `true` if all values passes the predicate, `false` otherwise +-function _.all(t, f, ...) +- return ((#_.select(_.map(t,f,...), isTrue)) == count(t)) ++function M.all(t, f) ++ for index,value in pairs(t) do ++ if not f(value,index) then return false end ++ end ++ return true + end + + --- Invokes a method on each value in a table. + -- @name invoke + -- @param t a table +--- @param method a function, prototyped as `f (v, ...)` +--- @param[opt] ... Optional args to be passed to `method` +--- @return the result of the call `f (v, ...)` ++-- @param method a function, prototyped as `f (v, k)` ++-- @return the result of the call `f (v, k)` + -- @see pluck +-function _.invoke(t, method, ...) +- local args = {...} +- return _.map(t, function(__,v) +- if _.isTable(v) then +- if _.has(v,method) then +- if _.isCallable(v[method]) then +- return v[method](v,unpack(args)) ++function M.invoke(t, method) ++ return M.map(t, function(v, k) ++ if (type(v) == 'table') then ++ if v[method] then ++ if M.isCallable(v[method]) then ++ return v[method](v,k) + else + return v[method] + end + else +- if _.isCallable(method) then +- return method(v,unpack(args)) ++ if M.isCallable(method) then ++ return method(v,k) + end + end +- elseif _.isCallable(method) then +- return method(v,unpack(args)) ++ elseif M.isCallable(method) then ++ return method(v,k) + end + end) + end +@@ -430,51 +663,34 @@ end + -- @param t a table + -- @param key a key, will be used to index in each value: `value[key]` + -- @return an array of values having the given key +-function _.pluck(t, key) +- return _.reject(_.map(t,function(__,value) +- return value[key] +- end), iNot) ++function M.pluck(t, key) ++ local _t = {} ++ for k, v in pairs(t) do ++ if v[key] then _t[#_t+1] = v[key] end ++ end ++ return _t + end + +---- Returns the max value in a collection. If an transformation function is passed, it will ++--- Returns the max value in a collection. If a `transform` function is passed, it will + -- be used to evaluate values by which all objects will be sorted. + -- @name max + -- @param t a table +--- @param[opt] transform a transformation function, prototyped as `transform (v, ...)`, defaults to @{identity} +--- @param[optchain] ... Optional args to be passed to `transform` ++-- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} + -- @return the max value found + -- @see min +-function _.max(t, transform, ...) +- return extract(t, f_max, transform, ...) ++function M.max(t, transform) ++ return extract(t, f_max, transform) + end + +---- Returns the min value in a collection. If an transformation function is passed, it will ++--- Returns the min value in a collection. If a `transform` function is passed, it will + -- be used to evaluate values by which all objects will be sorted. + -- @name min + -- @param t a table +--- @param[opt] transform a transformation function, prototyped as `transform (v, ...)`, defaults to @{identity} +--- @param[optchain] ... Optional args to be passed to `transform` ++-- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} + -- @return the min value found + -- @see max +-function _.min(t, transform, ...) +- return extract(t, f_min, transform, ...) +-end +- +---- Returns a shuffled copy of a given collection. If a seed is provided, it will +--- be used to init the pseudo random number generator (using `math.randomseed`). +--- @name shuffle +--- @param t a table +--- @param[opt] seed a seed +--- @return a shuffled copy of the given table +-function _.shuffle(t, seed) +- if seed then randomseed(seed) end +- local _shuffled = {} +- _.each(t,function(index,value) +- local randPos = floor(random()*index)+1 +- _shuffled[index] = _shuffled[randPos] +- _shuffled[randPos] = value +- end) +- return _shuffled ++function M.min(t, transform) ++ return extract(t, f_min, transform) + end + + --- Checks if two tables are the same. It compares if both tables features the same values, +@@ -483,76 +699,103 @@ end + -- @param a a table + -- @param b another table + -- @return `true` or `false` +-function _.same(a, b) +- return _.all(a, function (i,v) return _.include(b,v) end) +- and _.all(b, function (i,v) return _.include(a,v) end) ++function M.same(a, b) ++ return M.all(a, function(v) return M.include(b,v) end) ++ and M.all(b, function(v) return M.include(a,v) end) + end + + --- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. + -- @name sort + -- @param t a table + -- @param[opt] comp a comparison function prototyped as `comp (a, b)`, defaults to < operator. +--- @return the initial table, sorted. ++-- @return the given table, sorted. + -- @see sortBy +-function _.sort(t, comp) ++function M.sort(t, comp) + t_sort(t, comp) + return t + end + ++--- Iterates on values with respect to key order. Keys are sorted using `comp` function ++-- which defaults to `math.min`. It returns upon each call a `key, value` pair. ++-- @name sortedk ++-- @param t a table ++-- @param[opt] comp a comparison function. Defaults to `<` operator ++-- @return an iterator function ++-- @see sortedv ++function M.sortedk(t, comp) ++ local keys = M.keys(t) ++ t_sort(keys, comp) ++ local i = 0 ++ return function () ++ i = i + 1 ++ return keys[i], t[keys[i]] ++ end ++end ++ ++--- Iterates on values with respect to values order. Values are sorted using `comp` function ++-- which defaults to `math.min`. It returns upon each call a `key, value` pair. ++-- @name sortedv ++-- @param t a table ++-- @param[opt] comp a comparison function. Defaults to `<` operator ++-- @return an iterator function ++-- @see sortedk ++function M.sortedv(t, comp) ++ local keys = M.keys(t) ++ comp = comp or f_min ++ t_sort(keys, function(a,b) return comp(t[a],t[b]) end) ++ local i = 0 ++ return function () ++ i = i + 1 ++ return keys[i], t[keys[i]] ++ end ++end ++ + --- Sorts a table in-place using a transform. Values are ranked in a custom order of the results of + -- running `transform (v)` on all values. `transform` may also be a string name property sort by. + -- `comp` is a comparison function. + -- @name sortBy + -- @param t a table + -- @param[opt] transform a `transform` function to sort elements prototyped as `transform (v)`. Defaults to @{identity} +--- @param[optchain] comp a comparision function, defaults to the `<` operator ++-- @param[optchain] comp a comparison function, defaults to the `<` operator + -- @return a new array of sorted values + -- @see sort +-function _.sortBy(t, transform, comp) +- local f = transform or _.identity +- if _.isString(transform) then ++function M.sortBy(t, transform, comp) ++ local f = transform or M.identity ++ if (type(transform) == 'string') then + f = function(t) return t[transform] end + end + comp = comp or f_min +- local _t = {} +- _.each(t, function(__,v) +- _t[#_t+1] = {value = v, transform = f(v)} +- end) +- t_sort(_t, function(a,b) return comp(a.transform, b.transform) end) +- return _.pluck(_t, 'value') ++ t_sort(t, function(a,b) return comp(f(a), f(b)) end) ++ return t + end + + --- Splits a table into subsets groups. + -- @name groupBy + -- @param t a table +--- @param iter an iterator function, prototyped as `iter (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `iter` ++-- @param iter an iterator function, prototyped as `iter (v, k)` + -- @return a table of subsets groups +-function _.groupBy(t, iter, ...) +- local vararg = {...} ++function M.groupBy(t, iter) + local _t = {} +- _.each(t, function(i,v) +- local _key = iter(i,v, unpack(vararg)) +- if _t[_key] then _t[_key][#_t[_key]+1] = v +- else _t[_key] = {v} +- end +- end) ++ for k,v in pairs(t) do ++ local _key = iter(v,k) ++ if _t[_key] then _t[_key][#_t[_key]+1] = v ++ else _t[_key] = {v} ++ end ++ end + return _t + end + + --- Groups values in a collection and counts them. + -- @name countBy + -- @param t a table +--- @param iter an iterator function, prototyped as `iter (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `iter` ++-- @param iter an iterator function, prototyped as `iter (v, k)` + -- @return a table of subsets groups names paired with their count +-function _.countBy(t, iter, ...) +- local vararg = {...} ++function M.countBy(t, iter) + local stats = {} +- _.each(t,function(i,v) +- local key = iter(i,v,unpack(vararg)) +- stats[key] = (stats[key] or 0) +1 +- end) ++ for i,v in pairs(t) do ++ local key = iter(v,i) ++ stats[key] = (stats[key] or 0)+1 ++ end + return stats + end + +@@ -563,14 +806,10 @@ end + -- @return a count + -- @see count + -- @see countf +-function _.size(...) ++function M.size(...) + local args = {...} + local arg1 = args[1] +- if _.isTable(arg1) then +- return count(args[1]) +- else +- return count(args) +- end ++ return (type(arg1) == 'table') and count(args[1]) or count(args) + end + + --- Checks if all the keys of `other` table exists in table `t`. It does not +@@ -581,7 +820,7 @@ end + -- @param other another table + -- @return `true` or `false` + -- @see sameKeys +-function _.containsKeys(t, other) ++function M.containsKeys(t, other) + for key in pairs(other) do + if not t[key] then return false end + end +@@ -594,7 +833,7 @@ end + -- @param tB another table + -- @return `true` or `false` + -- @see containsKeys +-function _.sameKeys(tA, tB) ++function M.sameKeys(tA, tB) + for key in pairs(tA) do + if not tB[key] then return false end + end +@@ -614,16 +853,16 @@ end + -- @param array an array + -- @param[opt] n a number of elements to be sampled. Defaults to 1. + -- @param[optchain] seed an optional seed for shuffling +--- @return an array of selected values or a single value when `n` == 1 ++-- @return an array of selected values + -- @see sampleProb +-function _.sample(array, n, seed) +- n = n or 1 +- if n < 1 then return end ++function M.sample(array, n, seed) ++ n = n or 1 ++ if n == 0 then return {} end + if n == 1 then + if seed then randomseed(seed) end +- return array[random(1, #array)] ++ return {array[random(1, #array)]} + end +- return _.slice(_.shuffle(array, seed), 1, n) ++ return M.slice(M.shuffle(array, seed), 1, n) + end + + --- Return elements from a sequence with a given probability. It considers each value independently. +@@ -635,16 +874,57 @@ end + -- @param[opt] seed an optional seed for deterministic sampling + -- @return an array of selected values + -- @see sample +-function _.sampleProb(array, prob, seed) ++function M.sampleProb(array, prob, seed) + if seed then randomseed(seed) end +- return _.select(array, function(_,v) return random() < prob end) ++ local t = {} ++ for k, v in ipairs(array) do ++ if random() < prob then t[#t+1] = v end ++ end ++ return t ++end ++ ++--- Returns the n-top values satisfying a predicate. It takes a comparison function ++-- `comp` used to sort array values, and then picks the top n-values. It leaves the original array untouched. ++-- @name nsorted ++-- @param array an array ++-- @param[opt] n a number of values to retrieve. Defaults to 1. ++-- @param[optchain] comp a comparison function. Defaults to `<` operator. ++-- @return an array of top n values ++function M.nsorted(array, n, comp) ++ comp = comp or f_min ++ n = n or 1 ++ local values, count = {}, 0 ++ for k, v in M.sortedv(array, comp) do ++ if count < n then ++ count = count + 1 ++ values[count] = v ++ end ++ end ++ return values ++end ++ ++--- Returns a shuffled copy of a given array. If a seed is provided, it will ++-- be used to init the built-in pseudo random number generator (using `math.randomseed`). ++-- @name shuffle ++-- @param array an array ++-- @param[opt] seed a seed ++-- @return a shuffled copy of the given array ++function M.shuffle(array, seed) ++ if seed then randomseed(seed) end ++ local _shuffled = {} ++ for index, value in ipairs(array) do ++ local randPos = floor(random()*index)+1 ++ _shuffled[index] = _shuffled[randPos] ++ _shuffled[randPos] = value ++ end ++ return _shuffled + end + + --- Converts a list of arguments to an array. +--- @name toArray ++-- @name pack + -- @param ... a list of arguments + -- @return an array of all passed-in args +-function _.toArray(...) return {...} end ++function M.pack(...) return {...} end + + --- Looks for the first occurrence of a given value in an array. Returns the value index if found. + -- Uses @{isEqual} to compare values. +@@ -653,9 +933,10 @@ function _.toArray(...) return {...} end + -- @param value a value to lookup for + -- @param[opt] from the index from where the search will start. Defaults to 1. + -- @return the index of the value if found in the array, `nil` otherwise. +-function _.find(array, value, from) ++-- @see detect ++function M.find(array, value, from) + for i = from or 1, #array do +- if _.isEqual(array[i], value) then return i end ++ if M.isEqual(array[i], value) then return i end + end + end + +@@ -663,7 +944,7 @@ end + -- @name reverse + -- @param array an array + -- @return a reversed array +-function _.reverse(array) ++function M.reverse(array) + local _array = {} + for i = #array,1,-1 do + _array[#_array+1] = array[i] +@@ -672,33 +953,57 @@ function _.reverse(array) + end + + --- Replaces elements in a given array with a given value. In case `i` and `j` are given +--- it will only replaces values at indexes between `[i,j]`. In case `j` is greather than the array +--- size, it will append new values, increasing the array. ++-- it will only replaces values at indexes between `[i,j]`. In case `j` is greater than the array ++-- size, it will append new values, increasing the array size. + -- @name fill + -- @param array an array + -- @param value a value + -- @param[opt] i the index from which to start replacing values. Defaults to 1. + -- @param[optchain] j the index where to stop replacing values. Defaults to the array size. + -- @return the original array with values changed +-function _.fill(array, value, i, j) +- j = j or _.size(array) ++function M.fill(array, value, i, j) ++ j = j or M.size(array) + for i = i or 1, j do array[i] = value end + return array + end + ++--- Returns an array of `n` zeros. ++-- @name zeros ++-- @param n a number ++-- @return an array ++-- @see ones ++-- @see vector ++function M.zeros(n) return M.fill({}, 0, 1, n) end ++ ++--- Returns an array of `n` 1's. ++-- @name ones ++-- @param n a number ++-- @return an array ++-- @see zeros ++-- @see vector ++function M.ones(n) return M.fill({}, 1, 1, n) end ++ ++--- Returns an array of `n` times a given value. ++-- @name vector ++-- @param value a value ++-- @param n a number ++-- @return an array ++-- @see zeros ++-- @see ones ++function M.vector(value, n) return M.fill({}, value, 1, n) end ++ + --- Collects values from a given array. The passed-in array should not be sparse. + -- This function collects values as long as they satisfy a given predicate and returns on the first falsy test. + --
    Aliased as `takeWhile` + -- @name selectWhile + -- @param array an array +--- @param f an iterator function prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function prototyped as `f (v, k)` + -- @return a new table containing all values collected + -- @see dropWhile +-function _.selectWhile(array, f, ...) ++function M.selectWhile(array, f) + local t = {} + for i,v in ipairs(array) do +- if f(i,v,...) then t[i] = v else break end ++ if f(v,i) then t[i] = v else break end + end + return t + end +@@ -708,20 +1013,19 @@ end + --
    Aliased as `rejectWhile` + -- @name dropWhile + -- @param array an array +--- @param f an iterator function prototyped as `f (k,v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function prototyped as `f (v, k)` + -- @return a new table containing all values collected +--- @selectWhile +-function _.dropWhile(array, f, ...) ++-- @see selectWhile ++function M.dropWhile(array, f) + local _i + for i,v in ipairs(array) do +- if not f(i,v,...) then ++ if not f(v, i) then + _i = i + break + end + end +- if _.isNil(_i) then return {} end +- return _.rest(array,_i) ++ if (_i == nil) then return {} end ++ return M.rest(array,_i) + end + + --- Returns the index at which a value should be inserted. This index is evaluated so +@@ -733,22 +1037,22 @@ end + -- @param[opt] comp an comparison function prototyped as `f (a, b)`, defaults to < operator. + -- @param[optchain] sort whether or not the passed-in array should be sorted + -- @return number the index at which the passed-in value should be inserted +-function _.sortedIndex(array, value, comp, sort) ++function M.sortedIndex(array, value, comp, sort) + local _comp = comp or f_min +- if sort then _.sort(array,_comp) end ++ if (sort == true) then t_sort(array,_comp) end + for i = 1,#array do + if not _comp(array[i],value) then return i end + end + return #array+1 + end + +---- Returns the index of the first occurence of value in an array. ++--- Returns the index of the first occurrence of value in an array. + -- @name indexOf + -- @param array an array + -- @param value the value to search for + -- @return the index of the passed-in value + -- @see lastIndexOf +-function _.indexOf(array, value) ++function M.indexOf(array, value) + for k = 1,#array do + if array[k] == value then return k end + end +@@ -760,33 +1064,31 @@ end + -- @param value the value to search for + -- @return the index of the last occurrence of the passed-in value or __nil__ + -- @see indexOf +-function _.lastIndexOf(array, value) +- local key = _.indexOf(_.reverse(array),value) ++function M.lastIndexOf(array, value) ++ local key = M.indexOf(M.reverse(array),value) + if key then return #array-key+1 end + end + + --- Returns the first index at which a predicate returns true. + -- @name findIndex + -- @param array an array +--- @param predicate a predicate function prototyped as `predicate (k, v, ...)` +--- @param[opt] ... optional arguments to `pred` ++-- @param pred a predicate function prototyped as `pred (v, k)` + -- @return the index found or __nil__ + -- @see findLastIndex +-function _.findIndex(array, predicate, ...) ++function M.findIndex(array, pred) + for k = 1, #array do +- if predicate(k,array[k],...) then return k end ++ if pred(array[k],k) then return k end + end + end + + --- Returns the last index at which a predicate returns true. + -- @name findLastIndex + -- @param array an array +--- @param predicate a predicate function prototyped as `predicate (k, v, ...)` +--- @param[opt] ... optional arguments to `pred` ++-- @param pred a predicate function prototyped as `pred (k, v)` + -- @return the index found or __nil__ + -- @see findIndex +-function _.findLastIndex(array, predicate, ...) +- local key = _.findIndex(_.reverse(array),predicate,...) ++function M.findLastIndex(array, pred) ++ local key = M.findIndex(M.reverse(array),pred) + if key then return #array-key+1 end + end + +@@ -796,31 +1098,50 @@ end + -- @param array an array + -- @param ... a variable number of arguments + -- @return the passed-in array with new values added ++-- @see prepend + -- @see push +-function _.addTop(array, ...) +- _.each({...},function(i,v) t_insert(array,1,v) end) ++function M.addTop(array, ...) ++ for k,v in ipairs({...}) do ++ t_insert(array,1,v) ++ end + return array + end + ++--- Adds all passed-in values at the top of an array. As opposed to @{addTop}, it preserves the order ++-- of the passed-in elements. ++-- @name prepend ++-- @param array an array ++-- @param ... a variable number of arguments ++-- @return the passed-in array with new values added ++-- @see addTop ++-- @see push ++function M.prepend(array, ...) ++ return M.append({...}, array) ++end ++ + --- Pushes all passed-in values at the end of an array. + -- @name push + -- @param array an array + -- @param ... a variable number of arguments + -- @return the passed-in array with new added values + -- @see addTop +-function _.push(array, ...) +- _.each({...}, function(i,v) array[#array+1] = v end) ++-- @see prepend ++function M.push(array, ...) ++ local args = {...} ++ for k,v in ipairs({...}) do ++ array[#array+1] = v ++ end + return array + end + + --- Removes and returns the values at the top of a given array. +---
    Aliased as `shift` +--- @name pop ++--
    Aliased as `pop` ++-- @name shift + -- @param array an array + -- @param[opt] n the number of values to be popped. Defaults to 1. + -- @return the popped values + -- @see unshift +-function _.pop(array, n) ++function M.shift(array, n) + n = min(n or 1, #array) + local ret = {} + for i = 1, n do +@@ -836,8 +1157,8 @@ end + -- @param array an array + -- @param[opt] n the number of values to be unshifted. Defaults to 1. + -- @return the values +--- @see pop +-function _.unshift(array, n) ++-- @see shift ++function M.unshift(array, n) + n = min(n or 1, #array) + local ret = {} + for i = 1, n do +@@ -854,84 +1175,83 @@ end + -- @param array an array + -- @param ... a variable number of values to be removed from the array + -- @return the passed-in array with values removed +-function _.pull(array, ...) +- for __, rmValue in ipairs({...}) do +- for i = #array, 1, -1 do +- if _.isEqual(array[i], rmValue) then +- t_remove(array, i) ++function M.pull(array, ...) ++ local values = {...} ++ for i = #array, 1, -1 do ++ local remval = false ++ for k, rmValue in ipairs(values) do ++ if (remval == false) then ++ if M.isEqual(array[i], rmValue) then ++ t_remove(array, i) ++ remval = true ++ end + end + end + end + return array + end + +---- Removes values at index within the range `[start, finish]`. ++--- Removes values at an index within the range `[start, finish]`. + --
    Aliased as `rmRange`, `chop` + -- @name removeRange + -- @param array an array + -- @param[opt] start the lower bound index, defaults to the first index in the array. + -- @param[optchain] finish the upper bound index, defaults to the array length. + -- @return the passed-in array with values removed +-function _.removeRange(array, start, finish) +- local array = _.clone(array) +- local i,n = (next(array)),#array +- if n < 1 then return array end +- +- start = clamp(start or i,i,n) +- finish = clamp(finish or n,i,n) +- +- if finish < start then return array end +- +- local count = finish - start + 1 +- local i = start +- while count > 0 do +- t_remove(array,i) +- count = count - 1 ++function M.removeRange(array, start, finish) ++ start = start or 1 ++ finish = finish or #array ++ if start > finish then ++ error("start cannot be greater than finish.") ++ end ++ for i = finish, start, -1 do ++ t_remove(array, i) + end + return array + end + + --- Chunks together consecutive values. Values are chunked on the basis of the return +--- value of a provided predicate `f (k, v, ...)`. Consecutive elements which return ++-- value of a provided predicate `f (v, k)`. Consecutive elements which return + -- the same value are chunked together. Leaves the first argument untouched if it is not an array. + -- @name chunk + -- @param array an array +--- @param f an iterator function prototyped as `f (k, v, ...)` +--- @param[opt] ... Optional args to be passed to `f` ++-- @param f an iterator function prototyped as `f (v, k)`. Defaults to @{identity}. + -- @return a table of chunks (arrays) + -- @see zip +-function _.chunk(array, f, ...) +- if not _.isArray(array) then return array end +- local ch, ck, prev = {}, 0 +- local mask = _.map(array, f,...) +- _.each(mask, function(k,v) +- prev = (prev==nil) and v or prev +- ck = ((v~=prev) and (ck+1) or ck) ++function M.chunk(array, f) ++ local ch, ck, prev, val = {}, 0 ++ f = f or M.identity ++ for k,v in ipairs(array) do ++ val = f(v, k) ++ ck = ((val~=prev) and (ck+1) or ck) ++ prev = (prev==nil) and val or prev + if not ch[ck] then + ch[ck] = {array[k]} + else + ch[ck][#ch[ck]+1] = array[k] + end +- prev = v +- end) ++ prev = val ++ end + return ch + end + + --- Slices values indexed within `[start, finish]` range. +---
    Aliased as `_.sub` ++--
    Aliased as `M.sub` + -- @name slice + -- @param array an array + -- @param[opt] start the lower bound index, defaults to the first index in the array. + -- @param[optchain] finish the upper bound index, defaults to the array length. + -- @return a new array of sliced values +-function _.slice(array, start, finish) +- return _.select(array, function(index) +- return (index >= (start or next(array)) and index <= (finish or #array)) +- end) ++function M.slice(array, start, finish) ++ local t = {} ++ for k = start or 1, finish or #array do ++ t[#t+1] = array[k] ++ end ++ return t + end + + --- Returns the first N values in an array. +---
    Aliased as `head`, `take` ++--
    Aliased as `head`, `take` + -- @name first + -- @param array an array + -- @param[opt] n the number of values to be collected, defaults to 1. +@@ -939,9 +1259,13 @@ end + -- @see initial + -- @see last + -- @see rest +-function _.first(array, n) +- local n = n or 1 +- return _.slice(array,1, min(n,#array)) ++function M.first(array, n) ++ n = n or 1 ++ local t = {} ++ for k = 1, n do ++ t[k] = array[k] ++ end ++ return t + end + + --- Returns all values in an array excluding the last N values. +@@ -952,9 +1276,14 @@ end + -- @see first + -- @see last + -- @see rest +-function _.initial(array, n) +- if n and n < 0 then return end +- return _.slice(array,1, n and #array-(min(n,#array)) or #array-1) ++function M.initial(array, n) ++ local l = #array ++ n = n and l-(min(n,l)) or l-1 ++ local t = {} ++ for k = 1, n do ++ t[k] = array[k] ++ end ++ return t + end + + --- Returns the last N values in an array. +@@ -965,12 +1294,17 @@ end + -- @see first + -- @see initial + -- @see rest +-function _.last(array, n) +- if n and n <= 0 then return end +- return _.slice(array,n and #array-min(n-1,#array-1) or 2,#array) ++function M.last(array, n) ++ local l = #array ++ n = n and l-min(n-1,l-1) or 2 ++ local t = {} ++ for k = n, l do ++ t[#t+1] = array[k] ++ end ++ return t + end + +---- Removes all values before index. ++--- Returns all values after index. + --
    Aliased as `tail` + -- @name rest + -- @param array an array +@@ -979,9 +1313,12 @@ end + -- @see first + -- @see initial + -- @see last +-function _.rest(array,index) +- if index and index > #array then return {} end +- return _.slice(array,index and max(1,min(index,#array)) or 1,#array) ++function M.rest(array, index) ++ local t = {} ++ for k = index or 1, #array do ++ t[#t+1] = array[k] ++ end ++ return t + end + + --- Returns the value at a given index. +@@ -989,33 +1326,35 @@ end + -- @param array an array + -- @param index an index + -- @return the value at the given index +-function _.nth(array, index) ++function M.nth(array, index) + return array[index] + end + +---- Removes all falsy (false and nil) values. ++--- Returns all truthy values (removes `falses` and `nils`). + -- @name compact + -- @param array an array + -- @return a new array +-function _.compact(array) +- return _.reject(array, function (_,value) +- return not value +- end) ++function M.compact(array) ++ local t = {} ++ for k,v in pairs(array) do ++ if v then t[#t+1] = v end ++ end ++ return t + end + + --- Flattens a nested array. Passing `shallow` will only flatten at the first level. + -- @name flatten + -- @param array an array +--- @param[opt] shallow specifies the flattening depth +--- @return a new array, flattened +-function _.flatten(array, shallow) +- local shallow = shallow or false ++-- @param[opt] shallow specifies the flattening depth. Defaults to `false`.` ++-- @return a flattened array ++function M.flatten(array, shallow) ++ shallow = shallow or false + local new_flattened + local _flat = {} +- for key,value in pairs(array) do +- if _.isTable(value) then +- new_flattened = shallow and value or _.flatten (value) +- _.each(new_flattened, function(_,item) _flat[#_flat+1] = item end) ++ for key,value in ipairs(array) do ++ if type(value) == 'table' then ++ new_flattened = shallow and value or M.flatten (value) ++ for k,item in ipairs(new_flattened) do _flat[#_flat+1] = item end + else _flat[#_flat+1] = value + end + end +@@ -1031,11 +1370,11 @@ end + -- @see union + -- @see intersection + -- @see symmetricDifference +-function _.difference(array, array2) +- if not array2 then return _.clone(array) end +- return _.select(array,function(i,value) +- return not _.include(array2,value) +- end) ++function M.difference(array, array2) ++ if not array2 then return M.clone(array) end ++ return M.select(array,function(value) ++ return not M.include(array2,value) ++ end) + end + + --- Returns the duplicate-free union of all passed in arrays. +@@ -1045,32 +1384,40 @@ end + -- @see difference + -- @see intersection + -- @see symmetricDifference +-function _.union(...) +- return _.uniq(_.flatten({...})) ++function M.union(...) ++ return M.unique(M.flatten({...})) + end + + --- Returns the intersection of all passed-in arrays. + -- Each value in the result is present in each of the passed-in arrays. + -- @name intersection +--- @param array an array + -- @param ... a variable number of array arguments + -- @return a new array + -- @see difference + -- @see union + -- @see symmetricDifference +-function _.intersection(array, ...) ++function M.intersection(...) + local arg = {...} ++ local array = arg[1] ++ t_remove(arg, 1) + local _intersect = {} + for i,value in ipairs(array) do +- if _.all(arg,function(i,v) +- return _.include(v,value) +- end) then +- t_insert(_intersect,value) ++ if M.all(arg,function(v) return M.include(v,value) end) then ++ _intersect[#_intersect+1] = value + end + end + return _intersect + end + ++--- Checks if all passed in arrays are disjunct. ++-- @name disjoint ++-- @param ... a variable number of arrays ++-- @return `true` if the intersection of all arrays is not empty, `false` otherwise. ++-- @see intersection ++function M.disjoint(...) ++ return (#M.intersection(...) == 0) ++end ++ + --- Performs a symmetric difference. Returns values from `array` not present in `array2` and also values + -- from `array2` not present in `array`. + --
    Aliased as `symdiff` +@@ -1081,10 +1428,10 @@ end + -- @see difference + -- @see union + -- @see intersection +-function _.symmetricDifference(array, array2) +- return _.difference( +- _.union(array, array2), +- _.intersection(array,array2) ++function M.symmetricDifference(array, array2) ++ return M.difference( ++ M.union(array, array2), ++ M.intersection(array,array2) + ) + end + +@@ -1094,10 +1441,11 @@ end + -- @param array an array + -- @return a new array, duplicate-free + -- @see isunique +-function _.unique(array) ++-- @see duplicates ++function M.unique(array) + local ret = {} + for i = 1, #array do +- if not _.find(ret, array[i]) then ++ if not M.find(ret, array[i]) then + ret[#ret+1] = array[i] + end + end +@@ -1111,8 +1459,25 @@ end + -- @param array an array + -- @return `true` if the given array is unique, `false` otherwise. + -- @see unique +-function _.isunique(array) +- return _.isEqual(array, _.unique(array)) ++-- @see duplicates ++function M.isunique(array) ++ return #array == #(M.unique(array)) ++end ++ ++--- Returns an array list of all duplicates in array. ++-- @name duplicates ++-- @param array an array ++-- @return an array-list of duplicates ++-- @see unique ++function M.duplicates(array) ++ local dict = M.invert(array) ++ local dups = {} ++ for k, v in ipairs(array) do ++ if dict[v] ~= k and not M.find(dups, v) then ++ dups[#dups+1] = v ++ end ++ end ++ return dups + end + + --- Merges values of each of the passed-in arrays in subsets. +@@ -1121,24 +1486,45 @@ end + -- @name zip + -- @param ... a variable number of array arguments + -- @return a new array +-function _.zip(...) +- local arg = {...} +- local _len = _.max(_.map(arg,function(i,v) +- return #v +- end)) ++-- @see zipWith ++function M.zip(...) ++ local args = {...} ++ local n = M.max(args, function(array) return #array end) ++ local _ans = {} ++ for i = 1,n do ++ if not _ans[i] then _ans[i] = {} end ++ for k, array in ipairs(args) do ++ if (array[i]~= nil) then _ans[i][#_ans[i]+1] = array[i] end ++ end ++ end ++ return _ans ++end ++ ++--- Merges values using a given function. ++-- Only values indexed with the same key in the given arrays are merged in the same subset. ++-- Function `f` is used to combine values. ++--
    Aliased as `transposeWith` ++-- @name zipWith ++-- @param f a function ++-- @param ... a variable number of array arguments ++-- @return a flat array of results ++-- @see zip ++function M.zipWith(f, ...) ++ local args = {...} ++ local n = M.max(args, function(array) return #array end) + local _ans = {} +- for i = 1,_len do +- _ans[i] = _.pluck(arg,i) ++ for i = 1,n do ++ _ans[i] = f(unpack(M.pluck(args,i))) + end + return _ans + end + +---- Clones `array` and appends `other` values. ++--- Clones array and appends values from another array. + -- @name append + -- @param array an array + -- @param other an array + -- @return a new array +-function _.append(array, other) ++function M.append(array, other) + local t = {} + for i,v in ipairs(array) do t[i] = v end + for i,v in ipairs(other) do t[#t+1] = v end +@@ -1151,39 +1537,51 @@ end + -- @param ... a variable list of arrays + -- @return a new array + -- @see interpose +-function _.interleave(...) return _.flatten(_.zip(...)) end ++function M.interleave(...) ++ local args = {...} ++ local n = M.max(args, M.size) ++ local t = {} ++ for i = 1, n do ++ for k, array in ipairs(args) do ++ if array[i] then t[#t+1] = array[i] end ++ end ++ end ++ return t ++end + +---- Interposes value in-between consecutive pair of values in `array`. ++--- Interposes value in-between consecutive pair of values in array. ++--
    Aliased as `intersperse` + -- @name interpose +--- @param value a value + -- @param array an array ++-- @param value a value + -- @return a new array + -- @see interleave +-function _.interpose(value, array) +- return _.flatten(_.zip(array, _.rep(value, #array-1))) ++function M.interpose(array, value) ++ for k = #array, 2,-1 do ++ t_insert(array, k, value) ++ end ++ return array + end + +---- Produces a flexible list of numbers. If one positive value is passed, will count from 0 to that value, +--- with a default step of 1. If two values are passed, will count from the first one to the second one, with the +--- same default step of 1. A third value passed will be considered a step value. ++--- Produces a flexible list of numbers. If one value is passed, will count from 1 to that value, ++-- with a default step of 1 (or -1). If two values are passed, will count from the first one to the second one, ++-- using a default step of 1 (or -1). A third value passed will be considered a step value. + -- @name range + -- @param[opt] from the initial value of the range + -- @param[optchain] to the final value of the range +--- @param[optchain] step the step of count ++-- @param[optchain] step the step of count. Defaults to 1 or -1. + -- @return a new array of numbers +-function _.range(...) +- local arg = {...} +- local _start,_stop,_step +- if #arg==0 then return {} +- elseif #arg==1 then _stop,_start,_step = arg[1],0,1 +- elseif #arg==2 then _start,_stop,_step = arg[1],arg[2],1 +- elseif #arg == 3 then _start,_stop,_step = arg[1],arg[2],arg[3] +- end +- if (_step and _step==0) then return {} end +- local _ranged = {} +- local _steps = max(floor((_stop-_start)/_step),0) +- for i=1,_steps do _ranged[#_ranged+1] = _start+_step*i end +- if #_ranged>0 then t_insert(_ranged,1,_start) end ++function M.range(from, to, step) ++ if (from == nil) and (to == nil) and (step ==nil) then ++ return {} ++ elseif (from ~= nil) and (to == nil) and (step == nil) then ++ from, to, step = signum(from), from, signum(from) ++ elseif (from ~= nil) and (to ~= nil) and (step == nil) then ++ step = signum(to - from) ++ end ++ local _ranged = {from} ++ local steps = max(floor((to-from)/step),0) ++ for i=1,steps do _ranged[#_ranged+1] = from+step*i end + return _ranged + end + +@@ -1192,65 +1590,99 @@ end + -- @param value a value to be repeated + -- @param n the number of repetitions of value. + -- @return a new array of `n` values +-function _.rep(value, n) ++function M.rep(value, n) + local ret = {} +- for i = 1, n do ret[#ret+1] = value end ++ for i = 1, n do ret[i] = value end + return ret + end + ++--- Returns the powerset of array values. For instance, when given the set {1,2,3}, ++-- returns `{{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}}`. ++-- @name powerset ++-- @param array an array ++-- @return an array ++function M.powerset(array) ++ local n = #array ++ local powerset = {} ++ for i, v in ipairs(array) do ++ for j = 1, #powerset do ++ local set = powerset[j] ++ t_insert(powerset, M.push(M.slice(set), v)) ++ end ++ t_insert(powerset, {v}) ++ end ++ t_insert(powerset, {}) ++ return powerset ++end ++ + --- Iterator returning partitions of an array. It returns arrays of length `n` + -- made of values from the given array. If the last partition has lower elements than `n` and + -- `pad` is supplied, it will be adjusted to `n` of elements with `pad` value. + -- @name partition + -- @param array an array +--- @param[opt] n the size of partitions. Should be greater than 0. Defaults to 1. +--- @param[optchain] pad a value to adjust the last subsequence to the `n` elements ++-- @param[opt] n the size of partitions. Defaults to 1. ++-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements + -- @return an iterator function +-function _.partition(array, n, pad) ++-- @see overlapping ++-- @see aperture ++function M.partition(array, n, pad) + if n<=0 then return end +- return coroutine.wrap(function() +- partgen(array, n or 1, coroutine.yield, pad) ++ return wrap(function() ++ partgen(array, n or 1, yield, pad) + end) + end + +---- Iterator returning sliding partitions of an array. It returns overlapping subsequences +--- of length `n`. If the last subsequence has lower elements than `n` and `pad` is ++--- Iterator returning overlapping partitions of an array. ++-- If the last subsequence has lower elements than `n` and `pad` is + -- supplied, it will be adjusted to `n` elements with `pad` value. +--- @name sliding. ++-- @name overlapping ++-- @param array an array ++-- @param[opt] n the size of partitions. Defaults to 2. ++-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements ++-- @return an iterator function ++-- @see partition ++-- @see aperture ++function M.overlapping(array, n, pad) ++ if n<=1 then return end ++ return wrap(function() ++ partgen2(array, n or 2, yield, pad) ++ end) ++end ++ ++--- Iterator returning sliding partitions of an array. ++--
    Aliased as `sliding` ++-- @name aperture + -- @param array an array +--- @param[opt] n the size of partitions. Should be greater than 1. Defaults to 2. +--- @param[optchain] pad a value to adjust the last subsequence to the `n` elements ++-- @param[opt] n the size of partitions. Defaults to 2 (and then behaves like @{pairwise}) + -- @return an iterator function +-function _.sliding(array, n, pad) ++-- @see partition ++-- @see overlapping ++-- @see pairwise ++function M.aperture(array, n) + if n<=1 then return end +- return coroutine.wrap(function() +- partgen2(array, n or 2, coroutine.yield, pad) ++ return wrap(function() ++ partgen3(array, n or 2, yield) + end) + end + ++--- Iterator returning sliding pairs of an array. ++-- @name pairwise ++-- @param array an array ++-- @return an iterator function ++-- @see overlapping ++function M.pairwise(array) return M.aperture(array, 2) end ++ + --- Iterator returning the permutations of an array. It returns arrays made of all values + -- from the passed-in array, with values permuted. + -- @name permutation + -- @param array an array + -- @return an iterator function +-function _.permutation(array) +- return coroutine.wrap(function() +- permgen(array, #array, coroutine.yield) ++function M.permutation(array) ++ return wrap(function() ++ permgen(array, #array, yield) + end) + end + +---- Swaps keys with values. Produces a new array where previous keys are now values, +--- while previous values are now keys. +---
    Aliased as `mirror` +--- @name invert +--- @param array a given array +--- @return a new array +-function _.invert(array) +- local _ret = {} +- _.each(array,function(i,v) _ret[v] = i end) +- return _ret +-end +- + --- Concatenates values in a given array. Handles booleans as well. If `sep` string is + -- passed, it will be used as a separator. Passing `i` and `j` will result in concatenating + -- only values within `[i, j]` range. +@@ -1261,54 +1693,245 @@ end + -- @param[optchain] i the starting index, defaults to 1. + -- @param[optchain] j the final index, defaults to the array length. + -- @return a string +-function _.concat(array, sep, i, j) +- local _array = _.map(array,function(i,v) +- return tostring(v) +- end) +- return t_concat(_array,sep,i or 1,j or #array) ++function M.concat(array, sep, i, j) ++ return t_concat(M.map(array,tostring),sep,i,j) ++end ++ ++--- Returns all possible pairs built from given arrays. ++-- @name xprod ++-- @param array a first array ++-- @param array2 a second array ++-- @return an array list of all pairs ++function M.xprod(array, array2) ++ local p = {} ++ for i, v1 in ipairs(array) do ++ for j, v2 in ipairs(array2) do ++ p[#p+1] = {v1, v2} ++ end ++ end ++ return p ++end ++ ++--- Creates pairs from value and array. Value is always prepended to the pair. ++-- @name xpairs ++-- @param valua a value ++-- @param array an array ++-- @return an array list of all pairs ++function M.xpairs(value, array) ++ local xpairs = {} ++ for k, v in ipairs(array) do ++ xpairs[k] = {value, v} ++ end ++ return xpairs ++end + ++--- Creates pairs from value and array. Value is always appended as the last item to the pair. ++-- @name xpairsRight ++-- @param valua a value ++-- @param array an array ++-- @return an array list of all pairs ++function M.xpairsRight(value, array) ++ local xpairs = {} ++ for k, v in ipairs(array) do ++ xpairs[k] = {v, value} ++ end ++ return xpairs ++end ++ ++--- Returns the sum of array values. ++-- @name sum ++-- @param array a given array ++-- @return the sum of array values ++function M.sum(array) ++ local s = 0 ++ for k, v in ipairs(array) do s = s + v end ++ return s ++end ++ ++--- Returns the product of array values. ++-- @name product ++-- @param array a given array ++-- @return the product of array values ++function M.product(array) ++ local p = 1 ++ for k, v in ipairs(array) do p = p * v end ++ return p ++end ++ ++--- Returns the mean of an array of numbers. ++--
    Aliased as `average` ++-- @name mean ++-- @param array an array of numbers ++-- @return a number ++-- @see sum ++-- @see product ++-- @see median ++function M.mean(array) ++ return M.sum(array)/(#array) ++end ++ ++--- Returns the median of an array of numbers. ++-- @name median ++-- @param array an array of numbers ++-- @return a number ++-- @see sum ++-- @see product ++-- @see mean ++function M.median(array) ++ local t = M.sort(M.clone(array)) ++ local n = #t ++ if n == 0 then ++ return ++ elseif n==1 then ++ return t[1] ++ end ++ local mid = ceil(n/2) ++ return n%2==0 and (t[mid] + t[mid+1])/2 or t[mid] + end + + --- Utility functions + -- @section Utility functions + +---- The no-operation function. ++--- The no operation function. + -- @name noop + -- @return nothing +-function _.noop() return end ++function M.noop() return end + + --- Returns the passed-in value. This function is used internally + -- as a default iterator. + -- @name identity + -- @param value a value + -- @return the passed-in value +-function _.identity(value) return value end ++function M.identity(value) return value end ++ ++--- Calls `f` with the supplied arguments. Returns the results of `f(...)`. ++-- @name call ++-- @param f a function ++-- @param[opt] ... a vararg list of args to `f` ++-- @return the result of `f(...)` call. ++function M.call(f, ...) ++ return f(...) ++end + + --- Creates a constant function which returns the same output on every call. ++--
    Aliased as `always` + -- @name constant + -- @param value a constant value + -- @return a constant function +-function _.constant(value) return function() return value end end ++function M.constant(value) ++ return function() return value end ++end ++ ++--- Returns a function which applies `specs` on args. This function produces an object having ++-- the same structure than `specs` by mapping each property to the result of calling its ++-- associated function with the supplied arguments ++-- @name applySpec ++-- @param specs a table ++-- @return a function ++function M.applySpec(specs) ++ return function (...) ++ local spec = {} ++ for i, f in pairs(specs) do spec[i] = f(...) end ++ return spec ++ end ++end ++ ++--- Threads `value` through a series of functions. If a function expects more than one args, ++-- it can be specified using an array list, where the first item is the function and the following ++-- are the remaining args neeeded. The value is used as the first input. ++-- @name thread ++-- @param value a value ++-- @param ... a vararg list of functions or arrays ++-- @return a value ++-- @see threadRight ++function M.thread(value, ...) ++ local state = value ++ local arg = {...} ++ for k, t in ipairs(arg) do ++ if type(t) == 'function' then ++ state = t(state) ++ elseif type(t) == 'table' then ++ local f = t[1] ++ t_remove(t, 1) ++ state = M.reduce(t, f, state) ++ end ++ end ++ return state ++end ++ ++--- Threads `value` through a series of functions. If a function expects more than one args, ++-- it can be specified using an array list, where the first item is the function and the following ++-- are the remaining args neeeded. The value is used as the last input. ++-- @name threadRight ++-- @param value a value ++-- @param ... a vararg list of functions or arrays ++-- @return a value ++-- @see thread ++function M.threadRight(value, ...) ++ local state = value ++ local arg = {...} ++ for k, t in ipairs(arg) do ++ if type(t) == 'function' then ++ state = t(state) ++ elseif type(t) == 'table' then ++ local f = t[1] ++ t_remove(t, 1) ++ t_insert(t, state) ++ state = M.reduce(t, f) ++ end ++ end ++ return state ++end ++ ++--- Returns a dispatching function. When called with arguments, this function invokes each of its functions ++-- in the passed-in order and returns the results of the first non-nil evaluation. ++-- @name dispatch ++-- @param ... a vararg list of functions ++-- @return a dispatch function ++function M.dispatch(...) ++ local funcs = {...} ++ return function (...) ++ for k, f in ipairs(funcs) do ++ local r = {f(...)} ++ if #r > 0 then return unpack(r) end ++ end ++ end ++end + + --- Memoizes a given function by caching the computed result. +--- Useful for speeding-up slow-running functions. If a `hash` function is passed, +--- it will be used to compute hash keys for a set of input values for caching. ++-- Useful for speeding-up slow-running functions. + --
    Aliased as `cache` + -- @name memoize + -- @param f a function +--- @param[opt] hash a hash function, defaults to @{identity} + -- @return a new function +-function _.memoize(f, hash) ++function M.memoize(f) + local _cache = setmetatable({},{__mode = 'kv'}) +- local _hasher = hash or _.identity +- return function (...) +- local _hashKey = _hasher(...) +- local _result = _cache[_hashKey] +- if not _result then _cache[_hashKey] = f(...) end +- return _cache[_hashKey] ++ return function (key) ++ if (_cache[key] == nil) then ++ _cache[key] = f(key) ++ end ++ return _cache[key] + end + end + ++--- Builds a list from a seed value. Accepts an iterator function, which ++-- returns either nil to stop iteration or two values : the value to add to the list ++-- of results and the seed to be used in the next call to the iterator function. ++-- @name unfold ++-- @param f an iterator function ++-- @param seed a seed value ++-- @return an array of values ++function M.unfold(f, seed) ++ local t, result = {} ++ while true do ++ result, seed = f(seed) ++ if result ~= nil then t[#t+1] = result ++ else break ++ end ++ end ++ return t ++end ++ + --- Returns a version of `f` that runs only once. Successive calls to `f` + -- will keep yielding the same output, no matter what the passed-in arguments are. + -- It can be used to initialize variables. +@@ -1317,7 +1940,7 @@ end + -- @return a new function + -- @see before + -- @see after +-function _.once(f) ++function M.once(f) + local _internal = 0 + local _args = {} + return function(...) +@@ -1327,7 +1950,7 @@ function _.once(f) + end + end + +---- Returns a version of `f` that will run no more than `count` times. Next calls will ++--- Returns a version of `f` that will run no more than count times. Next calls will + -- keep yielding the results of the count-th call. + -- @name before + -- @param f a function +@@ -1335,7 +1958,7 @@ end + -- @return a new function + -- @see once + -- @see after +-function _.before(f, count) ++function M.before(f, count) + local _internal = 0 + local _args = {} + return function(...) +@@ -1353,7 +1976,7 @@ end + -- @return a new function + -- @see once + -- @see before +-function _.after(f, count) ++function M.after(f, count) + local _limit,_internal = count, 0 + return function(...) + _internal = _internal+1 +@@ -1367,9 +1990,9 @@ end + -- @param ... a variable number of functions + -- @return a new function + -- @see pipe +-function _.compose(...) ++function M.compose(...) + -- See: https://github.com/Yonaba/Moses/pull/15#issuecomment-139038895 +- local f = _.reverse {...} ++ local f = M.reverse {...} + return function (...) + local first, _temp = true + for i, func in ipairs(f) do +@@ -1391,8 +2014,8 @@ end + -- @param ... a variable number of functions + -- @return the result of the composition of function calls. + -- @see compose +-function _.pipe(value, ...) +- return _.compose(...)(value) ++function M.pipe(value, ...) ++ return M.compose(...)(value) + end + + --- Returns the logical complement of a given function. For a given input, the returned +@@ -1401,7 +2024,7 @@ end + -- @name complement + -- @param f a function + -- @return the logical complement of the given function `f`. +-function _.complement(f) ++function M.complement(f) + return function(...) return not f(...) end + end + +@@ -1412,9 +2035,11 @@ end + -- @param value a value + -- @param ... a variable number of functions + -- @return a list of results +-function _.juxtapose(value, ...) ++function M.juxtapose(value, ...) + local res = {} +- _.each({...}, function(_,f) res[#res+1] = f(value) end) ++ for i, func in ipairs({...}) do ++ res[i] = func(value) ++ end + return unpack(res) + end + +@@ -1425,20 +2050,19 @@ end + -- @param f a function to be wrapped, prototyped as `f (...)` + -- @param wrapper a wrapper function, prototyped as `wrapper (f, ...)` + -- @return the results +-function _.wrap(f, wrapper) ++function M.wrap(f, wrapper) + return function (...) return wrapper(f,...) end + end + + --- Runs `iter` function `n` times. Collects the results of each run and returns them in an array. + -- @name times +--- @param n the number of times `iter` should be called +--- @param iter an iterator function, prototyped as `iter (i, ...)` +--- @param ... args to be passed to `iter` function ++-- @param iter an iterator function, prototyped as `iter (i)` ++-- @param[opt] n the number of times `iter` should be called. Defaults to 1. + -- @return table an array of results +-function _.times(n, iter, ...) ++function M.times(iter, n) + local results = {} +- for i = 1,n do +- results[i] = iter(i,...) ++ for i = 1, (n or 1) do ++ results[i] = iter(i) + end + return results + end +@@ -1450,11 +2074,11 @@ end + -- @return a function + -- @see bind2 + -- @see bindn +--- @see bindAll +-function _.bind(f, v) ++-- @see bindall ++function M.bind(f, v) + return function (...) +- return f(v,...) +- end ++ return f(v,...) ++ end + end + + --- Binds `v` to be the second argument to `f`. Calling `f (a, ...)` will result to `f (a, v, ...)`. +@@ -1464,8 +2088,8 @@ end + -- @return a function + -- @see bind + -- @see bindn +--- @see bindAll +-function _.bind2(f, v) ++-- @see bindall ++function M.bind2(f, v) + return function (t, ...) + return f(t, v, ...) + end +@@ -1479,86 +2103,246 @@ end + -- @return a function + -- @see bind + -- @see bind2 +--- @see bindAll +-function _.bindn(f, ...) +- local iArg = {...} ++-- @see bindall ++function M.bindn(f, ...) ++ local args = {...} + return function (...) +- return f(unpack(_.append(iArg,{...}))) ++ return f(unpack(M.append(args,{...}))) + end + end + + --- Binds methods to object. As such, whenever any of these methods is invoked, it + -- always receives the object as its first argument. +--- @name bindAll ++-- @name bindall + -- @param obj an abject + -- @param ... a variable number of method names + -- @return the passed-in object with all methods bound to the object itself. + -- @see bind + -- @see bind2 + -- @see bindn +-function _.bindAll(obj, ...) ++function M.bindall(obj, ...) + local methodNames = {...} +- for __, methodName in ipairs(methodNames) do ++ for i, methodName in ipairs(methodNames) do + local method = obj[methodName] +- if method then obj[methodName] = _.bind(method, obj) end ++ if method then obj[methodName] = M.bind(method, obj) end + end + return obj + end + ++--- Returns a function which iterate over a set of conditions. It invokes each predicate, ++-- passing it given values. It returns the value of the corresponding function of the first ++-- predicate to return a non-nil value. ++-- @name cond ++-- @param conds an array list of predicate-function pairs ++-- @return the result of invoking `f(...)` of the first predicate to return a non-nil value ++function M.cond(conds) ++ return function(...) ++ for k, condset in ipairs(conds) do ++ if condset[1](...) then ++ return condset[2](...) ++ end ++ end ++ end ++end ++ ++--- Returns a validation function. Given a set of functions, the validation function evaluates ++-- to `true` only when all its funcs returns `true`. ++-- @name both ++-- @param ... an array list of functions ++-- @return `true` when all given funcs returns true with input, false otherwise ++function M.both(...) ++ local funcs = {...} ++ return function (...) ++ for k, f in ipairs(funcs) do ++ if not f(...) then return false end ++ end ++ return true ++ end ++end ++ ++--- Returns a validation function. Given a set of functions, the validation function evaluates ++-- to `true` when at least one of its funcs returns `true`. ++-- @name either ++-- @param ... an array list of functions ++-- @return `true` when one of the given funcs returns `true` with input, `false` otherwise ++function M.either(...) ++ local funcs = {...} ++ return function (...) ++ for k, f in ipairs(funcs) do ++ if f(...) then return true end ++ end ++ return false ++ end ++end ++ ++--- Returns a validation function. Given a set of functions, the validation function evaluates ++-- to `true` when neither of its func return `true`. ++-- @name neither ++-- @param ... an array list of functions ++-- @return `true` when neither of the given funcs returns `true` with input, `false` otherwise ++function M.neither(...) ++ local funcs = {...} ++ return function (...) ++ for k, f in ipairs(funcs) do ++ if f(...) then return false end ++ end ++ return true ++ end ++end ++ + --- Generates an unique ID for the current session. If given a string `template`, it + -- will use this template for output formatting. Otherwise, if `template` is a function, it +--- will evaluate `template (id, ...)`. ++-- will evaluate `template (id)`. + --
    Aliased as `uid`. + -- @name uniqueId + -- @param[opt] template either a string or a function template to format the ID +--- @param[optchain] ... a variable number of arguments to be passed to `template`, in case it is a function. + -- @return value an ID +-function _.uniqueId(template, ...) ++function M.uniqueId(template) + unique_id_counter = unique_id_counter + 1 + if template then +- if _.isString(template) then ++ if type(template) == 'string' then + return template:format(unique_id_counter) +- elseif _.isFunction(template) then +- return template(unique_id_counter,...) ++ elseif type(template) == 'function' then ++ return template(unique_id_counter) + end + end + return unique_id_counter + end + + --- Produces an iterator which repeatedly apply a function `f` onto an input. +--- Yields x, then f(x), then f(f(x)), continuously. ++-- Yields `value`, then `f(value)`, then `f(f(value))`, continuously. ++--
    Aliased as `iter`. + -- @name iterator + -- @param f a function +--- @param x an initial input to `f` +--- @return an iterator fnction +---
    Aliased as `iter`. +-function _.iterator(f, x) ++-- @param value an initial input to `f` ++-- @param[opt] n the number of times the iterator should run ++-- @return an iterator function ++function M.iterator(f, value, n) ++ local cnt = 0 + return function() +- x = f(x) +- return x ++ cnt = cnt + 1 ++ if n and cnt > n then return end ++ value = f(value) ++ return value + end + end + +---- Iterates an iterator and returns its values in an array. +--- @name array +--- @param ... an iterator (a function, a table and a value) ++--- Consumes the first `n` values of a iterator then returns it. ++-- @name skip ++-- @param iter an iterator function ++-- @param[opt] n a number. Defaults to 1. ++-- @return the given iterator ++function M.skip(iter, n) ++ for i = 1, (n or 1) do ++ if iter() == nil then return end ++ end ++ return iter ++end ++ ++--- Iterates over an iterator and returns its values in an array. ++-- @name tabulate ++-- @param ... an iterator function (returning a generator, a state and a value) + -- @return an array of results +-function _.array(...) ++function M.tabulate(...) + local r = {} + for v in ... do r[#r+1] = v end + return r + end + ++--- Returns the length of an iterator. It consumes the iterator itself. ++-- @name iterlen ++-- @param ... an iterator function (returning a generator, a state and a value) ++-- @return the iterator length ++function M.iterlen(...) ++ local l = 0 ++ for v in ... do l = l + 1 end ++ return l ++end ++ ++--- Casts value as an array if it is not one. ++-- @name castArray ++-- @param value a value ++-- @return an array containing the given value ++function M.castArray(value) ++ return (type(value)~='table') and {value} or value ++end ++ + --- Creates a function of `f` with arguments flipped in reverse order. + -- @name flip + -- @param f a function + -- @return a function +-function _.flip(f) ++function M.flip(f) + return function(...) +- return f(unpack(_.reverse({...}))) ++ return f(unpack(M.reverse({...}))) + end + end + ++--- Returns a function that gets the nth argument. ++-- If n is negative, the nth argument from the end is returned. ++-- @name nthArg ++-- @param n a number ++-- @return a function ++function M.nthArg(n) ++ return function (...) ++ local args = {...} ++ return args[(n < 0) and (#args + n + 1) or n] ++ end ++end ++ ++--- Returns a function which accepts up to one arg. It ignores any additional arguments. ++-- @name unary ++-- @param f a function ++-- @return a function ++-- @see ary ++function M.unary(f) ++ return function (...) ++ local args = {...} ++ return f(args[1]) ++ end ++end ++ ++--- Returns a function which accepts up to `n` args. It ignores any additional arguments. ++--
    Aliased as `nAry`. ++-- @name ary ++-- @param f a function ++-- @param[opt] n a number. Defaults to 1. ++-- @return a function ++-- @see unary ++function M.ary(f, n) ++ n = n or 1 ++ return function (...) ++ local args = {...} ++ local fargs = {} ++ for i = 1, n do fargs[i] = args[i] end ++ return f(unpack(fargs)) ++ end ++end ++ ++--- Returns a function with an arity of 0. The new function ignores any arguments passed to it. ++-- @name noarg ++-- @param f a function ++-- @return a new function ++function M.noarg(f) ++ return function () ++ return f() ++ end ++end ++ ++--- Returns a function which runs with arguments rearranged. Arguments are passed to the ++-- returned function in the order of supplied `indexes` at call-time. ++-- @name rearg ++-- @param f a function ++-- @param indexes an array list of indexes ++-- @return a function ++function M.rearg(f, indexes) ++ return function(...) ++ local args = {...} ++ local reargs = {} ++ for i, arg in ipairs(indexes) do reargs[i] = args[arg] end ++ return f(unpack(reargs)) ++ end ++end ++ + --- Creates a function that runs transforms on all arguments it receives. + -- @name over + -- @param ... a set of functions which will receive all arguments to the returned function +@@ -1566,11 +2350,11 @@ end + -- @see overEvery + -- @see overSome + -- @see overArgs +-function _.over(...) ++function M.over(...) + local transforms = {...} + return function(...) + local r = {} +- for __,transform in ipairs(transforms) do ++ for i,transform in ipairs(transforms) do + r[#r+1] = transform(...) + end + return r +@@ -1585,10 +2369,10 @@ end + -- @see over + -- @see overSome + -- @see overArgs +-function _.overEvery(...) +- local f = _.over(...) ++function M.overEvery(...) ++ local f = M.over(...) + return function(...) +- return _.reduce(f(...),function(state,v) return state and v end) ++ return M.reduce(f(...),function(state,v) return state and v end) + end + end + +@@ -1600,10 +2384,10 @@ end + -- @see over + -- @see overEvery + -- @see overArgs +-function _.overSome(...) +- local f = _.over(...) ++function M.overSome(...) ++ local f = M.over(...) + return function(...) +- return _.reduce(f(...),function(state,v) return state or v end) ++ return M.reduce(f(...),function(state,v) return state or v end) + end + end + +@@ -1616,20 +2400,28 @@ end + -- @see over + -- @see overEvery + -- @see overSome +-function _.overArgs(f,...) ++function M.overArgs(f,...) + local _argf = {...} + return function(...) + local _args = {...} + for i = 1,#_argf do +- local f = _argf[i] +- if _args[i] then _args[i] = f(_args[i]) end ++ local func = _argf[i] ++ if _args[i] then _args[i] = func(_args[i]) end + end + return f(unpack(_args)) + end + end + ++--- Converges two functions into one. ++-- @name converge ++-- @param f a function ++-- @param g a function ++-- @param h a function ++-- @return a new version of function f ++function M.converge(f, g, h) return function(...) return f(g(...),h(...)) end end ++ + --- Partially apply a function by filling in any number of its arguments. +--- One may pass a string `'_'` as a placeholder in the list of arguments to specify an argument ++-- One may pass a string `'M'` as a placeholder in the list of arguments to specify an argument + -- that should not be pre-filled, but left open to be supplied at call-time. + -- @name partial + -- @param f a function +@@ -1637,15 +2429,15 @@ end + -- @return a new version of function f having some of it original arguments filled + -- @see partialRight + -- @see curry +-function _.partial(f,...) ++function M.partial(f,...) + local partial_args = {...} + return function (...) + local n_args = {...} + local f_args = {} + for k,v in ipairs(partial_args) do +- f_args[k] = (v == '_') and _.pop(n_args) or v ++ f_args[k] = (v == '_') and M.shift(n_args) or v + end +- return f(unpack(_.append(f_args,n_args))) ++ return f(unpack(M.append(f_args,n_args))) + end + end + +@@ -1656,15 +2448,15 @@ end + -- @return a new version of function f having some of it original arguments filled + -- @see partialRight + -- @see curry +-function _.partialRight(f,...) ++function M.partialRight(f,...) + local partial_args = {...} + return function (...) + local n_args = {...} + local f_args = {} + for k = 1,#partial_args do +- f_args[k] = (partial_args[k] == '_') and _.pop(n_args) or partial_args[k] ++ f_args[k] = (partial_args[k] == '_') and M.shift(n_args) or partial_args[k] + end +- return f(unpack(_.append(n_args, f_args))) ++ return f(unpack(M.append(n_args, f_args))) + end + end + +@@ -1677,7 +2469,7 @@ end + -- @return a curried version of `f` + -- @see partial + -- @see partialRight +-function _.curry(f, n_args) ++function M.curry(f, n_args) + n_args = n_args or 2 + local _args = {} + local function scurry(v) +@@ -1699,67 +2491,133 @@ end + -- @param f a function + -- @param[opt] ... optional args to `f` + -- @return the execution time and the results of `f (...)` +-function _.time(f, ...) ++function M.time(f, ...) + local stime = clock() + local r = {f(...)} + return clock() - stime, unpack(r) + end + + --- Object functions +---@section Object functions ++-- @section Object functions + + --- Returns the keys of the object properties. + -- @name keys + -- @param obj an object + -- @return an array +-function _.keys(obj) +- local _oKeys = {} +- _.each(obj,function(key) _oKeys[#_oKeys+1]=key end) +- return _oKeys ++function M.keys(obj) ++ local keys = {} ++ for key in pairs(obj) do keys[#keys+1] = key end ++ return keys + end + + --- Returns the values of the object properties. + -- @name values + -- @param obj an object +--- @return an array +-function _.values(obj) +- local _oValues = {} +- _.each(obj,function(_,value) _oValues[#_oValues+1]=value end) +- return _oValues ++-- @return an array of values ++function M.values(obj) ++ local values = {} ++ for key, value in pairs(obj) do values[#values+1] = value end ++ return values ++end ++ ++--- Returns the value at a given path in an object. ++-- Path is given as a vararg list of keys. ++-- @name path ++-- @param obj an object ++-- @param ... a vararg list of keys ++-- @return a value or nil ++function M.path(obj, ...) ++ local value, path = obj, {...} ++ for i, p in ipairs(path) do ++ if (value[p] == nil) then return end ++ value = value[p] ++ end ++ return value + end + +---- Converts keys and values a an array-list of [k, v]. ++--- Spreads object under property path onto provided object. ++-- It is similar to @{flattenPath}, but removes object under the property path. ++-- @name spreadPath ++-- @param obj an object ++-- @param ... a property path given as a vararg list ++-- @return the passed-in object with changes ++-- @see flattenPath ++function M.spreadPath(obj, ...) ++ local path = {...} ++ for _, p in ipairs(path) do ++ if obj[p] then ++ for k, v in pairs(obj[p]) do ++ obj[k] = v ++ obj[p][k] = nil ++ end ++ end ++ end ++ return obj ++end ++ ++--- Flattens object under property path onto provided object. ++-- It is similar to @{spreadPath}, but preserves object under the property path. ++-- @name flattenPath ++-- @param obj an object ++-- @param ... a property path given as a vararg list ++-- @return the passed-in object with changes ++-- @see spreadPath ++function M.flattenPath(obj, ...) ++ local path = {...} ++ for _, p in ipairs(path) do ++ if obj[p] then ++ for k, v in pairs(obj[p]) do obj[k] = v end ++ end ++ end ++ return obj ++end ++ ++--- Converts key-value pairs to an array-list of `[k, v]` pairs. + -- @name kvpairs + -- @param obj an object +--- @return an array list of key-values pairs ++-- @return an array list of key-value pairs + -- @see toObj +-function _.kvpairs(obj) ++function M.kvpairs(obj) + local t = {} +- _.each(obj, function(k,v) t[#t+1] = {k,v} end) ++ for k,v in pairs(obj) do t[#t+1] = {k,v} end + return t + end + +---- Converts an array list of `kvpairs` to an object. Keys are taken +--- from the 1rst column in the `kvpairs` sequence, associated with values in the 2nd +--- column ++--- Converts an array list of `[k,v]` pairs to an object. Keys are taken ++-- from the 1rst column in the `[k,v]` pairs sequence, associated with values in the 2nd ++-- column. + -- @name toObj +--- @param kvpairs an array-list of `kvpairs` ++-- @param kvpairs an array-list of `[k,v]` pairs + -- @return an object + -- @see kvpairs +-function _.toObj(kvpairs) ++function M.toObj(kvpairs) + local obj = {} +- for __, v in ipairs(kvpairs) do ++ for k, v in ipairs(kvpairs) do + obj[v[1]] = v[2] + end + return obj + end + ++--- Swaps keys with values. Produces a new object where previous keys are now values, ++-- while previous values are now keys. ++--
    Aliased as `mirror` ++-- @name invert ++-- @param obj a given object ++-- @return a new object ++function M.invert(obj) ++ local _ret = {} ++ for k, v in pairs(obj) do ++ _ret[v] = k ++ end ++ return _ret ++end ++ + --- Returns a function that will return the key property of any passed-in object. + -- @name property + -- @param key a key property name + -- @return a function which should accept an object as argument + -- @see propertyOf +-function _.property(key) ++function M.property(key) + return function(obj) return obj[key] end + end + +@@ -1768,7 +2626,7 @@ end + -- @param obj an object + -- @return a function which should accept a key property argument + -- @see property +-function _.propertyOf(obj) ++function M.propertyOf(obj) + return function(key) return obj[key] end + end + +@@ -1776,7 +2634,7 @@ end + -- @name toBoolean + -- @param value a value. Can be of any type + -- @return `true` if value is true, `false` otherwise (false or nil). +-function _.toBoolean(value) ++function M.toBoolean(value) + return not not value + end + +@@ -1787,15 +2645,13 @@ end + -- @param destObj a destination object + -- @param ... a list of objects + -- @return the destination object extended +-function _.extend(destObj, ...) ++function M.extend(destObj, ...) + local sources = {...} +- _.each(sources,function(__,source) +- if _.isTable(source) then +- _.each(source,function(key,value) +- destObj[key] = value +- end) ++ for k, source in ipairs(sources) do ++ if type(source) == 'table' then ++ for key, value in pairs(source) do destObj[key] = value end + end +- end) ++ end + return destObj + end + +@@ -1806,25 +2662,24 @@ end + -- @name functions + -- @param[opt] obj an object. Defaults to Moses library functions. + -- @return an array-list of methods names +-function _.functions(obj, recurseMt) +- obj = obj or _ ++function M.functions(obj, recurseMt) ++ obj = obj or M + local _methods = {} +- _.each(obj,function(key,value) +- if _.isFunction(value) then +- _methods[#_methods+1]=key ++ for key, value in pairs(obj) do ++ if type(value) == 'function' then ++ _methods[#_methods+1] = key + end +- end) +- if not recurseMt then +- return _.sort(_methods) + end +- local mt = getmetatable(obj) +- if mt and mt.__index then +- local mt_methods = _.functions(mt.__index) +- _.each(mt_methods, function(k,fn) +- _methods[#_methods+1] = fn +- end) ++ if recurseMt then ++ local mt = getmetatable(obj) ++ if mt and mt.__index then ++ local mt_methods = M.functions(mt.__index, recurseMt) ++ for k, fn in ipairs(mt_methods) do ++ _methods[#_methods+1] = fn ++ end ++ end + end +- return _.sort(_methods) ++ return _methods + end + + --- Clones a given object properties. If `shallow` is passed will also clone nested array properties. +@@ -1832,19 +2687,19 @@ end + -- @param obj an object + -- @param[opt] shallow whether or not nested array-properties should be cloned, defaults to false. + -- @return a copy of the passed-in object +-function _.clone(obj, shallow) +- if not _.isTable(obj) then return obj end ++function M.clone(obj, shallow) ++ if type(obj) ~= 'table' then return obj end + local _obj = {} +- _.each(obj,function(i,v) +- if _.isTable(v) then ++ for i,v in pairs(obj) do ++ if type(v) == 'table' then + if not shallow then +- _obj[i] = _.clone(v,shallow) ++ _obj[i] = M.clone(v,shallow) + else _obj[i] = v + end + else + _obj[i] = v + end +- end) ++ end + return _obj + end + +@@ -1853,11 +2708,10 @@ end + -- on intermediate results within the chain. + -- @name tap + -- @param obj an object +--- @param f an interceptor function, should be prototyped as `f (obj, ...)` +--- @param[opt] ... args to be passed to `f` ++-- @param f an interceptor function, should be prototyped as `f (obj)` + -- @return the passed-in object +-function _.tap(obj, f, ...) +- f(obj,...) ++function M.tap(obj, f) ++ f(obj) + return obj + end + +@@ -1866,7 +2720,7 @@ end + -- @param obj an object + -- @param key a key property to be checked + -- @return `true` or `false` +-function _.has(obj, key) ++function M.has(obj, key) + return obj[key]~=nil + end + +@@ -1876,14 +2730,14 @@ end + -- @param obj an object + -- @param ... a variable number of string keys + -- @return the filtered object +-function _.pick(obj, ...) +- local whitelist = _.flatten {...} ++function M.pick(obj, ...) ++ local whitelist = M.flatten {...} + local _picked = {} +- _.each(whitelist,function(key,property) +- if not _.isNil(obj[property]) then +- _picked[property] = obj[property] +- end +- end) ++ for key, property in pairs(whitelist) do ++ if (obj[property])~=nil then ++ _picked[property] = obj[property] ++ end ++ end + return _picked + end + +@@ -1893,14 +2747,14 @@ end + -- @param obj an object + -- @param ... a variable number of string keys + -- @return the filtered object +-function _.omit(obj, ...) +- local blacklist = _.flatten {...} ++function M.omit(obj, ...) ++ local blacklist = M.flatten {...} + local _picked = {} +- _.each(obj,function(key,value) +- if not _.include(blacklist,key) then +- _picked[key] = value +- end +- end) ++ for key, value in pairs(obj) do ++ if not M.include(blacklist,key) then ++ _picked[key] = value ++ end ++ end + return _picked + end + +@@ -1908,12 +2762,13 @@ end + --
    Aliased as `defaults`. + -- @name template + -- @param obj an object +--- @param[opt] template a template object. Defaults to an empty table `{}`. ++-- @param[opt] template a template object. If `nil`, leaves `obj` untouched. + -- @return the passed-in object filled +-function _.template(obj, template) +- _.each(template or {},function(i,v) +- if not obj[i] then obj[i] = v end +- end) ++function M.template(obj, template) ++ if not template then return obj end ++ for i, v in pairs(template) do ++ if not obj[i] then obj[i] = v end ++ end + return obj + end + +@@ -1921,13 +2776,14 @@ end + -- (by reference), nil, booleans. Compares tables by reference or by values. If `useMt` + -- is passed, the equality operator `==` will be used if one of the given objects has a + -- metatable implementing `__eq`. +---
    Aliased as `_.compare` ++--
    Aliased as `M.compare`, `M.matches` + -- @name isEqual + -- @param objA an object + -- @param objB another object + -- @param[opt] useMt whether or not `__eq` should be used, defaults to false. + -- @return `true` or `false` +-function _.isEqual(objA, objB, useMt) ++-- @see allEqual ++function M.isEqual(objA, objB, useMt) + local typeObjA = type(objA) + local typeObjB = type(objB) + +@@ -1943,16 +2799,16 @@ function _.isEqual(objA, objB, useMt) + end + end + +- if _.size(objA)~=_.size(objB) then return false end +- +- for i,v1 in pairs(objA) do +- local v2 = objB[i] +- if _.isNil(v2) or not _.isEqual(v1,v2,useMt) then return false end ++ if M.size(objA)~=M.size(objB) then return false end ++ ++ local vB ++ for i,vA in pairs(objA) do ++ vB = objB[i] ++ if vB == nil or not M.isEqual(vA, vB, useMt) then return false end + end + +- for i,v1 in pairs(objB) do +- local v2 = objA[i] +- if _.isNil(v2) then return false end ++ for i in pairs(objB) do ++ if objA[i] == nil then return false end + end + + return true +@@ -1963,17 +2819,16 @@ end + -- @name result + -- @param obj an object + -- @param method a string key to index in object `obj`. +--- @param[opt] ... Optional args to be passed to `method` +--- @return the returned value of `method (obj, ...)` call +-function _.result(obj, method, ...) ++-- @return the returned value of `method (obj)` call ++function M.result(obj, method) + if obj[method] then +- if _.isCallable(obj[method]) then +- return obj[method](obj,...) ++ if M.isCallable(obj[method]) then ++ return obj[method](obj) + else return obj[method] + end + end +- if _.isCallable(method) then +- return method(obj,...) ++ if M.isCallable(method) then ++ return method(obj) + end + end + +@@ -1981,7 +2836,7 @@ end + -- @name isTable + -- @param t a value to be tested + -- @return `true` or `false` +-function _.isTable(t) ++function M.isTable(t) + return type(t) == 'table' + end + +@@ -1990,10 +2845,11 @@ end + -- @name isCallable + -- @param obj an object + -- @return `true` or `false` +-function _.isCallable(obj) +- return (_.isFunction(obj) or +- (_.isTable(obj) and getmetatable(obj) +- and getmetatable(obj).__call~=nil) or false) ++function M.isCallable(obj) ++ return ++ ((type(obj) == 'function') or ++ ((type(obj) == 'table') and getmetatable(obj) and getmetatable(obj).__call~=nil) or ++ false) + end + + --- Checks if the given argument is an array. Assumes `obj` is an array +@@ -2001,14 +2857,14 @@ end + -- @name isArray + -- @param obj an object + -- @return `true` or `false` +-function _.isArray(obj) +- if not _.isTable(obj) then return false end ++function M.isArray(obj) ++ if not (type(obj) == 'table') then return false end + -- Thanks @Wojak and @Enrique García Cota for suggesting this + -- See : http://love2d.org/forums/viewtopic.php?f=3&t=77255&start=40#p163624 + local i = 0 +- for __ in pairs(obj) do ++ for k in pairs(obj) do + i = i + 1 +- if _.isNil(obj[i]) then return false end ++ if obj[i] == nil then return false end + end + return true + end +@@ -2017,8 +2873,25 @@ end + -- @name isIterable + -- @param obj an object + -- @return `true` if the object can be iterated with `pairs` (or `ipairs`), `false` otherwise +-function _.isIterable(obj) +- return _.toBoolean((pcall(pairs, obj))) ++function M.isIterable(obj) ++ return M.toBoolean((pcall(pairs, obj))) ++end ++ ++--- Extends Lua's `type` function. It returns the type of the given object and also recognises ++-- file userdata ++-- @name type ++-- @param obj an object ++-- @return the given object type ++function M.type(obj) ++ local tp = type(obj) ++ if tp == 'userdata' then ++ local mt = getmetatable(obj) ++ local stdout = io and io.stdout or nil ++ if stdout ~= nil and mt == getmetatable(stdout) then ++ return 'file' ++ end ++ end ++ return tp + end + + --- Checks if the given pbject is empty. If `obj` is a string, will return `true` +@@ -2027,10 +2900,10 @@ end + -- @name isEmpty + -- @param[opt] obj an object + -- @return `true` or `false` +-function _.isEmpty(obj) +- if _.isNil(obj) then return true end +- if _.isString(obj) then return #obj==0 end +- if _.isTable(obj) then return next(obj)==nil end ++function M.isEmpty(obj) ++ if (obj == nil) then return true end ++ if type(obj) == 'string' then return #obj==0 end ++ if type(obj) == 'table' then return next(obj)==nil end + return true + end + +@@ -2038,7 +2911,7 @@ end + -- @name isString + -- @param obj an object + -- @return `true` or `false` +-function _.isString(obj) ++function M.isString(obj) + return type(obj) == 'string' + end + +@@ -2046,7 +2919,7 @@ end + -- @name isFunction + -- @param obj an object + -- @return `true` or `false` +-function _.isFunction(obj) ++function M.isFunction(obj) + return type(obj) == 'function' + end + +@@ -2054,7 +2927,7 @@ end + -- @name isNil + -- @param obj an object + -- @return `true` or `false` +-function _.isNil(obj) ++function M.isNil(obj) + return obj==nil + end + +@@ -2063,7 +2936,7 @@ end + -- @param obj an object + -- @return `true` or `false` + -- @see isNaN +-function _.isNumber(obj) ++function M.isNumber(obj) + return type(obj) == 'number' + end + +@@ -2072,16 +2945,16 @@ end + -- @param obj an object + -- @return `true` or `false` + -- @see isNumber +-function _.isNaN(obj) +- return _.isNumber(obj) and obj~=obj ++function M.isNaN(obj) ++ return type(obj) == 'number' and obj~=obj + end + + --- Checks if the given argument is a finite number. + -- @name isFinite + -- @param obj an object + -- @return `true` or `false` +-function _.isFinite(obj) +- if not _.isNumber(obj) then return false end ++function M.isFinite(obj) ++ if type(obj) ~= 'number' then return false end + return obj > -huge and obj < huge + end + +@@ -2089,7 +2962,7 @@ end + -- @name isBoolean + -- @param obj an object + -- @return `true` or `false` +-function _.isBoolean(obj) ++function M.isBoolean(obj) + return type(obj) == 'boolean' + end + +@@ -2097,8 +2970,8 @@ end + -- @name isInteger + -- @param obj an object + -- @return `true` or `false` +-function _.isInteger(obj) +- return _.isNumber(obj) and floor(obj)==obj ++function M.isInteger(obj) ++ return type(obj) == 'number' and floor(obj)==obj + end + + -- Aliases +@@ -2106,60 +2979,68 @@ end + do + + -- Table functions aliases +- _.forEach = _.each +- _.forEachi = _.eachi +- _.loop = _.cycle +- _.collect = _.map +- _.inject = _.reduce +- _.foldl = _.reduce +- _.injectr = _.reduceRight +- _.foldr = _.reduceRight +- _.mapr = _.mapReduce +- _.maprr = _.mapReduceRight +- _.any = _.include +- _.some = _.include +- _.contains = _.include +- _.filter = _.select +- _.discard = _.reject +- _.every = _.all ++ M.forEach = M.each ++ M.forEachi = M.eachi ++ M.update = M.adjust ++ M.alleq = M.allEqual ++ M.loop = M.cycle ++ M.collect = M.map ++ M.inject = M.reduce ++ M.foldl = M.reduce ++ M.injectr = M.reduceRight ++ M.foldr = M.reduceRight ++ M.mapr = M.mapReduce ++ M.maprr = M.mapReduceRight ++ M.any = M.include ++ M.some = M.include ++ M.contains = M.include ++ M.filter = M.select ++ M.discard = M.reject ++ M.every = M.all + + -- Array functions aliases +- _.takeWhile = _.selectWhile +- _.rejectWhile = _.dropWhile +- _.shift = _.pop +- _.remove = _.pull +- _.rmRange = _.removeRange +- _.chop = _.removeRange +- _.sub = _.slice +- _.head = _.first +- _.take = _.first +- _.tail = _.rest +- _.skip = _.last +- _.without = _.difference +- _.diff = _.difference +- _.symdiff = _.symmetricDifference +- _.xor = _.symmetricDifference +- _.uniq = _.unique +- _.isuniq = _.isunique +- _.transpose = _.zip +- _.part = _.partition +- _.perm = _.permutation +- _.mirror = _.invert +- _.join = _.concat ++ M.takeWhile = M.selectWhile ++ M.rejectWhile = M.dropWhile ++ M.pop = M.shift ++ M.remove = M.pull ++ M.rmRange = M.removeRange ++ M.chop = M.removeRange ++ M.sub = M.slice ++ M.head = M.first ++ M.take = M.first ++ M.tail = M.rest ++ M.without = M.difference ++ M.diff = M.difference ++ M.symdiff = M.symmetricDifference ++ M.xor = M.symmetricDifference ++ M.uniq = M.unique ++ M.isuniq = M.isunique ++ M.transpose = M.zip ++ M.part = M.partition ++ M.perm = M.permutation ++ M.transposeWith = M.zipWith ++ M.intersperse = M.interpose ++ M.sliding = M.aperture ++ M.mirror = M.invert ++ M.join = M.concat ++ M.average = M.mean + + -- Utility functions aliases +- _.cache = _.memoize +- _.juxt = _.juxtapose +- _.uid = _.uniqueId +- _.iter = _.iterator +- +- -- Object functions aliases +- _.methods = _.functions +- _.choose = _.pick +- _.drop = _.omit +- _.defaults = _.template +- _.compare = _.isEqual ++ M.always = M.constant ++ M.cache = M.memoize ++ M.juxt = M.juxtapose ++ M.uid = M.uniqueId ++ M.iter = M.iterator ++ M.nAry = M.ary + ++ -- Object functions aliases ++ M.methods = M.functions ++ M.choose = M.pick ++ M.drop = M.omit ++ M.defaults = M.template ++ M.compare = M.isEqual ++ M.matches = M.isEqual ++ + end + + -- Setting chaining and building interface +@@ -2170,27 +3051,26 @@ do + local f = {} + + -- Will be returned upon requiring, indexes into the wrapper +- local __ = {} +- __.__index = f ++ local Moses = {} ++ Moses.__index = f + + -- Wraps a value into an instance, and returns the wrapped object + local function new(value) +- local i = {_value = value, _wrapped = true} +- return setmetatable(i, __) ++ return setmetatable({_value = value, _wrapped = true}, Moses) + end + +- setmetatable(__,{ ++ setmetatable(Moses,{ + __call = function(self,v) return new(v) end, -- Calls returns to instantiation + __index = function(t,key,...) return f[key] end -- Redirects to the wrapper + }) + + --- Returns a wrapped object. Calling library functions as methods on this object +- -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `_(value)`. ++ -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `M(value)`. + -- @class function + -- @name chain + -- @param value a value to be wrapped + -- @return a wrapped object +- function __.chain(value) ++ function Moses.chain(value) + return new(value) + end + +@@ -2198,53 +3078,59 @@ do + -- @class function + -- @name obj:value + -- @return the value previously wrapped +- function __:value() ++ function Moses:value() + return self._value + end + + -- Register chaining methods into the wrapper +- f.chain, f.value = __.chain, __.value ++ f.chain, f.value = Moses.chain, Moses.value + + -- Register all functions into the wrapper +- for fname,fct in pairs(_) do +- f[fname] = function(v, ...) +- local wrapped = _.isTable(v) and v._wrapped or false +- if wrapped then +- local _arg = v._value +- local _rslt = fct(_arg,...) +- return new(_rslt) +- else +- return fct(v,...) ++ for fname,fct in pairs(M) do ++ if fname ~= 'operator' then -- Prevents from wrapping op functions ++ f[fname] = function(v, ...) ++ local wrapped = type(v) == 'table' and rawget(v,'_wrapped') or false ++ if wrapped then ++ local _arg = v._value ++ local _rslt = fct(_arg,...) ++ return new(_rslt) ++ else ++ return fct(v,...) ++ end + end + end + end ++ ++ -- Exports all op functions ++ f.operator = M.operator ++ f.op = M.operator + + --- Imports all library functions into a context. + -- @name import +- -- @param[opt] context a context. Defaults to `_G` (global environment) when not given. +- -- @param[optchain] noConflict if supplied, will not import functions having a key existing in the destination context. ++ -- @param[opt] context a context. Defaults to `_ENV or `_G`` (current environment). ++ -- @param[optchain] noConflict if supplied, will not import conflicting functions in the destination context. + -- @return the passed-in context + f.import = function(context, noConflict) + context = context or _ENV or _G +- local funcs = _.functions() +- _.each(funcs, function(k, fname) +- if rawget(context, fname) then ++ local funcs = M.functions() ++ for k, fname in ipairs(funcs) do ++ if rawget(context, fname)~= nil then + if not noConflict then +- context[fname] = _[fname] ++ rawset(context, fname, M[fname]) + end + else +- context[fname] = _[fname] ++ rawset(context, fname, M[fname]) + end +- end) ++ end + return context + end + + -- Descriptive tags +- __._VERSION = 'Moses v'.._MODULEVERSION +- __._URL = 'http://github.com/Yonaba/Moses' +- __._LICENSE = 'MIT ' +- __._DESCRIPTION = 'utility-belt library for functional programming in Lua' +- +- return __ ++ Moses._VERSION = 'Moses v'.._MODULEVERSION ++ Moses._URL = 'http://github.com/Yonaba/Moses' ++ Moses._LICENSE = 'MIT ' ++ Moses._DESCRIPTION = 'utility-belt library for functional programming in Lua' + ++ return Moses ++ + end +diff --git a/extra/moses/moses_min.lua b/extra/moses/moses_min.lua +index bb67dcc..8963d6e 100644 +--- a/extra/moses/moses_min.lua ++++ b/extra/moses/moses_min.lua +@@ -1,364 +1,574 @@ +-local _ba='1.6.1'local aba,bba,cba,dba=next,type,select,pcall;local _ca,aca=setmetatable,getmetatable +-local bca,cca=table.insert,table.sort;local dca,_da=table.remove,table.concat +-local ada,bda,cda=math.randomseed,math.random,math.huge;local dda,__b,a_b=math.floor,math.max,math.min;local b_b=rawget +-local c_b=table.unpack or unpack;local d_b,_ab=pairs,ipairs;local aab=os.clock;local bab={} +-local function cab(dcb,_db)return dcb>_db end;local function dab(dcb,_db)return dcb<_db end +-local function _bb(dcb,_db,adb)return(dcb<_db)and _db or +-(dcb>adb and adb or dcb)end;local function abb(dcb,_db)return _db and true end +-local function bbb(dcb)return not dcb end +-local function cbb(dcb)local _db=0;for adb,bdb in d_b(dcb)do _db=_db+1 end;return _db end +-local function dbb(dcb,_db,adb,...)local bdb;local cdb=adb or bab.identity;for ddb,__c in d_b(dcb)do +-if not bdb then bdb=cdb(__c,...)else +-local a_c=cdb(__c,...)bdb=_db(bdb,a_c)and bdb or a_c end end;return bdb end +-local function _cb(dcb,_db,adb,bdb)for i=0,#dcb,_db do local cdb=bab.slice(dcb,i+1,i+_db) +-if#cdb>0 then while +-(#cdb<_db and bdb)do cdb[#cdb+1]=bdb end;adb(cdb)end end end +-local function acb(dcb,_db,adb,bdb) +-for i=0,#dcb,_db-1 do local cdb=bab.slice(dcb,i+1,i+_db)if +-#cdb>0 and i+1 <#dcb then while(#cdb<_db and bdb)do cdb[#cdb+1]=bdb end +-adb(cdb)end end end +-local function bcb(dcb,_db,adb)if _db==0 then adb(dcb)end +-for i=1,_db do dcb[_db],dcb[i]=dcb[i],dcb[_db]bcb(dcb,_db- +-1,adb)dcb[_db],dcb[i]=dcb[i],dcb[_db]end end;local ccb=-1 +-function bab.clear(dcb)for _db in d_b(dcb)do dcb[_db]=nil end;return dcb end +-function bab.each(dcb,_db,...)for adb,bdb in d_b(dcb)do _db(adb,bdb,...)end end +-function bab.eachi(dcb,_db,...) +-local adb=bab.sort(bab.select(bab.keys(dcb),function(bdb,cdb)return bab.isInteger(cdb)end))for bdb,cdb in _ab(adb)do _db(cdb,dcb[cdb],...)end end +-function bab.at(dcb,...)local _db={}for adb,bdb in _ab({...})do +-if bab.has(dcb,bdb)then _db[#_db+1]=dcb[bdb]end end;return _db end +-function bab.count(dcb,_db)if bab.isNil(_db)then return bab.size(dcb)end;local adb=0 +-bab.each(dcb,function(bdb,cdb)if +-bab.isEqual(cdb,_db)then adb=adb+1 end end)return adb end +-function bab.countf(dcb,_db,...)return bab.count(bab.map(dcb,_db,...),true)end +-function bab.cycle(dcb,_db)_db=_db or 1;if _db<=0 then return bab.noop end;local adb,bdb;local cdb=0 ++local SWFtRywD='2.1.0'local e,v,l6Sm5=next,type,pcall;local oUA,QFKEzBf=setmetatable,getmetatable ++local odpE,p=table.insert,table.sort;local lIpFkbLI,JdUtcU=table.remove,table.concat ++local GQLN,toXyq,S9TO=math.randomseed,math.random,math.huge;local pS78Y,BCf7,RlMSrmdD,VCD=math.floor,math.max,math.min,math.ceil ++local OV7=coroutine.wrap;local X83a=coroutine.yield;local PizLA9mj=rawget ++local hUL=table.unpack or unpack;local l,kyWtqIf0=pairs,ipairs;local zupvsz=error;local Mw=os and os.clock or nil ++local S1wg_DG={}local function sf0(cmWo_v,RoXZEsn)return cmWo_v>RoXZEsn end;local function qxZa6ozV(BKLwtAVx,BMZNmf0) ++return BKLwtAVx0 then while(#ZmzyNm0 and cpdLk+1 <#akG0mUnS then while ++(#FbQX0 and gUfudNUg+eS0X<=#ejMVLYZd then while ++(#d3 =0 and 1 or-1 end ++local qJExeUn2=-1;S1wg_DG.operator={} ++S1wg_DG.operator.add=function(fBI,wMSY)return fBI+wMSY end ++S1wg_DG.operator.sub=function(_nD2rl,aVh8xSly)return _nD2rl-aVh8xSly end ++S1wg_DG.operator.mul=function(i,P_NNVDyt)return i*P_NNVDyt end ++S1wg_DG.operator.div=function(cVEyN,uj2AiF)return cVEyN/uj2AiF end ++S1wg_DG.operator.mod=function(W,lbHN2)return W%lbHN2 end ++S1wg_DG.operator.exp=function(PwgW3lfq,z)return PwgW3lfq^z end;S1wg_DG.operator.pow=S1wg_DG.operator.exp;S1wg_DG.operator.unm=function(K)return ++-K end ++S1wg_DG.operator.neg=S1wg_DG.operator.unm ++S1wg_DG.operator.floordiv=function(xx,aYb)return pS78Y(xx/aYb)end ++S1wg_DG.operator.intdiv=function(JM2,bmAjLT)return JM2 >=0 and pS78Y(JM2/bmAjLT)or ++VCD(JM2/bmAjLT)end ++S1wg_DG.operator.eq=function(eExYnwnh,XMBmJyiP)return eExYnwnh==XMBmJyiP end ++S1wg_DG.operator.neq=function(nowqEU6m,iKD8V)return nowqEU6m~=iKD8V end ++S1wg_DG.operator.lt=function(YtRS,A)return YtRSQ57BJ end ++S1wg_DG.operator.le=function(vM,JeGCDX)return vM<=JeGCDX end ++S1wg_DG.operator.ge=function(A,UFZlp)return A>=UFZlp end ++S1wg_DG.operator.land=function(VsrKM,uhIq)return VsrKM and uhIq end ++S1wg_DG.operator.lor=function(EEOUzhy,hbrt)return EEOUzhy or hbrt end;S1wg_DG.operator.lnot=function(D)return not D end;S1wg_DG.operator.concat=function(Q,mRqle)return ++Q..mRqle end;S1wg_DG.operator.length=function(sBEZ8)return ++#sBEZ8 end ++S1wg_DG.operator.len=S1wg_DG.operator.length;function S1wg_DG.clear(WhHB0ygh)for rYSD0 in l(WhHB0ygh)do WhHB0ygh[rYSD0]=nil end ++return WhHB0ygh end;function S1wg_DG.each(BIL5,GQLlkH)for aN4J2zRQ,eWca in l(BIL5)do ++GQLlkH(eWca,aN4J2zRQ)end end ++function S1wg_DG.eachi(AGUR2QK,FK) ++local _=S1wg_DG.sort(S1wg_DG.select(S1wg_DG.keys(AGUR2QK),S1wg_DG.isInteger)) ++for YQZ729qQ,rZh2wG in kyWtqIf0(_)do FK(AGUR2QK[rZh2wG],rZh2wG)end end;function S1wg_DG.at(sef4eW6Q,...)local Z={} ++for UacO6D,FdnzjW in kyWtqIf0({...})do Z[#Z+1]=sef4eW6Q[FdnzjW]end;return Z end ++function S1wg_DG.adjust(o,lMAL,CpQ) ++if( ++o[lMAL]==nil)then zupvsz("key not existing in table")end;local L=S1wg_DG.clone(o)L[lMAL]= ++v(CpQ)=='function'and CpQ(L[lMAL])or CpQ;return L end ++function S1wg_DG.count(HnQS_Z,rib) ++if rib==nil then return S1wg_DG.size(HnQS_Z)end;local hgW2H5=0;for w,YT6wZ in l(HnQS_Z)do ++if S1wg_DG.isEqual(YT6wZ,rib)then hgW2H5=hgW2H5+1 end end;return hgW2H5 end ++function S1wg_DG.countf(VYv,gU)local hgW2H5=0;for JzG8W4Ya,dZ54oc in l(VYv)do ++if gU(dZ54oc,JzG8W4Ya)then hgW2H5=hgW2H5+1 end end;return hgW2H5 end ++function S1wg_DG.allEqual(v_LoR,gRY)local z,ad=e(v_LoR) ++for z,Ui0Qa in l(v_LoR)do if gRY then ++if not gRY(ad,Ui0Qa)then return false end else ++if not S1wg_DG.isEqual(ad,Ui0Qa)then return false end end end;return true end ++function S1wg_DG.cycle(g,Itx)Itx=Itx or 1;if Itx<=0 then return S1wg_DG.noop end ++local JpoaGH,cyAcCT;local RCA=0 + while true do + return +-function()adb=adb and +-aba(dcb,adb)or aba(dcb) +-bdb=not bdb and adb or bdb;if _db then cdb=(adb==bdb)and cdb+1 or cdb +-if cdb>_db then return end end;return adb,dcb[adb]end end end +-function bab.map(dcb,_db,...)local adb={} +-for bdb,cdb in d_b(dcb)do local ddb,__c,a_c=bdb,_db(bdb,cdb,...)adb[a_c and __c or ddb]= +-a_c or __c end;return adb end;function bab.reduce(dcb,_db,adb) +-for bdb,cdb in d_b(dcb)do if adb==nil then adb=cdb else adb=_db(adb,cdb)end end;return adb end;function bab.reduceby(dcb,_db,adb,bdb,...)return +-bab.reduce(bab.select(dcb,bdb,...),_db,adb)end;function bab.reduceRight(dcb,_db,adb)return +-bab.reduce(bab.reverse(dcb),_db,adb)end +-function bab.mapReduce(dcb,_db,adb) +-local bdb={}for cdb,ddb in d_b(dcb)do bdb[cdb]=not adb and ddb or _db(adb,ddb) +-adb=bdb[cdb]end;return bdb end;function bab.mapReduceRight(dcb,_db,adb) +-return bab.mapReduce(bab.reverse(dcb),_db,adb)end +-function bab.include(dcb,_db)local adb= +-bab.isFunction(_db)and _db or bab.isEqual;for bdb,cdb in d_b(dcb)do if adb(cdb,_db)then +-return true end end;return false end +-function bab.detect(dcb,_db) +-local adb=bab.isFunction(_db)and _db or bab.isEqual;for bdb,cdb in d_b(dcb)do if adb(cdb,_db)then return bdb end end end +-function bab.where(dcb,_db) +-local adb=bab.select(dcb,function(bdb,cdb) +-for ddb in d_b(_db)do if cdb[ddb]~=_db[ddb]then return false end end;return true end)return#adb>0 and adb or nil end +-function bab.findWhere(dcb,_db) +-local adb=bab.detect(dcb,function(bdb)for cdb in d_b(_db)do +-if _db[cdb]~=bdb[cdb]then return false end end;return true end)return adb and dcb[adb]end +-function bab.select(dcb,_db,...)local adb={}for bdb,cdb in d_b(dcb)do +-if _db(bdb,cdb,...)then adb[#adb+1]=cdb end end;return adb end +-function bab.reject(dcb,_db,...)local adb=bab.map(dcb,_db,...)local bdb={}for cdb,ddb in d_b(adb)do if not ddb then +-bdb[#bdb+1]=dcb[cdb]end end;return bdb end +-function bab.all(dcb,_db,...)return( (#bab.select(bab.map(dcb,_db,...),abb))== +-cbb(dcb))end +-function bab.invoke(dcb,_db,...)local adb={...} ++function()JpoaGH=JpoaGH and e(g,JpoaGH)or e(g)cyAcCT=not ++cyAcCT and JpoaGH or cyAcCT;if Itx then RCA= ++(JpoaGH==cyAcCT)and RCA+1 or RCA ++if RCA>Itx then return end end;return g[JpoaGH],JpoaGH end end end ++function S1wg_DG.map(L46S,GKTYT)local hXSTz8FJ={} ++for C24r7o4G,b_4Q38cU in l(L46S)do local N,JbPw,j=C24r7o4G,GKTYT(b_4Q38cU,C24r7o4G)hXSTz8FJ[ ++j and JbPw or N]=j or JbPw end;return hXSTz8FJ end ++function S1wg_DG.mapi(S,cg4FV7bl)local flf9sWX={}for uNoS,ZWoH9V08 in kyWtqIf0(S)do ++local RWo,GWBQL,PCldTUn9=uNoS,cg4FV7bl(ZWoH9V08,uNoS) ++flf9sWX[PCldTUn9 and GWBQL or RWo]=PCldTUn9 or GWBQL end;return ++flf9sWX end ++function S1wg_DG.reduce(sO_,ALbdmINL,b)for DUgF0E,vGxJ6f in l(sO_)do ++if b==nil then b=vGxJ6f else b=ALbdmINL(b,vGxJ6f)end end;return b end ++function S1wg_DG.best(a4ga2I,syGyB_)local VO,J1r=e(a4ga2I) ++for iBcU3_7D,N in l(a4ga2I)do if J1r==nil then J1r=N else ++J1r=syGyB_(J1r,N)and J1r or N end end;return J1r end ++function S1wg_DG.reduceBy(M4V,_feve,OPz_7bk,H64aD)return ++S1wg_DG.reduce(S1wg_DG.select(M4V,OPz_7bk),_feve,H64aD)end ++function S1wg_DG.reduceRight(ny7,QDj6GAX,k6pXzd)return ++S1wg_DG.reduce(S1wg_DG.reverse(ny7),QDj6GAX,k6pXzd)end ++function S1wg_DG.mapReduce(hsLwu,R,JKZ)local yHbsh={}for d4z,i in l(hsLwu)do ++yHbsh[d4z]=not JKZ and i or R(JKZ,i)JKZ=yHbsh[d4z]end;return yHbsh end ++function S1wg_DG.mapReduceRight(HyEk4lbh,PhU,rWwbNge)return ++S1wg_DG.mapReduce(S1wg_DG.reverse(HyEk4lbh),PhU,rWwbNge)end ++function S1wg_DG.include(SKxD,o3uQKvJ)local vAZm=(v(o3uQKvJ)=='function')and o3uQKvJ or ++S1wg_DG.isEqual;for q,fFuE in l(SKxD)do if ++vAZm(fFuE,o3uQKvJ)then return true end end;return false end ++function S1wg_DG.detect(KypMW,JJT4nKO)local TFLF=(v(JJT4nKO)=='function')and JJT4nKO or ++S1wg_DG.isEqual;for hEoAa,PGN in l(KypMW)do if ++TFLF(PGN,JJT4nKO)then return hEoAa end end end ++function S1wg_DG.where(K2_kF5,YpimJ) ++local Gg7Ttui=S1wg_DG.select(K2_kF5,function(_)for EGeAf in l(YpimJ)do ++if _[EGeAf]~=YpimJ[EGeAf]then return false end end;return true end)return#Gg7Ttui>0 and Gg7Ttui or nil end ++function S1wg_DG.findWhere(ymP,z5pHKyoa) ++local h=S1wg_DG.detect(ymP,function(xwT)for y33ux in l(z5pHKyoa)do if z5pHKyoa[y33ux]~=xwT[y33ux]then ++return false end end;return true end)return h and ymP[h]end ++function S1wg_DG.select(Ut,GOijBp)local oUi={}for b2a3,xer in l(Ut)do ++if GOijBp(xer,b2a3)then oUi[#oUi+1]=xer end end;return oUi end ++function S1wg_DG.reject(SQHAAR,qybRcP1)local z={}for N0NaR,FBfW in l(SQHAAR)do ++if not qybRcP1(FBfW,N0NaR)then z[#z+1]=FBfW end end;return z end;function S1wg_DG.all(lnM4,_oDmX_) ++for t,K in l(lnM4)do if not _oDmX_(K,t)then return false end end;return true end ++function S1wg_DG.invoke(ppm021I,ASUXhD) + return +-bab.map(dcb,function(bdb,cdb) +-if bab.isTable(cdb)then +-if bab.has(cdb,_db)then +-if +-bab.isCallable(cdb[_db])then return cdb[_db](cdb,c_b(adb))else return cdb[_db]end else +-if bab.isCallable(_db)then return _db(cdb,c_b(adb))end end elseif bab.isCallable(_db)then return _db(cdb,c_b(adb))end end)end +-function bab.pluck(dcb,_db)return +-bab.reject(bab.map(dcb,function(adb,bdb)return bdb[_db]end),bbb)end;function bab.max(dcb,_db,...)return dbb(dcb,cab,_db,...)end;function bab.min(dcb,_db,...)return +-dbb(dcb,dab,_db,...)end +-function bab.shuffle(dcb,_db)if _db then ada(_db)end +-local adb={} +-bab.each(dcb,function(bdb,cdb)local ddb=dda(bda()*bdb)+1;adb[bdb]=adb[ddb] +-adb[ddb]=cdb end)return adb end +-function bab.same(dcb,_db) ++S1wg_DG.map(ppm021I,function(KCm,u) ++if( ++v(KCm)=='table')then ++if KCm[ASUXhD]then ++if S1wg_DG.isCallable(KCm[ASUXhD])then return ++KCm[ASUXhD](KCm,u)else return KCm[ASUXhD]end else ++if S1wg_DG.isCallable(ASUXhD)then return ASUXhD(KCm,u)end end elseif S1wg_DG.isCallable(ASUXhD)then return ASUXhD(KCm,u)end end)end ++function S1wg_DG.pluck(fDk,gxYY)local sVMxk={}for SyD,v4 in l(fDk)do ++if v4[gxYY]then sVMxk[#sVMxk+1]=v4[gxYY]end end;return sVMxk end;function S1wg_DG.max(j7siW,Hl)return z5i2i(j7siW,sf0,Hl)end;function S1wg_DG.min(AP060rq,DIEKD10)return ++z5i2i(AP060rq,qxZa6ozV,DIEKD10)end ++function S1wg_DG.same(lLJ,EicsS) + return +-bab.all(dcb,function(adb,bdb)return bab.include(_db,bdb)end)and +-bab.all(_db,function(adb,bdb)return bab.include(dcb,bdb)end)end;function bab.sort(dcb,_db)cca(dcb,_db)return dcb end +-function bab.sortBy(dcb,_db,adb) +-local bdb=_db or bab.identity +-if bab.isString(_db)then bdb=function(ddb)return ddb[_db]end end;adb=adb or dab;local cdb={} +-bab.each(dcb,function(ddb,__c) +-cdb[#cdb+1]={value=__c,transform=bdb(__c)}end) +-cca(cdb,function(ddb,__c)return adb(ddb.transform,__c.transform)end)return bab.pluck(cdb,'value')end +-function bab.groupBy(dcb,_db,...)local adb={...}local bdb={} +-bab.each(dcb,function(cdb,ddb)local __c=_db(cdb,ddb,c_b(adb)) ++S1wg_DG.all(lLJ,function(JubU)return ++S1wg_DG.include(EicsS,JubU)end)and ++S1wg_DG.all(EicsS,function(L)return S1wg_DG.include(lLJ,L)end)end;function S1wg_DG.sort(JKci,SsBe)p(JKci,SsBe)return JKci end ++function S1wg_DG.sortedk(o,ZOmcmO) ++local _G19JrRB=S1wg_DG.keys(o)p(_G19JrRB,ZOmcmO)local m0r3_J=0 ++return function()m0r3_J=m0r3_J+1;return _G19JrRB[m0r3_J], ++o[_G19JrRB[m0r3_J]]end end ++function S1wg_DG.sortedv(MLrs,hP5)local oqjhEZb0=S1wg_DG.keys(MLrs)hP5=hP5 or qxZa6ozV ++p(oqjhEZb0,function(G,MOrzq4)return ++hP5(MLrs[G],MLrs[MOrzq4])end)local Pha=0;return ++function()Pha=Pha+1;return oqjhEZb0[Pha],MLrs[oqjhEZb0[Pha]]end end ++function S1wg_DG.sortBy(bEMp,dd,MOQN)local O=dd or S1wg_DG.identity;if(v(dd)=='string')then O=function(bEMp)return ++bEMp[dd]end end;MOQN= ++MOQN or qxZa6ozV ++p(bEMp,function(FEpet,P)return MOQN(O(FEpet),O(P))end)return bEMp end ++function S1wg_DG.groupBy(G,EcLLM)local wo={}for ur,XTX in l(G)do local wc8hjKp1=EcLLM(XTX,ur) ++if wo[wc8hjKp1]then wo[wc8hjKp1][# ++wo[wc8hjKp1]+1]=XTX else wo[wc8hjKp1]={XTX}end end;return wo end ++function S1wg_DG.countBy(f,Hjag)local Yg={}for uc,bw in l(f)do local ad=Hjag(bw,uc) ++Yg[ad]=(Yg[ad]or 0)+1 end;return Yg end ++function S1wg_DG.size(...)local EG344W={...}local MVlUhPEM=EG344W[1]return ++ ++(v(MVlUhPEM)=='table')and hgW2H5(EG344W[1])or hgW2H5(EG344W)end;function S1wg_DG.containsKeys(LT,pfiWYrg) ++for smnX9H6 in l(pfiWYrg)do if not LT[smnX9H6]then return false end end;return true end ++function S1wg_DG.sameKeys(FzRhHR,mMBxOoQa) ++for xYSLIT in ++l(FzRhHR)do if not mMBxOoQa[xYSLIT]then return false end end ++for Eae7ILmk in l(mMBxOoQa)do if not FzRhHR[Eae7ILmk]then return false end end;return true end ++function S1wg_DG.sample(Jy23ZRAA,V8IWw,uyYdf)V8IWw=V8IWw or 1;if V8IWw==0 then return{}end;if V8IWw==1 then if uyYdf then ++GQLN(uyYdf)end ++return{Jy23ZRAA[toXyq(1,#Jy23ZRAA)]}end;return ++S1wg_DG.slice(S1wg_DG.shuffle(Jy23ZRAA,uyYdf),1,V8IWw)end ++function S1wg_DG.sampleProb(K,ZX,tbdC)if tbdC then GQLN(tbdC)end;local VaY3={} ++for HK7Mbgze,VXPfx in kyWtqIf0(K)do if toXyq()0 do +-dca(bdb,a_c)__c=__c-1 end;return bdb end +-function bab.chunk(dcb,_db,...)if not bab.isArray(dcb)then return dcb end;local adb,bdb,cdb={},0 +-local ddb=bab.map(dcb,_db,...) +-bab.each(ddb,function(__c,a_c)cdb=(cdb==nil)and a_c or cdb;bdb=( +-(a_c~=cdb)and(bdb+1)or bdb) +-if not adb[bdb]then adb[bdb]={dcb[__c]}else adb[bdb][ +-#adb[bdb]+1]=dcb[__c]end;cdb=a_c end)return adb end +-function bab.slice(dcb,_db,adb)return +-bab.select(dcb,function(bdb)return +-(bdb>= (_db or aba(dcb))and bdb<= (adb or#dcb))end)end;function bab.first(dcb,_db)local adb=_db or 1 +-return bab.slice(dcb,1,a_b(adb,#dcb))end +-function bab.initial(dcb,_db) +-if _db and _db<0 then return end;return +-bab.slice(dcb,1,_db and#dcb- (a_b(_db,#dcb))or#dcb-1)end;function bab.last(dcb,_db)if _db and _db<=0 then return end +-return bab.slice(dcb,_db and +-#dcb-a_b(_db-1,#dcb-1)or 2,#dcb)end;function bab.rest(dcb,_db)if _db and +-_db>#dcb then return{}end +-return bab.slice(dcb, +-_db and __b(1,a_b(_db,#dcb))or 1,#dcb)end;function bab.nth(dcb,_db) +-return dcb[_db]end;function bab.compact(dcb)return +-bab.reject(dcb,function(_db,adb)return not adb end)end +-function bab.flatten(dcb,_db)local adb= +-_db or false;local bdb;local cdb={} +-for ddb,__c in d_b(dcb)do +-if bab.isTable(__c)then bdb=adb and __c or +-bab.flatten(__c) +-bab.each(bdb,function(a_c,b_c)cdb[#cdb+1]=b_c end)else cdb[#cdb+1]=__c end end;return cdb end +-function bab.difference(dcb,_db)if not _db then return bab.clone(dcb)end;return +-bab.select(dcb,function(adb,bdb)return not +-bab.include(_db,bdb)end)end +-function bab.union(...)return bab.uniq(bab.flatten({...}))end +-function bab.intersection(dcb,...)local _db={...}local adb={} +-for bdb,cdb in _ab(dcb)do if +-bab.all(_db,function(ddb,__c)return bab.include(__c,cdb)end)then bca(adb,cdb)end end;return adb end +-function bab.symmetricDifference(dcb,_db)return +-bab.difference(bab.union(dcb,_db),bab.intersection(dcb,_db))end +-function bab.unique(dcb)local _db={}for i=1,#dcb do if not bab.find(_db,dcb[i])then +-_db[#_db+1]=dcb[i]end end;return _db end +-function bab.isunique(dcb)return bab.isEqual(dcb,bab.unique(dcb))end +-function bab.zip(...)local dcb={...} +-local _db=bab.max(bab.map(dcb,function(bdb,cdb)return#cdb end))local adb={}for i=1,_db do adb[i]=bab.pluck(dcb,i)end;return adb end +-function bab.append(dcb,_db)local adb={}for bdb,cdb in _ab(dcb)do adb[bdb]=cdb end;for bdb,cdb in _ab(_db)do +-adb[#adb+1]=cdb end;return adb end +-function bab.interleave(...)return bab.flatten(bab.zip(...))end;function bab.interpose(dcb,_db)return +-bab.flatten(bab.zip(_db,bab.rep(dcb,#_db-1)))end +-function bab.range(...) +-local dcb={...}local _db,adb,bdb +-if#dcb==0 then return{}elseif#dcb==1 then adb,_db,bdb=dcb[1],0,1 elseif#dcb==2 then +-_db,adb,bdb=dcb[1],dcb[2],1 elseif#dcb==3 then _db,adb,bdb=dcb[1],dcb[2],dcb[3]end;if(bdb and bdb==0)then return{}end;local cdb={} +-local ddb=__b(dda((adb-_db)/bdb),0)for i=1,ddb do cdb[#cdb+1]=_db+bdb*i end;if#cdb>0 then +-bca(cdb,1,_db)end;return cdb end +-function bab.rep(dcb,_db)local adb={}for i=1,_db do adb[#adb+1]=dcb end;return adb end;function bab.partition(dcb,_db,adb)if _db<=0 then return end +-return coroutine.wrap(function() +-_cb(dcb,_db or 1,coroutine.yield,adb)end)end;function bab.sliding(dcb,_db,adb)if +-_db<=1 then return end +-return coroutine.wrap(function() +-acb(dcb,_db or 2,coroutine.yield,adb)end)end +-function bab.permutation(dcb)return +-coroutine.wrap(function()bcb(dcb, +-#dcb,coroutine.yield)end)end;function bab.invert(dcb)local _db={} +-bab.each(dcb,function(adb,bdb)_db[bdb]=adb end)return _db end +-function bab.concat(dcb,_db,adb,bdb) +-local cdb=bab.map(dcb,function(ddb,__c)return +-tostring(__c)end)return _da(cdb,_db,adb or 1,bdb or#dcb)end;function bab.noop()return end;function bab.identity(dcb)return dcb end;function bab.constant(dcb)return +-function()return dcb end end +-function bab.memoize(dcb,_db) +-local adb=_ca({},{__mode='kv'})local bdb=_db or bab.identity;return +-function(...)local cdb=bdb(...)local ddb=adb[cdb]if not ddb then +-adb[cdb]=dcb(...)end;return adb[cdb]end end;function bab.once(dcb)local _db=0;local adb={} +-return function(...)_db=_db+1;if _db<=1 then adb={...}end +-return dcb(c_b(adb))end end +-function bab.before(dcb,_db) +-local adb=0;local bdb={}return +-function(...)adb=adb+1;if adb<=_db then bdb={...}end;return dcb(c_b(bdb))end end +-function bab.after(dcb,_db)local adb,bdb=_db,0;return +-function(...)bdb=bdb+1;if bdb>=adb then return dcb(...)end end end +-function bab.compose(...)local dcb=bab.reverse{...} +-return function(...)local _db,adb=true +-for bdb,cdb in _ab(dcb)do if _db then _db=false +-adb=cdb(...)else adb=cdb(adb)end end;return adb end end +-function bab.pipe(dcb,...)return bab.compose(...)(dcb)end +-function bab.complement(dcb)return function(...)return not dcb(...)end end;function bab.juxtapose(dcb,...)local _db={} +-bab.each({...},function(adb,bdb)_db[#_db+1]=bdb(dcb)end)return c_b(_db)end +-function bab.wrap(dcb,_db)return function(...)return +-_db(dcb,...)end end +-function bab.times(dcb,_db,...)local adb={}for i=1,dcb do adb[i]=_db(i,...)end;return adb end +-function bab.bind(dcb,_db)return function(...)return dcb(_db,...)end end;function bab.bind2(dcb,_db) +-return function(adb,...)return dcb(adb,_db,...)end end;function bab.bindn(dcb,...)local _db={...} +-return function(...)return +-dcb(c_b(bab.append(_db,{...})))end end +-function bab.bindAll(dcb,...)local _db={...} +-for adb,bdb in +-_ab(_db)do local cdb=dcb[bdb]if cdb then dcb[bdb]=bab.bind(cdb,dcb)end end;return dcb end +-function bab.uniqueId(dcb,...)ccb=ccb+1 +-if dcb then if bab.isString(dcb)then return dcb:format(ccb)elseif +-bab.isFunction(dcb)then return dcb(ccb,...)end end;return ccb end +-function bab.iterator(dcb,_db)return function()_db=dcb(_db)return _db end end +-function bab.array(...)local dcb={}for _db in...do dcb[#dcb+1]=_db end;return dcb end;function bab.flip(dcb)return +-function(...)return dcb(c_b(bab.reverse({...})))end end;function bab.over(...) +-local dcb={...} +-return function(...)local _db={}for adb,bdb in _ab(dcb)do _db[#_db+1]=bdb(...)end +-return _db end end;function bab.overEvery(...) +-local dcb=bab.over(...) +-return function(...)return +-bab.reduce(dcb(...),function(_db,adb)return _db and adb end)end end;function bab.overSome(...) +-local dcb=bab.over(...) +-return function(...)return +-bab.reduce(dcb(...),function(_db,adb)return _db or adb end)end end +-function bab.overArgs(dcb,...) +-local _db={...}return +-function(...)local adb={...}for i=1,#_db do local bdb=_db[i] +-if adb[i]then adb[i]=bdb(adb[i])end end;return dcb(c_b(adb))end end +-function bab.partial(dcb,...)local _db={...} +-return +-function(...)local adb={...}local bdb={}for cdb,ddb in _ab(_db)do bdb[cdb]= +-(ddb=='_')and bab.pop(adb)or ddb end;return +-dcb(c_b(bab.append(bdb,adb)))end end +-function bab.partialRight(dcb,...)local _db={...} +-return +-function(...)local adb={...}local bdb={} +-for k=1,#_db do bdb[k]= +-(_db[k]=='_')and bab.pop(adb)or _db[k]end;return dcb(c_b(bab.append(adb,bdb)))end end +-function bab.curry(dcb,_db)_db=_db or 2;local adb={} +-local function bdb(cdb)if _db==1 then return dcb(cdb)end;if cdb~=nil then +-adb[#adb+1]=cdb end;if#adb<_db then return bdb else local ddb={dcb(c_b(adb))}adb={}return +-c_b(ddb)end end;return bdb end +-function bab.time(dcb,...)local _db=aab()local adb={dcb(...)}return aab()-_db,c_b(adb)end;function bab.keys(dcb)local _db={} +-bab.each(dcb,function(adb)_db[#_db+1]=adb end)return _db end;function bab.values(dcb)local _db={} +-bab.each(dcb,function(adb,bdb)_db[ +-#_db+1]=bdb end)return _db end;function bab.kvpairs(dcb)local _db={} +-bab.each(dcb,function(adb,bdb)_db[ +-#_db+1]={adb,bdb}end)return _db end +-function bab.toObj(dcb)local _db={}for adb,bdb in +-_ab(dcb)do _db[bdb[1]]=bdb[2]end;return _db end +-function bab.property(dcb)return function(_db)return _db[dcb]end end +-function bab.propertyOf(dcb)return function(_db)return dcb[_db]end end;function bab.toBoolean(dcb)return not not dcb end +-function bab.extend(dcb,...)local _db={...} +-bab.each(_db,function(adb,bdb)if +-bab.isTable(bdb)then +-bab.each(bdb,function(cdb,ddb)dcb[cdb]=ddb end)end end)return dcb end +-function bab.functions(dcb,_db)dcb=dcb or bab;local adb={} +-bab.each(dcb,function(cdb,ddb)if bab.isFunction(ddb)then +-adb[#adb+1]=cdb end end)if not _db then return bab.sort(adb)end;local bdb=aca(dcb) ++S1wg_DG.isEqual(sDjMr[BLEXN_],FNSk_)then lIpFkbLI(sDjMr,BLEXN_)Ljc=true end end end end;return sDjMr end ++function S1wg_DG.removeRange(LmE,pZTFVP,XL)pZTFVP=pZTFVP or 1;XL=XL or#LmE;if pZTFVP>XL then ++zupvsz("start cannot be greater than finish.")end ++for L5vC0Jx=XL,pZTFVP,-1 do lIpFkbLI(LmE,L5vC0Jx)end;return LmE end ++function S1wg_DG.chunk(vpONJ,A)local LN,dA14qP,JcQc,hDih6_D={},0;A=A or S1wg_DG.identity ++for QKbZ464i,F1TsZ in ++kyWtqIf0(vpONJ)do hDih6_D=A(F1TsZ,QKbZ464i)dA14qP=( ++(hDih6_D~=JcQc)and(dA14qP+1)or dA14qP)JcQc= ++(JcQc==nil)and hDih6_D or JcQc;if not LN[dA14qP]then ++LN[dA14qP]={vpONJ[QKbZ464i]}else ++LN[dA14qP][#LN[dA14qP]+1]=vpONJ[QKbZ464i]end;JcQc=hDih6_D end;return LN end;function S1wg_DG.slice(uF2,T,pC_)local ju={} ++for deu1=T or 1,pC_ or#uF2 do ju[#ju+1]=uF2[deu1]end;return ju end;function S1wg_DG.first(IgZ6,kVRiv3F)kVRiv3F= ++kVRiv3F or 1;local kWMf={} ++for DawC=1,kVRiv3F do kWMf[DawC]=IgZ6[DawC]end;return kWMf end ++function S1wg_DG.initial(cP,w)local UZ= ++#cP ++w=w and UZ- (RlMSrmdD(w,UZ))or UZ-1;local tdH={}for ymt=1,w do tdH[ymt]=cP[ymt]end;return tdH end ++function S1wg_DG.last(WxGA,jBuHkH)local E3=#WxGA;jBuHkH= ++jBuHkH and E3-RlMSrmdD(jBuHkH-1,E3-1)or 2;local CZi_zK={}for _6KCMph=jBuHkH,E3 do ++CZi_zK[#CZi_zK+1]=WxGA[_6KCMph]end;return CZi_zK end;function S1wg_DG.rest(PY3VqYZ8,V)local y={} ++for QF=V or 1,#PY3VqYZ8 do y[#y+1]=PY3VqYZ8[QF]end;return y end;function S1wg_DG.nth(hN,hVflx4kh)return ++hN[hVflx4kh]end ++function S1wg_DG.compact(GP)local oCZYv2dT={} ++for RLaqM3,PoH in l(GP)do if PoH then oCZYv2dT[ ++#oCZYv2dT+1]=PoH end end;return oCZYv2dT end ++function S1wg_DG.flatten(xM709D,z50)z50=z50 or false;local sAPD;local AVFi={} ++for GGKI,gWaGu in kyWtqIf0(xM709D)do + if +-bdb and bdb.__index then local cdb=bab.functions(bdb.__index)bab.each(cdb,function(ddb,__c) +-adb[#adb+1]=__c end)end;return bab.sort(adb)end +-function bab.clone(dcb,_db)if not bab.isTable(dcb)then return dcb end;local adb={} +-bab.each(dcb,function(bdb,cdb)if +-bab.isTable(cdb)then +-if not _db then adb[bdb]=bab.clone(cdb,_db)else adb[bdb]=cdb end else adb[bdb]=cdb end end)return adb end;function bab.tap(dcb,_db,...)_db(dcb,...)return dcb end;function bab.has(dcb,_db)return +-dcb[_db]~=nil end +-function bab.pick(dcb,...)local _db=bab.flatten{...} +-local adb={} +-bab.each(_db,function(bdb,cdb) +-if not bab.isNil(dcb[cdb])then adb[cdb]=dcb[cdb]end end)return adb end +-function bab.omit(dcb,...)local _db=bab.flatten{...}local adb={} +-bab.each(dcb,function(bdb,cdb)if +-not bab.include(_db,bdb)then adb[bdb]=cdb end end)return adb end;function bab.template(dcb,_db) +-bab.each(_db or{},function(adb,bdb)if not dcb[adb]then dcb[adb]=bdb end end)return dcb end +-function bab.isEqual(dcb,_db,adb) +-local bdb=bba(dcb)local cdb=bba(_db)if bdb~=cdb then return false end +-if bdb~='table'then return(dcb==_db)end;local ddb=aca(dcb)local __c=aca(_db)if adb then ++v(gWaGu)=='table'then ++sAPD=z50 and gWaGu or S1wg_DG.flatten(gWaGu) ++for SFKM,j6jQmlbr in kyWtqIf0(sAPD)do AVFi[#AVFi+1]=j6jQmlbr end else AVFi[#AVFi+1]=gWaGu end end;return AVFi end ++function S1wg_DG.difference(m403CY,dL) ++if not dL then return S1wg_DG.clone(m403CY)end ++return S1wg_DG.select(m403CY,function(PrTsHeT) ++return not S1wg_DG.include(dL,PrTsHeT)end)end;function S1wg_DG.union(...) ++return S1wg_DG.unique(S1wg_DG.flatten({...}))end ++function S1wg_DG.intersection(...)local eNI3MT7={...} ++local Rfoo=eNI3MT7[1]lIpFkbLI(eNI3MT7,1)local eUJhGD={} ++for wot8,j9vJ in kyWtqIf0(Rfoo)do + if +-(ddb or __c)and(ddb.__eq or __c.__eq)then return +-ddb.__eq(dcb,_db)or __c.__eq(_db,dcb)or(dcb==_db)end end;if bab.size(dcb)~= +-bab.size(_db)then return false end;for a_c,b_c in d_b(dcb)do local c_c=_db[a_c] ++S1wg_DG.all(eNI3MT7,function(J6Qr27Mh)return ++S1wg_DG.include(J6Qr27Mh,j9vJ)end)then eUJhGD[#eUJhGD+1]=j9vJ end end;return eUJhGD end;function S1wg_DG.disjoint(...)return ++(#S1wg_DG.intersection(...)==0)end ++function S1wg_DG.symmetricDifference(AwxW8Do,_u)return ++S1wg_DG.difference(S1wg_DG.union(AwxW8Do,_u),S1wg_DG.intersection(AwxW8Do,_u))end ++function S1wg_DG.unique(B)local cdxFVpZw={} ++for Y=1,#B do if not S1wg_DG.find(cdxFVpZw,B[Y])then cdxFVpZw[# ++cdxFVpZw+1]=B[Y]end end;return cdxFVpZw end;function S1wg_DG.isunique(o9Uh)return ++#o9Uh==# (S1wg_DG.unique(o9Uh))end ++function S1wg_DG.duplicates(BuX1r) ++local Wyf83f2=S1wg_DG.invert(BuX1r)local P0olj={}for z,EHCCkt in kyWtqIf0(BuX1r)do ++if Wyf83f2[EHCCkt]~=z and ++not S1wg_DG.find(P0olj,EHCCkt)then P0olj[#P0olj+1]=EHCCkt end end;return P0olj end ++function S1wg_DG.zip(...)local x={...} ++local xNWVmS=S1wg_DG.max(x,function(Pkis6H28)return#Pkis6H28 end)local kGWnkgDu={} ++for tSE=1,xNWVmS do ++if not kGWnkgDu[tSE]then kGWnkgDu[tSE]={}end ++for abKH,LDp in kyWtqIf0(x)do if(LDp[tSE]~=nil)then ++kGWnkgDu[tSE][#kGWnkgDu[tSE]+1]=LDp[tSE]end end end;return kGWnkgDu end ++function S1wg_DG.zipWith(GWouUlzZ,...)local MqJhIr={...} ++local Q9=S1wg_DG.max(MqJhIr,function(qnZ81I)return#qnZ81I end)local c={}for N9uN=1,Q9 do ++c[N9uN]=GWouUlzZ(hUL(S1wg_DG.pluck(MqJhIr,N9uN)))end;return c end ++function S1wg_DG.append(QGC,K8iFU)local gbU={}for h,hS7 in kyWtqIf0(QGC)do gbU[h]=hS7 end;for KQjMKhN,R6PYgHHE in ++kyWtqIf0(K8iFU)do gbU[#gbU+1]=R6PYgHHE end;return gbU end ++function S1wg_DG.interleave(...)local ZwCXrLO={...} ++local lI=S1wg_DG.max(ZwCXrLO,S1wg_DG.size)local iMSMP5Lp={} ++for WoARZdZ3=1,lI do for n,Uj in kyWtqIf0(ZwCXrLO)do if Uj[WoARZdZ3]then ++iMSMP5Lp[#iMSMP5Lp+1]=Uj[WoARZdZ3]end end end;return iMSMP5Lp end;function S1wg_DG.interpose(HpN_N,yP3QEJ)for pwi=#HpN_N,2,-1 do odpE(HpN_N,pwi,yP3QEJ)end;return ++HpN_N end ++function S1wg_DG.range(QP,Iy,O9P0mj) + if +-bab.isNil(c_c)or not bab.isEqual(b_c,c_c,adb)then return false end end +-for a_c,b_c in d_b(_db)do +-local c_c=dcb[a_c]if bab.isNil(c_c)then return false end end;return true end +-function bab.result(dcb,_db,...) +-if dcb[_db]then if bab.isCallable(dcb[_db])then return dcb[_db](dcb,...)else return +-dcb[_db]end end;if bab.isCallable(_db)then return _db(dcb,...)end end;function bab.isTable(dcb)return bba(dcb)=='table'end +-function bab.isCallable(dcb)return +-( +-bab.isFunction(dcb)or +-(bab.isTable(dcb)and aca(dcb)and aca(dcb).__call~=nil)or false)end +-function bab.isArray(dcb)if not bab.isTable(dcb)then return false end;local _db=0 +-for adb in +-d_b(dcb)do _db=_db+1;if bab.isNil(dcb[_db])then return false end end;return true end +-function bab.isIterable(dcb)return bab.toBoolean((dba(d_b,dcb)))end +-function bab.isEmpty(dcb)if bab.isNil(dcb)then return true end;if bab.isString(dcb)then +-return#dcb==0 end +-if bab.isTable(dcb)then return aba(dcb)==nil end;return true end;function bab.isString(dcb)return bba(dcb)=='string'end;function bab.isFunction(dcb)return +-bba(dcb)=='function'end;function bab.isNil(dcb) +-return dcb==nil end +-function bab.isNumber(dcb)return bba(dcb)=='number'end +-function bab.isNaN(dcb)return bab.isNumber(dcb)and dcb~=dcb end +-function bab.isFinite(dcb)if not bab.isNumber(dcb)then return false end;return +-dcb>-cda and dcb0 then ++return hUL(UQ)end end end end ++function S1wg_DG.memoize(FG)local vLzqjJw=oUA({},{__mode='kv'}) ++return function(v2dsC21)if ++(vLzqjJw[v2dsC21]==nil)then vLzqjJw[v2dsC21]=FG(v2dsC21)end;return ++vLzqjJw[v2dsC21]end end ++function S1wg_DG.unfold(O,wx)local u,V_84V={}while true do V_84V,wx=O(wx) ++if V_84V~=nil then u[#u+1]=V_84V else break end end;return u end ++function S1wg_DG.once(qF)local IZbOX7TW=0;local Dd6ZLpU={}return ++function(...)IZbOX7TW=IZbOX7TW+1 ++if IZbOX7TW<=1 then Dd6ZLpU={...}end;return qF(hUL(Dd6ZLpU))end end;function S1wg_DG.before(MP,hgW2H5)local w4c=0;local C58={} ++return function(...)w4c=w4c+1;if w4c<=hgW2H5 then C58={...}end;return ++MP(hUL(C58))end end ++function S1wg_DG.after(Jk6Nh,hgW2H5) ++local s1Ws,desLYv=hgW2H5,0;return ++function(...)desLYv=desLYv+1;if desLYv>=s1Ws then return Jk6Nh(...)end end end ++function S1wg_DG.compose(...)local COq2NY9I=S1wg_DG.reverse{...} ++return ++function(...)local aoBEg65S,x6=true ++for t3cNa2l,Ik in ++kyWtqIf0(COq2NY9I)do if aoBEg65S then aoBEg65S=false;x6=Ik(...)else x6=Ik(x6)end end;return x6 end end ++function S1wg_DG.pipe(SeHOs,...)return S1wg_DG.compose(...)(SeHOs)end;function S1wg_DG.complement(P2rGsUx) ++return function(...)return not P2rGsUx(...)end end;function S1wg_DG.juxtapose(c,...)local v12AhMm={}for F2uxGC,Xs0 in ++kyWtqIf0({...})do v12AhMm[F2uxGC]=Xs0(c)end ++return hUL(v12AhMm)end ++function S1wg_DG.wrap(QK8ibF,TEio7k0z)return function(...)return ++TEio7k0z(QK8ibF,...)end end;function S1wg_DG.times(u,N)local O2YgxDc={} ++for VLsC67=1,(N or 1)do O2YgxDc[VLsC67]=u(VLsC67)end;return O2YgxDc end ++function S1wg_DG.bind(OHw4,FKZ)return function(...)return ++OHw4(FKZ,...)end end;function S1wg_DG.bind2(Fl,QhS8FvKI) ++return function(FaZIJL,...)return Fl(FaZIJL,QhS8FvKI,...)end end;function S1wg_DG.bindn(sOT2O5,...)local x={...} ++return function(...)return ++sOT2O5(hUL(S1wg_DG.append(x,{...})))end end ++function S1wg_DG.bindall(Wswd_OC,...) ++local E={...} ++for A0Un,nRHrI in kyWtqIf0(E)do local k=Wswd_OC[nRHrI]if k then ++Wswd_OC[nRHrI]=S1wg_DG.bind(k,Wswd_OC)end end;return Wswd_OC end;function S1wg_DG.cond(Zp) ++return function(...) ++for A,_L_ in kyWtqIf0(Zp)do if _L_[1](...)then return _L_[2](...)end end end end ++function S1wg_DG.both(...) ++local WHpm={...} ++return function(...) ++for g,HiR3yiw in kyWtqIf0(WHpm)do if not HiR3yiw(...)then return false end end;return true end end ++function S1wg_DG.either(...)local KeKbiDqN={...}return ++function(...)for WfrZqHH8,YX9s9O in kyWtqIf0(KeKbiDqN)do ++if YX9s9O(...)then return true end end;return false end end ++function S1wg_DG.neither(...)local y64dF={...} ++return function(...) ++for sNSsH,K in kyWtqIf0(y64dF)do if K(...)then return false end end;return true end end ++function S1wg_DG.uniqueId(o8T)qJExeUn2=qJExeUn2+1;if o8T then ++if v(o8T)=='string'then ++return o8T:format(qJExeUn2)elseif v(o8T)=='function'then return o8T(qJExeUn2)end end;return qJExeUn2 end ++function S1wg_DG.iterator(xeP,Tv_3VlmX,BT)local _y3z=0 ++return function()_y3z=_y3z+1;if BT and _y3z>BT then return end ++Tv_3VlmX=xeP(Tv_3VlmX)return Tv_3VlmX end end;function S1wg_DG.skip(rdl,NAP_5jYs) ++for BZnlpW=1,(NAP_5jYs or 1)do if rdl()==nil then return end end;return rdl end ++function S1wg_DG.tabulate(...) ++local isN={}for yRADzw1v in...do isN[#isN+1]=yRADzw1v end;return isN end ++function S1wg_DG.iterlen(...)local Jafp=0;for XWh8Ee in...do Jafp=Jafp+1 end;return Jafp end;function S1wg_DG.castArray(kpezL1e)return ++(v(kpezL1e)~='table')and{kpezL1e}or kpezL1e end ++function S1wg_DG.flip(h)return function(...)return ++h(hUL(S1wg_DG.reverse({...})))end end ++function S1wg_DG.nthArg(R7yfz_l9)return ++function(...)local D35PFLu={...}return ++D35PFLu[(R7yfz_l9 <0)and ++(#D35PFLu+R7yfz_l9+1)or R7yfz_l9]end end;function S1wg_DG.unary(wK) ++return function(...)local qeEwE={...}return wK(qeEwE[1])end end ++function S1wg_DG.ary(cbtvFnSa,fYKH_)fYKH_=fYKH_ or 1 ++return function(...) ++local W={...}local o={}for Mm99M=1,fYKH_ do o[Mm99M]=W[Mm99M]end ++return cbtvFnSa(hUL(o))end end ++function S1wg_DG.noarg(l6YH)return function()return l6YH()end end ++function S1wg_DG.rearg(gf2,F744Ew)return ++function(...)local zgxKF4={...}local UlvVvSBR={}for i2i,uRGAL in kyWtqIf0(F744Ew)do ++UlvVvSBR[i2i]=zgxKF4[uRGAL]end;return gf2(hUL(UlvVvSBR))end end ++function S1wg_DG.over(...)local UUlqXyb6={...}return ++function(...)local fOR92g8={}for jU26,WIPTsAPz in kyWtqIf0(UUlqXyb6)do ++fOR92g8[#fOR92g8+1]=WIPTsAPz(...)end;return fOR92g8 end end ++function S1wg_DG.overEvery(...)local DgUx8=S1wg_DG.over(...) ++return function(...) ++return S1wg_DG.reduce(DgUx8(...),function(imac,xX) ++return imac and xX end)end end ++function S1wg_DG.overSome(...)local Mfb6Kb=S1wg_DG.over(...) ++return function(...) ++return S1wg_DG.reduce(Mfb6Kb(...),function(RRjV,TDOaFo)return ++RRjV or TDOaFo end)end end ++function S1wg_DG.overArgs(tLo4,...)local m72l={...} ++return ++function(...)local npM3DSU={...}for HGp4e1=1,#m72l do local uzJt7E=m72l[HGp4e1] + if +-b_b(bdb,a_c)then if not cdb then bdb[a_c]=bab[a_c]end else bdb[a_c]=bab[a_c]end end)return bdb end;_db._VERSION='Moses v'.._ba +-_db._URL='http://github.com/Yonaba/Moses' +-_db._LICENSE='MIT '_db._DESCRIPTION='utility-belt library for functional programming in Lua'return +-_db end +\ No newline at end of file ++npM3DSU[HGp4e1]then npM3DSU[HGp4e1]=uzJt7E(npM3DSU[HGp4e1])end end;return ++tLo4(hUL(npM3DSU))end end ++function S1wg_DG.converge(sRe5S32N,Bp,rg)return ++function(...)return sRe5S32N(Bp(...),rg(...))end end ++function S1wg_DG.partial(S,...)local Fem={...} ++return ++function(...)local cHmVGY={...}local g29sXR={}for Vat,sfnkWAy8 in kyWtqIf0(Fem)do ++g29sXR[Vat]= ++(sfnkWAy8 =='_')and S1wg_DG.shift(cHmVGY)or sfnkWAy8 end;return ++S(hUL(S1wg_DG.append(g29sXR,cHmVGY)))end end ++function S1wg_DG.partialRight(hbJSGe9,...)local pI={...} ++return ++function(...)local B7jhm={...}local hj3={}for FKxU4=1,#pI do ++hj3[FKxU4]= ++(pI[FKxU4]=='_')and S1wg_DG.shift(B7jhm)or pI[FKxU4]end;return ++hbJSGe9(hUL(S1wg_DG.append(B7jhm,hj3)))end end ++function S1wg_DG.curry(UW,tReY)tReY=tReY or 2;local lex={} ++local function h79Pm(vksQpy4) ++if tReY==1 then return UW(vksQpy4)end;if vksQpy4 ~=nil then lex[#lex+1]=vksQpy4 end ++if#lex-S9TO and ++AN9 = 5.1, < 5.4" ++} ++build = { ++ type = "builtin", ++ modules = { ++ ["moses"] = "moses.lua", ++ ["moses_min"] = "moses_min.lua", ++ }, ++ copy_directories = {"doc","spec"} ++} +\ No newline at end of file +diff --git a/extra/moses/rockspec/moses-2.1.0-1.rockspec b/extra/moses/rockspec/moses-2.1.0-1.rockspec +new file mode 100644 +index 0000000..718c0c2 +--- /dev/null ++++ b/extra/moses/rockspec/moses-2.1.0-1.rockspec +@@ -0,0 +1,26 @@ ++package = "moses" ++version = "2.1.0-1" ++source = { ++ url = "https://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.tar.gz", ++ dir = "Moses-Moses-2.1.0-1" ++} ++description = { ++ summary = "Utility-belt library for functional programming in Lua", ++ detailed = [[ ++ A utility-belt library for functional programming, which complements the built-in ++ Lua table library, making easier operations on arrays, lists, collections. ++ ]], ++ homepage = "http://yonaba.github.com/Moses/", ++ license = "MIT " ++} ++dependencies = { ++ "lua >= 5.1, < 5.4" ++} ++build = { ++ type = "builtin", ++ modules = { ++ ["moses"] = "moses.lua", ++ ["moses_min"] = "moses_min.lua", ++ }, ++ copy_directories = {"doc","spec"} ++} +\ No newline at end of file +diff --git a/extra/moses/spec/array_spec.lua b/extra/moses/spec/array_spec.lua +index e79a6f7..725d6ec 100644 +--- a/extra/moses/spec/array_spec.lua ++++ b/extra/moses/spec/array_spec.lua +@@ -1,713 +1,934 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Array functions specs', function() ++describe('Array functions specs', function() + +- context('sample', function() ++ describe('sample', function() + +- test('samples n values from array', function() +- local array = _.range(1,20) +- local sample = _.sample(array, 5) +- assert_equal(#sample, 5) +- _.each(sample, function(__,v) +- assert_true(_.include(array, v)) ++ it('samples n values from array', function() ++ local array = M.range(1,20) ++ local sample = M.sample(array, 5) ++ assert.equal(#sample, 5) ++ M.each(sample, function(__,v) ++ assert.is_true(M.include(array, v)) + end) + end) + +- test('when not given, n defaults to 1', function() +- local array = _.range(1,20) +- local sample = _.sample(array) +- assert_true(_.include(array, sample)) ++ it('when not given, n defaults to 1', function() ++ local array = M.range(1,20) ++ local sample = M.sample(array) ++ assert.equal(#sample, 1) ++ assert.is_true(M.include(array, sample[1])) + end) + ++ it('if n == 0, returns an empty array', function() ++ local array = M.range(1,5) ++ local sample = M.sample(array, 0) ++ assert.is_true(#sample == 0) ++ end) ++ ++ it('if n < 0, returns an empty array', function() ++ local array = M.range(1,5) ++ assert.is_true(#M.sample(array, -1) == 0) ++ end) ++ + end) + +- context('sampleProb', function() ++ describe('sampleProb', function() + +- test('returns a sample of an array values', function() +- local array = _.range(1,20) +- local sample = _.sampleProb(array, 0.2) +- _.each(sample, function(__,v) +- assert_true(_.include(array, v)) ++ it('returns a sample of an array values', function() ++ local array = M.range(1,20) ++ local sample = M.sampleProb(array, 0.2) ++ M.each(sample, function(__,v) ++ assert.is_true(M.include(array, v)) + end) + end) + + end) ++ ++ describe('nsorted', function() ++ ++ it('returns the top n-values from an array', function() ++ local array = M.range(1,20) ++ assert.is_true(M.isEqual(M.nsorted(array,5),{1,2,3,4,5})) ++ ++ local function comp(a,b) return a > b end ++ assert.is_true(M.isEqual(M.nsorted(array,3,comp),{20,19,18})) ++ end) ++ ++ end) + +- context('toArray', function() ++ describe('shuffle', function() ++ ++ it('shuffles values and objects in a collection', function() ++ local values = {'a','b','c','d'} ++ assert.is_true(M.same(M.shuffle (values),values)) ++ end) ++ ++ it('can accept a seed value to init randomization', function() ++ local values = {'a','b','c','d'} ++ local seed = os.time() ++ assert.is_true(M.same(M.shuffle(values,seed),values)) ++ end) ++ ++ it('shuffled table has the same elements in a different order', function() ++ local values = {'a','b','c','d'} ++ assert.is_true(M.same(M.shuffle(values),values)) ++ assert.is_true(M.same(M.shuffle(values),values)) ++ end) ++ ++ end) ++ ++ describe('pack', function() + +- test('converts a vararg list to an array', function() +- assert_true(_.isArray(_.toArray(1,2,3,4))) +- assert_true(_.isEqual(_.toArray(1,2,8,'d','a',0),{1,2,8,'d','a',0})) ++ it('converts a vararg list to an array', function() ++ assert.is_true(M.isArray(M.pack(1,2,3,4))) ++ assert.is_true(M.isEqual(M.pack(1,2,8,'d','a',0),{1,2,8,'d','a',0})) + end) + +- test('preserves input order', function() +- local args = _.toArray(1,2,3,4,5) +- for i = 1, 5 do assert_equal(args[i], i) end ++ it('preserves input order', function() ++ local args = M.pack(1,2,3,4,5) ++ for i = 1, 5 do assert.equal(args[i], i) end + end) + + end) + +- context('find', function() ++ describe('find', function() + +- test('looks for a value in a given array and returns its position', function() +- assert_equal(_.find({4,3,2,1},2), 3) ++ it('looks for a value in a given array and returns its position', function() ++ assert.equal(M.find({4,3,2,1},2), 3) + end) + +- test('uses _.isEqual to compare values', function() +- assert_equal(_.find({{4},{3},{2},{1}},{3}), 2) ++ it('uses M.isEqual to compare values', function() ++ assert.equal(M.find({{4},{3},{2},{1}},{3}), 2) + end) + +- test('returns the index of the first occurence', function() +- assert_equal(_.find({4,4,3,3,2,2,1,1},2),5) ++ it('returns the index of the first occurence', function() ++ assert.equal(M.find({4,4,3,3,2,2,1,1},2),5) + end) + +- test('can start the search at a specific position', function() +- assert_equal(_.find({4,4,3,3,2,1,2,1,1},2,6),7) ++ it('can start the search at a specific position', function() ++ assert.equal(M.find({4,4,3,3,2,1,2,1,1},2,6),7) + end) + + end) + +- context('reverse', function() ++ describe('reverse', function() + +- test('reverse values and objects in a given array', function() +- assert_true(_.isEqual(_.reverse({1,2,3,'d'}),{'d',3,2,1})) ++ it('reverse values and objects in a given array', function() ++ assert.is_true(M.isEqual(M.reverse({1,2,3,'d'}),{'d',3,2,1})) + end) + + end) + +- context('fill', function() ++ describe('fill', function() + +- test('fills an array with a value', function() +- local array = _.range(1,5) +- assert_true(_.isEqual(_.fill(array,0),{0,0,0,0,0})) ++ it('fills an array with a value', function() ++ local array = M.range(1,5) ++ assert.is_true(M.isEqual(M.fill(array,0),{0,0,0,0,0})) + end) + +- test('fills an array starting from an index', function() +- local array = _.range(1,5) +- assert_true(_.isEqual(_.fill(array,0,4),{1,2,3,0,0})) ++ it('fills an array starting from an index', function() ++ local array = M.range(1,5) ++ assert.is_true(M.isEqual(M.fill(array,0,4),{1,2,3,0,0})) + end) + +- test('fills an array replacing values inside a range', function() +- local array = _.range(1,5) +- assert_true(_.isEqual(_.fill(array,0,3,4),{1,2,0,0,5})) ++ it('fills an array replacing values inside a range', function() ++ local array = M.range(1,5) ++ assert.is_true(M.isEqual(M.fill(array,0,3,4),{1,2,0,0,5})) + end) + +- test('enlarges the array when the last index is greater than array size', function() +- local array = _.range(1,5) +- assert_true(_.isEqual(_.fill(array,0,3,8),{1,2,0,0,0,0,0,0})) ++ it('enlarges the array when the last index is greater than array size', function() ++ local array = M.range(1,5) ++ assert.is_true(M.isEqual(M.fill(array,0,3,8),{1,2,0,0,0,0,0,0})) + end) + + end) + +- context('selectWhile', function() ++ describe('zeros', function() ++ ++ it('returns an array of n zeros', function() ++ assert.is_true(M.isEqual(M.zeros(5), {0,0,0,0,0})) ++ assert.is_true(M.isEqual(M.zeros(2), {0,0})) ++ assert.is_true(M.isEqual(M.zeros(1), {0})) ++ end) + +- test('collect values from an array while they pass a thruth test', function() +- assert_true(_.isEqual(_.selectWhile({2,4,6,8}, function(i,v) +- return v%2==0 +- end),{2,4,6,8})) ++ end) ++ ++ describe('ones', function() ++ ++ it('returns an array of n zeros', function() ++ assert.is_true(M.isEqual(M.ones(5), {1,1,1,1,1})) ++ assert.is_true(M.isEqual(M.ones(3), {1,1,1})) ++ assert.is_true(M.isEqual(M.ones(1), {1})) + end) ++ ++ end) ++ ++ describe('vector', function() + +- test('breaks as soon as one value do not pass the test', function() +- assert_true(_.isEqual(_.selectWhile({2,4,6,8,9,10,12}, function(i,v) +- return v%2==0 +- end),{2,4,6,8})) ++ it('returns an array of n times a given value', function() ++ assert.is_true(M.isEqual(M.vector(false,4), {false, false, false, false})) ++ local f = function() end ++ assert.is_true(M.isEqual(M.vector(f,2), {f, f})) ++ end) ++ ++ end) ++ ++ describe('selectWhile', function() ++ ++ it('collect values from an array while they pass a thruth test', function() ++ assert.is_true(M.isEqual(M.selectWhile({2,4,6,8}, function(v)return v%2==0 end),{2,4,6,8})) ++ end) ++ ++ it('breaks as soon as one value do not pass the test', function() ++ assert.is_true(M.isEqual(M.selectWhile({2,4,6,8,9,10,12}, function(v) return v%2==0 end),{2,4,6,8})) + end) + + end) + +- context('dropWhile', function() ++ describe('dropWhile', function() + +- test('rejects values from an array while they pass a thruth test', function() +- assert_true(_.isEqual(_.dropWhile({2,4,6,8}, function(i,v) +- return v%2==0 +- end),{})) ++ it('rejects values from an array while they pass a thruth test', function() ++ assert.is_true(M.isEqual(M.dropWhile({2,4,6,8}, function(v) return v%2==0 end),{})) + end) + +- test('breaks as soon as one value do not pass the test', function() +- assert_true(_.isEqual(_.dropWhile({2,4,6,8,9,10,12}, function(i,v) +- return v%2==0 +- end),{9,10,12})) ++ it('breaks as soon as one value do not pass the test', function() ++ assert.is_true(M.isEqual(M.dropWhile({2,4,6,8,9,10,12}, function(v) return v%2==0 end),{9,10,12})) + end) + + end) + +- context('sortedIndex', function() ++ describe('sortedIndex', function() + +- test('returns the index at which a value should be inserted to preserve order', function() ++ it('returns the index at which a value should be inserted to preserve order', function() + local comp = function(a,b) return a5 end)) ++ it('returns nil when nothing was found', function() ++ assert.is_nil(M.findIndex({1,2,3,4,5},function(_,v) return v>5 end)) + end) + + end) + +- context('findLastIndex', function() ++ describe('findLastIndex', function() + +- test('returns the last index at which a predicate passes a truth test', function() +- assert_equal(_.findLastIndex({1,2,3,4,5},function(_,v) return v%2==0 end),4) ++ it('returns the last index at which a predicate passes a truth test', function() ++ assert.equal(M.findLastIndex({1,2,3,4,5},function(_,v) return v%2==0 end),4) + end) + +- test('returns nil when nothing was found', function() +- assert_nil(_.findLastIndex({1,2,3,4,5},function(_,v) return v>5 end)) ++ it('returns nil when nothing was found', function() ++ assert.is_nil(M.findLastIndex({1,2,3,4,5},function(_,v) return v>5 end)) + end) + + end) + +- context('addTop', function() ++ describe('addTop', function() + +- test('adds values at the top of an array', function() +- assert_true(_.isEqual(_.addTop({},1,2,3),{3,2,1})) +- assert_true(_.isEqual(_.addTop({},'a',true,3),{3,true,'a'})) ++ it('adds values at the top of an array', function() ++ assert.is_true(M.isEqual(M.addTop({},1,2,3),{3,2,1})) ++ assert.is_true(M.isEqual(M.addTop({},'a',true,3),{3,true,'a'})) + end) + +- test('preserves the existing elements', function() +- assert_true(_.isEqual(_.addTop({1,2},1,2,3),{3,2,1,1,2})) +- assert_true(_.isEqual(_.addTop({'i','j'},'a',true,3),{3,true,'a','i','j'})) ++ it('preserves the existing elements', function() ++ assert.is_true(M.isEqual(M.addTop({1,2},1,2,3),{3,2,1,1,2})) ++ assert.is_true(M.isEqual(M.addTop({'i','j'},'a',true,3),{3,true,'a','i','j'})) + end) + + end) + +- context('push', function() ++ describe('prepend', function() + +- test('appends values at the end of an array', function() +- assert_true(_.isEqual(_.push({},1,2,3),{1,2,3})) +- assert_true(_.isEqual(_.push({},'a',true,3),{'a',true,3})) ++ it('adds values at the top of an array, preserving order', function() ++ assert.is_true(M.isEqual(M.prepend({},1,2,3),{1,2,3})) ++ assert.is_true(M.isEqual(M.prepend({},'a',true,3),{'a',true,3})) ++ end) ++ ++ it('preserves the existing elements', function() ++ assert.is_true(M.isEqual(M.prepend({1,2},1,2,3),{1,2,3,1,2})) ++ assert.is_true(M.isEqual(M.prepend({'i','j'},'a',true,3),{'a',true,3,'i','j'})) ++ end) ++ ++ end) ++ ++ describe('push', function() ++ ++ it('appends values at the end of an array', function() ++ assert.is_true(M.isEqual(M.push({},1,2,3),{1,2,3})) ++ assert.is_true(M.isEqual(M.push({},'a',true,3),{'a',true,3})) + end) + +- test('preserves the existing elements', function() +- assert_true(_.isEqual(_.push({1,2},1,2,3),{1,2,1,2,3})) +- assert_true(_.isEqual(_.push({'i','j'},'a',true,3),{'i','j','a',true,3})) ++ it('preserves the existing elements', function() ++ assert.is_true(M.isEqual(M.push({1,2},1,2,3),{1,2,1,2,3})) ++ assert.is_true(M.isEqual(M.push({'i','j'},'a',true,3),{'i','j','a',true,3})) + end) + + end) + +- context('pop', function() ++ describe('shift', function() + +- test('returns the value at the top of a given array', function() +- assert_equal(_.pop {1,7,9} ,1) ++ it('returns the value at the top of a given array', function() ++ assert.equal(M.shift {1,7,9} ,1) + end) + +- test('also removes this value from the given array', function() ++ it('also removes this value from the given array', function() + local array = {1,7,9} +- assert_equal(_.pop(array),1) +- assert_true(_.isEqual(array,{7,9})) ++ assert.equal(M.shift(array),1) ++ assert.is_true(M.isEqual(array,{7,9})) + end) + + end) + +- context('unshift', function() ++ describe('unshift', function() + +- test('returns the value at the end of a given array', function() +- assert_equal(_.unshift {1,7,9} ,9) ++ it('returns the value at the end of a given array', function() ++ assert.equal(M.unshift {1,7,9} ,9) + end) + +- test('also removes this value from the given array', function() ++ it('also removes this value from the given array', function() + local array = {1,7,9} +- assert_equal(_.unshift(array),9) +- assert_true(_.isEqual(array,{1,7})) ++ assert.equal(M.unshift(array),9) ++ assert.is_true(M.isEqual(array,{1,7})) + end) + + end) + +- context('pull', function() ++ describe('pull', function() + +- test('removes all listed values in a given array', function() +- assert_true(_.same(_.pull({1,4,3,1,2,3},1),{4,3,2,3})) +- assert_true(_.same(_.pull({1,4,3,1,2,3},1,3),{4,2})) ++ it('removes all listed values in a given array', function() ++ assert.is_true(M.same(M.pull({1,4,3,1,2,3},1),{4,3,2,3})) ++ assert.is_true(M.same(M.pull({1,4,3,1,2,3},1,3),{4,2})) + end) + + end) + +- context('removeRange', function() ++ describe('removeRange', function() + +- test('removes all values within "start" and "finish" indexes', function() +- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},2,4),{1,5,6})) ++ it('removes all values within "start" and "finish" indexes', function() ++ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6},2,4),{1,5,6})) + end) + +- test('arg "finish" defaults to the end of the array when not given ', function() +- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},3),{1,2})) ++ it('arg "finish" defaults to the end of the array when not given ', function() ++ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6},3),{1,2})) + end) + +- test('arg "start" defaults to the initial index when not given ', function() +- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6}),{})) ++ it('arg "start" defaults to the initial index when not given ', function() ++ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6}),{})) + end) +- +- test('args "start" and "finish" are be clamped to the array bound ', function() +- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},0,100),{})) +- end) + +- test('leaves the array untouched when "finish" < "start"', function() +- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},4,2),{1,2,3,4,5,6})) ++ it('throws an error when "finish" < "start"', function() ++ assert.error(function()M.removeRange({1,2,3,4,5,6},4,2) end) + end) + + end) + +- context('chunk', function() ++ describe('chunk', function() + +- test('chunks in blocks consecutive values returning the same value from a given function', function() ++ it('chunks in blocks consecutive values returning the same value from a given function', function() + local t = {1,2,2,3,3,4,4} +- local v = _.chunk(t, function(k,v) return v%2==0 end) +- assert_equal(#v, 4) +- _.each(v[1], function(k) +- assert_equal(v[1][k],1) +- end) +- assert_equal(#v[1],1) +- _.each(v[2], function(k) +- assert_equal(v[2][k],2) +- end) +- assert_equal(#v[2],2) +- _.each(v[3], function(k) +- assert_equal(v[3][k],3) +- end) +- assert_equal(#v[3],2) +- _.each(v[4], function(k) +- assert_equal(v[4][k],4) +- end) +- assert_equal(#v[4],2) +- end) +- +- test('Returns the first argument in case it is not an array', function() +- local t = {a = 1, b = 2} +- assert_equal(_.chunk(t, function(k,v) return v%2==0 end), t) ++ local v = M.chunk(t, function(v) return v%2==0 end) ++ assert.equal(#v, 4) ++ assert.is_true(M.isEqual(v[1], {1})) ++ assert.is_true(M.isEqual(v[2], {2,2})) ++ assert.is_true(M.isEqual(v[3], {3,3})) ++ assert.is_true(M.isEqual(v[4], {4,4})) ++ end) ++ ++ it('chunks in blocks consecutive values when using identity as function', function() ++ local t = {1,1,2,2,3,3,4} ++ local v = M.chunk(t, function(v) return v end) ++ assert.is_nil(v[0]) ++ assert.equal(#v, 4) ++ assert.is_true(M.isEqual(v[1], {1,1})) ++ assert.is_true(M.isEqual(v[2], {2,2})) ++ assert.is_true(M.isEqual(v[3], {3,3})) ++ assert.is_true(M.isEqual(v[4], {4})) + end) + + end) + +- context('slice',function() ++ describe('slice',function() + +- test('slices a portion of an array',function() +- assert_true(_.isEqual(_.slice({'a','b','c','d','e'},2,3),{'b','c'})) ++ it('slices a portion of an array',function() ++ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'},2,3),{'b','c'})) + end) + +- test('arg "right" bound defaults to the array length when not given',function() +- assert_true(_.isEqual(_.slice({'a','b','c','d','e'},3),{'c','d','e'})) ++ it('arg "right" bound defaults to the array length when not given',function() ++ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'},3),{'c','d','e'})) + end) + +- test('arg "left" bound defaults to the initial index when not given',function() +- assert_true(_.isEqual(_.slice({'a','b','c','d','e'}),{'a','b','c','d','e'})) ++ it('arg "left" bound defaults to the initial index when not given',function() ++ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'}),{'a','b','c','d','e'})) + end) + + end) + +- context('first',function() ++ describe('first',function() + +- test('returns the n-first elements', function() +- assert_true(_.isEqual(_.first({5,8,12,20},2),{5,8})) ++ it('returns the n-first elements', function() ++ assert.is_true(M.isEqual(M.first({5,8,12,20},2),{5,8})) + end) + +- test('arg "n" defaults 1 when not given', function() +- assert_true(_.isEqual(_.first({5,8,12,20}),{5})) ++ it('arg "n" defaults 1 when not given', function() ++ assert.is_true(M.isEqual(M.first({5,8,12,20}),{5})) + end) + + end) + +- context('initial',function() ++ describe('initial',function() + +- test('exludes the last N elements', function() +- assert_true(_.isEqual(_.initial({5,8,12,20},3),{5})) +- assert_true(_.isEqual(_.initial({5,8,12,20},4),{})) ++ it('exludes the last N elements', function() ++ assert.is_true(M.isEqual(M.initial({5,8,12,20},3),{5})) ++ assert.is_true(M.isEqual(M.initial({5,8,12,20},4),{})) + end) + +- test('returns all values but the last one if arg "n" was not given', function() +- assert_true(_.isEqual(_.initial({5,8,12,20}),{5,8,12})) ++ it('returns all values but the last one if arg "n" was not given', function() ++ assert.is_true(M.isEqual(M.initial({5,8,12,20}),{5,8,12})) + end) + +- test('passing "n" greather than the array size returns an empty', function() +- assert_true(_.isEqual(_.initial({5,8,12,20},5),{})) ++ it('passing "n" greather than the array size returns an empty', function() ++ assert.is_true(M.isEqual(M.initial({5,8,12,20},5),{})) + end) + +- test('returns the whole array when "n" equals 0', function() +- assert_true(_.isEqual(_.initial({5,8,12,20},0),{5,8,12,20})) +- end) +- +- test('returns "nil" when arg "n" < 0', function() +- assert_nil(_.initial({5,8,12,20},-1)) +- end) ++ it('returns the whole array when "n" equals 0', function() ++ assert.is_true(M.isEqual(M.initial({5,8,12,20},0),{5,8,12,20})) ++ end) + + end) + +- context('last',function() ++ describe('last',function() + +- test('returns the last N elements', function() +- assert_true(_.isEqual(_.last({5,8,12,20},3),{8,12,20})) +- assert_true(_.isEqual(_.last({5,8,12,20},1),{20})) +- assert_true(_.isEqual(_.last({5,8,12,20},2),{12,20})) +- assert_true(_.isEqual(_.last({5,8,12,20},4),{5,8,12,20})) ++ it('returns the last N elements', function() ++ assert.is_true(M.isEqual(M.last({5,8,12,20},3),{8,12,20})) ++ assert.is_true(M.isEqual(M.last({5,8,12,20},1),{20})) ++ assert.is_true(M.isEqual(M.last({5,8,12,20},2),{12,20})) ++ assert.is_true(M.isEqual(M.last({5,8,12,20},4),{5,8,12,20})) + end) + +- test('returns all values but the first one if arg "n" was not given', function() +- assert_true(_.isEqual(_.last({5,8,12,20}),{8,12,20})) ++ it('returns all values but the first one if arg "n" was not given', function() ++ assert.is_true(M.isEqual(M.last({5,8,12,20}),{8,12,20})) + end) + +- test('if arg "n" is lower than the array size, returns all values', function() +- assert_true(_.isEqual(_.last({5,8,12,20},5),{5,8,12,20})) +- end) +- +- test('returns "nil" when arg "n" <= 0', function() +- assert_nil(_.last({5,8,12,20},0)) +- assert_nil(_.last({5,8,12,20},-1)) +- end) ++ it('if arg "n" is lower than the array size, returns all values', function() ++ assert.is_true(M.isEqual(M.last({5,8,12,20},5),{5,8,12,20})) ++ end) + + end) + +- context('rest',function() ++ describe('rest',function() + +- test('excludes all values before a given index', function() +- assert_true(_.isEqual(_.rest({5,8,12,20},2),{8,12,20})) +- assert_true(_.isEqual(_.rest({5,8,12,20},1),{5,8,12,20})) +- assert_true(_.isEqual(_.rest({5,8,12,20},4),{20})) ++ it('excludes all values before a given index', function() ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},2),{8,12,20})) ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},1),{5,8,12,20})) ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},4),{20})) + end) + +- test('returns an empty array when arg "index" > #array', function() +- assert_true(_.isEqual(_.rest({5,8,12,20},5),{})) ++ it('returns an empty array when arg "index" > #array', function() ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},5),{})) + end) + +- test('returns all values if arg "index" <= 0', function() +- assert_true(_.isEqual(_.rest({5,8,12,20},0),{5,8,12,20})) +- assert_true(_.isEqual(_.rest({5,8,12,20},-1),{5,8,12,20})) ++ it('returns all values if arg "index" <= 0', function() ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},0),{5,8,12,20})) ++ assert.is_true(M.isEqual(M.rest({5,8,12,20},-1),{5,8,12,20})) + end) + + end) + +- context('nth', function() ++ describe('nth', function() + +- test('returns the value at "index"', function() +- assert_equal(3, _.nth({1,2,3,4,5,6}, 3)) ++ it('returns the value at "index"', function() ++ assert.equal(3, M.nth({1,2,3,4,5,6}, 3)) + end) + + end) + +- context('compact',function() ++ describe('compact',function() + +- test('trims out all falsy values from an array', function() +- assert_true(_.isEqual(_.compact({a,'a',false,'b',true}),{'a','b',true})) ++ it('trims out all falsy values from an array', function() ++ assert.is_true(M.isEqual(M.compact({a,'a',false,'b',true}),{'a','b',true})) + end) + + end) + +- context('flatten',function() ++ describe('flatten',function() + +- test('flattens nested arrays', function() +- assert_true(_.isEqual(_.flatten({1,{2,3},{4,5,{6,7}}}),{1,2,3,4,5,6,7})) ++ it('flattens nested arrays', function() ++ assert.is_true(M.isEqual(M.flatten({1,{2,3},{4,5,{6,7}}}),{1,2,3,4,5,6,7})) + end) + +- test('when given arg "shallow", flatten only first level', function() +- assert_true(_.isEqual(_.flatten({1,{2,3},{4,5,{6,7}}},true),{1,2,3,4,5,{6,7}})) ++ it('when given arg "shallow", flatten only first level', function() ++ assert.is_true(M.isEqual(M.flatten({1,{2,3},{4,5,{6,7}}},true),{1,2,3,4,5,{6,7}})) + end) + + end) + +- context('difference',function() ++ describe('difference',function() + +- test('returns values in the first array not present in the second array', function() ++ it('returns values in the first array not present in the second array', function() + local array = {1,2,'a',4,5} +- assert_true(_.isEqual(_.difference(array,{1,'a'}),{2,4,5})) +- assert_true(_.isEqual(_.difference(array,{5}),{1,2,'a',4})) ++ assert.is_true(M.isEqual(M.difference(array,{1,'a'}),{2,4,5})) ++ assert.is_true(M.isEqual(M.difference(array,{5}),{1,2,'a',4})) + end) + +- test('ignores values in the second array not found in the first array', function() ++ it('ignores values in the second array not found in the first array', function() + local array = {1,2,'a',4,5} +- assert_true(_.isEqual(_.difference(array,{1,'a','b','c'}),{2,4,5})) ++ assert.is_true(M.isEqual(M.difference(array,{1,'a','b','c'}),{2,4,5})) + end) + +- test('leaves array untouched when given no extra-args', function() +- assert_true(_.isEqual(_.difference({1,2,'a',4,5}),{1,2,'a',4,5})) ++ it('leaves array untouched when given no extra-args', function() ++ assert.is_true(M.isEqual(M.difference({1,2,'a',4,5}),{1,2,'a',4,5})) + end) + + end) + +- context('union',function() ++ describe('union',function() + +- test('returns the duplicate-free union of all passed-in arrays', function() ++ it('returns the duplicate-free union of all passed-in arrays', function() + local a = {"a"}; local b = {1,2,3}; local c = {2,10} +- assert_true(_.isEqual(_.union(a,b,c),{'a',1,2,3,10})) ++ assert.is_true(M.isEqual(M.union(a,b,c),{'a',1,2,3,10})) + end) + +- test('accepts nested arrays as well', function() ++ it('accepts nested arrays as well', function() + local a = {"a",{"b","c"}}; local b = {1,{2},3}; local c = {2,10} +- assert_true(_.isEqual(_.union(a,b,c),{'a','b','c',1,2,3,10})) ++ assert.is_true(M.isEqual(M.union(a,b,c),{'a','b','c',1,2,3,10})) + end) + + end) + +- context('intersection',function() ++ describe('intersection',function() + +- test('returns the intersection of all passed-in arrays', function() ++ it('returns the intersection of all passed-in arrays', function() + local a = {1,3}; local b = {4,2,3}; local c = {2,3,10} +- assert_true(_.isEqual(_.intersection(a,b,c),{3})) ++ assert.is_true(M.isEqual(M.intersection(a,b,c),{3})) + end) + +- test('fails with nested arrays', function() ++ it('fails with nested arrays', function() + local a = {1,{3}}; local b = {4,2,3}; local c = {2,3,10} +- assert_true(_.isEqual(_.intersection(a,b,c),{})) ++ assert.is_true(M.isEqual(M.intersection(a,b,c),{})) + end) + + end) + +- context('symmetricDifference',function() ++ describe('disjoint',function() + +- test('returns the symmetric difference from two arrays', function() ++ it('checks if all passed-in arrays are disjoint', function() ++ local A = {'a'} ++ local B = {'a',1,3} ++ local C = {3,10,2} ++ ++ assert.is_false(M.disjoint(A,B)) ++ assert.is_true(M.disjoint(A,C)) ++ assert.is_false(M.disjoint(B,C)) ++ end) ++ ++ end) ++ ++ describe('symmetricDifference',function() ++ ++ it('returns the symmetric difference from two arrays', function() + local a = {1,3}; local b = {4,2,3}; local c = {2,3,10} +- assert_true(_.same(_.symmetricDifference(a, b), {1,4,2})) +- assert_true(_.same(_.symmetricDifference(a, c), {1,2,10})) +- assert_true(_.same(_.symmetricDifference(b, c), {4,10})) ++ assert.is_true(M.same(M.symmetricDifference(a, b), {1,4,2})) ++ assert.is_true(M.same(M.symmetricDifference(a, c), {1,2,10})) ++ assert.is_true(M.same(M.symmetricDifference(b, c), {4,10})) + end) + + end) + +- context('unique',function() ++ describe('unique',function() + +- test('returns a duplicate-free array',function() +- assert_true(_.isEqual(_.unique({1,1,2,2,3,3,4,4,4,5}),{1,2,3,4,5})) ++ it('returns a duplicate-free array',function() ++ assert.is_true(M.isEqual(M.unique({1,1,2,2,3,3,4,4,4,5}),{1,2,3,4,5})) + end) + + end) + +- context('isunique',function() ++ describe('isunique',function() + +- test('Checks if a given array is duplicate-free',function() +- assert_true(_.isunique({1,2,3,4,5})) +- assert_false(_.isunique({1,2,3,4,4})) ++ it('Checks if a given array is duplicate-free',function() ++ assert.is_true(M.isunique({1,2,3,4,5})) ++ assert.is_false(M.isunique({1,2,3,4,4})) + end) + + end) + +- context('zip',function() +- test('zips together values from different arrays sharing the same index', function() ++ describe('duplicates',function() ++ it('returns a list of all duplicates in array', function() ++ assert.is_true(M.isEqual(M.duplicates({1,2,3,3,8,8,3,2,4}),{2,3,8})) ++ assert.is_true(M.isEqual(M.duplicates({true, false, true, 1, '5', '1', '5'}),{true, '5'})) ++ end) ++ end) ++ ++ describe('zip',function() ++ it('zips together values from different arrays sharing the same index', function() + local names = {'Bob','Alice','James'}; local ages = {22, 23} +- assert_true(_.isEqual(_.zip(names,ages),{{'Bob',22},{'Alice',23},{'James'}})) ++ assert.is_true(M.isEqual(M.zip(names,ages),{{'Bob',22},{'Alice',23},{'James'}})) ++ assert.is_true(M.isEqual(M.zip({false},{false}),{{false,false}})) + end) + end) + +- context('append',function() ++ describe('zipWith',function() ++ it('zips together values from different arrays sharing the same index using a function', function() ++ local names = {'Bob','Alice','James'}; local ages = {22, 23, 25} ++ local function introduce(name, age) return 'My name is '..name..' and I am '..age..' years old.' end ++ local t = M.zipWith(introduce,names,ages) ++ assert.equal(t[1],'My name is Bob and I am 22 years old.') ++ assert.equal(t[2],'My name is Alice and I am 23 years old.') ++ assert.equal(t[3],'My name is James and I am 25 years old.') ++ end) ++ end) ++ ++ describe('append',function() + +- test('appends two arrays together', function() +- assert_true(_.isEqual(_.append({1,2,3},{'a','b'}),{1,2,3,'a','b'})) ++ it('appends two arrays together', function() ++ assert.is_true(M.isEqual(M.append({1,2,3},{'a','b'}),{1,2,3,'a','b'})) + end) + + end) + +- context('interleave',function() ++ describe('interleave',function() + +- test('interleaves values from passed-in arrays', function() +- assert_true(_.isEqual(_.interleave({1,2,3},{'a','b','c'}),{1,'a',2,'b',3,'c'})) +- assert_true(_.isEqual(_.interleave({1},{'a','b','c'}),{1,'a','b','c'})) ++ it('interleaves values from passed-in arrays', function() ++ assert.is_true(M.isEqual(M.interleave({1,2,3},{'a','b','c'}),{1,'a',2,'b',3,'c'})) ++ assert.is_true(M.isEqual(M.interleave({1},{'a','b','c'}),{1,'a','b','c'})) + end) + + end) + +- context('interpose',function() ++ describe('interpose',function() + +- test('interposes a value in-between values from a passed-in array', function() +- assert_true(_.isEqual(_.interpose('a',{1,2,3}),{1,'a',2,'a',3})) +- assert_true(_.isEqual(_.interpose(false,{5,5,5,5}),{5,false,5,false,5,false,5})) +- end) ++ it('interposes a value in-between values from a passed-in array', function() ++ assert.is_true(M.isEqual(M.interpose({1,2,3},'a'),{1,'a',2,'a',3})) ++ assert.is_true(M.isEqual(M.interpose({5,5,5,5}, false),{5,false,5,false,5,false,5})) ++ end) ++ ++ it('leaves the array untouched if containing a single element', function() ++ assert.is_true(M.isEqual(M.interpose({1},'a'),{1})) ++ end) + + end) + +- context('range',function() ++ describe('range',function() + +- test('generate an arithmetic progression', function() +- assert_true(_.isEqual(_.range(1,5,1),{1,2,3,4,5})) +- assert_true(_.isEqual(_.range(-2,5,1),{-2,-1,0,1,2,3,4,5})) +- assert_true(_.isEqual(_.range(1,5,2),{1,3,5})) +- end) +- +- test('arg "step" default to 1 when no given', function() +- assert_true(_.isEqual(_.range(1,5),{1,2,3,4,5})) ++ it('generate an arithmetic progression', function() ++ assert.is_true(M.isEqual(M.range(1,5,1),{1,2,3,4,5})) ++ assert.is_true(M.isEqual(M.range(-2,5,1),{-2,-1,0,1,2,3,4,5})) ++ assert.is_true(M.isEqual(M.range(1,5,2),{1,3,5})) + end) + +- test('when a limit cannot be reached via "step", returns an empty array', function() +- assert_true(_.isEqual(_.range(1,5,0),{})) +- assert_true(_.isEqual(_.range(1,5,-1),{})) ++ it('arg "step" default to 1 or -1 when no given', function() ++ assert.is_true(M.isEqual(M.range(1,5),{1,2,3,4,5})) ++ assert.is_true(M.isEqual(M.range(5,1),{5,4,3,2,1})) + end) + +- test('handles real values as well', function() +- assert_true(_.isEqual(_.range(3.2,5,0.5),{3.2,3.7,4.2,4.7})) ++ it('handles real values as well', function() ++ assert.is_true(M.isEqual(M.range(3.2,5,0.5),{3.2,3.7,4.2,4.7})) + end) + +- test('when only one arg is passed, counts from 0', function() +- assert_true(_.isEqual(_.range(3),{0,1,2,3})) ++ it('when only one arg is passed, counts from 1', function() ++ assert.is_true(M.isEqual(M.range(3),{1,2,3})) ++ assert.is_true(M.isEqual(M.range(-3),{-1,-2,-3})) + end) + + end) + +- context('rep',function() ++ describe('rep',function() + +- test('generates a list of n repetitions of a value', function() +- assert_true(_.isEqual(_.rep('a',4),{'a','a','a','a'})) +- assert_true(_.isEqual(_.rep(false,3),{false, false, false})) ++ it('generates a list of n repetitions of a value', function() ++ assert.is_true(M.isEqual(M.rep('a',4),{'a','a','a','a'})) ++ assert.is_true(M.isEqual(M.rep(false,3),{false, false, false})) ++ end) ++ ++ end) ++ ++ describe('powerset',function() ++ ++ it('generates the powerset of a given array', function() ++ assert.is_true(M.same(M.powerset({1,2,3}),{{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}})) ++ assert.is_true(M.same(M.powerset({1,2,3,4}),{{},{1},{2},{3},{4},{1,2},{1,3},{1,4},{2,3},{2,4},{3,4},{1,2,3},{1,2,4},{1,3,4},{2,3,4},{1,2,3,4}})) + end) + + end) + +- context('partition',function() ++ describe('partition',function() + +- test('iterates on partitions of a given array', function() +- local array = _.range(1,10) +- local split5 = {_.range(1,5), _.range(6,10)} +- local split3 = {_.range(1,3), _.range(4,6), _.range(7,9), {10}} ++ it('iterates on partitions of a given array', function() ++ local array = M.range(1,10) ++ local split5 = {M.range(1,5), M.range(6,10)} ++ local split3 = {M.range(1,3), M.range(4,6), M.range(7,9), {10}} + local i = 0 +- for p in _.partition(array,5) do ++ for p in M.partition(array,5) do + i = i + 1 +- assert_true(_.isEqual(p, split5[i])) ++ assert.is_true(M.isEqual(p, split5[i])) + end + i = 0 +- for p in _.partition(array,3) do ++ for p in M.partition(array,3) do + i = i + 1 +- assert_true(_.isEqual(p, split3[i])) ++ assert.is_true(M.isEqual(p, split3[i])) + end + end) + +- test('if a 3rd argument pad is supplied, will adjust the last partition', function() +- local array = _.range(1,10) ++ it('if a 3rd argument pad is supplied, will adjust the last partition', function() ++ local array = M.range(1,10) + local split4 = {{1,2,3,4},{5,6,7,8},{9,10,0,0}} + local i = 0 +- for p in _.partition(array,4,0) do ++ for p in M.partition(array,4,0) do + i = i + 1 +- assert_true(_.isEqual(p, split4[i])) ++ assert.is_true(M.isEqual(p, split4[i])) + end + end) + + end) + +- context('sliding',function() ++ describe('overlapping',function() + +- test('returns overlapping subsequences', function() +- local array = _.range(1,10) ++ it('returns overlapping subsequences', function() ++ local array = M.range(1,10) + local sliding2 = {{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10}} + local sliding3 = {{1,2,3},{3,4,5},{5,6,7},{7,8,9},{9,10}} + local sliding5 = {{1,2,3,4,5},{5,6,7,8,9},{9,10}} + local i = 0 +- for p in _.sliding(array,2) do ++ for p in M.overlapping(array,2) do + i = i + 1 +- assert_true(_.isEqual(p, sliding2[i])) ++ assert.is_true(M.isEqual(p, sliding2[i])) + end + i = 0 +- for p in _.sliding(array,3) do ++ for p in M.overlapping(array,3) do + i = i + 1 +- assert_true(_.isEqual(p, sliding3[i])) ++ assert.is_true(M.isEqual(p, sliding3[i])) + end + i = 0 +- for p in _.sliding(array,5) do ++ for p in M.overlapping(array,5) do + i = i + 1 +- assert_true(_.isEqual(p, sliding5[i])) ++ assert.is_true(M.isEqual(p, sliding5[i])) + end + end) + +- test('if a 3rd argument pad is supplied, will adjust the last subsequence', function() +- local array = _.range(1,10) ++ it('if a 3rd argument pad is supplied, will adjust the last subsequence', function() ++ local array = M.range(1,10) + local sliding3 = {{1,2,3},{3,4,5},{5,6,7},{7,8,9},{9,10,0}} + local sliding5 = {{1,2,3,4,5},{5,6,7,8,9},{9,10,0,0,0}} + local i = 0 +- for p in _.sliding(array,3,0) do ++ for p in M.overlapping(array,3,0) do + i = i + 1 +- assert_true(_.isEqual(p, sliding3[i])) ++ assert.is_true(M.isEqual(p, sliding3[i])) + end + i = 0 +- for p in _.sliding(array,5,0) do ++ for p in M.overlapping(array,5,0) do + i = i + 1 +- assert_true(_.isEqual(p, sliding5[i])) ++ assert.is_true(M.isEqual(p, sliding5[i])) + end + end) + +- end) +- +- context('permutation',function() ++ end) ++ ++ describe('aperture', function() + +- test('iterates on permutations of a given array', function() +- local array = {'a','b', 'c'} +- local perm = {'abc','acb', 'bac', 'bca', 'cab', 'cba'} +- for p in _.permutation(array) do +- local strp = _.concat(p) +- _.pull(perm, strp) ++ it('returns sliding partitions of a given array', function() ++ local array = M.range(1,5) ++ local slides2 = {{1,2},{2,3},{3,4},{4,5}} ++ local slides3 = {{1,2,3},{2,3,4},{3,4,5}} ++ local slides4 = {{1,2,3,4},{2,3,4,5}} ++ local slides5 = {{1,2,3,4,5}} ++ ++ local i = 0 ++ for p in M.aperture(array, 2) do ++ i = i + 1 ++ assert.is_true(M.isEqual(p, slides2[i])) + end +- assert_true(#perm == 0) +- end) ++ ++ i = 0 ++ for p in M.aperture(array, 3) do ++ i = i + 1 ++ assert.is_true(M.isEqual(p, slides3[i])) ++ end ++ ++ i = 0 ++ for p in M.aperture(array, 4) do ++ i = i + 1 ++ assert.is_true(M.isEqual(p, slides4[i])) ++ end ++ ++ i = 0 ++ for p in M.aperture(array, 5) do ++ i = i + 1 ++ assert.is_true(M.isEqual(p, slides5[i])) ++ end ++ end) ++ ++ end) ++ ++ describe('pairwise', function() ++ ++ it('returns sliding partitions of a given array', function() ++ local array = M.range(1,5) ++ local pw = {{1,2},{2,3},{3,4},{4,5}} ++ ++ local i = 0 ++ for p in M.pairwise(array) do ++ i = i + 1 ++ assert.is_true(M.isEqual(p, pw[i])) ++ end ++ end) + + end) + +- context('invert',function() ++ describe('permutation',function() + +- test('switches key-values pairs', function() +- assert_true(_.isEqual(_.invert({1,2,3}),{1,2,3})) +- assert_true(_.isEqual(_.invert({'a','b','c'}),{a = 1,b = 2,c = 3})) +- end) ++ it('iterates on permutations of a given array', function() ++ local array = {'a','b', 'c'} ++ local perm = {'abc','acb', 'bac', 'bca', 'cab', 'cba'} ++ for p in M.permutation(array) do ++ local strp = M.concat(p) ++ M.pull(perm, strp) ++ end ++ assert.is_true(#perm == 0) ++ end) + +- end) ++ end) + +- context('concat',function() ++ describe('concat',function() + +- test('concatenates an array contents', function() +- assert_equal(_.concat({1,2,3,4}),'1234') +- assert_equal(_.concat({'a',1,0,1,'b'}),'a101b') ++ it('concatenates an array contents', function() ++ assert.equal(M.concat({1,2,3,4}),'1234') ++ assert.equal(M.concat({'a',1,0,1,'b'}),'a101b') + end) + +- test('handles boolean values', function() +- assert_equal(_.concat({1,true,3,false}),'1true3false') ++ it('handles boolean values', function() ++ assert.equal(M.concat({1,true,3,false}),'1true3false') + end) + +- test('when arg "sep" is given, uses "sep" as a separator', function() +- assert_equal(_.concat({1,3,false,'A'},' '),'1 3 false A') +- assert_equal(_.concat({1,3,false,'A'},', '),'1, 3, false, A') ++ it('when arg "sep" is given, uses "sep" as a separator', function() ++ assert.equal(M.concat({1,3,false,'A'},' '),'1 3 false A') ++ assert.equal(M.concat({1,3,false,'A'},', '),'1, 3, false, A') + end) + +- test('when args "i" and/or "j" are given, concats values within "i" and "j" indexes', function() +- assert_equal(_.concat({1,3,false,'A'},' ',2,3),'3 false') +- assert_equal(_.concat({1,3,false,'A'},', ',2,3),'3, false') +- assert_equal(_.concat({1,3,false,'A','K'},' ',2),'3 false A K') ++ it('when args "i" and/or "j" are given, concats values within "i" and "j" indexes', function() ++ assert.equal(M.concat({1,3,false,'A'},' ',2,3),'3 false') ++ assert.equal(M.concat({1,3,false,'A'},', ',2,3),'3, false') ++ assert.equal(M.concat({1,3,false,'A','K'},' ',2),'3 false A K') + end) + ++ end) ++ ++ describe('xprod',function() ++ ++ it('returns all possible pairs', function() ++ local r = M.xprod({1,2,3},{'a','b'}) ++ assert.is_true(M.isEqual(r[1],{1,'a'})) ++ assert.is_true(M.isEqual(r[2],{1,'b'})) ++ assert.is_true(M.isEqual(r[3],{2,'a'})) ++ assert.is_true(M.isEqual(r[4],{2,'b'})) ++ assert.is_true(M.isEqual(r[5],{3,'a'})) ++ assert.is_true(M.isEqual(r[6],{3,'b'})) ++ end) ++ ++ end) ++ ++ describe('xpairs',function() ++ ++ it('create pairs by prepending value to array values', function() ++ local r = M.xpairs(1,{1,2,3}) ++ assert.is_true(M.isEqual(r[1],{1,1})) ++ assert.is_true(M.isEqual(r[2],{1,2})) ++ assert.is_true(M.isEqual(r[3],{1,3})) ++ end) ++ ++ end) ++ ++ describe('xpairsRight',function() ++ ++ it('create pairs by appending value to array values', function() ++ local r = M.xpairsRight(1,{1,2,3}) ++ assert.is_true(M.isEqual(r[1],{1,1})) ++ assert.is_true(M.isEqual(r[2],{2,1})) ++ assert.is_true(M.isEqual(r[3],{3,1})) ++ end) ++ ++ end) ++ ++ describe('sum',function() ++ ++ it('returns the sum of array values', function() ++ assert.equal(M.sum {1,2,3,4,5}, 15) ++ assert.equal(M.sum {1,2,3,4}, 10) ++ assert.equal(M.sum {1,2,3}, 6) ++ end) ++ ++ end) ++ ++ describe('product',function() ++ ++ it('returns the product of array values', function() ++ assert.equal(M.product {1,2,3,4,5}, 120) ++ assert.equal(M.product {1,2,3,4}, 24) ++ assert.equal(M.product {1,2,3}, 6) ++ end) ++ ++ end) ++ ++ describe('mean',function() ++ ++ it('returns the mean of array values', function() ++ assert.equal(M.mean {1,2,3,4,5}, 3) ++ assert.equal(M.mean {1,2,3,4}, 2.5) ++ end) ++ ++ end) ++ ++ describe('meadian',function() ++ ++ it('returns the median of array values', function() ++ assert.equal(M.median {1,2,3,4,5}, 3) ++ assert.equal(M.median {1,2,3,4}, 2.5) ++ end) ++ + end) + + end) +\ No newline at end of file +diff --git a/extra/moses/spec/chaining_spec.lua b/extra/moses/spec/chaining_spec.lua +index 065491a..fb11b90 100644 +--- a/extra/moses/spec/chaining_spec.lua ++++ b/extra/moses/spec/chaining_spec.lua +@@ -1,34 +1,34 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Chaining specs', function() ++describe('Chaining specs', function() + +- context('chain', function() ++ describe('chain', function() + +- test('Chains a value',function() +- local v = _.chain({1,2,3,4}) +- :filter(function(i,k) return k%2~=0 end) ++ it('Chains a value',function() ++ local v = M.chain({1,2,3,4}) ++ :filter(function(k) return k%2~=0 end) + :max() + :value() +- assert_equal(v, 3) ++ assert.equal(v, 3) + end) + +- test('_(value) is the same as _.chain(value)', function() +- local v = _({1,2,3,4}) +- :filter(function(i,k) return k%2~=0 end) ++ it('M(value) is the same as M.chain(value)', function() ++ local v = M({1,2,3,4}) ++ :filter(function(k) return k%2~=0 end) + :max() + :value() +- assert_equal(v, 3) ++ assert.equal(v, 3) + end) + + end) + +- context('value', function() ++ describe('value', function() + +- test('Unwraps a chained object',function() ++ it('Unwraps a chained object',function() + local t = {1,2,3} +- assert_equal(_.chain(t):value(), t) +- assert_equal(_(t):value(), t) ++ assert.equal(M.chain(t):value(), t) ++ assert.equal(M(t):value(), t) + end) + + end) +diff --git a/extra/moses/spec/func_spec.lua b/extra/moses/spec/func_spec.lua +index 2803922..627f519 100644 +--- a/extra/moses/spec/func_spec.lua ++++ b/extra/moses/spec/func_spec.lua +@@ -1,537 +1,789 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Utility functions specs', function() ++describe('Utility functions specs', function() + +- context('noop', function() ++ describe('noop', function() + +- test('the no-operation function',function() +- assert_nil(_.noop()) +- assert_nil(_.noop(nil)) +- assert_nil(_.noop(false)) +- assert_nil(_.noop({})) +- assert_nil(_.noop(function() end)) +- assert_nil(_.noop(_.noop)) ++ it('the no-operation function',function() ++ assert.is_nil(M.noop()) ++ assert.is_nil(M.noop(nil)) ++ assert.is_nil(M.noop(false)) ++ assert.is_nil(M.noop({})) ++ assert.is_nil(M.noop(function() end)) ++ assert.is_nil(M.noop(M.noop)) + end) + + end) + +- context('identity', function() ++ describe('identity', function() + +- test('returns the received value',function() +- assert_equal(_.identity(1),1) ++ it('returns the received value',function() ++ assert.equal(M.identity(1),1) + local v = {x = 0} +- assert_equal(_.identity(v),v) +- assert_not_equal(v,{x = 0}) ++ assert.equal(M.identity(v),v) ++ assert.is_not.equals(v,{x = 0}) + end) + + end) + +- context('constant', function() ++ describe('call', function() + +- test('creates a constant function',function() +- local gravity = _.constant(9.81) +- assert_equal(gravity(),9.81) +- assert_equal(gravity(10), 9.81) +- assert_equal(gravity(nil), 9.81) ++ it('calls f(...) and returns the results',function() ++ assert.equal(M.call(math.pow, 2, 3), 8) ++ assert.equal(M.call(string.len, 'hello' ), 5) ++ assert.equal(M.call(table.concat, {1,2,3,4,5}, ',', 2, 4),"2,3,4") + end) + + end) + +- context('memoize', function() ++ describe('applySpec', function() ++ ++ it('returns a spec function which produces objects',function() ++ local stats = M.applySpec({ ++ min = function(...) return math.min(...) end, ++ max = function(...) return math.max(...) end, ++ }) + +- local fib_time, fib_value, mfib_time, mfib_value +- local fib, mfib +- +- before(function() +- local function fib(n) +- return n < 2 and n or fib(n-1)+fib(n-2) +- end +- local times = 10 +- local mfib = _.memoize(fib) +- fib_time = os.clock() +- for i = 1, times do fib_value = (fib_value or 0)+fib(20) end +- fib_time = (os.clock()-fib_time)*1000 +- +- mfib_time = os.clock() +- for i = 1, times do mfib_value = (mfib_value or 0)+mfib(20) end +- mfib_time = (os.clock()-mfib_time )*1000 +- end) ++ for i = 1, 10 do ++ local mn, mx = math.random(1,10), math.random(11,20) ++ local t = M.range(mn, mx) ++ table.sort(t) ++ local unpack = unpack or table.unpack ++ local r = stats(unpack(t)) ++ assert.equal(r.min, t[1]) ++ assert.equal(r.max, t[#t]) ++ end ++ end) ++ ++ end) ++ ++ describe('thread', function() ++ ++ it('threads a value through functions',function() ++ local function inc(x) return x + 1 end ++ local function double(x) return 2 * x end ++ local function square(x) return x * x end ++ assert.equal(M.thread(2, inc, double, square), 36) ++ assert.equal(M.thread(3, double, inc, square), 49) ++ assert.equal(M.thread(4, square, double, inc), 33) ++ assert.equal(M.thread(5, square, inc, double), 52) ++ end) ++ ++ it('accepts funcs taking more than one arg',function() ++ local function inc(x) return x + 1 end ++ local function add(x, y) return x + y end ++ local function pow(x, y) return x ^ y end ++ assert.equal(M.thread(2, inc, {add, 3}, {pow, 2}), 36) ++ assert.equal(M.thread(2, {add, 4}, inc, {pow, 2}), 49) ++ end) ++ ++ end) ++ ++ describe('threadRight', function() ++ ++ it('threads a value through functions',function() ++ local function inc(x) return x + 1 end ++ local function double(x) return 2 * x end ++ local function square(x) return x * x end ++ assert.equal(M.threadRight(2, inc, double, square), 36) ++ assert.equal(M.threadRight(3, double, inc, square), 49) ++ assert.equal(M.threadRight(4, square, double, inc), 33) ++ assert.equal(M.threadRight(5, square, inc, double), 52) ++ end) ++ ++ it('accepts funcs taking more than one arg',function() ++ local function inc(x) return x + 1 end ++ local function add(x, y) return x + y end ++ local function pow(x, y) return x ^ y end ++ assert.equal(M.threadRight(2, inc, {add, 3}, {pow, 2}), 64) ++ assert.equal(M.threadRight(2, {add, 4}, inc, {pow, 2}), 128) ++ end) ++ ++ end) ++ ++ describe('dispatch', function() + +- test('memoizes an expensive function by caching its results',function() +- assert_true(mfib_time<=fib_time) ++ it('produces a dispatch function',function() ++ local f = M.dispatch( ++ function() return nil end, ++ function (v) return v+1 end, ++ function (v) return 2*v end ++ ) ++ assert.equal(f(5),6) ++ assert.equal(f(7),8) + end) + +- test('can take a hash function to compute an unique output for multiple args',function() +- +- local function hash(a,b) return (a^13+b^19) end +- local function fact(a) return a <= 1 and 1 or a*fact(a-1) end +- local diffFact = function(a,b) return fact(a)-fact(b) end +- local mdiffFact = _.memoize(function(a,b) return fact(a)-fact(b) end,hash) +- local times, rep = 100, 10 +- +- local time = os.clock() +- for j = 1,times do +- for ai = 1,rep do +- for aj = 1,rep do diffFact(ai,aj) end +- end +- end +- time = (os.clock()-time)*1000 +- +- local mtime = os.clock() +- for j = 1,times do +- for ai = 1,rep do +- for aj = 1,rep do mdiffFact(ai,aj) end +- end +- end +- mtime = (os.clock()-mtime)*1000 +- +- assert_true(mtime<=time) ++ end) ++ ++ describe('memoize', function() + ++ local fib_time, fib_value, mfib_time, mfib_value ++ local fib, mfib ++ ++ it('memoizes an expensive function by caching its results',function() ++ local function fib(n) return n < 2 and n or fib(n-1)+fib(n-2) end ++ local mfib = M.memoize(fib) ++ assert.equal(fib(10), mfib(10)) ++ assert.equal(fib(15), mfib(15)) ++ assert.equal(fib(8), mfib(8)) ++ assert.equal(fib(13), mfib(13)) + end) + +- end) ++ end) ++ ++ describe('unfold', function() ++ ++ it('builds a list from a seed value using a iterator',function() ++ local function iter(seed) ++ if seed < 100 then return seed, seed * 2 end ++ end ++ assert.is_true(M.isEqual(M.unfold(iter,1),{1,2,4,8,16,32,64})) ++ assert.is_true(M.isEqual(M.unfold(iter,5),{5, 10,20,40,80})) ++ assert.is_true(M.isEqual(M.unfold(iter,10),{10,20,40,80})) ++ end) ++ ++ end) + +- context('once', function() ++ describe('once', function() + +- test('returns a version of a function that runs once',function() +- local sq = _.once(function(a) return a*a end) +- assert_equal(sq(2),4) ++ it('returns a version of a function that runs once',function() ++ local sq = M.once(function(a) return a*a end) ++ assert.equal(sq(2),4) + end) + +- test('successive calls will keep yielding the original answer',function() +- local sq = _.once(function(a) return a*a end) ++ it('successive calls will keep yielding the original answer',function() ++ local sq = M.once(function(a) return a*a end) + for i = 1,10 do +- assert_equal(sq(i),1) ++ assert.equal(sq(i),1) + end + end) + + end) + +- context('before', function() ++ describe('before', function() + +- test('returns a version of a function that runs no more than count-th calls',function() ++ it('returns a version of a function that runs no more than count-th calls',function() + local function say(something) return something end +- local speak3times = _.before(say, 3) +- assert_equal(speak3times('a'), 'a') +- assert_equal(speak3times('b'), 'b') +- assert_equal(speak3times('c'), 'c') +- assert_equal(speak3times('d'), 'c') +- assert_equal(speak3times('e'), 'c') +- assert_equal(speak3times('f'), 'c') ++ local speak3times = M.before(say, 3) ++ assert.equal(speak3times('a'), 'a') ++ assert.equal(speak3times('b'), 'b') ++ assert.equal(speak3times('c'), 'c') ++ assert.equal(speak3times('d'), 'c') ++ assert.equal(speak3times('e'), 'c') ++ assert.equal(speak3times('f'), 'c') + end) + + end) + + +- context('after', function() ++ describe('after', function() + +- test('returns a function that will respond on its count-th call',function() ++ it('returns a function that will respond on its count-th call',function() + local function a(r) return (r) end +- a = _.after(a,5) ++ a = M.after(a,5) + for i = 1,10 do + if i < 5 then +- assert_nil(a(i)) ++ assert.is_nil(a(i)) + else +- assert_equal(a(i),i) ++ assert.equal(a(i),i) + end + end + end) + + end) + +- context('compose', function() ++ describe('compose', function() + +- test('can compose commutative functions',function() ++ it('can compose commutative functions',function() + local greet = function(name) return "hi: " .. name end + local exclaim = function(sentence) return sentence .. "!" end +- assert_equal(_.compose(greet,exclaim)('moe'),'hi: moe!') +- assert_equal(_.compose(exclaim,greet)('moe'),'hi: moe!') ++ assert.equal(M.compose(greet,exclaim)('moe'),'hi: moe!') ++ assert.equal(M.compose(exclaim,greet)('moe'),'hi: moe!') + end) + +- test('composes mutiple functions',function() ++ it('composes mutiple functions',function() + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +- local compositae = _.compose(f,g,h) +- assert_equal(compositae(10),36) +- assert_equal(compositae(20),121) ++ local compositae = M.compose(f,g,h) ++ assert.equal(compositae(10),36) ++ assert.equal(compositae(20),121) + end) + +- test('compose non commutative functions in reverse order',function() ++ it('compose non commutative functions in reverse order',function() + local function f(s) return (s or '')..'f' end + local function g(s) return (s or '')..'g' end + local function h(s) return (s or '')..'h' end +- assert_equal(_.compose(f,g,h)(),'hgf') +- assert_equal(_.compose(h,g,f)(),'fgh') +- assert_equal(_.compose(f,h,g)(),'ghf') +- assert_equal(_.compose(g,h,f)(),'fhg') ++ assert.equal(M.compose(f,g,h)(),'hgf') ++ assert.equal(M.compose(h,g,f)(),'fgh') ++ assert.equal(M.compose(f,h,g)(),'ghf') ++ assert.equal(M.compose(g,h,f)(),'fhg') + end) + + end) + +- context('pipe', function() ++ describe('pipe', function() + +- test('pipes a value through a series of functions',function() ++ it('pipes a value through a series of functions',function() + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +- assert_equal(_.pipe(10,f,g,h),36) +- assert_equal(_.pipe(20,f,g,h),121) ++ assert.equal(M.pipe(10,f,g,h),36) ++ assert.equal(M.pipe(20,f,g,h),121) + end) + + end) + +- context('complement', function() ++ describe('complement', function() + +- test('returns a function which returns the logical complement of a given function',function() +- assert_false(_.complement(function() return true end)()) +- assert_true(_.complement(function() return false end)()) +- assert_true(_.complement(function() return nil end)()) +- assert_false(_.complement(function() return 1 end)()) ++ it('returns a function which returns the logical complement of a given function',function() ++ assert.is_false(M.complement(function() return true end)()) ++ assert.is_true(M.complement(function() return false end)()) ++ assert.is_true(M.complement(function() return nil end)()) ++ assert.is_false(M.complement(function() return 1 end)()) + end) + + end) + +- context('juxtapose', function() ++ describe('juxtapose', function() + +- test('calls a sequence of functions with the same set of args',function() ++ it('calls a sequence of functions with the same set of args',function() + local function f(x) return x^2 end + local function g(x) return x+1 end + local function h(x) return x/2 end +- local rf, rg, rh = _.juxtapose(10, f, g, h) +- assert_equal(rf, 100) +- assert_equal(rg, 11) +- assert_equal(rh, 5) ++ local rf, rg, rh = M.juxtapose(10, f, g, h) ++ assert.equal(rf, 100) ++ assert.equal(rg, 11) ++ assert.equal(rh, 5) + end) + + end) + +- context('wrap', function() ++ describe('wrap', function() + +- test('wraps a function and passes args',function() ++ it('wraps a function and passes args',function() + local greet = function(name) return "hi: " .. name end +- local backwards = _.wrap(greet, function(f,arg) ++ local backwards = M.wrap(greet, function(f,arg) + return f(arg) ..'\nhi: ' .. arg:reverse() + end) +- assert_equal(backwards('john'),'hi: john\nhi: nhoj') ++ assert.equal(backwards('john'),'hi: john\nhi: nhoj') + end) + + end) + +- context('times', function() ++ describe('times', function() + +- test('calls a given function n times',function() ++ it('calls a given function n times',function() + local f = ('Lua programming'):gmatch('.') +- local r = _.times(3,f) +- assert_true(_.isEqual(r,{'L','u','a'})) ++ local r = M.times(f, 3) ++ assert.is_true(M.isEqual(r,{'L','u','a'})) + + local count = 0 + local function counter() count = count+1 end +- _.times(10,counter) +- assert_equal(count,10) ++ M.times(counter, 10) ++ assert.equal(count,10) + end) + + end) + +- context('bind', function() ++ describe('bind', function() + +- test('binds a value to the first arg of a function',function() +- local sqrt2 = _.bind(math.sqrt,2) +- assert_equal(sqrt2(),math.sqrt(2)) ++ it('binds a value to the first arg of a function',function() ++ local sqrt2 = M.bind(math.sqrt,2) ++ assert.equal(sqrt2(),math.sqrt(2)) + end) + + end) + +- context('bind2', function() ++ describe('bind2', function() + +- test('binds a value to the second arg of a function',function() +- local last2 = _.bind2(_.last,2) ++ it('binds a value to the second arg of a function',function() ++ local last2 = M.bind2(M.last,2) + local r = last2({1,2,3,4,5,6}) +- assert_true(_.isEqual(r, {5,6})) ++ assert.is_true(M.isEqual(r, {5,6})) + end) + + end) + +- context('bindn', function() ++ describe('bindn', function() + +- test('binds n values to as the n-first args of a function',function() ++ it('binds n values to as the n-first args of a function',function() + local function out(...) + return table.concat {...} + end +- out = _.bindn(out,'OutPut',':',' ') +- assert_equal(out(1,2,3),'OutPut: 123') +- assert_equal(out('a','b','c','d'),'OutPut: abcd') ++ out = M.bindn(out,'OutPut',':',' ') ++ assert.equal(out(1,2,3),'OutPut: 123') ++ assert.equal(out('a','b','c','d'),'OutPut: abcd') + end) + + end) + +- context('bindAll', function() ++ describe('bindall', function() + +- test('binds methods to object',function() ++ it('binds methods to object',function() + local window = { + setPos = function(w,x,y) w.x, w.y = x, y end, + setName = function(w,name) w.name = name end, + getName = function(w) return w.name end, + } +- window = _.bindAll(window, 'setPos', 'setName', 'getName') ++ window = M.bindall(window, 'setPos', 'setName', 'getName') + window.setPos(10,15) + window.setName('fooApp') + +- assert_equal(window.x, 10) +- assert_equal(window.y, 15) +- assert_equal(window.name, 'fooApp') +- assert_equal(window.getName(), 'fooApp') ++ assert.equal(window.x, 10) ++ assert.equal(window.y, 15) ++ assert.equal(window.name, 'fooApp') ++ assert.equal(window.getName(), 'fooApp') ++ end) ++ ++ end) ++ ++ describe('cond', function() ++ ++ it('return a function which runs a set of predicates',function() ++ local multipleOf = M.cond({ ++ {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end}, ++ {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end}, ++ {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end}, ++ {function() return true end, function(v) return 'could not find an answer for '..v end} ++ }) ++ for i = 15, 20 do ++ assert.equal(multipleOf(i), ++ i%2 == 0 and i..' is multiple of 2' or ++ i%3 == 0 and i..' is multiple of 3' or ++ 'could not find an answer for '..i) ++ end + end) + + end) + +- context('uniqueId', function() ++ describe('both', function() ++ ++ it('returns a truthy func when all funcs returns true',function() ++ local f = M.both( ++ function(x) return x > 0 end, ++ function(x) return x < 10 end, ++ function(x) return x % 2 == 0 end ++ ) ++ assert.is_true(f(2)) ++ assert.is_true(f(8)) ++ assert.is_false(f(9)) ++ end) ++ ++ end) ++ ++ describe('either', function() ++ ++ it('returns a truthy func when at least one of its funcs returns true',function() ++ local f = M.either( ++ function(x) return x > 0 end, ++ function(x) return x % 2 == 0 end ++ ) ++ assert.is_true(f(0)) ++ assert.is_false(f(-3)) ++ end) ++ ++ end) ++ ++ describe('neither', function() ++ ++ it('returns a truthy func when neither of its funcs returns true',function() ++ local f = M.neither( ++ function(x) return x > 10 end, ++ function(x) return x % 2 == 0 end ++ ) ++ assert.is_false(f(12)) ++ assert.is_false(f(8)) ++ assert.is_true(f(7)) ++ end) ++ ++ end) + +- test('returns an unique (for the current session) integer Id',function() ++ describe('uniqueId', function() ++ ++ it('returns an unique (for the current session) integer Id',function() + local ids = {} + for i = 1,100 do +- local newId = _.uniqueId() +- assert_false(_.include(ids,newId)) +- _.push(ids,newId) ++ local newId = M.uniqueId() ++ assert.is_false(M.include(ids,newId)) ++ M.push(ids,newId) + end + end) + +- test('accepts a string template to format the returned id',function() ++ it('accepts a string template to format the returned id',function() + local ids = {} + for i = 1,100 do +- local newId = _.uniqueId('ID:%s') +- assert_equal(newId,'ID:'..newId:sub(4)) +- assert_false(_.include(ids,newId)) +- _.push(ids,newId) ++ local newId = M.uniqueId('ID:%s') ++ assert.equal(newId,'ID:'..newId:sub(4)) ++ assert.is_false(M.include(ids,newId)) ++ M.push(ids,newId) + end + end) + +- test('accepts a function as argument to format the returned id',function() ++ it('accepts a function as argument to format the returned id',function() + local ids = {} + local formatter = function(ID) return '$'..ID..'$' end + for i = 1,100 do +- local newId = _.uniqueId(formatter) +- assert_not_nil(newId:match('^%$%d+%$$')) +- assert_false(_.include(ids,newId)) +- _.push(ids,newId) ++ local newId = M.uniqueId(formatter) ++ assert.is_true(newId:match('^%$%d+%$$') ~= nil) ++ assert.is_false(M.include(ids,newId)) ++ M.push(ids,newId) + end + end) + + end) + +- context('iterator', function() ++ describe('iterator', function() ++ ++ it('creates an iterator which continuously applies f on an input',function() ++ local next_even = M.iterator(function(x) return x + 2 end, 0) ++ assert.equal(next_even(), 2) ++ assert.equal(next_even(), 4) ++ assert.equal(next_even(), 6) ++ assert.equal(next_even(), 8) ++ assert.equal(next_even(),10) ++ end) ++ ++ it('can be set to run up to a maximum number of calls',function() ++ local next_even = M.iterator(function(x) return x + 2 end, 0, 3) ++ assert.equal(next_even(), 2) ++ assert.equal(next_even(), 4) ++ assert.equal(next_even(), 6) ++ assert.is_nil(next_even()) ++ end) ++ ++ end) ++ ++ describe('skip', function() + +- test('creates an iterator which continuously applies f on an input',function() +- local next_even = _.iterator(function(x) return x + 2 end, 0) +- assert_equal(next_even(), 2) +- assert_equal(next_even(), 4) +- assert_equal(next_even(), 6) +- assert_equal(next_even(), 8) +- assert_equal(next_even(),10) ++ it('consumes the first n values of an iterator',function() ++ local w = "hello" ++ local char = string.gmatch(w,'.') ++ local iter = M.skip(char, 3) ++ assert.equal(iter(), 'l') ++ assert.equal(iter(), 'o') + end) ++ ++ it('consumes the first n values of an iterator',function() ++ local w = "lua" ++ local char = string.gmatch(w,'.') ++ local iter = M.skip(char) ++ assert.equal(iter(), 'u') ++ assert.equal(iter(), 'a') ++ end) + + end) + +- context('array', function() ++ describe('tabulate', function() + +- test('iterates a given iterator and returns its values in an array',function() +- local letters = _.array(('Lua'):gmatch('.')) +- assert_true(_.isEqual(letters,{'L','u','a'})) ++ it('iterates a given iterator and returns its values in an array',function() ++ local letters = M.tabulate(('Lua'):gmatch('.')) ++ assert.is_true(M.isEqual(letters,{'L','u','a'})) + +- local numbers = _.array(pairs(_.range(1,10))) +- assert_true(_.isEqual(numbers,_.range(1,10))) ++ local numbers = M.tabulate(pairs(M.range(1,10))) ++ assert.is_true(M.isEqual(numbers,M.range(1,10))) + end) + +- end) ++ end) ++ ++ describe('iterlen', function() ++ ++ it('returns the iterator length',function() ++ local text = 'letters' ++ local chars = string.gmatch(text, '.') ++ assert.equal(M.iterlen(chars),7) ++ end) ++ ++ it('it consumes the iterator',function() ++ local text = 'lua' ++ local chars = string.gmatch(text, '.') ++ assert.equal(M.iterlen(chars),3) ++ assert.is_nil(chars()) ++ end) ++ ++ end) ++ ++ describe('castArray', function() ++ ++ it('converts value to an array',function() ++ assert.is_true(M.isEqual(M.castArray(1),{1})) ++ assert.is_true(M.isEqual(M.castArray(print),{print})) ++ assert.is_true(M.isEqual(M.castArray(true),{true})) ++ end) ++ ++ it('leaves given value untouched if it is an array',function() ++ local t1 = {1,2} ++ local t2 = {nil, true, false} ++ assert.is_true(M.isEqual(M.castArray(t1),t1)) ++ assert.is_true(M.isEqual(M.castArray(t2),t2)) ++ end) ++ ++ end) + +- context('flip', function() ++ describe('flip', function() + +- test('creates a function which runs f with arguments flipped',function() ++ it('creates a function which runs f with arguments flipped',function() + local function f(...) return table.concat({...}) end +- local flipped = _.flip(f) +- assert_equal(flipped('a','b','c'),'cba') ++ local flipped = M.flip(f) ++ assert.equal(flipped('a','b','c'),'cba') + end) + +- end) ++ end) ++ ++ describe('nthArg', function() ++ ++ it('creates a function which returns the nth arg',function() ++ local f2 = M.nthArg(2) ++ local f3 = M.nthArg(3) ++ local f4 = M.nthArg(4) ++ assert.equal(f2(4,8,5,4,6),8) ++ assert.equal(f3(4,8,5,4,6),5) ++ assert.equal(f4(4,8,5,4,6),4) ++ end) ++ ++ it('if n is negative, will count from the end',function() ++ local f2 = M.nthArg(-2) ++ local f3 = M.nthArg(-3) ++ local f4 = M.nthArg(-4) ++ assert.equal(f2(4,8,5,4,6),4) ++ assert.equal(f3(4,8,5,4,6),5) ++ assert.equal(f4(4,8,5,4,6),8) ++ end) ++ ++ end) ++ ++ describe('unary', function() ++ ++ it('creates a function which accepts only one arg',function() ++ local f = M.unary(function(...) return ... end) ++ assert.equal(f(1),1) ++ assert.equal(f(1,2),1) ++ assert.equal(f(1,2,3),1) ++ end) ++ ++ end) ++ ++ describe('ary', function() ++ ++ it('creates a function which accepts up to n args',function() ++ local f = M.ary(function(...) return ... end, 2) ++ assert.is_true(M.isEqual({f(1,2)},{1,2})) ++ assert.is_true(M.isEqual({f(1,2,3)},{1,2})) ++ assert.is_true(M.isEqual({f(1,2,3,4)},{1,2})) ++ end) ++ ++ end) ++ ++ describe('noarg', function() ++ ++ it('returns a function with an arity of 0',function() ++ local f = M.noarg(function (x) return x or 'default' end) ++ assert.equal(f(1), 'default') ++ assert.equal(f(function() end, 3), 'default') ++ assert.equal(f(nil), 'default') ++ end) ++ ++ end) ++ ++ describe('rearg', function() ++ ++ it('creates a function with args reordered',function() ++ local f = M.rearg(function(...) return ... end, {3,2,1}) ++ assert.is_true(M.isEqual({f(1,2,3)},{3,2,1})) ++ assert.is_true(M.isEqual({f(2,1,3)},{3,1,2})) ++ assert.is_true(M.isEqual({f(3,2,1)},{1,2,3})) ++ end) ++ ++ end) + +- context('over', function() ++ describe('over', function() + +- test('returns a function which applies a set of transforms to its args',function() +- local minmax = _.over(math.min, math.max) +- local maxmin = _.over(math.max, math.min) +- assert_true(_.isEqual(minmax(5,10,12,4,3),{3,12})) +- assert_true(_.isEqual(maxmin(5,10,12,4,3),{12,3})) ++ it('returns a function which applies a set of transforms to its args',function() ++ local minmax = M.over(math.min, math.max) ++ local maxmin = M.over(math.max, math.min) ++ assert.is_true(M.isEqual(minmax(5,10,12,4,3),{3,12})) ++ assert.is_true(M.isEqual(maxmin(5,10,12,4,3),{12,3})) + end) + + end) + +- context('overEvery', function() ++ describe('overEvery', function() + + local alleven, allpositive + +- before(function() +- alleven = function(...) +- for i, v in ipairs({...}) do if v%2~=0 then return false end end +- return true +- end +- +- allpositive = function(...) +- for i, v in ipairs({...}) do if v < 0 then return false end end +- return true +- end +- end) ++ alleven = function(...) ++ for i, v in ipairs({...}) do if v%2~=0 then return false end end ++ return true ++ end ++ ++ allpositive = function(...) ++ for i, v in ipairs({...}) do if v < 0 then return false end end ++ return true ++ end + +- test('checks if all predicates passes truth with args. ',function() +- local allok = _.overEvery(alleven, allpositive) +- assert_false(allok(2,4,-1,8)) +- assert_false(allok(10,3,2,6)) +- assert_true(allok(8,4,6,10)) ++ it('checks if all predicates passes truth with args. ',function() ++ local allok = M.overEvery(alleven, allpositive) ++ assert.is_false(allok(2,4,-1,8)) ++ assert.is_false(allok(10,3,2,6)) ++ assert.is_true(allok(8,4,6,10)) + end) + + end) + +- context('overSome', function() ++ describe('overSome', function() + + local alleven, allpositive + +- before(function() +- alleven = function(...) +- for i, v in ipairs({...}) do if v%2~=0 then return false end end +- return true +- end +- +- allpositive = function(...) +- for i, v in ipairs({...}) do if v < 0 then return false end end +- return true +- end +- end) ++ alleven = function(...) ++ for i, v in ipairs({...}) do if v%2~=0 then return false end end ++ return true ++ end ++ ++ allpositive = function(...) ++ for i, v in ipairs({...}) do if v < 0 then return false end end ++ return true ++ end + +- test('checks if all predicates passes truth with args. ',function() +- local anyok = _.overSome(alleven, allpositive) +- assert_false(anyok(2,4,-1,8)) +- assert_true(anyok(10,3,2,6)) +- assert_false(anyok(-1,-5,-3)) ++ it('checks if all predicates passes truth with args. ',function() ++ local anyok = M.overSome(alleven, allpositive) ++ assert.is_false(anyok(2,4,-1,8)) ++ assert.is_true(anyok(10,3,2,6)) ++ assert.is_false(anyok(-1,-5,-3)) + end) + + end) + +- context('overArgs', function() ++ describe('overArgs', function() + +- test('Creates a function that invokes `f` with its arguments transformed',function() ++ it('Creates a function that invokes `f` with its arguments transformed',function() + local function f(x, y) return {x, y} end + local function triple(x) return x*3 end + local function square(x) return x^2 end +- local new_f = _.overArgs(f, triple, square) +- assert_true(_.isEqual(new_f(1,2), {3,4})) +- assert_true(_.isEqual(new_f(10,10), {30,100})) ++ local new_f = M.overArgs(f, triple, square) ++ assert.is_true(M.isEqual(new_f(1,2), {3,4})) ++ assert.is_true(M.isEqual(new_f(10,10), {30,100})) + end) + +- test('when supplied more args than transforms, remaining are left as-is',function() ++ it('when supplied more args than transforms, remaining are left as-is',function() + local function f(x, y, z, k) return {x, y, z, k} end + local function triple(x) return x*3 end + local function square(x) return x^2 end +- local new_f = _.overArgs(f, triple, square) +- assert_true(_.isEqual(new_f(1,2,3,4), {3,4,3,4})) +- assert_true(_.isEqual(new_f(10,10,10,10), {30,100,10,10})) ++ local new_f = M.overArgs(f, triple, square) ++ assert.is_true(M.isEqual(new_f(1,2,3,4), {3,4,3,4})) ++ assert.is_true(M.isEqual(new_f(10,10,10,10), {30,100,10,10})) + end) + + end) + +- context('partial', function() ++ describe('converge', function() ++ ++ it('', function() ++ local function pow2(x) return x*x end ++ local function pow3(x) return x*x*x end ++ local function sum(a,b) return a+b end ++ local poly = M.converge(sum, pow2, pow3) ++ assert.equal(poly(5), 150) ++ assert.equal(poly(1), 2) ++ assert.equal(poly(3), 36) ++ end) ++ ++ end) ++ ++ describe('partial', function() + +- test('applies partially f',function() ++ it('applies partially f',function() + local function diff(a, b) return a - b end +- local diffFrom20 = _.partial(diff, 20) +- assert_equal(diffFrom20(5), 15) +- assert_equal(diffFrom20(10), 10) +- assert_equal(diffFrom20(-5), 25) ++ local diffFrom20 = M.partial(diff, 20) ++ assert.equal(diffFrom20(5), 15) ++ assert.equal(diffFrom20(10), 10) ++ assert.equal(diffFrom20(-5), 25) + end) + +- test('\'_\' can be used as a placeholder',function() ++ it('\'_\' can be used as a placeholder',function() + local function diff(a, b) return a - b end +- local remove10 = _.partial(diff, '_',10) +- assert_equal(remove10(5), -5) +- assert_equal(remove10(10), 0) +- assert_equal(remove10(15), 5) ++ local remove10 = M.partial(diff, '_',10) ++ assert.equal(remove10(5), -5) ++ assert.equal(remove10(10), 0) ++ assert.equal(remove10(15), 5) + end) + + end) + +- context('partialRight', function() ++ describe('partialRight', function() + +- test('applies partial but from the right',function() ++ it('applies partial but from the right',function() + local function concat(a,b,c,d) return a..b..c..d end +- assert_equal(_.partialRight(concat,'a','b','c')('d'), 'dabc') +- assert_equal(_.partialRight(concat,'a','b')('c','d'), 'cdab') +- assert_equal(_.partialRight(concat,'a')('b','c','d'), 'bcda') ++ assert.equal(M.partialRight(concat,'a','b','c')('d'), 'dabc') ++ assert.equal(M.partialRight(concat,'a','b')('c','d'), 'cdab') ++ assert.equal(M.partialRight(concat,'a')('b','c','d'), 'bcda') + end) + +- test('\'_\' can be used as a placeholder',function() ++ it('\'_\' can be used as a placeholder',function() + local function concat(a,b,c,d) return a..b..c..d end +- assert_equal(_.partialRight(concat,'a','_','c')('d','b'), 'badc') +- assert_equal(_.partialRight(concat,'a','b','_')('c','d'), 'dabc') +- assert_equal(_.partialRight(concat,'_','a')('b','c','d'), 'cdba') ++ assert.equal(M.partialRight(concat,'a','_','c')('d','b'), 'badc') ++ assert.equal(M.partialRight(concat,'a','b','_')('c','d'), 'dabc') ++ assert.equal(M.partialRight(concat,'_','a')('b','c','d'), 'cdba') + end) + + end) + +- context('curry', function() ++ describe('curry', function() + +- test('curries a function for a specific number of args',function() ++ it('curries a function for a specific number of args',function() + local function sumOf5args(a,b,c,d,e) return a+b+c+d+e end +- local curried_sumOf5args = _.curry(sumOf5args, 5) +- assert_equal(curried_sumOf5args(1)(2)(3)(4)(5),15) +- assert_equal(curried_sumOf5args(8)(-2)(4)(-10)(1),1) ++ local curried_sumOf5args = M.curry(sumOf5args, 5) ++ assert.equal(curried_sumOf5args(1)(2)(3)(4)(5),15) ++ assert.equal(curried_sumOf5args(8)(-2)(4)(-10)(1),1) + end) + +- test('n_args defaults to 2 when not supplied',function() ++ it('n_args defaults to 2 when not supplied',function() + local function prod(x,y) return x*y end +- local curried_prod = _.curry(prod) +- assert_equal(curried_prod(2)(3), (_.curry(prod,2))(2)(3)) +- assert_equal(curried_prod(-2)(6), (_.curry(prod,2))(-2)(6)) ++ local curried_prod = M.curry(prod) ++ assert.equal(curried_prod(2)(3), (M.curry(prod,2))(2)(3)) ++ assert.equal(curried_prod(-2)(6), (M.curry(prod,2))(-2)(6)) + end) + +- test('n_args can be equal to 1',function() +- local curried_identity = _.curry(_.identity,1) +- assert_equal(curried_identity('value'), 'value') +- assert_equal(curried_identity(1), 1) +- assert_equal(curried_identity(true), true) +- assert_equal(curried_identity(false), false) ++ it('n_args can be equal to 1',function() ++ local curried_identity = M.curry(M.identity,1) ++ assert.equal(curried_identity('value'), 'value') ++ assert.equal(curried_identity(1), 1) ++ assert.equal(curried_identity(true), true) ++ assert.equal(curried_identity(false), false) + end) + +- test('giving more args than n_args will raise an error',function() ++ it('giving more args than n_args will raise an error',function() + local function add(a,b) return a+b end +- local curried_add = _.curry(add, 2) +- assert_error(function() curried_add(1)(2)(3) end) +- assert_error(function() curried_add(4)(5)(6)(7)(8) end) ++ local curried_add = M.curry(add, 2) ++ assert.error(function() curried_add(1)(2)(3) end) ++ assert.error(function() curried_add(4)(5)(6)(7)(8) end) + end) + +- test('When given less than n_args, it will wait for missing args',function() ++ it('When given less than n_args, it will wait for missing args',function() + local function add(a,b,c) return a+b+c end +- local curried_add = _.curry(add, 3) ++ local curried_add = M.curry(add, 3) + local c1 = curried_add(1) + local c2 = c1(2) + local c3 = c2(3) +- assert_type(c1, 'function') +- assert_type(c2, 'function') +- assert_equal(c3, 6) ++ assert.equal(type(c1), 'function') ++ assert.equal(type(c2), 'function') ++ assert.equal(c3, 6) + end) + + end) + +- context('time', function() ++ describe('time', function() + +- test('returns the execution time of a function and its results', function() ++ it('returns the execution time of a function and its results', function() + local function f(...) return ... end + +- local duration, r = _.time(f, 'a') +- assert_type(duration, 'number') +- assert_equal(r, 'a') ++ local duration, r = M.time(f, 'a') ++ assert.equal(type(duration), 'number') ++ assert.equal(r, 'a') + +- local duration, a, b, c = _.time(f, 1, 2, 3) +- assert_type(duration, 'number') +- assert_true(a == 1 and b == 2 and c == 3) ++ local duration, a, b, c = M.time(f, 1, 2, 3) ++ assert.equal(type(duration), 'number') ++ assert.is_true(a == 1 and b == 2 and c == 3) + end) + + end) +diff --git a/extra/moses/spec/import_spec.lua b/extra/moses/spec/import_spec.lua +index c158510..1a64cd3 100644 +--- a/extra/moses/spec/import_spec.lua ++++ b/extra/moses/spec/import_spec.lua +@@ -1,32 +1,20 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Import specs', function() ++describe('Import specs', function() + +- test('imports all library function to a given context', function() +- local funcs = _.functions() +- local context = _.import({}) +- assert_true(_.all(funcs, function(k, n) return _.has(context, n) end)) ++ it('imports all library function to a given context', function() ++ local funcs = M.functions() ++ local context = M.import({}) ++ assert.is_true(M.all(funcs, function(n) return M.has(context, n) end)) + end) + +- test('passing "noConflict" will preserve already existing keys', function() +- local funcs = _.functions() +- local context = _.import({each = 1, all = 2}, true) +- assert_true(_.all(funcs, function(k, n) return _.has(context, n) end)) +- assert_equal(context.each, 1) +- assert_equal(context.all, 2) +- end) +- +- test('The context will default to the global _G if not supplied', function() +- local oldG = _.clone(_G,true) +- assert_not_equal(_G, oldG) +- _.import() +- local funcs = _.functions() +- _.each(funcs, function(__, fname) +- assert_not_nil(_G[fname]) +- assert_true(type(_G[fname]) == 'function') +- end) +- _G = oldG ++ it('passing "noConflict" will preserve already existing keys', function() ++ local funcs = M.functions() ++ local context = M.import({each = 1, all = 2}, true) ++ assert.is_true(M.all(funcs, function(n) return M.has(context, n) end)) ++ assert.equal(context.each, 1) ++ assert.equal(context.all, 2) + end) + + end) +\ No newline at end of file +diff --git a/extra/moses/spec/object_spec.lua b/extra/moses/spec/object_spec.lua +index 139083a..c82bb42 100644 +--- a/extra/moses/spec/object_spec.lua ++++ b/extra/moses/spec/object_spec.lua +@@ -1,612 +1,681 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Object functions specs', function() ++describe('Object functions specs', function() + +- context('keys', function() ++ describe('keys', function() + +- test('collects a given object attributes',function() +- assert_true(_.isEqual(_.keys({1,2,3}),{1,2,3})) +- assert_true(_.isEqual(_.keys({4,5,6}),{1,2,3})) +- assert_true(_.same(_.keys({x = 1, y = 2, 3}),{'x','y',1})) ++ it('collects a given object attributes',function() ++ assert.is_true(M.isEqual(M.keys({1,2,3}),{1,2,3})) ++ assert.is_true(M.isEqual(M.keys({4,5,6}),{1,2,3})) ++ assert.is_true(M.same(M.keys({x = 1, y = 2, 3}),{'x','y',1})) + end) + + end) + +- context('values', function() ++ describe('values', function() + +- test('collects an given object values',function() +- assert_true(_.isEqual(_.values({1,2,3}),{1,2,3})) +- assert_true(_.isEqual(_.values({4,5,6}),{4,5,6})) +- assert_true(_.same(_.values({x = 1, y = 2, 3}),{1,2,3})) ++ it('collects an given object values',function() ++ assert.is_true(M.isEqual(M.values({1,2,3}),{1,2,3})) ++ assert.is_true(M.isEqual(M.values({4,5,6}),{4,5,6})) ++ assert.is_true(M.same(M.values({x = 1, y = 2, 3}),{1,2,3})) + end) + +- end) ++ end) ++ ++ describe('path', function() ++ ++ it('return the value at a given path in object',function() ++ local entity = { ++ pos = {x = 1}, ++ engine = { ++ left = {status = 'active'}, ++ right = {damage = 10} ++ }, ++ boost = false ++ } ++ assert.equal(M.path(entity, 'pos','x'), 1) ++ assert.equal(M.path(entity, 'engine','left','status'), 'active') ++ assert.equal(M.path(entity, 'engine','right','damage'), 10) ++ assert.equal(M.path(entity, 'boost'), false) ++ assert.is_nil(M.path(entity, 'x')) ++ end) ++ ++ end) + +- context('kvpairs', function() ++ describe('spreadPath', function() ++ ++ it('spreads objects under property path',function() ++ local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} ++ M.spreadPath(obj, 'c', 'f') ++ assert.equal(obj.a, 1) ++ assert.equal(obj.b, 2) ++ assert.equal(obj.d, 3) ++ assert.equal(obj.e, 4) ++ assert.equal(obj.g, 5) ++ assert.is_true(M.isEmpty(obj.c)) ++ assert.is_true(M.isEmpty(obj.c.f)) ++ end) ++ ++ end) ++ ++ describe('flattenPath', function() ++ ++ it('flattens objects under property path',function() ++ local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} ++ M.flattenPath(obj, 'c', 'f') -- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}} ++ assert.equal(obj.a, 1) ++ assert.equal(obj.b, 2) ++ assert.equal(obj.d, 3) ++ assert.equal(obj.e, 4) ++ assert.equal(obj.g, 5) ++ assert.equal(M.size(obj.c), 3) ++ assert.equal(obj.c.d, 3) ++ assert.equal(obj.c.e, 4) ++ assert.equal(M.size(obj.c.f), 1) ++ assert.equal(obj.c.f.g, 5) ++ end) ++ ++ end) ++ ++ describe('kvpairs', function() + +- test('converts key-values pairs in object to array-list of k,v pairs',function() +- local obj = _.kvpairs({x = 1, y = 2, z = 3}) ++ it('converts key-values pairs in object to array-list of k,v pairs',function() ++ local obj = M.kvpairs({x = 1, y = 2, z = 3}) + table.sort(obj, function(a,b) return a[1] < b[1] end) +- assert_true(_.isEqual(obj[1],{'x',1})) +- assert_true(_.isEqual(obj[2],{'y',2})) +- assert_true(_.isEqual(obj[3],{'z',3})) ++ assert.is_true(M.isEqual(obj[1],{'x',1})) ++ assert.is_true(M.isEqual(obj[2],{'y',2})) ++ assert.is_true(M.isEqual(obj[3],{'z',3})) + end) + + end) + +- context('toObj', function() ++ describe('toObj', function() + +- test('converts an array-list of {k,v} pairs to an object',function() +- local obj = _.toObj({{'x',1},{'y',2},{'z',3}}) +- assert_equal(obj.x,1) +- assert_equal(obj.y,2) +- assert_equal(obj.z,3) ++ it('converts an array-list of {k,v} pairs to an object',function() ++ local obj = M.toObj({{'x',1},{'y',2},{'z',3}}) ++ assert.equal(obj.x,1) ++ assert.equal(obj.y,2) ++ assert.equal(obj.z,3) + end) + + end) + +- context('property', function() ++ describe('invert',function() + +- test('Returns a function that will return the key property of any passed-in object.',function() +- assert_equal(_.property('sin')(math), math.sin) +- assert_equal(_.property('find')(string), string.find) +- assert_equal(_.property('insert')(table), table.insert) +- assert_equal(_.property('yield')(coroutine), coroutine.yield) ++ it('switches key-values pairs', function() ++ assert.is_true(M.isEqual(M.invert({1,2,3}),{1,2,3})) ++ assert.is_true(M.isEqual(M.invert({'a','b','c'}),{a = 1,b = 2,c = 3})) ++ assert.is_true(M.isEqual(M.invert({x = 4, y = 2}),{[2]='y',[4]='x'})) ++ end) ++ ++ end) ++ ++ describe('property', function() ++ ++ it('Returns a function that will return the key property of any passed-in object.',function() ++ assert.equal(M.property('sin')(math), math.sin) ++ assert.equal(M.property('find')(string), string.find) ++ assert.equal(M.property('insert')(table), table.insert) ++ assert.equal(M.property('yield')(coroutine), coroutine.yield) + end) + + end) + +- context('propertyOf', function() ++ describe('propertyOf', function() + +- test('Returns a function which will return the value of an object property.',function() +- assert_equal(_.propertyOf(math)('cos'), math.cos) +- assert_equal(_.propertyOf(string)('char'), string.char) +- assert_equal(_.propertyOf(table)('remove'), table.remove) +- assert_equal(_.propertyOf(_)('propertyOf'), _.propertyOf) ++ it('Returns a function which will return the value of an object property.',function() ++ assert.equal(M.propertyOf(math)('cos'), math.cos) ++ assert.equal(M.propertyOf(string)('char'), string.char) ++ assert.equal(M.propertyOf(table)('remove'), table.remove) ++ assert.equal(M.propertyOf(M)('propertyOf'), M.propertyOf) + end) + + end) + + +- context('toBoolean', function() +- +- test('converts a value to a boolean',function() +- assert_true(type(_.toBoolean(true)) == 'boolean') +- assert_true(type(_.toBoolean(1)) == 'boolean') +- assert_true(type(_.toBoolean(false)) == 'boolean') +- assert_true(type(_.toBoolean(nil)) == 'boolean') +- assert_true(type(_.toBoolean({})) == 'boolean') +- assert_true(type(_.toBoolean(1/0)) == 'boolean') ++ describe('toBoolean', function() ++ ++ it('converts a value to a boolean',function() ++ assert.is_true(type(M.toBoolean(true)) == 'boolean') ++ assert.is_true(type(M.toBoolean(1)) == 'boolean') ++ assert.is_true(type(M.toBoolean(false)) == 'boolean') ++ assert.is_true(type(M.toBoolean(nil)) == 'boolean') ++ assert.is_true(type(M.toBoolean({})) == 'boolean') ++ assert.is_true(type(M.toBoolean(1/0)) == 'boolean') + +- assert_true(_.toBoolean(true)) +- assert_true(_.toBoolean(1)) +- assert_false(_.toBoolean(false)) +- assert_false(_.toBoolean(nil)) +- assert_true(_.toBoolean({})) +- assert_true(_.toBoolean(1/0)) ++ assert.is_true(M.toBoolean(true)) ++ assert.is_true(M.toBoolean(1)) ++ assert.is_false(M.toBoolean(false)) ++ assert.is_false(M.toBoolean(nil)) ++ assert.is_true(M.toBoolean({})) ++ assert.is_true(M.toBoolean(1/0)) + end) + + end) + +- context('extend', function() ++ describe('extend', function() + +- test('extends a destination objects with key-values a source object',function() +- assert_true(_.isEqual(_.extend({},{a = 'b'}),{a = 'b'})) ++ it('extends a destination objects with key-values a source object',function() ++ assert.is_true(M.isEqual(M.extend({},{a = 'b'}),{a = 'b'})) + end) + +- test('source properties overrides destination properties',function() +- assert_true(_.isEqual(_.extend({a = 'a'},{a = 'b'}),{a = 'b'})) ++ it('source properties overrides destination properties',function() ++ assert.is_true(M.isEqual(M.extend({a = 'a'},{a = 'b'}),{a = 'b'})) + end) + +- test('leaves source object untouched',function() ++ it('leaves source object untouched',function() + local source = {i = 'i'} +- assert_true(_.isEqual(_.extend({a = 'a'},source),{a = 'a',i = 'i'})) +- assert_true(_.isEqual(source,{i = 'i'})) ++ assert.is_true(M.isEqual(M.extend({a = 'a'},source),{a = 'a',i = 'i'})) ++ assert.is_true(M.isEqual(source,{i = 'i'})) + end) + +- test('can extend destination from multiple sources',function() ++ it('can extend destination from multiple sources',function() + local sourceA = {a = 'a'}; local sourceBC = {b = 'b', c = 'c'} +- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{a = 'a', b = 'b', c = 'c'})) ++ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{a = 'a', b = 'b', c = 'c'})) + end) + +- test('extending from multiple source, latter properties overrides',function() ++ it('extending from multiple source, latter properties overrides',function() + local sourceA = {a = 'a'}; local sourceBC = {b = 'b', a = 'c'} +- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{a = 'c', b = 'b'})) ++ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{a = 'c', b = 'b'})) + end) + +- test('will not copy nil values',function() ++ it('will not copy nil values',function() + local sourceA = {a = nil}; local sourceBC = {b = 'b', c = nil} +- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{b = 'b'})) ++ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{b = 'b'})) + end) + end) + +- context('functions', function() ++ describe('functions', function() + +- test('collects function names within an object',function() ++ it('collects function names within an object',function() + local x = {} + function x.a() return end; function x.b() return end +- assert_true(_.isEqual(_.functions(x),{'a','b'})) ++ assert.is_true(M.same(M.functions(x),{'a','b'})) + end) + +- test('collects metatable functions if "recurseMt" arg is supplied',function() ++ it('collects metatable functions if "recurseMt" arg is supplied',function() + local x = {} ; x.__index = x + function x.a() return end; function x.b() return end + local xx = setmetatable({},x) + function xx.c() return end +- assert_true(_.same(_.functions(xx),{'c'})) +- assert_true(_.same(_.functions(xx,true),{'a','b','c'})) ++ assert.is_true(M.same(M.functions(xx),{'c'})) ++ assert.is_true(M.same(M.functions(xx,true),{'a','b','c'})) + end) + +- test('when given no obj as argument, returns all library functions',function() +- local functions = _.functions() +- _.each(functions, function(k,v) +- assert_true(_.isFunction(_[v])) ++ it('when given no obj as argument, returns all library functions',function() ++ local functions = M.functions() ++ M.each(functions, function(v) ++ assert.is_true(M.isFunction(M[v])) + end) + end) + + end) + +- context('clone', function() ++ describe('clone', function() + +- test('clones the attributes of an object',function() ++ it('clones the attributes of an object',function() + local vector = {x = 0, y = 0} +- assert_true(_.isEqual(_.clone(vector),vector)) ++ assert.is_true(M.isEqual(M.clone(vector),vector)) + end) + +- test('By default, cloning is deep (clones nested tables)',function() ++ it('By default, cloning is deep (clones nested tables)',function() + local particle = {position = {x = 0,y=0},mass = 1} +- local particle_clone = _.clone (particle) +- assert_true(_.isEqual(particle_clone,particle)) ++ local particle_clone = M.clone (particle) ++ assert.is_true(M.isEqual(particle_clone,particle)) + particle_clone.position.x = 3 +- assert_false(_.isEqual(particle_clone,particle)) ++ assert.is_false(M.isEqual(particle_clone,particle)) + end) + +- test('Unless "shallow" arg is provided',function() ++ it('Unless "shallow" arg is provided',function() + local particle = {position = {x = 0,y=0},mass = 1} +- local particle_clone = _.clone (particle,true) +- assert_true(_.isEqual(particle_clone,particle)) ++ local particle_clone = M.clone (particle,true) ++ assert.is_true(M.isEqual(particle_clone,particle)) + particle_clone.position.x = 3 +- assert_true(_.isEqual(particle_clone,particle)) ++ assert.is_true(M.isEqual(particle_clone,particle)) + end) + +- test('Non objects are simply returned',function() +- assert_equal(_.clone(1),1) +- assert_equal(_.clone(false),false) +- assert_equal(_.clone(true),true) +- assert_equal(_.clone(nil),nil) +- assert_equal(_.clone('hello'),'hello') +- assert_equal(_.clone(print),print) ++ it('Non objects are simply returned',function() ++ assert.equal(M.clone(1),1) ++ assert.equal(M.clone(false),false) ++ assert.equal(M.clone(true),true) ++ assert.equal(M.clone(nil),nil) ++ assert.equal(M.clone('hello'),'hello') ++ assert.equal(M.clone(print),print) + end) + + end) + +- context('tap', function() ++ describe('tap', function() + +- test('tap-into a method chain', function() ++ it('tap-into a method chain', function() + local t = {} +- local catchMax = function(k) t[#t+1] = _.max(k) end +- local catchMin = function(k) t[#t+1] = _.min(k) end ++ local catchMax = function(k) t[#t+1] = M.max(k) end ++ local catchMin = function(k) t[#t+1] = M.min(k) end + +- _.chain({1,2,3}) +- :map(function(i,j) return j*2 end) ++ M.chain({1,2,3}) ++ :map(function(j) return j*2 end) + :tap(catchMax) +- :map(function(i,k) return k^2 end) ++ :map(function(k) return k^2 end) + :tap(catchMin) + :value() + +- assert_equal(t[1],6) +- assert_equal(t[2],4) ++ assert.equal(t[1],6) ++ assert.equal(t[2],4) + end) + + end) + +- context('has', function() ++ describe('has', function() + +- test('checks if an object has an attribute',function() +- assert_true(_.has(_,'has')) +- assert_true(_.has(table,'concat')) +- assert_true(_.has(string,'format')) +- assert_true(_.has(os,'time')) +- assert_true(_.has(math,'random')) ++ it('checks if an object has an attribute',function() ++ assert.is_true(M.has(M,'has')) ++ assert.is_true(M.has(table,'concat')) ++ assert.is_true(M.has(string,'format')) ++ assert.is_true(M.has(os,'time')) ++ assert.is_true(M.has(math,'random')) + end) + + end) + +- context('pick', function() ++ describe('pick', function() + +- test('collect specified properties',function() ++ it('collect specified properties',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.pick(object,'a','c'),{a = 1, c = 3})) ++ assert.is_true(M.isEqual(M.pick(object,'a','c'),{a = 1, c = 3})) + end) + +- test('given args can be nested as well',function() ++ it('given args can be nested as well',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.pick(object,{{'b','a'}},'c'),{a = 1,b = 2, c = 3})) ++ assert.is_true(M.isEqual(M.pick(object,{{'b','a'}},'c'),{a = 1,b = 2, c = 3})) + end) + +- test('will ignore properties the object do not have',function() ++ it('will ignore properties the object do not have',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.pick(object,{{'k'}},'c'),{c = 3})) ++ assert.is_true(M.isEqual(M.pick(object,{{'k'}},'c'),{c = 3})) + end) + +- test('returns an empty table when given no properties to pick',function() ++ it('returns an empty table when given no properties to pick',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.pick(object),{})) ++ assert.is_true(M.isEqual(M.pick(object),{})) + end) + +- test('should also pick attributes having falsy values',function() ++ it('should also pick attributes having falsy values',function() + local object = {a = false, b = false, c = true} +- assert_true(_.isEqual(_.pick(object,'a','b'),{a = false,b = false})) ++ assert.is_true(M.isEqual(M.pick(object,'a','b'),{a = false,b = false})) + end) + + end) + +- context('omit', function() ++ describe('omit', function() + +- test('collect all properties leaving those given as args',function() ++ it('collect all properties leaving those given as args',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.omit(object,'a','c'),{b=2})) ++ assert.is_true(M.isEqual(M.omit(object,'a','c'),{b=2})) + end) + +- test('given args can be nested as well',function() ++ it('given args can be nested as well',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.omit(object,{{'b'}},'c'),{a = 1})) ++ assert.is_true(M.isEqual(M.omit(object,{{'b'}},'c'),{a = 1})) + end) + +- test('will ignore properties the object do not have',function() ++ it('will ignore properties the object do not have',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.omit(object,{{'k'}},'c'),{a = 1, b=2})) ++ assert.is_true(M.isEqual(M.omit(object,{{'k'}},'c'),{a = 1, b=2})) + end) + +- test('returns the original object clone when given no properties to omit',function() ++ it('returns the original object clone when given no properties to omit',function() + local object = {a = 1, b = 2, c = 3} +- assert_true(_.isEqual(_.omit(object),{a = 1, b = 2, c = 3})) ++ assert.is_true(M.isEqual(M.omit(object),{a = 1, b = 2, c = 3})) + end) + + end) + +- context('template', function() ++ describe('template', function() + +- test('applies a template on an object',function() +- assert_true(_.isEqual(_.template({},{a = 1, b = 2, c = 3}),{a = 1, b = 2, c = 3})) ++ it('applies a template on an object',function() ++ assert.is_true(M.isEqual(M.template({},{a = 1, b = 2, c = 3}),{a = 1, b = 2, c = 3})) + end) + +- test('does not override existing properies',function() +- assert_true(_.isEqual(_.template({a = 10, b = 10},{a = 1, b = 2, c = 3}),{a = 10, b = 10, c = 3})) ++ it('does not override existing properies',function() ++ assert.is_true(M.isEqual(M.template({a = 10, b = 10},{a = 1, b = 2, c = 3}),{a = 10, b = 10, c = 3})) + end) + +- test('returns the object when given no template arg',function() +- assert_true(_.isEqual(_.template({a = 10, b = 10}),{a = 10, b = 10})) ++ it('returns the object when given no template arg',function() ++ assert.is_true(M.isEqual(M.template({a = 10, b = 10}),{a = 10, b = 10})) + end) + + end) + +- context('isEqual', function() ++ describe('isEqual', function() + +- test('compares values',function() +- assert_true(_.isEqual(1,1)) +- assert_true(_.isEqual(1.0,1)) +- assert_false(_.isEqual(1,2)) +- assert_false(_.isEqual(2,2.0001)) ++ it('compares values',function() ++ assert.is_true(M.isEqual(1,1)) ++ assert.is_true(M.isEqual(1.0,1)) ++ assert.is_false(M.isEqual(1,2)) ++ assert.is_false(M.isEqual(2,2.0001)) + end) + +- test('compares objects by reference and components',function() ++ it('compares objects by reference and components',function() + local oldprint = print +- assert_true(_.isEqual(print,oldprint)) ++ assert.is_true(M.isEqual(print,oldprint)) + + local t = {} + local v = t +- assert_true(_.isEqual(t,v)) +- assert_true(_.isEqual({},{})) ++ assert.is_true(M.isEqual(t,v)) ++ assert.is_true(M.isEqual({},{})) + +- assert_false(_.isEqual('a','b')) ++ assert.is_false(M.isEqual('a','b')) + +- assert_false(_.isEqual(true, false)) +- assert_false(_.isEqual(nil, false)) +- assert_false(_.isEqual(true, nil)) ++ assert.is_false(M.isEqual(true, false)) ++ assert.is_false(M.isEqual(nil, false)) ++ assert.is_false(M.isEqual(true, nil)) + + end) + +- test('compares nested properties',function() +- assert_true(_.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =0}}})) +- assert_false(_.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =1}}})) ++ it('compares nested properties',function() ++ assert.is_true(M.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =0}}})) ++ assert.is_false(M.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =1}}})) + end) + +- test('can compare tables on the basis of their metatable',function() ++ it('can compare tables on the basis of their metatable',function() + local a, b = {x = 1, y = 2}, {x = 2, y = 1} + setmetatable(a,{__eq = function(a,b) return (a.x and b.x and a.y and b.y)~=nil end}) +- assert_false(_.isEqual(a, b)) +- assert_true(_.isEqual(a, b, true)) ++ assert.is_false(M.isEqual(a, b)) ++ assert.is_true(M.isEqual(a, b, true)) + end) + + + end) + +- context('result', function() ++ describe('result', function() + +- test('calls an object method, passing it as a first arg the object itself',function() +- assert_equal(_.result('a','len'),1) +- assert_equal(_.result('hello','reverse'),'olleh') +- assert_equal(_.result({'a','b','c'},table.concat),'abc') ++ it('calls an object method, passing it as a first arg the object itself',function() ++ assert.equal(M.result('a','len'),1) ++ assert.equal(M.result('hello','reverse'),'olleh') ++ assert.equal(M.result({'a','b','c'},table.concat),'abc') + end) + +- test('handles extra-args to be passed to the so-called method',function() +- assert_equal(_.result('Hello','match','%u'),'H') +- assert_equal(_.result({'a','b','c'},table.concat,' '),'a b c') +- end) +- +- test('returns the property itself if not callable',function() +- assert_equal(_.result({size = 0},'size'),0) ++ it('returns the property itself if not callable',function() ++ assert.equal(M.result({size = 0},'size'),0) + end) + + end) + +- context('isTable', function() +- +- test('returns "true" if arg is table or array',function() +- assert_true(_.isTable({})) +- assert_true(_.isTable({1,2})) +- assert_true(_.isTable({x = 1, 2})) +- assert_true(_.isTable(string)) +- assert_true(_.isTable(table)) +- assert_true(_.isTable(math)) ++ describe('isTable', function() ++ ++ it('returns "true" if arg is table or array',function() ++ assert.is_true(M.isTable({})) ++ assert.is_true(M.isTable({1,2})) ++ assert.is_true(M.isTable({x = 1, 2})) ++ assert.is_true(M.isTable(string)) ++ assert.is_true(M.isTable(table)) ++ assert.is_true(M.isTable(math)) + + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isTable(1)) +- assert_false(_.isTable('')) +- assert_false(_.isTable(function() end)) +- assert_false(_.isTable(print)) +- assert_false(_.isTable(false)) +- assert_false(_.isTable(nil)) +- assert_false(_.isTable(true)) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isTable(1)) ++ assert.is_false(M.isTable('')) ++ assert.is_false(M.isTable(function() end)) ++ assert.is_false(M.isTable(print)) ++ assert.is_false(M.isTable(false)) ++ assert.is_false(M.isTable(nil)) ++ assert.is_false(M.isTable(true)) + end) + + end) + +- context('isCallable', function() ++ describe('isCallable', function() + +- test('returns "true" if arg is callable',function() +- assert_true(_.isCallable(print)) +- assert_true(_.isCallable(function() end)) +- assert_true(_.isCallable(string.gmatch)) +- assert_true(_.isCallable(setmetatable({},{__index = string}).upper)) +- assert_true(_.isCallable(setmetatable({},{__call = function() return end}))) ++ it('returns "true" if arg is callable',function() ++ assert.is_true(M.isCallable(print)) ++ assert.is_true(M.isCallable(function() end)) ++ assert.is_true(M.isCallable(string.gmatch)) ++ assert.is_true(M.isCallable(setmetatable({},{__index = string}).upper)) ++ assert.is_true(M.isCallable(setmetatable({},{__call = function() return end}))) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isCallable(1)) +- assert_false(_.isCallable('')) +- assert_false(_.isCallable({})) +- assert_false(_.isCallable(false)) +- assert_false(_.isCallable(nil)) +- assert_false(_.isCallable(true)) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isCallable(1)) ++ assert.is_false(M.isCallable('')) ++ assert.is_false(M.isCallable({})) ++ assert.is_false(M.isCallable(false)) ++ assert.is_false(M.isCallable(nil)) ++ assert.is_false(M.isCallable(true)) + end) + + end) + +- context('isArray', function() ++ describe('isArray', function() + +- test('returns "true" if arg is an array',function() +- assert_true(_.isArray({})) +- assert_true(_.isArray({1,2,3})) +- assert_true(_.isArray({'a','b','c',{}})) +- assert_true(_.isArray({false,true})) +- assert_true(_.isArray({1,nil})) ++ it('returns "true" if arg is an array',function() ++ assert.is_true(M.isArray({})) ++ assert.is_true(M.isArray({1,2,3})) ++ assert.is_true(M.isArray({'a','b','c',{}})) ++ assert.is_true(M.isArray({false,true})) ++ assert.is_true(M.isArray({1,nil})) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isArray(1)) +- assert_false(_.isArray('')) +- assert_false(_.isArray(false)) +- assert_false(_.isArray(nil)) +- assert_false(_.isArray(true)) +- assert_false(_.isArray(print)) +- assert_false(_.isArray({a = 1, x = 1})) +- assert_false(_.isArray({a = 1, 1, 2,3})) +- assert_false(_.isArray({1,nil,2})) +- assert_false(_.isArray({1,nil,3,k=4})) +- assert_false(_.isArray({a=1})) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isArray(1)) ++ assert.is_false(M.isArray('')) ++ assert.is_false(M.isArray(false)) ++ assert.is_false(M.isArray(nil)) ++ assert.is_false(M.isArray(true)) ++ assert.is_false(M.isArray(print)) ++ assert.is_false(M.isArray({a = 1, x = 1})) ++ assert.is_false(M.isArray({a = 1, 1, 2,3})) ++ assert.is_false(M.isArray({1,nil,2})) ++ assert.is_false(M.isArray({1,nil,3,k=4})) ++ assert.is_false(M.isArray({a=1})) + end) + +- test('returns false on "sparse arrays"',function() +- assert_false(_.isArray({[1] = true, [10] = false})) ++ it('returns false on "sparse arrays"',function() ++ assert.is_false(M.isArray({[1] = true, [10] = false})) + end) + + end) + +- context('isIterable', function() ++ describe('isIterable', function() + +- test('checks if the given object is iterable with pairs',function() +- assert_true(_.isIterable({})) +- assert_false(_.isIterable(function() end)) +- assert_false(_.isIterable(false)) +- assert_false(_.isIterable(1)) ++ it('checks if the given object is iterable with pairs',function() ++ assert.is_true(M.isIterable({})) ++ assert.is_true(M.isIterable({1,2,3})) + end) + + end) + +- context('isEmpty', function() ++ describe('type', function() ++ ++ it('returns the type of the passed-in object',function() ++ assert.equal(M.type('string'),'string') ++ assert.equal(M.type(table),'table') ++ assert.equal(M.type(1), 'number') ++ assert.equal(M.type(io.open('f','w')),'file') ++ end) ++ ++ end) ++ ++ describe('isEmpty', function() + +- test('returns "true" if arg is an empty array',function() +- assert_true(_.isEmpty({})) ++ it('returns "true" if arg is an empty array',function() ++ assert.is_true(M.isEmpty({})) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isEmpty({1,2,3})) +- assert_false(_.isEmpty({'a','b','c',{}})) +- assert_false(_.isEmpty({nil,false,true})) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isEmpty({1,2,3})) ++ assert.is_false(M.isEmpty({'a','b','c',{}})) ++ assert.is_false(M.isEmpty({nil,false,true})) + end) + +- test('booleans, nil and functions are considered empty',function() +- assert_true(_.isEmpty(print)) +- assert_true(_.isEmpty(nil)) +- assert_true(_.isEmpty(false)) +- assert_true(_.isEmpty(true)) +- end) +- +- test('handles strings',function() +- assert_true(_.isEmpty('')) +- assert_false(_.isEmpty('a')) +- assert_false(_.isEmpty('bcd')) +- assert_false(_.isEmpty(' ')) ++ it('booleans, nil and functions are considered empty',function() ++ assert.is_true(M.isEmpty(print)) ++ assert.is_true(M.isEmpty(nil)) ++ assert.is_true(M.isEmpty(false)) ++ assert.is_true(M.isEmpty(true)) ++ end) ++ ++ it('handles strings',function() ++ assert.is_true(M.isEmpty('')) ++ assert.is_false(M.isEmpty('a')) ++ assert.is_false(M.isEmpty('bcd')) ++ assert.is_false(M.isEmpty(' ')) + end) + + end) + +- context('isString', function() ++ describe('isString', function() + +- test('returns "true" if arg is a string',function() +- assert_true(_.isString('')) +- assert_true(_.isString('a')) +- assert_true(_.isString(' ')) +- assert_true(_.isString(type(nil))) ++ it('returns "true" if arg is a string',function() ++ assert.is_true(M.isString('')) ++ assert.is_true(M.isString('a')) ++ assert.is_true(M.isString(' ')) ++ assert.is_true(M.isString(type(nil))) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isString(false)) +- assert_false(_.isString(print)) +- assert_false(_.isString(nil)) +- assert_false(_.isString(true)) +- assert_false(_.isString({})) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isString(false)) ++ assert.is_false(M.isString(print)) ++ assert.is_false(M.isString(nil)) ++ assert.is_false(M.isString(true)) ++ assert.is_false(M.isString({})) + end) + + end) + +- context('isFunction', function() ++ describe('isFunction', function() + +- test('returns "true" if arg is a function',function() +- assert_true(_.isFunction(print)) +- assert_true(_.isFunction(string.match)) +- assert_true(_.isFunction(function() end)) ++ it('returns "true" if arg is a function',function() ++ assert.is_true(M.isFunction(print)) ++ assert.is_true(M.isFunction(string.match)) ++ assert.is_true(M.isFunction(function() end)) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isFunction({})) +- assert_false(_.isFunction(nil)) +- assert_false(_.isFunction(false)) +- assert_false(_.isFunction(true)) +- assert_false(_.isFunction('a')) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isFunction({})) ++ assert.is_false(M.isFunction(nil)) ++ assert.is_false(M.isFunction(false)) ++ assert.is_false(M.isFunction(true)) ++ assert.is_false(M.isFunction('a')) + end) + + end) + +- context('isNil', function() ++ describe('isNil', function() + +- test('returns "true" if arg is nil',function() +- assert_true(_.isNil(nil)) +- assert_true(_.isNil()) +- assert_true(_.isNil(a)) ++ it('returns "true" if arg is nil',function() ++ assert.is_true(M.isNil(nil)) ++ assert.is_true(M.isNil()) ++ assert.is_true(M.isNil(a)) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isNil(false)) +- assert_false(_.isNil(true)) +- assert_false(_.isNil(table)) +- assert_false(_.isNil(function() end)) +- assert_false(_.isNil('a')) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isNil(false)) ++ assert.is_false(M.isNil(true)) ++ assert.is_false(M.isNil(table)) ++ assert.is_false(M.isNil(function() end)) ++ assert.is_false(M.isNil('a')) + end) + + end) + +- context('isNumber', function() +- +- test('returns "true" if arg is a number',function() +- assert_true(_.isNumber(1)) +- assert_true(_.isNumber(0.5)) +- assert_true(_.isNumber(math.pi)) +- assert_true(_.isNumber(1/0)) +- assert_true(_.isNumber(math.huge)) +- assert_true(_.isNumber(0/0)) +- end) +- +- test('returns "false" otherwise',function() +- assert_false(_.isNumber(print)) +- assert_false(_.isNumber(nil)) +- assert_false(_.isNumber(true)) +- assert_false(_.isNumber(false)) +- assert_false(_.isNumber({1})) +- assert_false(_.isNumber('1')) ++ describe('isNumber', function() ++ ++ it('returns "true" if arg is a number',function() ++ assert.is_true(M.isNumber(1)) ++ assert.is_true(M.isNumber(0.5)) ++ assert.is_true(M.isNumber(math.pi)) ++ assert.is_true(M.isNumber(1/0)) ++ assert.is_true(M.isNumber(math.huge)) ++ assert.is_true(M.isNumber(0/0)) ++ end) ++ ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isNumber(print)) ++ assert.is_false(M.isNumber(nil)) ++ assert.is_false(M.isNumber(true)) ++ assert.is_false(M.isNumber(false)) ++ assert.is_false(M.isNumber({1})) ++ assert.is_false(M.isNumber('1')) + end) + + end) + +- context('isNaN', function() +- +- test('returns "true" if arg is NaN',function() +- assert_true(_.isNaN(0/0)) +- end) +- +- test('returns "false" for not NaN values',function() +- assert_false(_.isNaN(1/0)) +- assert_false(_.isNaN(math.huge)) +- assert_false(_.isNaN(math.pi)) +- assert_false(_.isNaN(1)) +- assert_false(_.isNaN('')) +- assert_false(_.isNaN('0')) +- assert_false(_.isNaN({})) +- assert_false(_.isNaN(nil)) +- assert_false(_.isNaN(false)) +- assert_false(_.isNaN(true)) ++ describe('isNaN', function() ++ ++ it('returns "true" if arg is NaN',function() ++ assert.is_true(M.isNaN(0/0)) ++ end) ++ ++ it('returns "false" for not NaN values',function() ++ assert.is_false(M.isNaN(1/0)) ++ assert.is_false(M.isNaN(math.huge)) ++ assert.is_false(M.isNaN(math.pi)) ++ assert.is_false(M.isNaN(1)) ++ assert.is_false(M.isNaN('')) ++ assert.is_false(M.isNaN('0')) ++ assert.is_false(M.isNaN({})) ++ assert.is_false(M.isNaN(nil)) ++ assert.is_false(M.isNaN(false)) ++ assert.is_false(M.isNaN(true)) + end) + + end) + +- context('isFinite', function() ++ describe('isFinite', function() + +- test('returns "true" if arg is a finite number',function() +- assert_true(_.isFinite(1)) +- assert_true(_.isFinite(0)) +- assert_true(_.isFinite(math.pi)) +- assert_true(_.isFinite(99e99)) ++ it('returns "true" if arg is a finite number',function() ++ assert.is_true(M.isFinite(1)) ++ assert.is_true(M.isFinite(0)) ++ assert.is_true(M.isFinite(math.pi)) ++ assert.is_true(M.isFinite(99e99)) + end) + +- test('returns "false" otherwise',function() +- assert_false(_.isFinite(math.huge)) +- assert_false(_.isFinite(1/0)) +- assert_false(_.isFinite(0/0)) ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isFinite(math.huge)) ++ assert.is_false(M.isFinite(1/0)) ++ assert.is_false(M.isFinite(0/0)) + end) + +- test('returns "false" for non-numbers',function() +- assert_false(_.isFinite('')) +- assert_false(_.isFinite(function() end)) +- assert_false(_.isFinite({})) ++ it('returns "false" for non-numbers',function() ++ assert.is_false(M.isFinite('')) ++ assert.is_false(M.isFinite(function() end)) ++ assert.is_false(M.isFinite({})) + end) + + end) + +- context('isBoolean', function() ++ describe('isBoolean', function() + +- test('returns "true" if arg is a boolean or a thruthy statement',function() +- assert_true(_.isBoolean(true)) +- assert_true(_.isBoolean(false)) +- assert_true(_.isBoolean(1==1)) +- assert_true(_.isBoolean(print==tostring)) +- end) +- +- test('returns "false" otherwise',function() +- assert_false(_.isBoolean('')) +- assert_false(_.isBoolean(nil)) +- assert_false(_.isBoolean({})) +- assert_false(_.isBoolean(function() end)) ++ it('returns "true" if arg is a boolean or a thruthy statement',function() ++ assert.is_true(M.isBoolean(true)) ++ assert.is_true(M.isBoolean(false)) ++ assert.is_true(M.isBoolean(1==1)) ++ assert.is_true(M.isBoolean(print==tostring)) ++ end) ++ ++ it('returns "false" otherwise',function() ++ assert.is_false(M.isBoolean('')) ++ assert.is_false(M.isBoolean(nil)) ++ assert.is_false(M.isBoolean({})) ++ assert.is_false(M.isBoolean(function() end)) + +- assert_false(_.isBoolean(0)) +- assert_false(_.isBoolean('1')) ++ assert.is_false(M.isBoolean(0)) ++ assert.is_false(M.isBoolean('1')) + end) + + end) + +- context('isInteger', function() ++ describe('isInteger', function() + +- test('returns "true" if arg is a integer, "false" otherwise',function() +- assert_true(_.isInteger(1)) +- assert_true(_.isInteger(0)) +- assert_false(_.isInteger(math.pi)) +- assert_true(_.isInteger(1/0)) +- assert_true(_.isInteger(math.huge)) +- assert_false(_.isInteger(0/0)) ++ it('returns "true" if arg is a integer, "false" otherwise',function() ++ assert.is_true(M.isInteger(1)) ++ assert.is_true(M.isInteger(0)) ++ assert.is_false(M.isInteger(math.pi)) ++ assert.is_true(M.isInteger(1/0)) ++ assert.is_true(M.isInteger(math.huge)) ++ assert.is_false(M.isInteger(0/0)) + end) + + end) +diff --git a/extra/moses/spec/op_spec.lua b/extra/moses/spec/op_spec.lua +new file mode 100644 +index 0000000..a6ceabd +--- /dev/null ++++ b/extra/moses/spec/op_spec.lua +@@ -0,0 +1,162 @@ ++require 'luacov' ++local M = require 'moses' ++ ++describe('Operators specs', function() ++ ++ describe('Arithmetic operators', function() ++ ++ it('add returns a + b', function() ++ assert.equal(M.operator.add(1,2), 3) ++ assert.equal(M.operator.add(0,0), 0) ++ assert.equal(M.operator.add(0,-5), -5) ++ end) ++ ++ it('sub returns a - b', function() ++ assert.equal(M.operator.sub(1,2), -1) ++ assert.equal(M.operator.sub(0,0), 0) ++ assert.equal(M.operator.sub(0,-5), 5) ++ end) ++ ++ it('mul returns a * b', function() ++ assert.equal(M.operator.mul(1,2), 2) ++ assert.equal(M.operator.mul(0,0), 0) ++ assert.equal(M.operator.mul(0,-5), 0) ++ end) ++ ++ it('div returns a / b', function() ++ assert.equal(M.operator.div(1,2), 0.5) ++ assert.equal(M.operator.div(5,5), 1) ++ assert.equal(M.operator.div(8,-2), -4) ++ end) ++ ++ it('mod returns a % b', function() ++ assert.equal(M.operator.mod(6,3), 0) ++ assert.equal(M.operator.mod(5,2), 1) ++ end) ++ ++ it('exp returns a ^ b', function() ++ assert.equal(M.operator.exp(3,3), 27) ++ assert.equal(M.operator.exp(5,2), 25) ++ end) ++ ++ it('unm returns -a', function() ++ assert.equal(M.operator.unm(3), -3) ++ assert.equal(M.operator.unm(-5), 5) ++ end) ++ ++ it('floordiv returns a//b', function() ++ assert.equal(M.operator.floordiv(5,2), 2) ++ end) ++ ++ it('intdiv performs integer division', function() ++ assert.equal(M.operator.intdiv(5,2), 2) ++ assert.equal(M.operator.intdiv(-5,2), -2) ++ end) ++ ++ end) ++ ++ describe('Relational operators', function() ++ ++ it('eq returns a == b', function() ++ assert.equal(M.operator.eq(5,5), true) ++ assert.equal(M.operator.eq(5,4.99), false) ++ end) ++ ++ it('neq returns a ~= b', function() ++ assert.equal(M.operator.neq(5,5), false) ++ assert.equal(M.operator.neq(5,4.99), true) ++ end) ++ ++ it('lt returns a < b', function() ++ assert.equal(M.operator.lt(5,5), false) ++ assert.equal(M.operator.lt(4.99,5), true) ++ end) ++ ++ it('gt returns a > b', function() ++ assert.equal(M.operator.gt(5,5), false) ++ assert.equal(M.operator.gt(5,4.99), true) ++ end) ++ ++ it('le returns a <= b', function() ++ assert.equal(M.operator.le(5,5), true) ++ assert.equal(M.operator.le(4.99,5), true) ++ assert.equal(M.operator.le(5,4.99), false) ++ end) ++ ++ it('ge returns a >= b', function() ++ assert.equal(M.operator.ge(5,5), true) ++ assert.equal(M.operator.ge(4.99,5), false) ++ assert.equal(M.operator.ge(5,4.99), true) ++ end) ++ ++ end) ++ ++ describe('Logical operators', function() ++ ++ it('land returns a and b', function() ++ assert.equal(M.operator.land(true, true),true) ++ assert.equal(M.operator.land(true, false),false) ++ assert.equal(M.operator.land(false, true),false) ++ assert.equal(M.operator.land(false, false),false) ++ assert.equal(M.operator.land(true, nil),nil) ++ assert.equal(M.operator.land(false, nil),false) ++ end) ++ ++ it('lor returns a or b', function() ++ assert.equal(M.operator.lor(true, true),true) ++ assert.equal(M.operator.lor(true, false),true) ++ assert.equal(M.operator.lor(false, true),true) ++ assert.equal(M.operator.lor(false, false),false) ++ assert.equal(M.operator.lor(true, nil),true) ++ assert.equal(M.operator.lor(false, nil),nil) ++ end) ++ ++ it('lnot returns not a', function() ++ assert.equal(M.operator.lnot(true),false) ++ assert.equal(M.operator.lnot(false),true) ++ assert.equal(M.operator.lnot(nil),true) ++ end) ++ ++ end) ++ ++ describe('Length operator', function() ++ ++ it('length returns #a', function() ++ assert.equal(M.operator.length({}),0) ++ assert.equal(M.operator.length({2}),1) ++ assert.equal(M.operator.length({3,5,3}),3) ++ assert.equal(M.operator.length('hello'),5) ++ end) ++ ++ end) ++ ++ describe('Concatenation operator', function() ++ ++ it('concat returns a..b', function() ++ assert.equal(M.operator.concat('a','b'),'ab') ++ assert.equal(M.operator.concat('1','2'),'12') ++ end) ++ ++ end) ++ ++ describe('Aliases', function() ++ ++ it('op is an alias to operator', function() ++ assert.equal(M.operator, M.op) ++ end) ++ ++ it('pow is an alias to exp', function() ++ assert.equal(M.operator.exp, M.operator.pow) ++ end) ++ ++ it('neg is an alias to unm', function() ++ assert.equal(M.operator.neg, M.operator.unm) ++ end) ++ ++ it('len is alias to length', function() ++ assert.equal(M.operator.length,M.operator.len) ++ end) ++ ++ end) ++ ++end) +\ No newline at end of file +diff --git a/extra/moses/spec/table_spec.lua b/extra/moses/spec/table_spec.lua +index 209969a..f808aed 100644 +--- a/extra/moses/spec/table_spec.lua ++++ b/extra/moses/spec/table_spec.lua +@@ -1,665 +1,754 @@ + require 'luacov' +-local _ = require 'moses' ++local M = require 'moses' + +-context('Table functions specs', function() ++describe('Table functions specs', function() + +- context('clear', function() ++ describe('clear', function() + +- test('', function() +- local t = _.clear({'a', true, 'hello'}) +- assert_true(_.isEqual(t,{})) +- assert_nil(next(t)) ++ it('', function() ++ local t = M.clear({'a', true, 'hello'}) ++ assert.is_true(M.isEqual(t,{})) ++ assert.is_nil(next(t)) + end) + + end) + +- context('each', function() +- +- test('provides values and iteration count ', function() +- local t = {1,2,3} +- local inc = 0 +- _.each(t,function(i,v) +- inc = inc+1 +- assert_equal(i,inc) +- assert_equal(t[i],v) +- end) +- end) +- +- test('can reference the given table', function() +- local t = {1,2,3} +- _.each(t,function(i,v,mul) +- t[i] = v*mul +- end,5) +- assert_true(_.isEqual(t,{5,10,15})) +- end) +- +- test('iterates over non-numeric keys and objects', function() +- local t = {one = 1, two = 2, three = 3} +- local copy = {} +- _.each(t,function(i,v) copy[i] = v end) +- assert_true(_.isEqual(t,copy)) +- end) +- +- end) ++ describe('each', function() ++ ++ it('provides values and iteration count ', function() ++ local t = {4,2,1} ++ local inc = 0 ++ M.each(t,function(v, i) ++ inc = inc+1 ++ assert.equal(i,inc) ++ assert.equal(t[i],v) ++ end) ++ end) ++ ++ it('can reference the given table', function() ++ local t = {1,2,3} ++ local mul = 5 ++ M.each(t,function(v,i) ++ t[i] = v*mul ++ end) ++ assert.is_true(M.isEqual(t,{5,10,15})) ++ end) ++ ++ it('iterates over non-numeric keys and objects', function() ++ local t = {one = 1, two = 2, three = 3} ++ local copy = {} ++ M.each(t,function(v,i) copy[i] = v end) ++ assert.is_true(M.isEqual(t,copy)) ++ end) + +- context('eachi', function() ++ end) ++ ++ describe('eachi', function() + +- test('provides values and iteration count for integer keys only, in a sorted way', function() +- local t = {1,2,3} ++ it('provides values and iteration count for integer keys only, in a sorted way', function() ++ local t = {4,2,1} + local inc = 0 +- _.eachi(t,function(i,v) ++ M.eachi(t,function(v,i) + inc = inc+1 +- assert_equal(i,inc) +- assert_equal(t[i],v) ++ assert.equal(i,inc) ++ assert.equal(t[i],v) + end) + end) + +- test('ignores non-integer keys', function() ++ it('ignores non-integer keys', function() + local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5} + local rk = {-1, 0, 1, 2} + local rv = {6, 1, 3, 5} + local inc = 0 +- _.eachi(t,function(i,v) ++ M.eachi(t,function(v,i) + inc = inc+1 +- assert_equal(i,rk[inc]) +- assert_equal(v,rv[inc]) ++ assert.equal(i,rk[inc]) ++ assert.equal(v,rv[inc]) + end) + end) + + end) + +- context('at', function() ++ describe('at', function() + +- test('returns an array of values at numeric keys', function() ++ it('returns an array of values at numeric keys', function() + local t = {4,5,6} +- local values = _.at(t,1,2,3) +- assert_true(_.isEqual(values, t)) ++ local values = M.at(t,1,2,3) ++ assert.is_true(M.isEqual(values, t)) + + local t = {a = 4, bb = true, ccc = false} +- local values = _.at(t,'a', 'ccc') +- assert_true(_.isEqual(values, {4, false})) ++ local values = M.at(t,'a', 'ccc') ++ assert.is_true(M.isEqual(values, {4, false})) + end) + + end) + +- context('count', function() ++ describe('adjust', function() ++ ++ it('adjusts a given value in a table using a function', function() ++ local double = function(v) return v * 2 end ++ local t = {1,2,3} ++ assert.is_true(M.isEqual(M.adjust(t,1,double),{2,2,3})) ++ assert.is_true(M.isEqual(M.adjust(t,2,double),{1,4,3})) ++ assert.is_true(M.isEqual(M.adjust(t,3,double),{1,2,6})) ++ end) ++ ++ it('adjusts a given value in a table using a value', function() ++ local t = {1,2,3} ++ assert.is_true(M.isEqual(M.adjust(t,1,5),{5,2,3})) ++ assert.is_true(M.isEqual(M.adjust(t,2,-2),{1,-2,3})) ++ end) ++ ++ it('throws an error if key is not found in table', function() ++ local double = function(v) return v * 2 end ++ local t = {x = 1} ++ assert.error(function() M.adjust(t,'y', 2) end) ++ end) ++ ++ end) ++ ++ describe('count', function() + +- test('count the occurences of value in a list', function() +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},1),2) +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},2),3) +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},3),4) +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},4),1) +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},5),0) +- assert_equal(_.count({false, false, true},false),2) +- assert_equal(_.count({false, false, true},true),1) +- assert_equal(_.count({{1,1},{1,1},{1,1},{2,2}},{1,1}),3) +- assert_equal(_.count({{1,1},{1,1},{1,1},{2,2}},{2,2}),1) ++ it('count the occurences of value in a list', function() ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},1),2) ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},2),3) ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},3),4) ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},4),1) ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},5),0) ++ assert.equal(M.count({false, false, true},false),2) ++ assert.equal(M.count({false, false, true},true),1) ++ assert.equal(M.count({{1,1},{1,1},{1,1},{2,2}},{1,1}),3) ++ assert.equal(M.count({{1,1},{1,1},{1,1},{2,2}},{2,2}),1) + end) + +- test('defaults to size when value is not given', function() +- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2}),_.size({1,1,2,3,3,3,2,4,3,2})) +- assert_equal(_.count({false, false, true}),_.size({false, false, true})) ++ it('defaults to size when value is not given', function() ++ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2}),M.size({1,1,2,3,3,3,2,4,3,2})) ++ assert.equal(M.count({false, false, true}),M.size({false, false, true})) + end) + end) + +- context('countf', function() +- +- test('count the occurences of values passing an iterator test in a list', function() +- assert_equal(_.countf({1,2,3,4,5,6}, function(i,v) +- return v%2==0 +- end),3) +- assert_equal(_.countf({print, pairs, os, assert, ipairs}, function(i,v) +- return type(v)=='function' +- end),4) ++ describe('countf', function() ++ it('count the occurences of values passing an iterator test in a list', function() ++ assert.equal(M.countf({1,2,3,4,5,6}, function(v) return v%2==0 end),3) ++ assert.equal(M.countf({print, pairs, ipairs}, function(v) return type(v)=='function' end),3) ++ end) ++ end) ++ ++ describe('allEq', function() ++ ++ it('checks if all values are equal', function() ++ assert.is_true(M.allEqual({1,1,1,1,1}, comp)) ++ assert.is_false(M.allEqual({1,1,2,1,1}, comp)) ++ ++ local t1 = {1, 2, {3}} ++ local t2 = {1, 2, {3}} ++ assert.is_true(M.allEqual({t1, t2})) ++ end) ++ ++ it('can use a custom comp function to compare values', function() ++ local t1 = {x = 1, y = 0} ++ local t2 = {x = 1, y = 0} ++ local t3 = {x = 1, y = 2} ++ local t4 = {x = 1, y = 2} ++ local function compx(a, b) return a.x == b.x end ++ local function compy(a, b) return a.y == b.y end ++ ++ assert.is_true(M.allEqual({t1, t2}, compx)) ++ assert.is_true(M.allEqual({t1, t2}, compy)) ++ assert.is_true(M.allEqual({t3, t4}, compx)) ++ assert.is_true(M.allEqual({t3, t4}, compy)) ++ assert.is_true(M.allEqual({t1, t2, t3, t4}, compx)) ++ assert.is_false(M.allEqual({t1, t2, t3, t4}, compy)) + end) ++ + end) + +- context('cycle', function() ++ describe('cycle', function() + +- test('loops n times on a list', function() ++ it('loops n times on a list', function() + local times = 3 + local t = {1,2,3,4,5} + local kv = {} +- for k,v in _.cycle(t,times) do +- assert_equal(t[k],v) ++ for v,k in M.cycle(t,times) do ++ assert.equal(t[k],v) + kv[#kv+1] = v + end + for k,v in ipairs(kv) do +- assert_equal(_.count(kv,v),times) ++ assert.equal(M.count(kv,v),times) + end + end) + +- test('support array-like and map-like tables', function() ++ it('support array-like and map-like tables', function() + local times = 10 + local t = {x = 1, z = 2} + local keys = {} + local values = {} +- for k,v in _.cycle(t,times) do +- assert_equal(t[k],v) ++ for v,k in M.cycle(t,times) do ++ assert.equal(t[k],v) + keys[#keys+1] = k + values[#values+1] = v + end + for k,v in ipairs(keys) do +- assert_equal(_.count(keys,v),times) ++ assert.equal(M.count(keys,v),times) + end + for k,v in ipairs(values) do +- assert_equal(_.count(values,v),times) ++ assert.equal(M.count(values,v),times) + end + end) + +- test('n defaults to 1, if not supplied', function() ++ it('n defaults to 1, if not supplied', function() + local t = {1,2,3,4,5} +- for k,v in _.cycle(t) do ++ for v,k in M.cycle(t) do + t[k] = v + 1 + end +- _.each(t, function(k, v) +- assert_equal(v, k + 1) ++ M.each(t, function(v, k) ++ assert.equal(v, k + 1) + end) + end) + +- test('if n is negative or equal to 0, it does nothing', function() ++ it('if n is negative or equal to 0, it does nothing', function() + local t = {1,2,3,4,5} +- for k,v in _.cycle(t, 0) do ++ for v,k in M.cycle(t, 0) do + t[k] = v + 1 + end +- for k,v in _.cycle(t, -2) do ++ for v,k in M.cycle(t, -2) do + t[k] = v + 1 + end +- _.each(t, function(k, v) +- assert_equal(v, k) ++ M.each(t, function(v, k) ++ assert.equal(v, k) + end) + end) + end) + +- context('map', function() ++ describe('map', function() + +- test('applies an iterator function over each key-value pair ', function() +- assert_true(_.isEqual(_.map({1,2,3},function(i,v) ++ it('applies an iterator function over each key-value pair ', function() ++ assert.is_true(M.isEqual(M.map({1,2,3},function(v) + return v+10 + end),{11,12,13})) + end) + +- test('iterates over non-numeric keys and objects', function() +- assert_true(_.isEqual(_.map({a = 1, b = 2},function(k,v) ++ it('iterates over non-numeric keys and objects', function() ++ assert.is_true(M.isEqual(M.map({a = 1, b = 2},function(v,k) + return k..v + end),{a = 'a1',b = 'b2'})) + end) + +- test('maps key-value pairs to key-value pairs', function() +- assert_true(_.isEqual(_.map({a = 1, b = 2}, function(k, v) ++ it('maps key-value pairs to key-value pairs', function() ++ assert.is_true(M.isEqual(M.map({a = 1, b = 2}, function(v, k) + return k .. k, v + 10 + end), {aa = 11, bb = 12})) + end) + + end) + +- context('reduce', function() ++ describe('mapi', function() ++ ++ it('applies an iterator function over each key-value pair ', function() ++ assert.is_true(M.isEqual(M.mapi({1,2,3},function(v) ++ return v+10 ++ end),{11,12,13})) ++ end) ++ ++ it('iterates only on array values', function() ++ assert.is_true(M.isEqual(M.mapi({a = 1, 2, 3, 4},function(v,k) ++ return k..v ++ end),{'12','23','34'})) ++ end) ++ ++ end) ++ ++ describe('reduce', function() + +- test('folds a collection (left to right) from an initial state', function() +- assert_equal(_.reduce({1,2,3,4},function(memo,v) return memo+v end,0),10) ++ it('folds a collection (left to right) from an initial state', function() ++ assert.equal(M.reduce({1,2,3,4},function(memo,v) return memo+v end,0),10) + end) + +- test('initial state defaults to the first value when not given', function() +- assert_equal(_.reduce({'a','b','c'},function(memo,v) return memo..v end),'abc') ++ it('initial state defaults to the first value when not given', function() ++ assert.equal(M.reduce({'a','b','c'},function(memo,v) return memo..v end),'abc') + end) + +- test('supports arrays of booleans', function() +- assert_equal(_.reduce({true, false, true, true},function(memo,v) return memo and v end), false) +- assert_equal(_.reduce({true, true, true},function(memo,v) return memo and v end), true) +- assert_equal(_.reduce({false, false, false},function(memo,v) return memo and v end), false) +- assert_equal(_.reduce({false, false, true},function(memo,v) return memo or v end), true) ++ it('supports arrays of booleans', function() ++ assert.equal(M.reduce({true, false, true, true},function(memo,v) return memo and v end), false) ++ assert.equal(M.reduce({true, true, true},function(memo,v) return memo and v end), true) ++ assert.equal(M.reduce({false, false, false},function(memo,v) return memo and v end), false) ++ assert.equal(M.reduce({false, false, true},function(memo,v) return memo or v end), true) ++ end) ++ ++ end) ++ ++ describe('best', function() ++ ++ it('select the best candidate in a table', function() ++ local words = {'Lua', 'Programming', 'Language'} ++ assert.equal(M.best(words, function(a,b) return #a > #b end), 'Programming') ++ assert.equal(M.best(words, function(a,b) return #a < #b end), 'Lua') + end) + + end) + +- context('reduceby', function() ++ describe('reduceBy', function() + +- test('folds a collection (left to right) for specific values', function() +- local function even(_,v) return v%2==0 end +- local function odd(_,v) return v%2~=0 end +- assert_equal(_.reduceby({1,2,3,4},function(memo,v) return memo+v end,0,even), 6) +- assert_equal(_.reduceby({1,2,3,4},function(memo,v) return memo+v end,0,odd), 4) ++ it('folds a collection (left to right) for specific values', function() ++ local function even(v) return v%2==0 end ++ local function odd(v) return v%2~=0 end ++ assert.equal(M.reduceBy({1,2,3,4},function(memo,v) return memo+v end,even,0), 6) ++ assert.equal(M.reduceBy({1,2,3,4},function(memo,v) return memo+v end,odd,0), 4) + end) + + end) + +- context('reduceRight', function() ++ describe('reduceRight', function() + +- test('folds a collection (right to left) from an initial state', function() +- assert_equal(_.reduceRight({1,2,4,16},function(memo,v) return memo/v end,256),2) ++ it('folds a collection (right to left) from an initial state', function() ++ assert.equal(M.reduceRight({1,2,4,16},function(memo,v) return memo/v end,256),2) + end) + +- test('initial state defaults to the first value when not given', function() +- assert_equal(_.reduceRight({'a','b','c'},function(memo,v) return memo..v end),'cba') ++ it('initial state defaults to the first value when not given', function() ++ assert.equal(M.reduceRight({'a','b','c'},function(memo,v) return memo..v end),'cba') + end) + + end) + +- context('mapReduce', function() ++ describe('mapReduce', function() + +- test('folds a collection (left to right) saving intermediate states', function() +- assert_true(_.isEqual(_.mapReduce({1,2,4,16},function(memo,v) ++ it('folds a collection (left to right) saving intermediate states', function() ++ assert.is_true(M.isEqual(M.mapReduce({1,2,4,16},function(memo,v) + return memo*v + end,0),{0,0,0,0})) + end) + +- test('initial state defaults to the first value when not given', function() +- assert_true(_.isEqual(_.mapReduce({'a','b','c'},function(memo,v) ++ it('initial state defaults to the first value when not given', function() ++ assert.is_true(M.isEqual(M.mapReduce({'a','b','c'},function(memo,v) + return memo..v + end),{'a','ab','abc'})) + end) + + end) + +- context('mapReduceRight', function() ++ describe('mapReduceRight', function() + +- test('folds a collection (right to left) saving intermediate states', function() +- assert_true(_.isEqual(_.mapReduceRight({1,2,4,16},function(memo,v) ++ it('folds a collection (right to left) saving intermediate states', function() ++ assert.is_true(M.isEqual(M.mapReduceRight({1,2,4,16},function(memo,v) + return memo/v + end,256),{16,4,2,2})) + end) + +- test('initial state defaults to the first value when not given', function() +- assert_true(_.isEqual(_.mapReduceRight({'a','b','c'},function(memo,v) ++ it('initial state defaults to the first value when not given', function() ++ assert.is_true(M.isEqual(M.mapReduceRight({'a','b','c'},function(memo,v) + return memo..v + end),{'c','cb','cba'})) + end) + + end) + +- context('include', function() ++ describe('include', function() + +- test('looks for a value in a collection, returns true when found', function() +- assert_true(_.include({6,8,10,16,29},16)) ++ it('looks for a value in a collection, returns true when found', function() ++ assert.is_true(M.include({6,8,10,16,29},16)) + end) + +- test('returns false when value was not found', function() +- assert_false(_.include({6,8,10,16,29},1)) ++ it('returns false when value was not found', function() ++ assert.is_false(M.include({6,8,10,16,29},1)) + end) + +- test('can lookup for a object', function() +- assert_true(_.include({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,{3}}})) ++ it('can lookup for a object', function() ++ assert.is_true(M.include({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,{3}}})) + end) + +- test('given an iterator, return the first value passing a truth test', function() +- assert_true(_.include({'a','B','c'}, function(array_value) ++ it('given an iterator, return the first value passing a truth test', function() ++ assert.is_true(M.include({'a','B','c'}, function(array_value) + return (array_value:upper() == array_value) + end)) + end) + + end) + +- context('detect', function() ++ describe('detect', function() + +- test('looks for the first occurence of value, returns the key where it was found', function() +- assert_equal(_.detect({6,8,10,16},8),2) ++ it('looks for the first occurence of value, returns the key where it was found', function() ++ assert.equal(M.detect({6,8,10,16},8),2) + end) + +- test('returns nil when value was not found', function() +- assert_nil(_.detect({nil,true,0,true,true},false)) ++ it('returns nil when value was not found', function() ++ assert.is_nil(M.detect({nil,true,0,true,true},false)) + end) + +- test('can lookup for a object', function() +- assert_equal(_.detect({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,6}}),2) ++ it('can lookup for a object', function() ++ assert.equal(M.detect({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,6}}),2) + end) + +- test('given an iterator, return the key of the first value passing a truth test', function() +- assert_equal(_.detect({'a','B','c'}, function(array_value) ++ it('given an iterator, return the key of the first value passing a truth test', function() ++ assert.equal(M.detect({'a','B','c'}, function(array_value) + return (array_value:upper() == array_value) + end),2) + end) + + end) + +- context('where', function() ++ describe('where', function() + +- test('Returns all values in a list having all of a given set of properties', function() ++ it('Returns all values in a list having all of a given set of properties', function() + local set = { + {a = 1, b = 2}, + {a = 2, b = 2}, + {a = 2, b = 4}, + {a = 3, b = 4} + } +- assert_true(_.isEqual(_.where(set, {a = 2}), {set[2],set[3]})) +- assert_true(_.isEqual(_.where(set, {b = 4}), {set[3],set[4]})) +- assert_true(_.isEqual(_.where(set, {a = 2, b = 2}), {set[2]})) ++ assert.is_true(M.isEqual(M.where(set, {a = 2}), {set[2],set[3]})) ++ assert.is_true(M.isEqual(M.where(set, {b = 4}), {set[3],set[4]})) ++ assert.is_true(M.isEqual(M.where(set, {a = 2, b = 2}), {set[2]})) + end) + +- test('returns nil when value was not found', function() ++ it('returns nil when value was not found', function() + local set = { + {a = 1, b = 2}, + {a = 2, b = 2}, + } +- assert_nil(_.where(set, {a = 3})) +- assert_nil(_.where(set, {b = 1})) ++ assert.is_nil(M.where(set, {a = 3})) ++ assert.is_nil(M.where(set, {b = 1})) + end) + + end) + +- context('findWhere', function() ++ describe('findWhere', function() + +- test('Returns the first value in a list having all of a given set of properties', function() ++ it('Returns the first value in a list having all of a given set of properties', function() + local a = {a = 1, b = 2} + local b = {a = 2, b = 3} + local c = {a = 3, b = 4} +- assert_equal(_.findWhere({a, b, c}, {a = 3, b = 4}), c) ++ assert.equal(M.findWhere({a, b, c}, {a = 3, b = 4}), c) + end) + +- test('returns nil when value was not found', function() ++ it('returns nil when value was not found', function() + local a = {a = 1, b = 2} + local b = {a = 2, b = 3} + local c = {a = 3, b = 4} +- assert_nil(_.findWhere({a, b, c}, {a = 3, b = 0})) ++ assert.is_nil(M.findWhere({a, b, c}, {a = 3, b = 0})) + end) + + end) + +- context('select', function() ++ describe('select', function() + +- test('collects all values passing a truth test with an iterator', function() +- assert_true(_.isEqual(_.select({1,2,3,4,5,6,7}, function(key,value) ++ it('collects all values passing a truth test with an iterator', function() ++ assert.is_true(M.isEqual(M.select({7,6,5,4,3,2,1}, function(value) + return (value%2==0) +- end),{2,4,6})) ++ end),{6,4,2})) + +- assert_true(_.isEqual(_.select({1,2,3,4,5,6,7}, function(key,value) ++ assert.is_true(M.isEqual(M.select({7,6,5,4,3,2,1}, function(value) + return (value%2~=0) +- end),{1,3,5,7})) ++ end),{7,5,3,1})) + end) + + end) + +- context('reject', function() ++ describe('reject', function() + +- test('collects all values failing a truth test with an iterator', function() +- assert_true(_.isEqual(_.reject({1,2,3,4,5,6,7}, function(key,value) ++ it('collects all values failing a truth test with an iterator', function() ++ assert.is_true(M.isEqual(M.reject({7,6,5,4,3,2,1}, function(value) + return (value%2==0) +- end),{1,3,5,7})) ++ end),{7,5,3,1})) + +- assert_true(_.isEqual(_.reject({1,2,3,4,5,6,7}, function(key,value) ++ assert.is_true(M.isEqual(M.reject({7,6,5,4,3,2,1}, function(value) + return (value%2~=0) +- end),{2,4,6})) ++ end),{6,4,2})) + end) + + end) + +- context('all', function() ++ describe('all', function() + +- test('returns whether all elements matches a truth test', function() +- assert_true(_.all({2,4,6}, function(key,value) ++ it('returns whether all elements matches a truth test', function() ++ assert.is_true(M.all({2,4,6}, function(value) + return (value%2==0) + end)) + +- assert_false(_.all({false,true,false}, function(key,value) ++ assert.is_false(M.all({false,true,false}, function(value) + return value == false + end)) + end) + + end) + +- context('invoke', function() ++ describe('invoke', function() + +- test('calls an iterator over each object, passing it as a first arg', function() +- assert_true(_.isEqual(_.invoke({'a','bea','cdhza'},string.len), ++ it('calls an iterator over each object, passing it as a first arg', function() ++ assert.is_true(M.isEqual(M.invoke({'a','bea','cdhza'},string.len), + {1,3,5})) + +- assert_true(_.isEqual(_.invoke({{2,3,2},{13,8,10},{0,-5}},_.sort), ++ assert.is_true(M.isEqual(M.invoke({{2,3,2},{13,8,10},{0,-5}},M.ary(M.sort,1)), + {{2,2,3},{8,10,13},{-5,0}})) + +- assert_true(_.isEqual(_.invoke({{x = 1, y = 2},{x = 3, y=4}},'x'), {1,3})) ++ assert.is_true(M.isEqual(M.invoke({{x = 1, y = 2},{x = 3, y=4}},'x'), {1,3})) + end) + +- test('given a string, calls the matching object property the same way', function() ++ it('given a string, calls the matching object property the same way', function() + local a = {}; function a:call() return self end + local b, c, d = {}, {}, {} + b.call, c.call, d.call = a.call, a.call, a.call +- assert_true(_.isEqual(_.invoke({a,b,c,d},'call'), ++ assert.is_true(M.isEqual(M.invoke({a,b,c,d},'call'), + {a,b,c,d})) + end) + + end) + +- context('pluck', function() ++ describe('pluck', function() + +- test('fetches a property value in a collection of objects', function() ++ it('fetches a property value in a collection of objects', function() + + local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} + +- assert_true(_.isEqual(_.pluck(peoples,'age'), ++ assert.is_true(M.isEqual(M.pluck(peoples,'age'), + {23,17,15,33})) +- assert_true(_.isEqual(_.pluck(peoples,'name'), ++ assert.is_true(M.isEqual(M.pluck(peoples,'name'), + {'John','Peter','Steve'})) + + end) + + end) + +- context('max', function() ++ describe('max', function() + +- test('returns the maximum targetted property value in a collection of objects', function() ++ it('returns the maximum targetted property value in a collection of objects', function() + local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} +- assert_equal(_.max(_.pluck(peoples,'age')),33) +- assert_equal(_.max(peoples,function(people) return people.age end),33) ++ assert.equal(M.max(M.pluck(peoples,'age')),33) ++ assert.equal(M.max(peoples,function(people) return people.age end),33) + end) + +- test('directly compares items when given no iterator', function() +- assert_equal(_.max({'a','b','c'}),'c') ++ it('directly compares items when given no iterator', function() ++ assert.equal(M.max({'a','b','c'}),'c') + end) + + end) + +- context('min', function() ++ describe('min', function() + +- test('returns the maximum targetted property value in a collection of objects', function() ++ it('returns the maximum targetted property value in a collection of objects', function() + local peoples = { + {name = 'John', age = 23},{name = 'Peter', age = 17}, + {name = 'Steve', age = 15},{age = 33}} +- assert_equal(_.min(_.pluck(peoples,'age')),15) +- assert_equal(_.min(peoples,function(people) return people.age end),15) ++ assert.equal(M.min(M.pluck(peoples,'age')),15) ++ assert.equal(M.min(peoples,function(people) return people.age end),15) + end) + +- test('directly compares items when given no iterator', function() +- assert_equal(_.min({'a','b','c'}),'a') ++ it('directly compares items when given no iterator', function() ++ assert.equal(M.min({'a','b','c'}),'a') + end) + +- end) +- +- context('shuffle', function() +- +- test('shuffles values and objects in a collection', function() +- local values = {'a','b','c','d'} +- assert_true(_.same(_.shuffle (values),values)) +- end) +- +- test('can accept a seed value to init randomization', function() +- local values = {'a','b','c','d'} +- local seed = os.time() +- assert_true(_.same(_.shuffle(values,seed),values)) +- end) +- +- test('shuffled table has the same elements in a different order', function() +- local values = {'a','b','c','d'} +- assert_true(_.same(_.shuffle(values),values)) +- assert_true(_.same(_.shuffle(values),values)) +- end) +- + end) + +- context('same', function() ++ describe('same', function() + +- test('returns whether all objects from both given tables exists in each other', function() ++ it('returns whether all objects from both given tables exists in each other', function() + local a = {'a','b','c','d'} + local b = {'b','a','d','c'} +- assert_true(_.same(a,b)) ++ assert.is_true(M.same(a,b)) + b[#b+1] = 'e' +- assert_false(_.same(a,b)) ++ assert.is_false(M.same(a,b)) + end) + + end) + +- context('sort', function() ++ describe('sort', function() + +- test('sorts a collection with respect to a given comparison function', function() +- assert_true(_.isEqual(_.sort({'b','a','d','c'}, function(a,b) ++ it('sorts a collection with respect to a given comparison function', function() ++ assert.is_true(M.isEqual(M.sort({'b','a','d','c'}, function(a,b) + return a:byte() < b:byte() + end),{'a','b','c','d'})) + end) + +- test('uses "<" operator when no comparison function is given', function() +- assert_true(_.isEqual(_.sort({'b','a','d','c'}),{'a','b','c','d'})) ++ it('uses "<" operator when no comparison function is given', function() ++ assert.is_true(M.isEqual(M.sort({'b','a','d','c'}),{'a','b','c','d'})) + end) + + end) ++ ++ describe('sortedk', function() ++ ++ it('iterates on sorted keys', function() ++ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++ local ok_tbl = {12, 6, 5, 10, 8} ++ local sorted = {} ++ for k, v in M.sortedk(tbl) do sorted[k] = v end ++ for k in ipairs(sorted) do ++ assert.equal(sorted[k], ok_tbl[k]) ++ end ++ end) ++ ++ it('can take a comparison function', function() ++ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++ local ok_tbl = {8, 10, 5, 6, 12} ++ local sorted = {} ++ for k, v in M.sortedk(tbl, function(a, b) return a > b end) do ++ sorted[#sorted +1] = {k, v} ++ end ++ for k, pr in ipairs(sorted) do ++ assert.equal(pr[2], ok_tbl[k]) ++ end ++ end) ++ ++ end) ++ ++ describe('sortedv', function() ++ ++ it('iterates on sorted values', function() ++ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++ local ok_tbl = {5, 6, 8, 10, 12} ++ local sorted = {} ++ for k, v in M.sortedv(tbl) do sorted[#sorted + 1] = {k, v} end ++ for k, pr in ipairs(sorted) do ++ assert.equal(pr[2], ok_tbl[k]) ++ end ++ end) ++ ++ it('can take a comparison function', function() ++ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 ++ local ok_tbl = {12, 10, 8, 6, 5} ++ local sorted = {} ++ for k, v in M.sortedv(tbl, function(a, b) return a > b end) do ++ sorted[#sorted +1] = {k, v} ++ end ++ for k, pr in ipairs(sorted) do ++ assert.equal(pr[2], ok_tbl[k]) ++ end ++ end) + +- context('sortBy', function() ++ end) ++ ++ describe('sortBy', function() + +- test('sort values on the result of a transform function', function() +- assert_true(_.isEqual(_.sortBy({1,2,3,4,5}, math.sin), {5,4,3,1,2})) ++ it('sort values on the result of a transform function', function() ++ assert.is_true(M.isEqual(M.sortBy({1,2,3,4,5}, math.sin), {5,4,3,1,2})) + end) + +- test('the transform function defaults to _.identity', function() +- assert_true(_.isEqual(_.sortBy({1,2,3,4,5}), {1,2,3,4,5})) ++ it('the transform function defaults to M.identity', function() ++ assert.is_true(M.isEqual(M.sortBy({1,2,3,4,5}), {1,2,3,4,5})) + end) + +- test('transform function can be a string name property', function() ++ it('transform function can be a string name property', function() + local unsorted = {{item = 1, value = 10},{item = 2, value = 5},{item = 3, value = 8}} + local sorted = {{item = 2, value = 5},{item = 3, value = 8},{item = 1, value = 10}} +- assert_true(_.isEqual(_.sortBy(unsorted, 'value'), sorted)) ++ assert.is_true(M.isEqual(M.sortBy(unsorted, 'value'), sorted)) + end) + +- test('can use a custom comparison function', function() ++ it('can use a custom comparison function', function() + local unsorted = {{item = 1, value = 10},{item = 2, value = 5},{item = 3, value = 8}} + local sorted = {{item = 1, value = 10},{item = 3, value = 8},{item = 2, value = 5}} + local function comp(a,b) return a > b end +- assert_true(_.isEqual(_.sortBy(unsorted, 'value', comp), sorted)) ++ assert.is_true(M.isEqual(M.sortBy(unsorted, 'value', comp), sorted)) + end) + + end) + +- context('groupBy', function() ++ describe('groupBy', function() + +- test('splits a collection into subsets of items behaving the same', function() ++ it('splits a collection into subsets of items behaving the same', function() + +- assert_true(_.isEqual(_.groupBy({0,1,2,3,4,5,6},function(i,value) ++ assert.is_true(M.isEqual(M.groupBy({0,1,2,3,4,5,6},function(value) + return value%2==0 and 'even' or 'odd' + end),{even = {0,2,4,6},odd = {1,3,5}})) ++ assert.is_true(M.isEqual(M.groupBy({0,'a',true, false,nil,b,0.5},type),{number = {0,0.5},string = {'a'},boolean = {true,false}})) ++ assert.is_true(M.isEqual(M.groupBy({'one','two','three','four','five'},string.len),{[3] = {'one','two'},[4] = {'four','five'},[5] = {'three'}})) + +- assert_true(_.isEqual(_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value) +- return type(value) +- end),{number = {0,0.5},string = {'a'},boolean = {true,false}})) +- +- assert_true(_.isEqual(_.groupBy({'one','two','three','four','five'},function(i,value) +- return value:len() +- end),{[3] = {'one','two'},[4] = {'four','five'},[5] = {'three'}})) +- +- end) +- +- test('can takes extra-args', function() +- +- assert_true(_.isEqual(_.groupBy({3,9,10,12,15}, function(k,v,x) return v%x == 0 end,2), {[false] = {3,9,15}, [true] = {10,12}})) +- assert_true(_.isEqual(_.groupBy({3,9,10,12,15}, function(k,v,x) return v%x == 0 end,3), {[false] = {10}, [true] = {3,9,12,15}})) +- + end) + + end) + +- context('countBy', function() ++ describe('countBy', function() + +- test('splits a collection in subsets and counts items inside', function() ++ it('splits a collection in subsets and counts items inside', function() + +- assert_true(_.isEqual(_.countBy({0,1,2,3,4,5,6},function(i,value) ++ assert.is_true(M.isEqual(M.countBy({0,1,2,3,4,5,6},function(value) + return value%2==0 and 'even' or 'odd' + end),{even = 4,odd = 3})) + +- assert_true(_.isEqual(_.countBy({0,'a',true, false,nil,b,0.5},function(i,value) +- return type(value) +- end),{number = 2,string = 1,boolean = 2})) ++ assert.is_true(M.isEqual(M.countBy({0,'a',true, false,nil,b,0.5},type),{number = 2,string = 1,boolean = 2})) + +- assert_true(_.isEqual(_.countBy({'one','two','three','four','five'},function(i,value) +- return value:len() +- end),{[3] = 2,[4] = 2,[5] = 1})) ++ assert.is_true(M.isEqual(M.countBy({'one','two','three','four','five'},string.len),{[3] = 2,[4] = 2,[5] = 1})) + + end) + + end) + +- context('size', function() ++ describe('size', function() + +- test('counts the very number of objects in a collection', function() +- assert_equal(_.size {1,2,3},3) ++ it('counts the very number of objects in a collection', function() ++ assert.equal(M.size {1,2,3},3) + end) + +- test('counts nested tables elements as an unique value', function() +- assert_equal(_.size {1,2,3,{4,5}},4) ++ it('counts nested tables elements as an unique value', function() ++ assert.equal(M.size {1,2,3,{4,5}},4) + end) + +- test('leaves nil values', function() +- assert_equal(_.size {1,2,3,nil,8},4) ++ it('leaves nil values', function() ++ assert.equal(M.size {1,2,3,nil,8},4) + end) + +- test('counts objects', function() +- assert_equal(_.size {one = 1,2,b = 3, [{}] = 'nil', 'c', [function() end] = 'foo'},6) ++ it('counts objects', function() ++ assert.equal(M.size {one = 1,2,b = 3, [{}] = 'nil', 'c', [function() end] = 'foo'},6) + end) + +- test('returns the size of the first arg when it is a table', function() +- assert_equal(_.size ({1,2},3,4,5),2) ++ it('returns the size of the first arg when it is a table', function() ++ assert.equal(M.size ({1,2},3,4,5),2) + end) + +- test('counts the number of non-nil args when the first one is not a table', function() +- assert_equal(_.size (1,3,4,5),4) +- assert_equal(_.size (nil,1,3,4,5),4) +- assert_equal(_.size (nil,1,3,4,nil,5),4) ++ it('counts the number of non-nil args when the first one is not a table', function() ++ assert.equal(M.size (1,3,4,5),4) ++ assert.equal(M.size (nil,1,3,4,5),4) ++ assert.equal(M.size (nil,1,3,4,nil,5),4) + end) + +- test('handles nil', function() +- assert_equal(_.size(),0) +- assert_equal(_.size(nil),0) ++ it('handles nil', function() ++ assert.equal(M.size(),0) ++ assert.equal(M.size(nil),0) + end) + + + end) + +- context('containsKeys', function() ++ describe('containsKeys', function() + +- test('returns whether a table has all the keys from a given list', function() +- assert_true(_.containsKeys({1,2,3},{1,2,3})) +- assert_true(_.containsKeys({x = 1, y = 2},{x = 1,y =2})) ++ it('returns whether a table has all the keys from a given list', function() ++ assert.is_true(M.containsKeys({1,2,3},{1,2,3})) ++ assert.is_true(M.containsKeys({x = 1, y = 2},{x = 1,y =2})) + end) + +- test('does not compare values', function() +- assert_true(_.containsKeys({1,2,3},{4,5,6})) +- assert_true(_.containsKeys({x = 1, y = 2},{x = 4,y = -1})) ++ it('does not compare values', function() ++ assert.is_true(M.containsKeys({1,2,3},{4,5,6})) ++ assert.is_true(M.containsKeys({x = 1, y = 2},{x = 4,y = -1})) + end) + +- test('is not commutative', function() +- assert_true(_.containsKeys({1,2,3,4},{4,5,6})) +- assert_true(_.containsKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) +- assert_false(_.containsKeys({1,2,3},{4,5,6,7})) +- assert_false(_.containsKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) ++ it('is not commutative', function() ++ assert.is_true(M.containsKeys({1,2,3,4},{4,5,6})) ++ assert.is_true(M.containsKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) ++ assert.is_false(M.containsKeys({1,2,3},{4,5,6,7})) ++ assert.is_false(M.containsKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) + end) + + end) + +- context('sameKeys', function() ++ describe('sameKeys', function() + +- test('returns whether both tables features the same keys', function() +- assert_true(_.sameKeys({1,2,3},{1,2,3})) +- assert_true(_.sameKeys({x = 1, y = 2},{x = 1,y =2})) ++ it('returns whether both tables features the same keys', function() ++ assert.is_true(M.sameKeys({1,2,3},{1,2,3})) ++ assert.is_true(M.sameKeys({x = 1, y = 2},{x = 1,y =2})) + end) + +- test('does not compare values', function() +- assert_true(_.sameKeys({1,2,3},{4,5,6})) +- assert_true(_.sameKeys({x = 1, y = 2},{x = 4,y = -1})) ++ it('does not compare values', function() ++ assert.is_true(M.sameKeys({1,2,3},{4,5,6})) ++ assert.is_true(M.sameKeys({x = 1, y = 2},{x = 4,y = -1})) + end) + +- test('is commutative', function() +- assert_false(_.sameKeys({1,2,3,4},{4,5,6})) +- assert_false(_.sameKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) +- assert_false(_.sameKeys({1,2,3},{4,5,6,7})) +- assert_false(_.sameKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) ++ it('is commutative', function() ++ assert.is_false(M.sameKeys({1,2,3,4},{4,5,6})) ++ assert.is_false(M.sameKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) ++ assert.is_false(M.sameKeys({1,2,3},{4,5,6,7})) ++ assert.is_false(M.sameKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) + end) + + end) +Submodule extra/penlight 6d108e6..bd26cb9: +diff --git a/extra/penlight/.busted b/extra/penlight/.busted +new file mode 100644 +index 0000000..065847b +--- /dev/null ++++ b/extra/penlight/.busted +@@ -0,0 +1,8 @@ ++return { ++ default = { ++ verbose = true, ++ output = "gtest", ++ lpath = "./lua/?.lua;./lua/?/init.lua", ++ } ++} ++-- vim: ft=lua +diff --git a/extra/penlight/.editorconfig b/extra/penlight/.editorconfig +new file mode 100644 +index 0000000..3434e8a +--- /dev/null ++++ b/extra/penlight/.editorconfig +@@ -0,0 +1,22 @@ ++root = true ++ ++[*] ++end_of_line = lf ++insert_final_newline = true ++trim_trailing_whitespace = true ++charset = utf-8 ++ ++[*.lua] ++indent_style = space ++indent_size = 2 ++ ++[kong/templates/nginx*] ++indent_style = space ++indent_size = 4 ++ ++[*.template] ++indent_style = space ++indent_size = 4 ++ ++[Makefile] ++indent_style = tab +diff --git a/extra/penlight/.github/workflows/luacheck.yml b/extra/penlight/.github/workflows/luacheck.yml +new file mode 100644 +index 0000000..f6d7b93 +--- /dev/null ++++ b/extra/penlight/.github/workflows/luacheck.yml +@@ -0,0 +1,13 @@ ++name: Luacheck ++ ++on: [push, pull_request] ++ ++jobs: ++ ++ luacheck: ++ runs-on: ubuntu-latest ++ steps: ++ - name: Checkout ++ uses: actions/checkout@v4 ++ - name: Luacheck ++ uses: lunarmodules/luacheck@v1 +diff --git a/extra/penlight/.github/workflows/unix_build.yml b/extra/penlight/.github/workflows/unix_build.yml +new file mode 100644 +index 0000000..0a60c05 +--- /dev/null ++++ b/extra/penlight/.github/workflows/unix_build.yml +@@ -0,0 +1,52 @@ ++name: "Unix build" ++ ++on: [push, pull_request] ++ ++jobs: ++ test: ++ runs-on: ubuntu-latest ++ ++ strategy: ++ fail-fast: false ++ matrix: ++ luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3", "luajit-openresty"] ++ ++ steps: ++ - uses: actions/checkout@v4 ++ ++ - uses: hishamhm/gh-actions-lua@master ++ with: ++ luaVersion: ${{ matrix.luaVersion }} ++ ++ - uses: hishamhm/gh-actions-luarocks@master ++ with: ++ luaRocksVersion: "3.12.0" ++ ++ - name: dependencies ++ run: | ++ luarocks install busted ++ luarocks install luacov-coveralls ++ ++ - name: build ++ run: | ++ luarocks remove penlight --force ++ luarocks make ++ ++ - name: Busted tests ++ run: | ++ busted --coverage --Xoutput "--color" ++ ++ - name: Old test suite ++ run: | ++ lua run.lua tests --luacov ++ ++ - name: Examples ++ run: | ++ lua run.lua examples ++ ++ - name: Report test coverage ++ if: success() ++ continue-on-error: true ++ run: luacov-coveralls ++ env: ++ COVERALLS_REPO_TOKEN: ${{ github.token }} +diff --git a/extra/penlight/.gitignore b/extra/penlight/.gitignore +new file mode 100644 +index 0000000..1237f8f +--- /dev/null ++++ b/extra/penlight/.gitignore +@@ -0,0 +1,2 @@ ++luacov.stats.out ++*.rock +diff --git a/extra/penlight/.luacheckrc b/extra/penlight/.luacheckrc +new file mode 100644 +index 0000000..82dab88 +--- /dev/null ++++ b/extra/penlight/.luacheckrc +@@ -0,0 +1,40 @@ ++unused_args = false ++redefined = false ++max_line_length = false ++ ++globals = { ++ "ngx", ++} ++ ++not_globals = { ++ "string.len", ++ "table.getn", ++} ++ ++include_files = { ++ "**/*.lua", ++ "*.rockspec", ++ ".busted", ++ ".luacheckrc", ++} ++ ++files["spec/**/*.lua"] = { ++ std = "+busted", ++} ++ ++exclude_files = { ++ "tests/*.lua", ++ "tests/**/*.lua", ++ -- Travis Lua environment ++ "here/*.lua", ++ "here/**/*.lua", ++ -- GH Actions Lua Environment ++ ".lua", ++ ".luarocks", ++ ".install", ++ ++ -- TODO: fix these files ++ "examples/symbols.lua", ++ "examples/test-symbols.lua", ++} ++ +diff --git a/extra/penlight/.travis.yml b/extra/penlight/.travis.yml +deleted file mode 100644 +index f0dd90f..0000000 +--- a/extra/penlight/.travis.yml ++++ /dev/null +@@ -1,26 +0,0 @@ +-language: python +-sudo: false +- +-env: +- - LUA="lua 5.1" +- - LUA="lua 5.2" +- - LUA="lua 5.3" +- - LUA="luajit 2.0" +- - LUA="luajit 2.0 --compat 5.2" +- - LUA="luajit 2.1" +- - LUA="luajit 2.1 --compat 5.2" +- +-before_install: +- - pip install hererocks +- - hererocks here -r^ --$LUA +- - source here/bin/activate +- +-install: +- - luarocks make +- - luarocks install luacov-coveralls +- +-script: +- - lua run.lua tests --luacov +- +-after_success: +- - luacov-coveralls +diff --git a/extra/penlight/CHANGELOG.md b/extra/penlight/CHANGELOG.md +new file mode 100644 +index 0000000..a96fd10 +--- /dev/null ++++ b/extra/penlight/CHANGELOG.md +@@ -0,0 +1,687 @@ ++# Changelog ++ ++Versioning is strictly according to [Semantic Versioning](https://semver.org/), ++see the [README.md](README.md#versioning) for details on version scoping and ++deprecation policy. ++ ++see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions ++ ++## unreleased ++ - fix(types): callable would return false positive if `__call` was nested ++ [#489](https://github.com/lunarmodules/Penlight/pull/489) ++ ++## 1.14.0 (2024-Apr-15) ++ - fix(path): make `path.expanduser` more sturdy ++ [#469](https://github.com/lunarmodules/Penlight/pull/469) ++ - feat(func): extend `compose` to support N functions ++ [#448](https://github.com/lunarmodules/Penlight/pull/448) ++ - fix(utils) `nil` values in `utils.choose(cond, val1, val2)` ++ [#447](https://github.com/lunarmodules/Penlight/pull/447) ++ - fix(template) using `%` as an escape character caused the expression to not be recognized ++ [#452](https://github.com/lunarmodules/Penlight/pull/452) ++ - enhance(template): Preserve line numbers ++ [#468](https://github.com/lunarmodules/Penlight/pull/468) ++ - fix(pretty) integers for Lua 5.4 ++ [#456](https://github.com/lunarmodules/Penlight/pull/456) ++ ++## 1.13.1 (2022-Jul-22) ++ - fix: `warn` unquoted argument ++ [#439](https://github.com/lunarmodules/Penlight/pull/439) ++ ++## 1.13.0 (2022-Jul-22) ++ - fix: `xml.parse` returned nonsense when given a file name ++ [#431](https://github.com/lunarmodules/Penlight/pull/431) ++ - feat: `app.require_here` now follows symlink'd main modules to their directory ++ [#423](https://github.com/lunarmodules/Penlight/pull/423) ++ - fix: `pretty.write` invalid order function for sorting ++ [#430](https://github.com/lunarmodules/Penlight/pull/430) ++ - fix: `compat.warn` raised write guard warning in OpenResty ++ [#414](https://github.com/lunarmodules/Penlight/pull/414) ++ - feat: `utils.enum` now accepts hash tables, to enable better error handling ++ [#413](https://github.com/lunarmodules/Penlight/pull/413) ++ - feat: `utils.kpairs` new iterator over all non-integer keys ++ [#413](https://github.com/lunarmodules/Penlight/pull/413) ++ - fix: `warn` use rawget to not trigger strict-checkers ++ [#437](https://github.com/lunarmodules/Penlight/pull/437) ++ - fix: `lapp` provides the file name when using the default argument ++ [#427](https://github.com/lunarmodules/Penlight/pull/427) ++ - fix: `lapp` positional arguments now allow digits after the first character ++ [#428](https://github.com/lunarmodules/Penlight/pull/428) ++ - fix: `path.isdir` windows root directories (including drive letter) were not considered valid ++ [#436](https://github.com/lunarmodules/Penlight/pull/436) ++ ++ ++## 1.12.0 (2022-Jan-10) ++ - deprecate: module `pl.text` the contents have moved to `pl.stringx` (removal later) ++ [#407](https://github.com/lunarmodules/Penlight/pull/407) ++ - deprecate: module `pl.xml`, please switch to a more specialized library (removal later) ++ [#409](https://github.com/lunarmodules/Penlight/pull/409) ++ - feat: `utils.npairs` added. An iterator with a range that honours the `n` field ++ [#387](https://github.com/lunarmodules/Penlight/pull/387) ++ - fix: `xml.maptags` would hang if it encountered text-nodes ++ [#396](https://github.com/lunarmodules/Penlight/pull/396) ++ - fix: `text.dedent` didn't handle declining indents nor empty lines ++ [#402](https://github.com/lunarmodules/Penlight/pull/402) ++ - fix: `dir.getfiles`, `dir.getdirectories`, and `dir.getallfiles` now have the ++ directory optional, as was already documented ++ [#405](https://github.com/lunarmodules/Penlight/pull/405) ++ - feat: `array2d.default_range` now also takes a spreadsheet range, which means ++ also other functions now take a range. [#404](https://github.com/lunarmodules/Penlight/pull/404) ++ - fix: `lapp` enums allow [patterns magic characters](https://www.lua.org/pil/20.2.html) ++ [#393](https://github.com/lunarmodules/Penlight/pull/393) ++ - fix: `text.wrap` and `text.fill` numerous fixes for handling whitespace, ++ accented characters, honouring width, etc. ++ [#400](https://github.com/lunarmodules/Penlight/pull/400) ++ - feat: `text.wrap` and `text.fill` have a new parameter to forcefully break words ++ longer than the width given. ++ [#400](https://github.com/lunarmodules/Penlight/pull/400) ++ - fix: `stringx.expandtabs` could error out on Lua 5.3+ ++ [#406](https://github.com/lunarmodules/Penlight/pull/406) ++ - fix: `pl` the module would not properly forward the `newindex` metamethod ++ on the global table. ++ [#395](https://github.com/lunarmodules/Penlight/pull/395) ++ - feat: `utils.enum` added to create enums and prevent magic strings ++ [#408](https://github.com/lunarmodules/Penlight/pull/408) ++ - change: `xml.new` added some sanity checks on input ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - added: `xml.xml_escape` and `xml.xml_unescape` functions (previously private) ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - feat: `xml.tostring` now also takes numeric indents (previously only strings) ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.walk` now detects recursion (errors out) ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.clone` now detects recursion (errors out) ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.compare` now detects recursion (errors out) ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.compare` text compares now work ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.compare` attribute order compares now only compare if both inputs provide an order ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ - fix: `xml.compare` child comparisons failing now report proper error ++ [#397](https://github.com/lunarmodules/Penlight/pull/397) ++ ++ ++## 1.11.0 (2021-Aug-18) ++ ++ - fix: `stringx.strip` behaved badly with string lengths > 200 ++ [#382](https://github.com/lunarmodules/Penlight/pull/382) ++ - fix: `path.currentdir` now takes no arguments and calls `lfs.currentdir` without argument ++ [#383](https://github.com/lunarmodules/Penlight/pull/383) ++ - feat: `utils.raise_deprecation` now has an option to NOT include a ++ stack-trace [#385](https://github.com/lunarmodules/Penlight/pull/385) ++ ++ ++## 1.10.0 (2021-Apr-27) ++ ++ - deprecate: `permute.iter`, renamed to `permute.order_iter` (removal later) ++ [#360](https://github.com/lunarmodules/Penlight/pull/360) ++ - deprecate: `permute.table`, renamed to `permute.order_table` (removal later) ++ [#360](https://github.com/lunarmodules/Penlight/pull/360) ++ - deprecate: `Date` module (removal later) ++ [#367](https://github.com/lunarmodules/Penlight/pull/367) ++ - feat: `permute.list_iter` to iterate over different sets of values ++ [#360](https://github.com/lunarmodules/Penlight/pull/360) ++ - feat: `permute.list_table` generate table with different sets of values ++ [#360](https://github.com/lunarmodules/Penlight/pull/360) ++ - feat: Lua 5.4 'warn' compatibility function ++ [#366](https://github.com/lunarmodules/Penlight/pull/366) ++ - feat: deprecation functionality `utils.raise_deprecation` ++ [#361](https://github.com/lunarmodules/Penlight/pull/361) ++ - feat: `utils.splitv` now takes same args as `split` ++ [#373](https://github.com/lunarmodules/Penlight/pull/373) ++ - fix: `dir.rmtree` failed to remove symlinks to directories ++ [#365](https://github.com/lunarmodules/Penlight/pull/365) ++ - fix: `pretty.write` could error out on failing metamethods (Lua 5.3+) ++ [#368](https://github.com/lunarmodules/Penlight/pull/368) ++ - fix: `app.parse` now correctly parses values containing '=' or ':' ++ [#373](https://github.com/lunarmodules/Penlight/pull/373) ++ - fix: `dir.makepath` failed to create top-level directories ++ [#372](https://github.com/lunarmodules/Penlight/pull/372) ++ - overhaul: `array2d` module was updated, got additional tests and several ++ documentation updates ++ [#377](https://github.com/lunarmodules/Penlight/pull/377) ++ - feat: `array2d` now accepts negative indices ++ - feat: `array2d.row` added to align with `column` ++ - fix: bad error message in `array2d.map` ++ - fix: `array2d.flatten` now ensures to deliver a 'square' result if `nil` is ++ encountered ++ - feat: `array2d.transpose` added ++ - feat: `array2d.swap_rows` and `array2d.swap_cols` now return the array ++ - fix: `array2d.range` correctly recognizes `R` column in spreadsheet format, was ++ mistaken for `R1C1` format. ++ - fix: `array2d.range` correctly recognizes 2 char column in spreadsheet format ++ - feat: `array2d.default_range` added (previously private) ++ - feat: `array2d.set` if used with a function now passes `i,j` to the function ++ in line with the `new` implementation. ++ - fix: `array2d.iter` didn't properly iterate the indices ++ [#376](https://github.com/lunarmodules/Penlight/issues/376) ++ - feat: `array2d.columns` now returns a second value; the column index ++ - feat: `array2d.rows` added to be in line with `columns` ++ ++ ++## 1.9.2 (2020-Sep-27) ++ ++ - fix: dir.walk [#350](https://github.com/lunarmodules/Penlight/pull/350) ++ ++ ++## 1.9.1 (2020-Sep-24) ++ ++ - released to superseed the 1.9.0 version which was retagged in git after some ++ distro's already had picked it up. This version is identical to 1.8.1. ++ ++## 1.8.1 (2020-Sep-24) (replacing a briefly released but broken 1.9.0 version) ++ ++## Fixes ++ ++ - In `pl.class`, `_init` can now be inherited from grandparent (or older ancestor) classes. [#289](https://github.com/lunarmodules/Penlight/pull/289) ++ - Fixes `dir`, `lexer`, and `permute` to no longer use coroutines. [#344](https://github.com/lunarmodules/Penlight/pull/344) ++ ++## 1.8.0 (2020-Aug-05) ++ ++### New features ++ ++ - `pretty.debug` quickly dumps a set of values to stdout for debug purposes ++ ++### Changes ++ ++ - `pretty.write`: now also sorts non-string keys [#319](https://github.com/lunarmodules/Penlight/pull/319) ++ - `stringx.count` has an extra option to allow overlapping matches ++ [#326](https://github.com/lunarmodules/Penlight/pull/326) ++ - added an extra changelog entry for `types.is_empty` on the 1.6.0 changelog, due ++ to additional fixed behaviour not called out appropriately [#313](https://github.com/lunarmodules/Penlight/pull/313) ++ - `path.packagepath` now returns a proper error message with names tried if ++ it fails ++ ++### Fixes ++ ++ - Fix: `stringx.rfind` now properly works with overlapping matches ++ [#314](https://github.com/lunarmodules/Penlight/pull/314) ++ - Fix: `package.searchpath` (in module `pl.compat`) ++ [#328](https://github.com/lunarmodules/Penlight/pull/328) ++ - Fix: `path.isabs` now reports drive + relative-path as `false`, eg. "c:some/path" (Windows only) ++ - Fix: OpenResty coroutines, used by `dir.dirtree`, `pl.lexer`, `pl.permute`. If ++ available the original coroutine functions are now used [#329](https://github.com/lunarmodules/Penlight/pull/329) ++ - Fix: in `pl.strict` also predefine global `_PROMPT2` ++ - Fix: in `pl.strict` apply `tostring` to the given name, in case it is not a string. ++ - Fix: the lexer would not recognize numbers without leading zero; "-.123". ++ See [#315](https://github.com/lunarmodules/Penlight/issues/315) ++ ++## 1.7.0 (2019-Oct-14) ++ ++### New features ++ ++ - `utils.quote_arg` will now optionally take an array of arguments and escape ++ them all into a single string. ++ - `app.parse_args` now accepts a 3rd parameter with a list of valid flags and aliases ++ - `app.script_name` returns the name of the current script (previously a private function) ++ ++### Changes ++ ++ - Documentation updates ++ - `utils.quit`: exit message is no longer required, and closes the Lua state (on 5.2+). ++ - `utils.assert_arg` and `utils.assert_string`: now return the validated value ++ - `pl.compat`: now exports the `jit` and `jit52` flags ++ - `pretty.write`: now sorts the output for easier diffs [#293](https://github.com/lunarmodules/Penlight/pull/293) ++ ++### Fixes ++ ++ - `utils.raise` changed the global `on_error`-level when passing in bad arguments ++ - `utils.writefile` now checks and returns errors when writing ++ - `compat.execute` now handles the Windows exitcode -1 properly ++ - `types.is_empty` would return true on spaces always, independent of the parameter ++ - `types.to_bool` will now compare case-insensitive for the extra passed strings ++ - `app.require_here` will now properly handle an absolute base path ++ - `stringx.split` will no longer append an empty match if the number of requested ++ elements has already been reached [#295](https://github.com/lunarmodules/Penlight/pull/295) ++ - `path.common_prefix` and `path.relpath` return the result in the original casing ++ (only impacted Windows) [#297](https://github.com/lunarmodules/Penlight/pull/297) ++ - `dir.copyfile`, `dir.movefile`, and `dir.makepath` create the new file/path with ++ the requested casing, and no longer force lowercase (only impacted Windows) ++ [#297](https://github.com/lunarmodules/Penlight/pull/297) ++ - added a missing assertion on `path.getmtime` [#291](https://github.com/lunarmodules/Penlight/pull/291) ++ - `stringx.rpartition` returned bad results on a not-found [#299](https://github.com/lunarmodules/Penlight/pull/299) ++ ++## 1.6.0 (2018-Nov-23) ++ ++### New features ++ ++ - `pl.compat` now provides `unpack` as `table.unpack` on Lua 5.1 ++ ++### Changes ++ ++ - `utils.unpack` is now documented and respects `.n` field of its argument. ++ - `tablex.deepcopy` and `tablex.deepcompare` are now cycle aware (#262) ++ - Installing through LuaRocks will now include the full rendered documentation ++ ++### Fixes ++ ++ - Fixed `seq.last` returning `nil` instead of an empty list when given an empty iterator (#253). ++ - `pl.template` now applies `tostring` when substituting values in templates, avoiding errors when they are not strings or numbers (#256). ++ - Fixed `pl.import_into` not importing some Penlight modules (#268). ++ - Fixed version number stuck at 1.5.2 (#260). ++ - Fixed `types.is_empty` returning `true` on tables containing `false` key (#267). ++ - Fixed `types.is_empty` returning `false` if not a nil/table/string ++ - Fixed `test.assertraise` throwing an error when passed an array with a function to call plus its arguments (#272). ++ - Fixed `test.assertraise` not throwing an error when given function does not error but instead returns a string matching given error pattern. ++ - Fixed placeholder expressions being evaluated with wrong precedence of binary and unary negation. ++ - Fixed placeholder expressions being evaluated assuming wrong binary operator associativity (e.g. `_1-(_2+_3)` was evaluated as `(_1-_2)+_3`. ++ - Fixed placeholder expressions being evaluated as if unary operators take precedence over power operator (e.g. `(-_1)^_2`) was evaluated as `-(_1^2)`). ++ - Fixed vulnerable backtracking pattern in `pl.stringx.strip` (#275) ++ ++## 1.5.4 (2017-07-17) ++ ++### Fixes ++ ++ - Fixed `compat.execute` behaving differently on Lua 5.1 and 5.1+. ++ - Fixed `lapp.process_options_string` setting global `success` variable. ++ ++## 1.5.3 (2017-07-16) ++ ++### Changes ++ ++ - Added `template.compile` function that allows caching compiled template and rendering it multiple times. ++ - Added special `_debug` field to environment table argument in `template.substitute` for printing generated template code upon render error. ++ ++### Fixes ++ ++ - Fixed error (`attempt to concatenate a nil value (local 'vtype')`) in `lapp.process_options_string`. ++ ++## 1.5.2 (2017-04-08) ++ ++### Fixes ++ ++ - Removed leftover debug pring in `lapp.process_options_string`. ++ ++## 1.5.1 (2017-04-02) ++ ++### Fixes ++ ++ - Fixed `dir.getfiles` matching given pattern against full paths from base directory instead of file names. ++ ++## 1.5.0 (2017-04-01) ++ ++### Changes ++ ++ - `stringx.splitlines` considers `\r\n` a single line ending. ++ - `stringx.splitlines` returns an empty list for an empty string. ++ ++### Fixes ++ ++ - `tablex.count_map` no longer raises an error. ++ - `strict.module` correctly handles existing `__index` metamethod returning `false`. ++ - `app.parse_args` accepts colon as a separator between option name and value, as advertised. ++ - `pretty.load` handles case where a C hook is present. ++ ' `os.execute` had issue with LuaJIT in 5.2 compat mode. ++ ++### Features ++ ++ - `template` supports customizing inline escape character and chunk name. ++ - `seq` constructor supports iterators with a state object as the second argument. ++ - `stringx.splitlines` has `keep_ends` argument. ++ - `tablex.reduce` can take an optional initial value. ++ ++## 1.4.1 (2016-08-16) ++ ++### Changes ++ ++ - All functions that return instances of `pl.List`, `pl.Map` and `pl.Set` now require corresponding modules, ++ so that their methods always work right away. ++ ++### Fixes ++ ++ - Fixed `dir.getallfiles` returning an empty array when called without `pattern` argument. ++ ++### Features ++ ++## 1.4.0 (2016-08-14) ++ ++### Changes ++ ++### Fixes ++ ++ - `pl.path` covers edge cases better (e.g `path.normpath` was broken) ++ - `p.dir` shell patterns fixed ++ - `os.tmpname` broken on modern Windows/MSVC14 ++ - (likewise for `utils.executeex` which depends on it) ++ - `pretty.write` more robust and does not lose floating-point precision; ++ saves and restores debug hooks when loading. ++ - `pl.lexer` fixes: `cpp` lexer now filters space by default ++ - `tablex.sortv` no longer assumes that the values are all unique ++ - `stringx.center` is now consistent with Python; `stringx.rfind` and ++ `string.quote_string` fixed. ++ - `data.write` had a problem with default delimiter, properly returns error now. ++ - `pl.Set` `+` and `-` now have correct semantics ++ ++### Features ++ ++ - `pl.tablex` has `union` and `merge` convenience functions ++ - `pl.lapp` understands '--' meaning end of parsed arguments ++ - `utils.quote_arg` quotes command arguments for `os.execute`, ++ correctly handling all special characters. ++ - `utils.writefile` has optional `is_bin` argument ++ - 'pl.lexer' supports line numbers with string argument ++ - `stringx.endswith` may be passed an array of possible suffixes. ++ - `data.read` - in CSV mode, assume empty fields are numerical zero ++ ++ ++## 1.3.2 (2015-05-10) ++ ++### Changes ++ ++ - now works and passes tests with Lua 5.3 ++ - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) ++ - Updated pl.dir.file_op to return true on success and false on failure... ++ - workaround for issues with pl.lapp with amalg.lua - will look at global LAPP_SCRIPT if arg[0] is nil ++ ++### Fixes ++ ++ - func was broken: do NOT use ipairs to iterate if __index is overridden! ++ - issue #133 pretty.read (naively) confused by unbalanced brackets ++ - xml attribute underscore fix for simple parser ++ - Fix path.normpath ++ - lexer: fix parsing block comments/string. fix hang on empty string. ++ - Fixed utils.execute returning different values for Lua 5.1 and Lua 5.2 ++ - Issue #97; fixed attempt to put a month into a day ++ - problem with tablex.count_map with custom comparison ++ - tablex.pairmap overwrites result if key already exists; instead, upon detection that key already exists ++ for a returned value, we modify the key's value to be a table and insert values into that table ++ ++### Features ++ ++ - Add Python style url module for quote and unquote. ++ - stringx.quote_string, which scans for embedded long-string quote matches and escapes them by creating a long-string quote. ++ - issue #117: tablex.range now works with decreasing numbers, consistent with numerical for loop ++ - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) ++ - issue #125: DOCTYPE ignored in xml documents as well ++ - Allow XML tostring() function to customize the default prefacing with `` ++ - More Robust Quoted Strings ++ - lapp: improved detection of unsupported short flags ++ ++## 1.3.1 (2013-09-24) ++ ++## 1.3.0 (2013-09-14) ++ ++### Changes ++ ++ - class: RIP base method - not possible to implement correctly ++ - lapp: short flags can now always be followed directly by their value, for instance, ++`-I/usr/include/lua/5.1` ++ - Date: new explicit `Date.Interval` class; `toUTC/toLocal` return new object; `Date.__tostring` ++always returns ISO 8601 times for exact serialization. `+/-` explicit operators. Date objects ++are explicitly flagged as being UTC or not. ++ ++### Fixes ++ ++ - class: super method fixed. ++ - Date: DST is now accounted for properly. ++ - Date: weekday calculation borked. ++ ++### Features ++ ++ - All tests pass with no-5.1-compatible Lua 5.2; now always uses `utils.load` and ++`utils.unpack` is always available. ++ - types: new module containing `utils.is_xxx` methods plus new `to_bool`. ++ - class: can be passed methods in a table (see `test=klass.lua`). This is ++particularly convenient for using from Moonscript. ++ - general documentation improvements, e.g `class` ++ ++## 1.2.1 (2013-06-21) ++ ++### Changes ++ ++ - utils.set(get)fenv always defined (_not_ set as globals for 5.2 anymore!). ++ These are defined in new module pl.compat, but still available through utils. ++ - class.Frodo now puts 'Frodo' in _current environment_ ++ ++### Fixes ++ ++ - lapp.add_type was broken (Pete Kazmier) ++ - class broke with classes that redefined __newindex ++ - Set.isdisjoint was broken because of misspelling; default ctor Set() now works as expected ++ - tablex.transform was broken; result now has same keys as original (CoolistheName007) ++ - xml match not handling empty matches (royalbee) ++ - pl.strict: assigning nil to global declares it, as God intended. (Pierre Chapuis) ++ - tests all work with pl.strict ++ - 5.2 compatible load now respects mode ++ - tablex.difference thought that a value of `false` meant 'not present' (Andrew Starke) ++ ++### Features ++ ++ - tablex.sort(t) iterates over sorted keys, tablex.sortv(t) iterates over sorted values (Pete Kazmier) ++ - tablex.readonly(t) creates a read-only proxy for a table (John Schember) ++ - utils.is_empty(o) true if o==nil, o is an empty table, or o is an empty string (John Schember) ++ - utils.executeex(cmd,bin) returns true if successful, return code, plus stdout and stderr output as strings. (tieske) ++ - class method base for calling inherited methods (theypsilon) ++ - class supports pre-constructor _create for making a custom self (used in pl.List) ++ - xml HTML mode improvements - can parse non-trivial well-formed HTML documents. ++ xml.parsehtml is a parse function, no longer a flag ++ - if a LOM document has ordered attributes, use these when stringifying ++ - xml.tostring has yet another extra parm to force prefacing with `` ++ - lapp boolean flags may have `true` default ++ - lapp slack mode where 'short' flags can be multi-char ++ - test.asserteq etc take extra arg, which is extra level where error must be reported at ++ - path.currentdir,chdir,rmdir,mkdir and dir as alias to lfs are exported; no dependencies on luafilesystem outside pl.path, making it easier to plug in different implementations. ++ ++## 1.2.0 (2013-05-28) ++ ++## 1.1.1 (2013-05-14) ++ ++## 1.1.0 (2013-03-18) ++ ++## 1.0.3 (2012-12-07) ++ ++## 1.0.2 (2012-05-12) ++ ++## 1.0.1 (2012-05-26) ++ ++## 1.0.0 (2012-04-26) ++ ++## 0.9.8 (2011-11-27) ++ ++## 0.9.7 (2011-11-27) ++ ++### Lua 5.2 compatibility ++ ++(These are all now defined in pl.utils) ++ ++- setfenv, getfenv defined for Lua 5.2 (by Sergey Rozhenko) ++ ++### Changes ++ ++- array2d.flatten is new ++- OrderedMap:insert is new ++ ++### Fixes ++ ++- seq.reduce re-implemented to give correct order (Carl Ådahl) ++- seq.unique was broken: new test ++- tablex.icopy broken for last argument; new test ++- utils.function_arg last parm 'msg' was missing ++- array2d.product was broken; more sensible implementation ++- array2d.range, .slice, .write were broken ++- text optional operator % overload broken for 'fmt % fun'; new tests ++- a few occurrences of non-existent function utils.error removed ++ ++ ++## 0.9.6 (2011-09-11) ++ ++### Lua 5.2 compatibility ++ ++- Bad string escape in tests fixed ++ ++### Changes ++ ++- LuaJIT FFI used on Windows for Copy/MoveFile functionality ++ ++### Fixes ++ ++- Issue 13 seq.sort now calls seq.copy ++- issue 14 bad pattern to escape trailing separators in path.abspath ++- lexer: string tokens broken with some combinations ++- lexer: long comments broken for Lua and C ++- stringx.split behaves according to Python spec; extra parm meaning 'max splits' ++- stringx.title behaves according to Python spec ++- stringx.endswith broken for 2nd arg being table of postfixes ++- OrderedMap.set broken when value was nil and key did not exist in map; ctor throws ++ error if unhappy ++ ++## 0.9.5 (2011-07-05) ++ ++### Lua 5.2 compatibility ++ ++ - defines Lua 5.2 beta compatible load() ++ - defines table.pack() ++ ++### New functions ++ ++ - stringx.title(): translates "a dog's day" to "A Dog's Day" ++ - path.normpath(): translates 'A//B','A/./B' and 'A/C/../B' to 'A/B' ++ - utils.execute(): returns ok,return-code: compatible with 5.1 and 5.2 ++ ++### Fixes ++ ++ - pretty.write() _always_ returns a string, but will return also an error string ++if the argument is not a table. Non-integer indices between 1 and #t are no longer falsely considered part of the array ++ - stringx.expandtabs() now works like the Python string method; it will expand each field up to the next tab stop ++ - path.normcase() was broken, because of a misguided attempt to normalize the path. ++ - UNC specific fix to path.abspath() ++ - UNC paths recognized as absolute; dir.makedir() works here ++ - utils.quit() varargs broken, e.g. utils.quit("answer was %d",42) ++ - some stray globals caused trouble with 'strict' ++ ++## 0.9.4 (2011-04-08) ++ ++## 0.9.3 (2011-03-05) ++ ++## 0.9.2 (2011-02-16) ++ ++## 0.9.1 (2011-02-12) ++ ++## 0.9.0 (2010-12-20) ++ ++## 0.8.5 (2010-12-16) ++ ++### What's new with 0.8b ? ++ ++#### Features: ++ ++pl.app provides useful stuff like simple command-line argument parsing and require_here(), which ++makes subsequent require() calls look in the local directory by preference. ++ ++p.file provides useful functions like copy(),move(), read() and write(). (These are aliases to ++dir.copyfile(),movefile(),utils.readfile(),writefile()) ++ ++Custom error trace will only show the functions in user code. ++ ++More robust argument checking. ++ ++In function arguments, now supports 'string lambdas', e.g. `'|x| 2*x'` ++ ++utils.readfile,writefile now insist on being given filenames. This will cause less confusion. ++ ++tablex.search() is new: will look recursively in an arbitrary table; can specify tables not to follow. ++tablex.move() will work with source and destination tables the same, with overlapping ranges. ++ ++#### Bug Fixes: ++ ++dir.copyfile() now works fine without Alien on Windows ++ ++dir.makepath() and rmtree() had problems. ++ ++tablex.compare_no_order() is now O(NlogN), as expected. ++tablex.move() had a problem with source size ++ ++### What's New with 0.7.0b? ++ ++#### Features: ++ ++utils.is_type(v,tp) can say is_type(s,'string') and is_type(l,List). ++utils.is_callable(v) either a function, or has a `__call` metamethod. ++ ++Sequence wrappers: can write things like this: ++ ++seq(s):last():filter('<'):copy() ++ ++seq:mapmethod(s,name) - map using a named method over a sequence. ++ ++seq:enum(s) If s is a simple sequence, then ++ for i,v in seq.enum(s) do print(i,v) end ++ ++seq:take(s,n) Grab the next n values from a (possibly infinite) ++sequence. ++ ++In a related change suggested by Flemming Madsden, the in-place List ++methods like reverse() and sort() return the list, allowing for ++method chaining. ++ ++list.join() explicitly converts using tostring first. ++ ++tablex.count_map() like seq.count_map(), but takes an equality function. ++ ++tablex.difference() set difference ++tablex.set() explicit set generator given a list of values ++ ++Template.indent_substitute() is a new Template method which adjusts ++for indentation and can also substitute templates themselves. ++ ++pretty.read(). This reads a Lua table (as dumped by pretty.write) ++and attempts to be paranoid about its contents. ++ ++sip.match_at_start(). Convenience function for anchored SIP matches. ++ ++#### Bug Fixes: ++ ++tablex.deepcompare() was confused by false boolean values, which ++it thought were synonymous with being nil. ++ ++pretty.write() did not handle cycles, and could not display tables ++with 'holes' properly (Flemming Madsden) ++ ++The SIP pattern '$(' was not escaped properly. ++sip.match() did not pass on options table. ++ ++seq.map() was broken for double-valued sequences. ++seq.copy_tuples() did not use default_iter(), so did not e.g. like ++table arguments. ++ ++dir.copyfile() returns the wrong result for \*nix operations. ++dir.makepath() was broken for non-Windows paths. ++ ++### What's New with 0.6.3? ++ ++The map and reduce functions now take the function first, as Nature intended. ++ ++The Python-like overloading of '\*' for strings has been dropped, since it ++is silly. Also, strings are no longer callable; use 's:at(1)' instead of ++'s(1)' - this tended to cause Obscure Error messages. ++ ++Wherever a function argument is expected, you can use the operator strings ++like '+','==',etc as well as pl.operator.add, pl.operator.eq, etc. ++(see end of pl/operator.lua for the full list.) ++ ++tablex now has compare() and compare_no_order(). An explicit set() ++function has been added which constructs a table with the specified ++keys, all set to a value of true. ++ ++List has reduce() and partition() (This is a cool function which ++separates out elements of a list depending on a classifier function.) ++ ++There is a new array module which generalizes tablex operations like ++map and reduce for two-dimensional arrays. ++ ++The famous iterator over permutations from PiL 9.3 has been included. ++ ++David Manura's list comprehension library has been included. ++ ++Also, utils now contains his memoize function, plus a useful function ++args which captures the case where varargs contains nils. ++ ++There was a bug with dir.copyfile() where the flag was the wrong way round. ++ ++config.lines() had a problem with continued lines. ++ ++Some operators were missing in pl.operator; have renamed them to be ++consistent with the Lua metamethod names. ++ ++ +diff --git a/extra/penlight/CHANGES.md b/extra/penlight/CHANGES.md +deleted file mode 100644 +index bf5a157..0000000 +--- a/extra/penlight/CHANGES.md ++++ /dev/null +@@ -1,358 +0,0 @@ +-## 1.5.0 [in progress] +- +-### Changes +- +- - `stringx.splitlines` considers `\r\n` a single line ending. +- - `stringx.splitlines` returns an empty list for an empty string. +- +-### Fixes +- +- - `tablex.count_map` no longer raises an error. +- - `strict.module` correctly handles existing `__index` metamethod returning `false`. +- - `app.parse_args` accepts colon as a separator between option name and value, as advertised. +- - `pretty.load` handles case where a C hook is present. +- ' `os.execute` had issue with LuaJIT in 5.2 compat mode. +- +-### Features +- +- - `template` supports customizing inline escape character and chunk name. +- - `seq` constructor supports iterators with a state object as the second argument. +- - `stringx.splitlines` has `keep_ends` argument. +- - `tablex.reduce` can take an optional initial value. +- +-## 1.4.1 +- +-### Changes +- +- - All functions that return instances of `pl.List`, `pl.Map` and `pl.Set` now require corresponding modules, +- so that their methods always work right away. +- +-### Fixes +- +- - Fixed `dir.getallfiles` returning an empty array when called without `pattern` argument. +- +-### Features +- +-## 1.4.0 +- +-### Changes +- +-### Fixes +- +- - `pl.path` covers edge cases better (e.g 'path.normpath` was broken) +- - `p.dir` shell patterns fixed +- - `os.tmpname` broken on modern Windows/MSVC14 +- - (likewise for `utils.executeex` which depends on it) +- - `pretty.write` more robust and does not lose floating-point precision; +- saves and restores debug hooks when loading. +- - `pl.lexer` fixes: `cpp` lexer now filters space by default +- - `tablex.sortv` no longer assumes that the values are all unique +- - `stringx.center` is now consistent with Python; `stringx.rfind` and +- `string.quote_string` fixed. +- - `data.write` had a problem with default delimiter, properly returns error now. +- - `pl.Set` `+` and `-` now have correct semantics +- +-### Features +- +- - `pl.tablex` has `union` and `merge` convenience functions +- - `pl.lapp` understands '--' meaning end of parsed arguments +- - `utils.quote_arg` quotes command arguments for `os.execute`, +- correctly handling all special characters. +- - `utils.writefile` has optional `is_bin` argument +- - 'pl.lexer' supports line numbers with string argument +- - `stringx.endswith` may be passed an array of possible suffixes. +- - `data.read` - in CSV mode, assume empty fields are numerical zero +- +- +-## 1.3.2 +- +-### Changes +- +- - now works and passes tests with Lua 5.3 +- - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) +- - Updated pl.dir.file_op to return true on success and false on failure... +- - workaround for issues with pl.lapp with amalg.lua - will look at global LAPP_SCRIPT if arg[0] is nil +- +-### Fixes +- +- - func was broken: do NOT use ipairs to iterate if __index is overriden! +- - issue #133 pretty.read (naively) confused by unbalanced brackets +- - xml attribute underscore fix for simple parser +- - Fix path.normpath +- - lexer: fix parsing block comments/string. fix hang on empty string. +- - Fixed utils.execute returning different values for Lua 5.1 and Lua 5.2 +- - Issue #97; fixed attempt to put a month into a day +- - problem with tablex.count_map with custom comparison +- - tablex.pairmap overwrites result if key already exists; instead, upon detection that key already exists +- for a returned value, we modify the key's value to be a table and insert values into that table +- +-### Features +- +- - Add Python style url module for quote and unquote. +- - stringx.quote_string, which scans for embedded long-string quote matches and escapes them by creating a long-string quote. +- - issue #117: tablex.range now works with decreasing numbers, consistent with numerical for loop +- - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) +- - issue #125: DOCTYPE ignored in xml documents as well +- - Allow XML tostring() function to customize the default prefacing with +- - More Robust Quoted Strings +- - lapp: improved detection of unsupported short flags +- +-## 1.3.0 +- +-### Changes +- +- - class: RIP base method - not possible to implement correctly +- - lapp: short flags can now always be followed directly by their value, for instance, +-`-I/usr/include/lua/5.1` +- - Date: new explicit `Date.Interval` class; `toUTC/toLocal` return new object; `Date.__tostring` +-always returns ISO 8601 times for exact serialization. `+/-` explicit operators. Date objects +-are explicitly flagged as being UTC or not. +- +-### Fixes +- +- - class: super method fixed. +- - Date: DST is now accounted for properly. +- - Date: weekday calculation borked. +- +-### Features +- +- - All tests pass with no-5.1-compatible Lua 5.2; now always uses `utils.load` and +-`utils.unpack` is always available. +- - types: new module containing `utils.is_xxx` methods plus new `to_bool`. +- - class: can be passed methods in a table (see `test=klass.lua`). This is +-particularly convenient for using from Moonscript. +- - general documentation improvements, e.g `class` +- +-## 1.2.1 +- +-### Changes +- +- - utils.set(get)fenv always defined (_not_ set as globals for 5.2 anymore!). +- These are defined in new module pl.compat, but still available through utils. +- - class.Frodo now puts 'Frodo' in _current environment_ +- +-### Fixes +- +- - lapp.add_type was broken (Pete Kazmier) +- - class broke with classes that redefined __newindex +- - Set.isdisjoint was broken because of misspelling; default ctor Set() now works as expected +- - tablex.transform was broken; result now has same keys as original (CoolistheName007) +- - xml match not handling empty matches (royalbee) +- - pl.strict: assigning nil to global declares it, as God intended. (Pierre Chapuis) +- - tests all work with pl.strict +- - 5.2 compatible load now respects mode +- - tablex.difference thought that a value of `false` meant 'not present' (Andrew Starke) +- +-### Features +- +- - tablex.sort(t) iterates over sorted keys, tablex.sortv(t) iterates over sorted values (Pete Kazmier) +- - tablex.readonly(t) creates a read-only proxy for a table (John Schember) +- - utils.is_empty(o) true if o==nil, o is an empty table, or o is an empty string (John Schember) +- - utils.executeex(cmd,bin) returns true if successful, return code, plus stdout and stderr output as strings. (tieske) +- - class method base for calling inherited methods (theypsilon) +- - class supports pre-constructor _create for making a custom self (used in pl.List) +- - xml HTML mode improvements - can parse non-trivial well-formed HTML documents. +- xml.parsehtml is a parse function, no longer a flag +- - if a LOM document has ordered attributes, use these when stringifying +- - xml.tostring has yet another extra parm to force prefacing with +- - lapp boolean flags may have `true` default +- - lapp slack mode where 'short' flags can be multi-char +- - test.asserteq etc take extra arg, which is extra level where error must be reported at +- - path.currentdir,chdir,rmdir,mkdir and dir as alias to lfs are exported; no dependencies on luafilesystem outside pl.path, making it easier to plug in different implementations. +- +- +- +-## 0.9.7 +- +-### Lua 5.2 compatibility +- +-(These are all now defined in pl.utils) +- +-- setfenv, getfenv defined for Lua 5.2 (by Sergey Rozhenko) +- +-### Changes +- +-- array2d.flatten is new +-- OrderedMap:insert is new +- +-### Fixes +- +-- seq.reduce re-implemented to give correct order (Carl Ådahl) +-- seq.unique was broken: new test +-- tablex.icopy broken for last argument; new test +-- utils.function_arg last parm 'msg' was missing +-- array2d.product was broken; more sensible implementation +-- array2d.range, .slice, .write were broken +-- text optional operator % overload broken for 'fmt % fun'; new tests +-- a few occurances of non-existent function utils.error removed +- +- +-## 0.9.6 +- +-### Lua 5.2 compatibility +- +-- Bad string escape in tests fixed +- +-### Changes +- +-- LuaJIT FFI used on Windows for Copy/MoveFile functionality +- +-### Fixes +- +-- Issue 13 seq.sort now calls seq.copy +-- issue 14 bad pattern to escape trailing separators in path.abspath +-- lexer: string tokens broken with some combinations +-- lexer: long comments broken for Lua and C +-- stringx.split behaves according to Python spec; extra parm meaning 'max splits' +-- stringx.title behaves according to Python spec +-- stringx.endswith broken for 2nd arg being table of postfixes +-- OrderedMap.set broken when value was nil and key did not exist in map; ctor throws +- error if unhappy +- +-## 0.9.5 +- +-### Lua 5.2 compatibility +- +- - defines Lua 5.2 beta compatible load() +- - defines table.pack() +- +-### New functions +- +- - stringx.title(): translates "a dog's day" to "A Dog's Day" +- - path.normpath(): translates 'A//B','A/./B' and 'A/C/../B' to 'A/B' +- - utils.execute(): returns ok,return-code: compatible with 5.1 and 5.2 +- +-### Fixes +- +- - pretty.write() _always_ returns a string, but will return also an error string +-if the argument is not a table. Non-integer indices between 1 and #t are no longer falsely considered part of the array +- - stringx.expandtabs() now works like the Python string method; it will expand each field up to the next tab stop +- - path.normcase() was broken, because of a misguided attempt to normalize the path. +- - UNC specific fix to path.abspath() +- - UNC paths recognized as absolute; dir.makedir() works here +- - utils.quit() varargs broken, e.g. utils.quit("answer was %d",42) +- - some stray globals caused trouble with 'strict' +- +-###What's new with 0.8b ? +- +-####Features: +- +-pl.app provides useful stuff like simple command-line argument parsing and require_here(), which +-makes subsequent require() calls look in the local directory by preference. +- +-p.file provides useful functions like copy(),move(), read() and write(). (These are aliases to +-dir.copyfile(),movefile(),utils.readfile(),writefile()) +- +-Custom error trace will only show the functions in user code. +- +-More robust argument checking. +- +-In function arguments, now supports 'string lambdas', e.g. '|x| 2*x' +- +-utils.readfile,writefile now insist on being given filenames. This will cause less confusion. +- +-tablex.search() is new: will look recursively in an arbitrary table; can specify tables not to follow. +-tablex.move() will work with source and destination tables the same, with overlapping ranges. +- +-####Bug Fixes: +- +-dir.copyfile() now works fine without Alien on Windows +- +-dir.makepath() and rmtree() had problems. +- +-tablex.compare_no_order() is now O(NlogN), as expected. +-tablex.move() had a problem with source size +- +-###What's New with 0.7.0b? +- +-####Features: +- +-utils.is_type(v,tp) can say is_type(s,'string') and is_type(l,List). +-utils.is_callable(v) either a function, or has a __call metamethod. +- +-Sequence wrappers: can write things like this: +- +-seq(s):last():filter('<'):copy() +- +-seq:mapmethod(s,name) - map using a named method over a sequence. +- +-seq:enum(s) If s is a simple sequence, then +- for i,v in seq.enum(s) do print(i,v) end +- +-seq:take(s,n) Grab the next n values from a (possibly infinite) +-sequence. +- +-In a related change suggested by Flemming Madsden, the in-place List +-methods like reverse() and sort() return the list, allowing for +-method chaining. +- +-list.join() explicitly converts using tostring first. +- +-tablex.count_map() like seq.count_map(), but takes an equality function. +- +-tablex.difference() set difference +-tablex.set() explicit set generator given a list of values +- +-Template.indent_substitute() is a new Template method which adjusts +-for indentation and can also substitute templates themselves. +- +-pretty.read(). This reads a Lua table (as dumped by pretty.write) +-and attempts to be paranoid about its contents. +- +-sip.match_at_start(). Convenience function for anchored SIP matches. +- +-####Bug Fixes: +- +-tablex.deepcompare() was confused by false boolean values, which +-it thought were synonymous with being nil. +- +-pretty.write() did not handle cycles, and could not display tables +-with 'holes' properly (Flemming Madsden) +- +-The SIP pattern '$(' was not escaped properly. +-sip.match() did not pass on options table. +- +-seq.map() was broken for double-valued sequences. +-seq.copy_tuples() did not use default_iter(), so did not e.g. like +-table arguments. +- +-dir.copyfile() returns the wrong result for *nix operations. +-dir.makepath() was broken for non-Windows paths. +- +-###What's New with 0.6.3? +- +-The map and reduce functions now take the function first, as Nature intended. +- +-The Python-like overloading of '*' for strings has been dropped, since it +-is silly. Also, strings are no longer callable; use 's:at(1)' instead of +-'s(1)' - this tended to cause Obscure Error messages. +- +-Wherever a function argument is expected, you can use the operator strings +-like '+','==',etc as well as pl.operator.add, pl.operator.eq, etc. +-(see end of pl/operator.lua for the full list.) +- +-tablex now has compare() and compare_no_order(). An explicit set() +-function has been added which constructs a table with the specified +-keys, all set to a value of true. +- +-List has reduce() and partition() (This is a cool function which +-separates out elements of a list depending on a classifier function.) +- +-There is a new array module which generalizes tablex operations like +-map and reduce for two-dimensional arrays. +- +-The famous iterator over permutations from PiL 9.3 has been included. +- +-David Manura's list comprehension library has been included. +- +-Also, utils now contains his memoize function, plus a useful function +-args which captures the case where varargs contains nils. +- +-There was a bug with dir.copyfile() where the flag was the wrong way round. +- +-config.lines() had a problem with continued lines. +- +-Some operators were missing in pl.operator; have renamed them to be +-consistent with the Lua metamethod names. +- +- +diff --git a/extra/penlight/CONTRIBUTING.md b/extra/penlight/CONTRIBUTING.md +index 90ea9e4..69c75cd 100644 +--- a/extra/penlight/CONTRIBUTING.md ++++ b/extra/penlight/CONTRIBUTING.md +@@ -14,7 +14,7 @@ Here's some examples of things you might want to make a pull request for: + + If you have a more deeply-rooted problem with how the library is built or some + of the stylistic decisions made in the code, it's best to +-[create an issue](https://github.com/stevedonovan/Penlight/issues) before putting ++[create an issue](https://github.com/lunarmodules/Penlight/issues) before putting + the effort into a pull request. The same goes for new features - it might be + best to check the project's direction, existing pull requests, and currently open + and closed issues first. +@@ -23,9 +23,9 @@ and closed issues first. + + Here's how to go about contributing to Penlight: + +-1. [Fork the repository](https://github.com/stevedonovan/Penlight/fork) to ++1. [Fork the repository](https://github.com/lunarmodules/Penlight/fork) to + your Github account. +-2. Create a *topical branch* - a branch whose name is succint but explains what ++2. Create a *topical branch* - a branch whose name is succinct but explains what + you're doing, such as _"added-klingon-cloacking-device"_ - from `master` branch. + 3. Make your changes, committing at logical breaks. + 4. Push your branch to your personal account +@@ -34,9 +34,27 @@ you're doing, such as _"added-klingon-cloacking-device"_ - from `master` branch. + + If you wanna be a rockstar; + +-1. Update the [CHANGES.md](https://github.com/stevedonovan/Penlight/blob/master/CHANGES.md) file +-2. [Add tests](https://github.com/stevedonovan/Penlight/tree/master/tests) that show the defect your fix repairs, or that tests your new feature ++1. Update the [CHANGELOG.md](https://github.com/lunarmodules/Penlight/blob/master/CHANGELOG.md) file ++2. [Add tests](https://github.com/lunarmodules/Penlight/tree/master/tests) that show the defect your fix repairs, or that tests your new feature + + Please note - if you want to change multiple things that don't depend on each + other, make sure you check out the `master` branch again and create a different topical branch + before making more changes - that way we can take in each change separately. ++ ++## Release instructions for a new version ++ ++ - create a new release branch ++ - update `./lua/pl/utils.lua` (the `_VERSION` constant) ++ - update `./config.ld` with the new version number ++ - create a new rockspec file for the version in `./rockspecs` ++ - check the `./CHANGELOG.md` files for completeness ++ - commit the release related changes with `release x.y.z` ++ - render the documentation using `ldoc .` ++ - commit the documentation as a separate commit with `release x.y.z docs` ++ - push the release branch and create a PR ++ - merge the PR ++ - tag the release as `x.y.z` and push the tag to the github repo ++ - upload the rockspec, and source rock files to LuaRocks ++ - test installing through LuaRocks ++ - announce the release on the Lua mailing list ++ +diff --git a/extra/penlight/README.md b/extra/penlight/README.md +index ef9095a..f6e822f 100644 +--- a/extra/penlight/README.md ++++ b/extra/penlight/README.md +@@ -1,8 +1,10 @@ + # Penlight Lua Libraries + +-[![Build Status](https://travis-ci.org/stevedonovan/Penlight.svg)](https://travis-ci.org/stevedonovan/Penlight) +-[![Coverage Status](https://coveralls.io/repos/mpeterv/Penlight/badge.svg?branch=master&service=github)](https://coveralls.io/github/stevedonovan/Penlight?branch=master) +-[![AppVeyor status](https://ci.appveyor.com/api/projects/status/2ypffmsatb5rh9yw/branch/master?svg=true)](https://ci.appveyor.com/project/stevedonovan/penlight/branch/master) ++[![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/penlight/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/Penlight/actions) ++[![AppVeyor build status](https://img.shields.io/appveyor/build/Tieske/penlight-ta1gi/master?label=Windows%20build&logo=windows)](https://ci.appveyor.com/project/Tieske/penlight-ta1gi/branch/master) ++[![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/Penlight?logo=coveralls)](https://coveralls.io/github/lunarmodules/Penlight) ++[![Luacheck](https://github.com/lunarmodules/Penlight/workflows/Luacheck/badge.svg)](https://github.com/lunarmodules/Penlight/actions) ++[![SemVer](https://img.shields.io/github/v/tag/lunarmodules/Penlight?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) + + ## Why a new set of libraries? + +@@ -57,9 +59,44 @@ Python standard libraries. + * `utils`: `utils.string_lambda` converts short strings like '|x| x^2' into functions + * `comprehension`: list comprehensions: `C'x for x=1,4'()=={1,2,3,4}` + ++## Versioning ++ ++Penlight is strictly versioned according to [Semantic Versioning](https://semver.org/). ++ ++In scope of the version: ++ * functionality provided by Penlight modules/classes ++ * based on stock Lua PuC-Rio or LuaJIT ++ ++Not in scope of the version: ++ * Documentation ++ * Error messages (textual changes) ++ * Deprecation warnings (by default to `stderr`) ++ ++### Deprecating functionality ++ ++Any version may deprecate functionality. So new deprecation notices may appear ++in major, minor, and patch releases. Final removal of functionality (assuming it ++is a breaking change) will only be done in a major version. ++ ++It is strongly suggested to use the deprecation warning mechanism to test usage ++of deprecated functionalities when upgrading. This is done by enabling the ++warning system (in Lua 5.4, or the Penlight compatibility function for earlier ++versions): ++ ++```lua ++require "pl.compat" ++warn "@on" ++``` ++ ++See `pl.utils.raise_deprecation` for more info. ++ ++## License ++ ++Penlight is distributed under the [MIT license](LICENSE.md). ++ + ## Installation + +-Using [LuaRocks](https://luarocks.org): simply run `luarocks install penlight`. ++Using [LuaRocks](https://luarocks.org/modules/tieske/penlight): simply run `luarocks install penlight`. + + Manually: copy `lua/pl` directory into your Lua module path. It's typically + `/usr/local/share/lua/5.x` on a Linux system and `C:\Program Files\Lua\5.x\lua` +@@ -67,7 +104,7 @@ for Lua for Windows. + + ## Dependencies + +-The file and directory functions depend on [LuaFileSystem](https://keplerproject.github.io/luafilesystem/), ++The file and directory functions depend on [LuaFileSystem](https://lunarmodules.github.io/luafilesystem/), + which is installed automatically if you are using LuaRocks. Additionally, if you want `dir.copyfile` to work + elegantly on Windows, then you need [Alien](http://mascarenhas.github.io/alien/). Both libraries are present + in Lua for Windows. +@@ -75,12 +112,16 @@ in Lua for Windows. + ## Building the Documentation + + Requires [ldoc](https://github.com/stevedonovan/LDoc), which is available +-through LuaRocks. Then it's a simple matter of running `ldoc` in the docs folder. ++through LuaRocks. Then it's a simple matter of running `ldoc .` from the repo. + +-``` +-Penlight/docs$ ldoc . +-``` ++## Contributing ++ ++Contributions are most welcome, please check the [contribution guidelines](CONTRIBUTING.md). + + ## Running tests + + Execute `lua run.lua tests` to run the tests. Execute `lua run.lua examples` to run examples. ++ ++## History ++ ++For a complete history of the development of Penlight, please check the [changelog](CHANGELOG.md). +diff --git a/extra/penlight/appveyor.yml b/extra/penlight/appveyor.yml +index 5083e10..31e1202 100644 +--- a/extra/penlight/appveyor.yml ++++ b/extra/penlight/appveyor.yml +@@ -1,10 +1,13 @@ + shallow_clone: true + + environment: ++ COVERALLS_REPO_TOKEN: ++ secure: Ot23JDCk/sDpsIa8DkjX6u1ym09mVht+aFyIOmY09Ro6VFs/+VzQzqcldP0haP7r + matrix: + - LUA: "lua 5.1" + - LUA: "lua 5.2" + - LUA: "lua 5.3" ++ - LUA: "lua 5.4" + - LUA: "luajit 2.0" + - LUA: "luajit 2.0 --compat 5.2" + - LUA: "luajit 2.1" +@@ -12,12 +15,24 @@ environment: + + before_build: + - set PATH=C:\Python27\Scripts;%PATH% ++ - pip install --upgrade certifi ++ - FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "import certifi;print(certifi.where())"`) DO ( SET SSL_CERT_FILE=%%F ) + - pip install hererocks + - hererocks here --%LUA% -rlatest + - call here\bin\activate ++ - luarocks install luacov-coveralls ++ - luarocks install busted + + build_script: + - luarocks make + + test_script: +- - lua run.lua tests ++ - busted --coverage ++ - lua run.lua tests --luacov ++ - lua run.lua examples ++ ++on_success: ++ # secure coveralls token not available on PR builds, only BRANCH builds ++ - "if not \"%COVERALLS_REPO_TOKEN%\"==\"\" ( ++ luacov-coveralls ++ )" +diff --git a/extra/penlight/config.ld b/extra/penlight/config.ld +new file mode 100644 +index 0000000..0c901e8 +--- /dev/null ++++ b/extra/penlight/config.ld +@@ -0,0 +1,18 @@ ++project = 'Penlight' ++description = 'Penlight Lua Libraries 1.14.0' ++full_description = 'Penlight is a set of pure Lua libraries for making it easier to work with common tasks like iterating over directories, reading configuration files and the like. Provides functional operations on tables and sequences. Visit the GitHub project to review the code or file issues. Skip to the @{01-introduction.md|introduction}.' ++title = 'Penlight Documentation' ++dir = 'docs' ++style = '!fixed' ++template = true ++use_markdown_titles = true ++topics = 'docs_topics' ++examples = {'./examples','./tests/test-data.lua'} ++package = 'pl' ++format = 'discount' ++sort_modules=true ++file = './lua/pl' ++kind_names={topic='Manual',module='Libraries'} ++tparam_alias('array','array') ++tparam_alias('array2d','array') ++alias('ret',{'return',modifiers={type="$1"}}) +diff --git a/extra/penlight/doc/config.ld b/extra/penlight/doc/config.ld +deleted file mode 100644 +index 25c46f2..0000000 +--- a/extra/penlight/doc/config.ld ++++ /dev/null +@@ -1,20 +0,0 @@ +-project = 'Penlight' +-description = 'Penlight Lua Libraries 1.5.3' +-full_description = 'The documentation is available @{01-introduction.md|here}.' +-title = 'Penlight Documentation' +-dir = 'api' +-style = '!fixed' +-use_markdown_titles = true +-topics = 'manual' +-examples = {'../examples','../tests/test-data.lua'} +-package = 'pl' +-format = 'discount' +-sort_modules=true +-file = '../lua/pl' +-kind_names={topic='Manual',module='Libraries'} +-tparam_alias('array','array') +-tparam_alias('array2d','array') +-alias('ret',{'return',modifiers={type="$1"}}) +- +- +- +diff --git a/extra/penlight/docs/classes/pl.Date.html b/extra/penlight/docs/classes/pl.Date.html +new file mode 100644 +index 0000000..ab131b3 +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.Date.html +@@ -0,0 +1,1049 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.Date

    ++

    Date and Date Format classes.

    ++

    See the Guide.

    ++ ++

    NOTE: the date module is deprecated! see ++ https://github.com/lunarmodules/Penlight/issues/285

    ++ ++

    Dependencies: pl.class, pl.stringx, pl.utils

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Date:set (t)set the current time of this Date object.
    Date.tzone (ts)get the time zone offset from UTC.
    Date:toUTC ()convert this date to UTC.
    Date:toLocal ()convert this UTC date to local.
    Date:year (y)set the year.
    Date:month (m)set the month.
    Date:day (d)set the day.
    Date:hour (h)set the hour.
    Date:min (min)set the minutes.
    Date:sec (sec)set the seconds.
    Date:yday (yday)set the day of year.
    Date:year (y)get the year.
    Date:month ()get the month.
    Date:day ()get the day.
    Date:hour ()get the hour.
    Date:min ()get the minutes.
    Date:sec ()get the seconds.
    Date:yday ()get the day of year.
    Date:weekday_name (full)name of day of week.
    Date:month_name (full)name of month.
    Date:is_weekend ()is this day on a weekend?.
    Date:add (t)add to a date object.
    Date:last_day ()last day of the month.
    Date:diff (other)difference between two Date objects.
    Date:__tostring ()long numerical ISO data format version of this date.
    Date:__eq (other)equality between Date objects.
    Date:__lt (other)ordering between Date objects.
    Date:__sub ()difference between Date objects.
    Date:__add (other)add a date and an interval.
    Date.Interval (t)Date.Interval constructor
    Date.Interval:__tostring ()If it's an interval then the format is '2 hours 29 sec' etc.
    Date.Format (fmt.)Date.Format constructor.
    Date.Format:parse (str)parse a string into a Date object.
    Date.Format:tostring (d)convert a Date object into a string.
    Date.Format:US_order (yesno)force US order in dates like 9/11/2001
    ++

    Methods

    ++ ++ ++ ++ ++ ++
    pl.date:Date (t, ...)Date constructor.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ Date:set (t) ++
    ++
    ++ set the current time of this Date object. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ integer ++ seconds since epoch ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date.tzone (ts) ++
    ++
    ++ get the time zone offset from UTC. ++ ++ ++

    Parameters:

    ++
      ++
    • ts ++ integer ++ seconds ahead of UTC ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:toUTC () ++
    ++
    ++ convert this date to UTC. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:toLocal () ++
    ++
    ++ convert this UTC date to local. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:year (y) ++
    ++
    ++ set the year. ++ ++ ++

    Parameters:

    ++
      ++
    • y ++ integer ++ Four-digit year ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:month (m) ++
    ++
    ++ set the month. ++ ++ ++

    Parameters:

    ++
      ++
    • m ++ integer ++ month ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:day (d) ++
    ++
    ++ set the day. ++ ++ ++

    Parameters:

    ++
      ++
    • d ++ integer ++ day ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:hour (h) ++
    ++
    ++ set the hour. ++ ++ ++

    Parameters:

    ++
      ++
    • h ++ integer ++ hour ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:min (min) ++
    ++
    ++ set the minutes. ++ ++ ++

    Parameters:

    ++
      ++
    • min ++ integer ++ minutes ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:sec (sec) ++
    ++
    ++ set the seconds. ++ ++ ++

    Parameters:

    ++
      ++
    • sec ++ integer ++ seconds ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:yday (yday) ++
    ++
    ++ set the day of year. ++ ++ ++

    Parameters:

    ++
      ++
    • yday ++ integer ++ day of year ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:year (y) ++
    ++
    ++ get the year. ++ ++ ++

    Parameters:

    ++
      ++
    • y ++ integer ++ Four-digit year ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:month () ++
    ++
    ++ get the month. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:day () ++
    ++
    ++ get the day. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:hour () ++
    ++
    ++ get the hour. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:min () ++
    ++
    ++ get the minutes. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:sec () ++
    ++
    ++ get the seconds. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:yday () ++
    ++
    ++ get the day of year. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:weekday_name (full) ++
    ++
    ++ name of day of week. ++ ++ ++

    Parameters:

    ++
      ++
    • full ++ boolean ++ abbreviated if true, full otherwise. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ name ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date:month_name (full) ++
    ++
    ++ name of month. ++ ++ ++

    Parameters:

    ++
      ++
    • full ++ integer ++ abbreviated if true, full otherwise. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ name ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date:is_weekend () ++
    ++
    ++ is this day on a weekend?. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:add (t) ++
    ++
    ++ add to a date object. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table containing one of the following keys and a value: ++ one of year,month,day,hour,min,sec ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ this date ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date:last_day () ++
    ++
    ++ last day of the month. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ int day ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date:diff (other) ++
    ++
    ++ difference between two Date objects. ++ ++ ++

    Parameters:

    ++
      ++
    • other ++ Date ++ Date object ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ Date.Interval ++ object ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date:__tostring () ++
    ++
    ++ long numerical ISO data format version of this date. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:__eq (other) ++
    ++
    ++ equality between Date objects. ++ ++ ++

    Parameters:

    ++
      ++
    • other ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:__lt (other) ++
    ++
    ++ ordering between Date objects. ++ ++ ++

    Parameters:

    ++
      ++
    • other ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:__sub () ++
    ++
    ++ difference between Date objects. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date:__add (other) ++
    ++
    ++ add a date and an interval. ++ ++ ++

    Parameters:

    ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date.Interval (t) ++
    ++
    ++ Date.Interval constructor ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ integer ++ an interval in seconds ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date.Interval:__tostring () ++
    ++
    ++ If it's an interval then the format is '2 hours 29 sec' etc. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Date.Format (fmt.) ++
    ++
    ++ Date.Format constructor. ++ ++ ++

    Parameters:

    ++
      ++
    • fmt. ++ string ++ A string where the following fields are significant:

      ++ ++
        ++
      • d day (either d or dd)
      • ++
      • y year (either yy or yyy)
      • ++
      • m month (either m or mm)
      • ++
      • H hour (either H or HH)
      • ++
      • M minute (either M or MM)
      • ++
      • S second (either S or SS)
      • ++
      ++ ++

      Alternatively, if fmt is nil then this returns a flexible date parser ++ that tries various date/time schemes in turn:

      ++ ++
        ++
      • ISO 8601, like 2010-05-10 12:35:23Z or 2008-10-03T14:30+02
      • ++
      • times like 15:30 or 8.05pm (assumed to be today's date)
      • ++
      • dates like 28/10/02 (European order!) or 5 Feb 2012
      • ++
      • month name like march or Mar (case-insensitive, first 3 letters); here the ++ day will be 1 and the year this current year
      • ++
      ++ ++

      A date in format 3 can be optionally followed by a time in format 2. ++ Please see test-date.lua in the tests folder for more examples. ++

    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      df = Date.Format("yyyy-mm-dd HH:MM:SS")
      ++
    ++ ++
    ++
    ++ ++ Date.Format:parse (str) ++
    ++
    ++ parse a string into a Date object. ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ a date string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ date object ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date.Format:tostring (d) ++
    ++
    ++ convert a Date object into a string. ++ ++ ++

    Parameters:

    ++
      ++
    • d ++ a date object, or a time value as returned by os.time ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Date.Format:US_order (yesno) ++
    ++
    ++ force US order in dates like 9/11/2001 ++ ++ ++

    Parameters:

    ++
      ++
    • yesno ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Methods

    ++ ++
    ++
    ++ ++ pl.date:Date (t, ...) ++
    ++
    ++ Date constructor. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ ++

      this can be either

      ++ ++
        ++
      • nil or empty - use current date and time
      • ++
      • number - seconds since epoch (as returned by os.time). Resulting time is UTC
      • ++
      • Date - make a copy of this date
      • ++
      • table - table containing year, month, etc as for os.time. You may leave out year, month or day, ++ in which case current values will be used.
      • ++
      • year (will be followed by month, day etc)
      • ++
      ++ ++ ++
    • ++
    • ... ++ true if Universal Coordinated Time, or two to five numbers: month,day,hour,min,sec ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/classes/pl.List.html b/extra/penlight/docs/classes/pl.List.html +new file mode 100644 +index 0000000..33e3d84 +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.List.html +@@ -0,0 +1,1443 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.List

    ++

    Python-style list class.

    ++

    Please Note: methods that change the list will return the list. ++ This is to allow for method chaining, but please note that ls = ls:sort() ++ does not mean that a new copy of the list is made. In-place (mutable) methods ++ are marked as returning 'the list' in this documentation.

    ++ ++

    See the Guide for further discussion

    ++ ++

    See http://www.python.org/doc/current/tut/tut.html, section 5.1

    ++ ++

    Note: The comments before some of the functions are from the Python docs ++ and contain Python code.

    ++ ++

    Written for Lua version Nick Trout 4.0; Redone for Lua 5.1, Steve Donovan.

    ++ ++

    Dependencies: pl.utils, pl.tablex, pl.class

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    List.new ([t])Create a new list.
    List:clone ()Make a copy of an existing list.
    List:append (i)Add an item to the end of the list.
    List:extend (L)Extend the list by appending all the items in the given list.
    List:insert (i, x)Insert an item at a given position.
    List:put (x)Insert an item at the beginning of the list.
    List:remove (i)Remove an element given its index.
    List:remove_value (x)Remove the first item from the list whose value is given.
    List:pop ([i])Remove the item at the given position in the list, and return it.
    List:index (x[, idx=1])Return the index in the list of the first item whose value is given.
    List:contains (x)Does this list contain the value?
    List:count (x)Return the number of times value appears in the list.
    List:sort ([cmp='<'])Sort the items of the list, in place.
    List:sorted ([cmp='<'])Return a sorted copy of this list.
    List:reverse ()Reverse the elements of the list, in place.
    List:minmax ()Return the minimum and the maximum value of the list.
    List:slice (first, last)Emulate list slicing.
    List:clear ()Empty the list.
    List.range (start[, finish[, incr=1]])Emulate Python's range(x) function.
    List:len ()list:len() is the same as #list.
    List:chop (i1, i2)Remove a subrange of elements.
    List:splice (idx, list)Insert a sublist into a list ++ equivalent to 's[idx:idx] = list' in Python
    List:slice_assign (i1, i2, seq)General slice assignment s[i1:i2] = seq.
    List:join ([delim=''])Join the elements of a list using a delimiter.
    List:concat ([delim=''])Join a list of strings.
    List:foreach (fun, ...)Call the function on each element of the list.
    List:foreachm (name, ...)Call the named method on each element of the list.
    List:filter (fun[, arg])Create a list of all elements which match a function.
    List.split (s[, delim])Split a string using a delimiter.
    List:map (fun, ...)Apply a function to all elements.
    List:transform (fun, ...)Apply a function to all elements, in-place.
    List:map2 (fun, ls, ...)Apply a function to elements of two lists.
    List:mapm (name, ...)apply a named method to all elements.
    List:reduce (fun)'reduce' a list using a binary function.
    List:partition (fun, ...)Partition a list using a classifier function.
    List:iter ()return an iterator over all values.
    List.iterate (seq)Create an iterator over a sequence.
    ++

    metamethods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    List:__concat (L)Concatenation operator.
    List:__eq (L)Equality operator ==.
    List:__tostring ()How our list should be rendered as a string.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ List.new ([t]) ++
    ++
    ++ Create a new list. Can optionally pass a table; ++ passing another instance of List will cause a copy to be created; ++ this will return a plain table with an appropriate metatable. ++ we pass anything which isn't a simple table to iterate() to work out ++ an appropriate iterator ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ An optional list-like table ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new List ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      ls = List();  ls = List {1,2,3,4}
      ++
    ++ ++
    ++
    ++ ++ List:clone () ++
    ++
    ++ Make a copy of an existing list. ++ The difference from a plain 'copy constructor' is that this returns ++ the actual List subtype. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ List:append (i) ++
    ++
    ++ Add an item to the end of the list. ++ ++ ++

    Parameters:

    ++
      ++
    • i ++ An item ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:extend (L) ++
    ++
    ++ Extend the list by appending all the items in the given list. ++ equivalent to 'a[len(a):] = L'. ++ ++ ++

    Parameters:

    ++
      ++
    • L ++ List ++ Another List ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:insert (i, x) ++
    ++
    ++ Insert an item at a given position. i is the index of the ++ element before which to insert. ++ ++ ++

    Parameters:

    ++
      ++
    • i ++ integer ++ index of element before whichh to insert ++
    • ++
    • x ++ A data item ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:put (x) ++
    ++
    ++ Insert an item at the beginning of the list. ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ a data item ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:remove (i) ++
    ++
    ++ Remove an element given its index. ++ (equivalent of Python's del s[i]) ++ ++ ++

    Parameters:

    ++
      ++
    • i ++ integer ++ the index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:remove_value (x) ++
    ++
    ++ Remove the first item from the list whose value is given. ++ (This is called 'remove' in Python; renamed to avoid confusion ++ with table.remove) ++ Return nil if there is no such item. ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ A data value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:pop ([i]) ++
    ++
    ++ Remove the item at the given position in the list, and return it. ++ If no index is specified, a:pop() returns the last item in the list. ++ The item is also removed from the list. ++ ++ ++

    Parameters:

    ++
      ++
    • i ++ integer ++ An index ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the item ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:index (x[, idx=1]) ++
    ++
    ++ Return the index in the list of the first item whose value is given. ++ Return nil if there is no such item. ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ A data value ++
    • ++
    • idx ++ integer ++ where to start search ++ (default 1) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the index, or nil if not found. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:contains (x) ++
    ++
    ++ Does this list contain the value? ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ A data value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:count (x) ++
    ++
    ++ Return the number of times value appears in the list. ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ A data value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ number of times x appears ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:sort ([cmp='<']) ++
    ++
    ++ Sort the items of the list, in place. ++ ++ ++

    Parameters:

    ++
      ++
    • cmp ++ function ++ an optional comparison function ++ (default '<') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:sorted ([cmp='<']) ++
    ++
    ++ Return a sorted copy of this list. ++ ++ ++

    Parameters:

    ++
      ++
    • cmp ++ function ++ an optional comparison function ++ (default '<') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:reverse () ++
    ++
    ++ Reverse the elements of the list, in place. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:minmax () ++
    ++
    ++ Return the minimum and the maximum value of the list. ++ ++ ++ ++

    Returns:

    ++
      ++
    1. ++ minimum value
    2. ++
    3. ++ maximum value
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:slice (first, last) ++
    ++
    ++ Emulate list slicing. like 'list[first:last]' in Python. ++ If first or last are negative then they are relative to the end of the list ++ eg. slice(-2) gives last 2 entries in a list, and ++ slice(-4,-2) gives from -4th to -2nd ++ ++ ++

    Parameters:

    ++
      ++
    • first ++ An index ++
    • ++
    • last ++ An index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new List ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:clear () ++
    ++
    ++ Empty the list. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List.range (start[, finish[, incr=1]]) ++
    ++
    ++ Emulate Python's range(x) function. ++ Include it in List table for tidiness ++ ++ ++

    Parameters:

    ++
      ++
    • start ++ integer ++ A number ++
    • ++
    • finish ++ integer ++ A number greater than start; if absent, ++ then start is 1 and finish is start ++ (optional) ++
    • ++
    • incr ++ integer ++ an increment (may be less than 1) ++ (default 1) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a List from start .. finish ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • List.range(0,3) == List{0,1,2,3}
    • ++
    • List.range(4) = List{1,2,3,4}
    • ++
    • List.range(5,1,-1) == List{5,4,3,2,1}
    • ++
    ++ ++
    ++
    ++ ++ List:len () ++
    ++
    ++ list:len() is the same as #list. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ List:chop (i1, i2) ++
    ++
    ++ Remove a subrange of elements. ++ equivalent to 'del s[i1:i2]' in Python. ++ ++ ++

    Parameters:

    ++
      ++
    • i1 ++ integer ++ start of range ++
    • ++
    • i2 ++ integer ++ end of range ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:splice (idx, list) ++
    ++
    ++ Insert a sublist into a list ++ equivalent to 's[idx:idx] = list' in Python ++ ++ ++

    Parameters:

    ++
      ++
    • idx ++ integer ++ index ++
    • ++
    • list ++ List ++ list to insert ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      l = List{10,20}; l:splice(2,{21,22});  assert(l == List{10,21,22,20})
      ++
    ++ ++
    ++
    ++ ++ List:slice_assign (i1, i2, seq) ++
    ++
    ++ General slice assignment s[i1:i2] = seq. ++ ++ ++

    Parameters:

    ++
      ++
    • i1 ++ integer ++ start index ++
    • ++
    • i2 ++ integer ++ end index ++
    • ++
    • seq ++ List ++ a list ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:join ([delim='']) ++
    ++
    ++ Join the elements of a list using a delimiter. ++ This method uses tostring on all elements. ++ ++ ++

    Parameters:

    ++
      ++
    • delim ++ string ++ a delimiter string, can be empty. ++ (default '') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:concat ([delim='']) ++
    ++
    ++ Join a list of strings.
    ++ Uses table.concat directly. ++ ++ ++

    Parameters:

    ++
      ++
    • delim ++ string ++ a delimiter ++ (default '') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:foreach (fun, ...) ++
    ++
    ++ Call the function on each element of the list. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function or callable object ++
    • ++
    • ... ++ optional values to pass to function ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ List:foreachm (name, ...) ++
    ++
    ++ Call the named method on each element of the list. ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ the method name ++
    • ++
    • ... ++ optional values to pass to function ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ List:filter (fun[, arg]) ++
    ++
    ++ Create a list of all elements which match a function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a boolean function ++
    • ++
    • arg ++ optional argument to be passed as second argument of the predicate ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new filtered list. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List.split (s[, delim]) ++
    ++
    ++ Split a string using a delimiter. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • delim ++ string ++ the delimiter (default spaces) ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a List of strings ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ List:map (fun, ...) ++
    ++
    ++ Apply a function to all elements. ++ Any extra arguments will be passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of at least one argument ++
    • ++
    • ... ++ arbitrary extra arguments. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list: {f(x) for x in self} ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      List{'one','two'}:map(string.upper) == {'ONE','TWO'}
      ++
    ++ ++
    ++
    ++ ++ List:transform (fun, ...) ++
    ++
    ++ Apply a function to all elements, in-place. ++ Any extra arguments are passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ A function that takes at least one argument ++
    • ++
    • ... ++ arbitrary extra arguments. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the list. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:map2 (fun, ls, ...) ++
    ++
    ++ Apply a function to elements of two lists. ++ Any extra arguments will be passed to the function ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of at least two arguments ++
    • ++
    • ls ++ List ++ another list ++
    • ++
    • ... ++ arbitrary extra arguments. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list: {f(x,y) for x in self, for x in arg1} ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ List:mapm (name, ...) ++
    ++
    ++ apply a named method to all elements. ++ Any extra arguments will be passed to the method. ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ name of method ++
    • ++
    • ... ++ extra arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list of the results ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ List:reduce (fun) ++
    ++
    ++ 'reduce' a list using a binary function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of two arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ result of the function ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ List:partition (fun, ...) ++
    ++
    ++ Partition a list using a classifier function. ++ The function may return nil, but this will be converted to the string key ''. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of at least one argument ++
    • ++
    • ... ++ will also be passed to the function ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ MultiMap ++ a table where the keys are the returned values, and the values are Lists ++ of values where the function returned that key. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ List:iter () ++
    ++
    ++ return an iterator over all values. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ List.iterate (seq) ++
    ++
    ++ Create an iterator over a sequence. ++ This captures the Python concept of 'sequence'. ++ For tables, iterates over all values with integer indices. ++ ++ ++

    Parameters:

    ++
      ++
    • seq ++ a sequence; a string (over characters), a table, a file object (over lines) or an iterator function ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • for x in iterate {1,10,22,55} do io.write(x,',') end ==> 1,10,22,55
    • ++
    • for ch in iterate 'help' do do io.write(ch,' ') end ==> h e l p
    • ++
    ++ ++
    ++
    ++

    metamethods

    ++ ++
    ++
    ++ ++ List:__concat (L) ++
    ++
    ++ Concatenation operator. ++ ++ ++

    Parameters:

    ++
      ++
    • L ++ List ++ another List ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list consisting of the list with the elements of the new list appended ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:__eq (L) ++
    ++
    ++ Equality operator ==. True iff all elements of two lists are equal. ++ ++ ++

    Parameters:

    ++
      ++
    • L ++ List ++ another List ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ List:__tostring () ++
    ++
    ++ How our list should be rendered as a string. Uses join(). ++ ++ ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/classes/pl.Map.html b/extra/penlight/docs/classes/pl.Map.html +new file mode 100644 +index 0000000..9ef1741 +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.Map.html +@@ -0,0 +1,454 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.Map

    ++

    A Map class.

    ++

    ++ ++ ++ ++

    ++> Map = require 'pl.Map'
    ++> m = Map{one=1,two=2}
    ++> m:update {three=3,four=4,two=20}
    ++> = m == M{one=1,two=20,three=3,four=4}
    ++true
    ++
    ++ ++

    Dependencies: pl.utils, pl.class, pl.tablex, pl.pretty

    ++

    ++ ++ ++

    Fields

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.map.keyslist of keys.
    pl.map.valueslist of values.
    ++

    Methods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.map:iter ()return an iterator over all key-value pairs.
    pl.map:items ()return a List of all key-value pairs, sorted by the keys.
    pl.map:setdefault (key, default)set a value in the map if it doesn't exist yet.
    pl.map:len ()size of map.
    pl.map:set (key, val)put a value into the map.
    pl.map:get (key)get a value from the map.
    pl.map:getvalues (keys)get a list of values indexed by a list of keys.
    pl.map:update (table)update the map using key/value pairs from another table.
    ++

    Metamethods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.map:__eq (m)equality between maps.
    pl.map:__tostring ()string representation of a map.
    ++ ++
    ++
    ++ ++ ++

    Fields

    ++ ++
    ++
    ++ ++ pl.map.keys ++
    ++
    ++ list of keys. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map.values ++
    ++
    ++ list of values. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++

    Methods

    ++ ++
    ++
    ++ ++ pl.map:iter () ++
    ++
    ++ return an iterator over all key-value pairs. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:items () ++
    ++
    ++ return a List of all key-value pairs, sorted by the keys. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:setdefault (key, default) ++
    ++
    ++ set a value in the map if it doesn't exist yet. ++ ++ ++

    Parameters:

    ++
      ++
    • key ++ the key ++
    • ++
    • default ++ value to set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the value stored in the map (existing value, or the new value) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:len () ++
    ++
    ++ size of map. ++ note: this is a relatively expensive operation! ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:set (key, val) ++
    ++
    ++ put a value into the map. ++ This will remove the key if the value is nil ++ ++ ++

    Parameters:

    ++
      ++
    • key ++ the key ++
    • ++
    • val ++ the value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:get (key) ++
    ++
    ++ get a value from the map. ++ ++ ++

    Parameters:

    ++
      ++
    • key ++ the key ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the value, or nil if not found. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:getvalues (keys) ++
    ++
    ++ get a list of values indexed by a list of keys. ++ ++ ++

    Parameters:

    ++
      ++
    • keys ++ a list-like table of keys ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:update (table) ++
    ++
    ++ update the map using key/value pairs from another table. ++ ++ ++

    Parameters:

    ++
      ++
    • table ++ table ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Metamethods

    ++ ++
    ++
    ++ ++ pl.map:__eq (m) ++
    ++
    ++ equality between maps. ++ ++ ++

    Parameters:

    ++
      ++
    • m ++ Map ++ another map. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.map:__tostring () ++
    ++
    ++ string representation of a map. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/classes/pl.MultiMap.html b/extra/penlight/docs/classes/pl.MultiMap.html +new file mode 100644 +index 0000000..be7fe39 +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.MultiMap.html +@@ -0,0 +1,207 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.MultiMap

    ++

    MultiMap, a Map which has multiple values per key.

    ++

    Dependencies: pl.utils, pl.class, pl.List, pl.Map

    ++ ++ ++

    Methods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.multimap:update (t)update a MultiMap using a table.
    pl.multimap:set (key, val)add a new value to a key.
    ++ ++
    ++
    ++ ++ ++

    Methods

    ++ ++
    ++
    ++ ++ pl.multimap:update (t) ++
    ++
    ++ update a MultiMap using a table. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ either a Multimap or a map-like table. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the map ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.multimap:set (key, val) ++
    ++
    ++ add a new value to a key. Setting a nil value removes the key. ++ ++ ++

    Parameters:

    ++
      ++
    • key ++ the key ++
    • ++
    • val ++ the value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the map ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/classes/pl.OrderedMap.html b/extra/penlight/docs/classes/pl.OrderedMap.html +new file mode 100644 +index 0000000..efc1947 +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.OrderedMap.html +@@ -0,0 +1,417 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.OrderedMap

    ++

    OrderedMap, a map which preserves ordering.

    ++

    Derived from pl.Map.

    ++ ++

    Dependencies: pl.utils, pl.tablex, pl.class, pl.List, pl.Map

    ++ ++ ++

    Methods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.orderedmap:_init (t)construct an OrderedMap.
    pl.orderedmap:update (t)update an OrderedMap using a table.
    pl.orderedmap:set (key, val)set the key's value.
    pl.orderedmap:insert (pos, key, val)insert a key/value pair before a given position.
    pl.orderedmap:keys ()return the keys in order.
    pl.orderedmap:values ()return the values in order.
    pl.orderedmap:sort (cmp)sort the keys.
    pl.orderedmap:iter ()iterate over key-value pairs in order.
    ++

    Metamethods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.orderedmap:__pairs ()iterate over an ordered map (5.2).
    pl.orderedmap:__tostring ()string representation of an ordered map.
    ++ ++
    ++
    ++ ++ ++

    Methods

    ++ ++
    ++
    ++ ++ pl.orderedmap:_init (t) ++
    ++
    ++ construct an OrderedMap. ++ Will throw an error if the argument is bad. ++ ++ ++

    Parameters:

    ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:update (t) ++
    ++
    ++ update an OrderedMap using a table. ++ If the table is itself an OrderedMap, then its entries will be appended. ++ if it s a table of the form {{key1=val1},{key2=val2},...} these will be appended.

    ++ ++

    Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ a table. ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ the map, or nil in case of error
    2. ++
    3. ++ the error message
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:set (key, val) ++
    ++
    ++ set the key's value. This key will be appended at the end of the map.

    ++ ++

    If the value is nil, then the key is removed. ++ ++ ++

    Parameters:

    ++
      ++
    • key ++ the key ++
    • ++
    • val ++ the value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the map ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:insert (pos, key, val) ++
    ++
    ++ insert a key/value pair before a given position. ++ Note: if the map already contains the key, then this effectively ++ moves the item to the new position by first removing at the old position. ++ Has no effect if the key does not exist and val is nil ++ ++ ++

    Parameters:

    ++
      ++
    • pos ++ integer ++ a position starting at 1 ++
    • ++
    • key ++ the key ++
    • ++
    • val ++ the value; if nil use the old value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:keys () ++
    ++
    ++ return the keys in order. ++ (Not a copy!) ++ ++ ++ ++

    Returns:

    ++
      ++ ++ List ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:values () ++
    ++
    ++ return the values in order. ++ this is relatively expensive. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ List ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:sort (cmp) ++
    ++
    ++ sort the keys. ++ ++ ++

    Parameters:

    ++
      ++
    • cmp ++ function ++ a comparison function as for table.sort ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the map ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:iter () ++
    ++
    ++ iterate over key-value pairs in order. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++

    Metamethods

    ++ ++
    ++
    ++ ++ pl.orderedmap:__pairs () ++
    ++
    ++ iterate over an ordered map (5.2). ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.orderedmap:__tostring () ++
    ++
    ++ string representation of an ordered map. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/classes/pl.Set.html b/extra/penlight/docs/classes/pl.Set.html +new file mode 100644 +index 0000000..f351e4d +--- /dev/null ++++ b/extra/penlight/docs/classes/pl.Set.html +@@ -0,0 +1,655 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Class pl.Set

    ++

    A Set class.

    ++

    ++ ++ ++ ++

    ++> Set = require 'pl.Set'
    ++> = Set{'one','two'} == Set{'two','one'}
    ++true
    ++> fruit = Set{'apple','banana','orange'}
    ++> = fruit['banana']
    ++true
    ++> = fruit['hazelnut']
    ++nil
    ++> colours = Set{'red','orange','green','blue'}
    ++> = fruit,colours
    ++[apple,orange,banana]   [blue,green,orange,red]
    ++> = fruit+colours
    ++[blue,green,apple,red,orange,banana]
    ++[orange]
    ++> more_fruits = fruit + 'apricot'
    ++> = fruit*colours
    ++ =  more_fruits, fruit
    ++banana,apricot,apple,orange]    [banana,apple,orange]
    ++
    ++ ++

    Dependencies: pl.utils, pl.tablex, pl.class, pl.Map, (pl.List if __tostring is used)

    ++

    ++ ++ ++

    Methods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.set:Set (t)create a set.
    pl.set:values (self)get a list of the values in a set.
    pl.set:map (self, fn, ...)map a function over the values of a set.
    pl.set:union (self, set)union of two sets (also +).
    pl.set:intersection (self, set)intersection of two sets (also *).
    pl.set:difference (self, set)new set with elements in the set that are not in the other (also -).
    pl.set:issubset (self, set)is the first set a subset of the second (also <)?.
    pl.set:isempty (self)is the set empty?.
    pl.set:isdisjoint (s1, s2)are the sets disjoint?
    pl.set:len (s)size of this set (also # for 5.2).
    ++

    Metamethods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.set:__tostring ()string representation of a set.
    pl.set:__add ()union of sets.
    pl.set:__mul ()intersection of sets.
    pl.set:__sub ()difference of sets.
    pl.set:__pow ()symmetric difference of sets.
    pl.set:__lt ()first set subset of second?
    pl.set:__len ()cardinality of set (5.2).
    pl.set:__eq (s1, s2)equality between sets.
    ++ ++
    ++
    ++ ++ ++

    Methods

    ++ ++
    ++
    ++ ++ pl.set:Set (t) ++
    ++
    ++ create a set.
    ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ may be a Set, Map or list-like table. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:values (self) ++
    ++
    ++ get a list of the values in a set. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:map (self, fn, ...) ++
    ++
    ++ map a function over the values of a set. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • fn ++ a function ++
    • ++
    • ... ++ extra arguments to pass to the function. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:union (self, set) ++
    ++
    ++ union of two sets (also +). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:intersection (self, set) ++
    ++
    ++ intersection of two sets (also *). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      > s = Set{10,20,30}
      ++> t = Set{20,30,40}
      ++> = t
      ++[20,30,40]
      ++> = Set.intersection(s,t)
      ++[30,20]
      ++> = s*t
      ++[30,20]
      ++
    ++ ++
    ++
    ++ ++ pl.set:difference (self, set) ++
    ++
    ++ new set with elements in the set that are not in the other (also -). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:issubset (self, set) ++
    ++
    ++ is the first set a subset of the second (also <)?. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:isempty (self) ++
    ++
    ++ is the set empty?. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:isdisjoint (s1, s2) ++
    ++
    ++ are the sets disjoint? (no elements in common). ++ Uses naive definition, i.e. that intersection is empty ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ a Set ++
    • ++
    • s2 ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:len (s) ++
    ++
    ++ size of this set (also # for 5.2). ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ size ++
    ++ ++ ++ ++ ++
    ++
    ++

    Metamethods

    ++ ++
    ++
    ++ ++ pl.set:__tostring () ++
    ++
    ++ string representation of a set. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__add () ++
    ++
    ++ union of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__mul () ++
    ++
    ++ intersection of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__sub () ++
    ++
    ++ difference of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__pow () ++
    ++
    ++ symmetric difference of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__lt () ++
    ++
    ++ first set subset of second? ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__len () ++
    ++
    ++ cardinality of set (5.2). ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pl.set:__eq (s1, s2) ++
    ++
    ++ equality between sets. ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ ++ ++ ++
    • ++
    • s2 ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/seesubst.lua.html b/extra/penlight/docs/examples/seesubst.lua.html +new file mode 100644 +index 0000000..1679fd7 +--- /dev/null ++++ b/extra/penlight/docs/examples/seesubst.lua.html +@@ -0,0 +1,174 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    seesubst.lua

    ++
    ++-- shows how replacing '@see module' in the Markdown documentation
    ++-- can be done more elegantly using PL.
    ++-- We either have something like 'pl.config' (a module reference)
    ++-- or 'pl.seq.map' (a function reference); these cases must be distinguished
    ++-- and a Markdown link generated pointing to the LuaDoc file.
    ++
    ++local sip = require 'pl.sip'
    ++local stringx = require 'pl.stringx'
    ++
    ++local res = {}
    ++local s = [[
    ++(@see pl.bonzo.dog)
    ++remember about @see pl.bonzo
    ++
    ++]]
    ++
    ++local _gsub_patterns = {}
    ++
    ++local function gsub (s,pat,subst,start)
    ++    local fpat = _gsub_patterns[pat]
    ++    if not fpat then
    ++        -- use SIP to generate a proper string pattern.
    ++        -- the _whole thing_ is a capture, to get the whole match
    ++        -- and the unnamed capture.
    ++        fpat = '('..sip.create_pattern(pat)..')'
    ++        _gsub_patterns[pat] = fpat
    ++    end
    ++    return s:gsub(fpat,subst,start)
    ++end
    ++
    ++
    ++local mod = sip.compile '$v.$v'
    ++local fun = sip.compile '$v.$v.$v'
    ++
    ++for line in stringx.lines(s) do
    ++    line = gsub(line,'@see $p',function(see,path)
    ++        if fun(path,res) or mod(path,res) then
    ++            local ret = ('[see %s](%s.%s.html'):format(path,res[1],res[2])
    ++            if res[3] then
    ++                return ret..'#'..res[3]..')'
    ++            else
    ++                return ret..')'
    ++            end
    ++        end
    ++    end)
    ++    print(line)
    ++end
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/sipscan.lua.html b/extra/penlight/docs/examples/sipscan.lua.html +new file mode 100644 +index 0000000..b881358 +--- /dev/null ++++ b/extra/penlight/docs/examples/sipscan.lua.html +@@ -0,0 +1,161 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    sipscan.lua

    ++
    ++-- another SIP example, shows how an awkward log file format
    ++-- can be parsed. It also prints out the actual Lua string
    ++-- pattern generated:
    ++-- SYNC%s*%[([+%-%d]%d*)%]%s*([+%-%d]%d*)%s*([+%-%d]%d*)
    ++
    ++local sip = require 'pl.sip'
    ++local stringx = require 'pl.stringx'
    ++
    ++local s = [[
    ++SYNC [1] 0 547 (14679 sec)
    ++SYNC [2] 0 555 (14679 sec)
    ++SYNC [3] 0 563 (14679 sec)
    ++SYNC [4] 0 571 (14679 sec)
    ++SYNC [5] -1 580 (14679 sec)
    ++SYNC [6] 0 587 (14679 sec)
    ++]]
    ++
    ++
    ++local first = true
    ++local expected
    ++local res = {}
    ++local pat = 'SYNC [$i{seq}] $i{diff} $i{val}'
    ++print(sip.create_pattern(pat))
    ++local match = sip.compile(pat)
    ++for line in stringx.lines(s) do
    ++  if match(line,res) then
    ++    if first then
    ++      expected = res.val
    ++      first = false
    ++    end
    ++    print(res.val,expected - res.val)
    ++    expected = expected + 8
    ++  end
    ++end
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/symbols.lua.html b/extra/penlight/docs/examples/symbols.lua.html +new file mode 100644 +index 0000000..3a3b4a3 +--- /dev/null ++++ b/extra/penlight/docs/examples/symbols.lua.html +@@ -0,0 +1,347 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    symbols.lua

    ++
    ++require 'pl'
    ++utils.import 'pl.func'
    ++local ops = require 'pl.operator'
    ++local List = require 'pl.List'
    ++local append,concat = table.insert,table.concat
    ++local compare,find_if,compare_no_order,imap,reduce,count_map = tablex.compare,tablex.find_if,tablex.compare_no_order,tablex.imap,tablex.reduce,tablex.count_map
    ++local unpack = table.unpack
    ++
    ++function bindval (self,val)
    ++    rawset(self,'value',val)
    ++end
    ++
    ++local optable = ops.optable
    ++
    ++function sexpr (e)
    ++	if isPE(e) then
    ++		if e.op ~= 'X' then
    ++			local args = tablex.imap(sexpr,e)
    ++			return '('..e.op..' '..table.concat(args,' ')..')'
    ++		else
    ++			return e.repr
    ++		end
    ++	else
    ++		return tostring(e)
    ++	end
    ++end
    ++
    ++
    ++psexpr = compose(print,sexpr)
    ++
    ++
    ++
    ++function equals (e1,e2)
    ++    local p1,p2 = isPE(e1),isPE(e2)
    ++    if p1 ~= p2 then return false end  -- different kinds of animals!
    ++    if p1 and p2 then -- both PEs
    ++        -- operators must be the same
    ++        if e1.op ~= e2.op then return false end
    ++        -- PHs are equal if their representations are equal
    ++        if e1.op == 'X' then return e1.repr == e2.repr
    ++        -- commutative operators
    ++        elseif e1.op == '+' or e1.op == '*' then
    ++            return compare_no_order(e1,e2,equals)
    ++        else
    ++            -- arguments must be the same
    ++            return compare(e1,e2,equals)
    ++        end
    ++    else -- fall back on simple equality for non PEs
    ++        return e1 == e2
    ++    end
    ++end
    ++
    ++-- run down an unbalanced operator chain (like a+b+c) and return the arguments {a,b,c}
    ++function tcollect (op,e,ls)
    ++    if isPE(e) and e.op == op then
    ++        for i = 1,#e do
    ++            tcollect(op,e[i],ls)
    ++        end
    ++    else
    ++        ls:append(e)
    ++        return
    ++    end
    ++end
    ++
    ++function rcollect (e)
    ++    local res = List()
    ++    tcollect(e.op,e,res)
    ++    return res
    ++end
    ++
    ++
    ++-- balance ensures that +/* chains are collected together, operates in-place.
    ++-- thus (+(+ a b) c) or (+ a (+ b c)) becomes (+ a b c), order immaterial
    ++function balance (e)
    ++    if isPE(e) and e.op ~= 'X' then
    ++        local op,args = e.op
    ++        if op == '+' or op == '*' then
    ++            args = rcollect(e)
    ++        else
    ++            args = imap(balance,e)
    ++        end
    ++        for i = 1,#args do
    ++            e[i] = args[i]
    ++        end
    ++    end
    ++    return e
    ++end
    ++
    ++-- fold constants in an expression
    ++function fold (e)
    ++    if isPE(e) then
    ++        if e.op == 'X' then
    ++            -- there could be _bound values_!
    ++            local val = rawget(e,'value')
    ++            return val and val or e
    ++        else
    ++            local op = e.op
    ++            local addmul = op == '*' or op == '+'
    ++            -- first fold all arguments
    ++            local args = imap(fold,e)
    ++            if not addmul and not find_if(args,isPE) then
    ++                -- no placeholders in these args, we can fold the expression.
    ++                local opfn = optable[op]
    ++                if opfn then
    ++                    return opfn(unpack(args))
    ++                else
    ++                    return '?'
    ++                end
    ++            elseif addmul then
    ++                -- enforce a few rules for + and *
    ++                -- split the args into two classes, PE args and non-PE args.
    ++                local classes = List.partition(args,isPE)
    ++                local pe,npe = classes[true],classes[false]
    ++                if npe then -- there's at least one non PE argument
    ++                    -- so fold them
    ++                    if #npe == 1 then npe = npe[1]
    ++                    else npe = npe:reduce(optable[op])
    ++                    end
    ++                    -- if the result is a constant, return it
    ++                    if not pe then return npe end
    ++
    ++                    -- either (* 1 x) => x or (* 1 x y ...) => (* x y ...)
    ++                    if op == '*' then
    ++                        if npe == 0 then return 0
    ++                        elseif npe == 1 then -- identity
    ++                            if #pe == 1 then return pe[1] else npe = nil end
    ++                        end
    ++                    else -- special cases for +
    ++                        if npe == 0 then -- identity
    ++                            if #pe == 1 then return pe[1] else npe = nil end
    ++                        end
    ++                    end
    ++                end
    ++                -- build up the final arguments
    ++                local res = {}
    ++                if npe then append(res,npe) end
    ++                for val,count in pairs(count_map(pe,equals)) do
    ++                    if count > 1 then
    ++                        if op == '*' then val = val ^ count
    ++                        else val = val * count
    ++                        end
    ++                    end
    ++                    append(res,val)
    ++                end
    ++                if #res == 1 then return res[1] end
    ++                return PE{op=op,unpack(res)}
    ++            elseif op == '^' then
    ++                if args[2] == 1 then return args[1] end -- identity
    ++                if args[2] == 0 then return 1 end
    ++            end
    ++            return PE{op=op,unpack(args)}
    ++        end
    ++    else
    ++        return e
    ++    end
    ++end
    ++
    ++function expand (e)
    ++    if isPE(e) and e.op == '*' and isPE(e[2]) and e[2].op == '+' then
    ++        local a,b = e[1],e[2]
    ++        return expand(b[1]*a) + expand(b[2]*a)
    ++    else
    ++        return e
    ++    end
    ++end
    ++
    ++function isnumber (x)
    ++    return type(x) == 'number'
    ++end
    ++
    ++-- does this PE contain a reference to x?
    ++function references (e,x)
    ++    if isPE(e) then
    ++        if e.op == 'X' then return x.repr == e.repr
    ++        else
    ++            return find_if(e,references,x)
    ++        end
    ++    else
    ++        return false
    ++    end
    ++end
    ++
    ++local function muli (args)
    ++    return PE{op='*',unpack(args)}
    ++end
    ++
    ++local function addi (args)
    ++    return PE{op='+',unpack(args)}
    ++end
    ++
    ++function diff (e,x)
    ++    if isPE(e) and references(e,x) then
    ++        local op = e.op
    ++        if op == 'X' then
    ++            return 1
    ++        else
    ++            local a,b = e[1],e[2]
    ++            if op == '+' then -- differentiation is linear
    ++                local args = imap(diff,e,x)
    ++                return balance(addi(args))
    ++            elseif op == '*' then -- product rule
    ++                local res,d,ee = {}
    ++                for i = 1,#e do
    ++                    d = fold(diff(e[i],x))
    ++                    if d ~= 0 then
    ++                        ee = {unpack(e)}
    ++                        ee[i] = d
    ++                        append(res,balance(muli(ee)))
    ++                    end
    ++                end
    ++                if #res > 1 then return addi(res)
    ++                else return res[1] end
    ++            elseif op == '^' and isnumber(b) then -- power rule
    ++                return b*x^(b-1)
    ++            end
    ++        end
    ++    else
    ++        return 0
    ++    end
    ++end
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/test-cmp.lua.html b/extra/penlight/docs/examples/test-cmp.lua.html +new file mode 100644 +index 0000000..f755fd1 +--- /dev/null ++++ b/extra/penlight/docs/examples/test-cmp.lua.html +@@ -0,0 +1,130 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    test-cmp.lua

    ++
    ++local A = require 'pl.tablex'
    ++print(A.compare_no_order({1,2,3},{2,1,3}))
    ++print(A.compare_no_order({1,2,3},{2,1,3},'=='))
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/test-data.lua.html b/extra/penlight/docs/examples/test-data.lua.html +new file mode 100644 +index 0000000..bef712c +--- /dev/null ++++ b/extra/penlight/docs/examples/test-data.lua.html +@@ -0,0 +1,372 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    test-data.lua

    ++
    ++local data = require 'pl.data'
    ++local List = require 'pl.List'
    ++local array = require 'pl.array2d'
    ++local func = require 'pl.func'
    ++local seq = require 'pl.seq'
    ++local stringio = require 'pl.stringio'
    ++local open = stringio. open
    ++local asserteq = require 'pl.test' . asserteq
    ++local T = require 'pl.test'. tuple
    ++
    ++--[=[
    ++dat,err = data.read(open [[
    ++1.0 0.1
    ++0.2 1.3
    ++]])
    ++
    ++if err then print(err) end
    ++
    ++require 'pl.pretty'.dump(dat)
    ++os.exit(0)
    ++--]=]
    ++
    ++-- tab-separated data, explicit column names
    ++local t1f = open [[
    ++EventID	Magnitude	LocationX	LocationY	LocationZ	LocationError	EventDate	DataFile
    ++981124001	2.0	18988.4	10047.1	4149.7	33.8	24/11/1998 11:18:05	981124DF.AAB
    ++981125001	0.8	19104.0	9970.4	5088.7	3.0	25/11/1998 05:44:54	981125DF.AAB
    ++981127003	0.5	19012.5	9946.9	3831.2	46.0	27/11/1998 17:15:17	981127DF.AAD
    ++981127005	0.6	18676.4	10606.2	3761.9	4.4	27/11/1998 17:46:36	981127DF.AAF
    ++981127006	0.2	19109.9	9716.5	3612.0	11.8	27/11/1998 19:29:51	981127DF.AAG
    ++]]
    ++
    ++local t1 = data.read (t1f)
    ++-- column_by_name returns a List
    ++asserteq(t1:column_by_name 'Magnitude',List{2,0.8,0.5,0.6,0.2})
    ++-- can use array.column as well
    ++asserteq(array.column(t1,2),{2,0.8,0.5,0.6,0.2})
    ++
    ++-- only numerical columns (deduced from first data row) are converted by default
    ++-- can look up indices in the list fieldnames.
    ++local EDI = t1.fieldnames:index 'EventDate'
    ++assert(type(t1[1][EDI]) == 'string')
    ++
    ++-- select method returns a sequence, in this case single-valued.
    ++-- (Note that seq.copy returns a List)
    ++asserteq(seq(t1:select 'LocationX where Magnitude > 0.5'):copy(),List{18988.4,19104,18676.4})
    ++
    ++--[[
    ++--a common select usage pattern:
    ++for event,mag in t1:select 'EventID,Magnitude sort by Magnitude desc' do
    ++    print(event,mag)
    ++end
    ++--]]
    ++
    ++-- space-separated, but with last field containing spaces.
    ++local t2f = open [[
    ++USER PID %MEM %CPU COMMAND
    ++sdonovan 2333  0.3 0.1 background --n=2
    ++root 2332  0.4  0.2 fred --start=yes
    ++root 2338  0.2  0.1 backyard-process
    ++]]
    ++
    ++local t2,err = data.read(t2f,{last_field_collect=true})
    ++if not t2 then return print (err) end
    ++
    ++-- the last_field_collect option is useful with space-delimited data where the last
    ++-- field may contain spaces. Otherwise, a record count mismatch should be an error!
    ++local lt2 = List(t2[2])
    ++asserteq(lt2:join ',','root,2332,0.4,0.2,fred --start=yes')
    ++
    ++-- fieldnames are converted into valid identifiers by substituting _
    ++-- (we do this to make select queries parseable by Lua)
    ++asserteq(t2.fieldnames,List{'USER','PID','_MEM','_CPU','COMMAND'})
    ++
    ++-- select queries are NOT SQL so remember to use == ! (and no 'between' operator, sorry)
    ++--s,err = t2:select('_MEM where USER="root"')
    ++--assert(err == [[[string "tmp"]:9: unexpected symbol near '=']])
    ++
    ++local s = t2:select('_MEM where USER=="root"')
    ++assert(s() == 0.4)
    ++assert(s() == 0.2)
    ++assert(s() == nil)
    ++
    ++-- CSV, Excel style. Double-quoted fields are allowed, and they may contain commas!
    ++local t3f = open [[
    ++"Department Name","Employee ID",Project,"Hours Booked"
    ++sales,1231,overhead,4
    ++sales,1255,overhead,3
    ++engineering,1501,development,5
    ++engineering,1501,maintenance,3
    ++engineering,1433,maintenance,10
    ++]]
    ++
    ++local t3 = data.read(t3f,{csv=true})
    ++
    ++-- although fieldnames are turned in valid Lua identifiers, there is always original_fieldnames
    ++asserteq(t3.fieldnames,List{'Department_Name','Employee_ID','Project','Hours_Booked'})
    ++asserteq(t3.original_fieldnames,List{'Department Name','Employee ID','Project','Hours Booked'})
    ++
    ++-- a common operation is to select using a given list of columns, and each row
    ++-- on some explicit condition. The select() method can take a table with these
    ++-- parameters
    ++local keepcols = {'Employee_ID','Hours_Booked'}
    ++
    ++local q = t3:select { fields = keepcols,
    ++    where = function(row) return row[1]=='engineering' end
    ++    }
    ++
    ++asserteq(seq.copy2(q),{{1501,5},{1501,3},{1433,10}})
    ++
    ++-- another pattern is doing a select to restrict rows & columns, process some
    ++-- fields and write out the modified rows.
    ++
    ++local outf = stringio.create()
    ++
    ++local names = {[1501]='don',[1433]='dilbert'}
    ++
    ++t3:write_row (outf,{'Employee','Hours_Booked'})
    ++q = t3:select_row {fields=keepcols,where=func.Eq(func._1[1],'engineering')}
    ++for row in q do
    ++    row[1] = names[row[1]]
    ++    t3:write_row(outf,row)
    ++end
    ++
    ++asserteq(outf:value(),
    ++[[
    ++Employee,Hours_Booked
    ++don,5
    ++don,3
    ++dilbert,10
    ++]])
    ++
    ++-- data may not always have column headers. When creating a data object
    ++-- from a two-dimensional array, may specify the fieldnames, as a list or a string.
    ++-- The delimiter is deduced from the fieldname string, so a string just containing
    ++-- the delimiter will set it,  and the fieldnames will be empty.
    ++local dat = List()
    ++local row = List.range(1,10)
    ++for i = 1,10 do
    ++    dat:append(row:map('*',i))
    ++end
    ++dat = data.new(dat,',')
    ++local out = stringio.create()
    ++dat:write(out,',')
    ++asserteq(out:value(), [[
    ++1,2,3,4,5,6,7,8,9,10
    ++2,4,6,8,10,12,14,16,18,20
    ++3,6,9,12,15,18,21,24,27,30
    ++4,8,12,16,20,24,28,32,36,40
    ++5,10,15,20,25,30,35,40,45,50
    ++6,12,18,24,30,36,42,48,54,60
    ++7,14,21,28,35,42,49,56,63,70
    ++8,16,24,32,40,48,56,64,72,80
    ++9,18,27,36,45,54,63,72,81,90
    ++10,20,30,40,50,60,70,80,90,100
    ++]])
    ++
    ++-- you can always use numerical field indices, AWK-style;
    ++-- note how the copy_select method gives you a data object instead of an
    ++-- iterator over the fields
    ++local res = dat:copy_select '$1,$3 where $1 > 5'
    ++local L = List
    ++asserteq(L(res),L{
    ++    L{6, 18},
    ++    L{7,21},
    ++    L{8,24},
    ++    L{9,27},
    ++    L{10,30},
    ++})
    ++
    ++-- the column_by_name method may take a fieldname or an index
    ++asserteq(dat:column_by_name(2), L{2,4,6,8,10,12,14,16,18,20})
    ++
    ++-- the field list may contain expressions or even constants
    ++local q = dat:select '$3,2*$4 where $1 == 8'
    ++asserteq(T(q()),T(24,64))
    ++
    ++dat,err = data.read(open [[
    ++1.0 0.1
    ++0.2 1.3
    ++]])
    ++
    ++if err then print(err) end
    ++
    ++-- if a method cannot be found, then we look up in array2d
    ++-- array2d.flatten(t) makes a 1D list out of a 2D array,
    ++-- and then List.minmax() gets the extrema.
    ++
    ++asserteq(T(dat:flatten():minmax()),T(0.1,1.3))
    ++
    ++local f = open [[
    ++Time Message
    ++1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00
    ++1266840760 closure data 0.000000 1972 1972 0
    ++1266840760 ++ 1266840760 EE 1
    ++1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00
    ++1266840764 closure data 0.000000 1972 1972 0
    ++1266840764 ++ 1266840764 EE 1
    ++1266840764 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00
    ++1266840768 duplicate?
    ++1266840768 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00
    ++1266840768 closure data 0.000000 1972 1972 0
    ++]]
    ++
    ++-- the convert option provides custom converters for each specified column.
    ++-- Here we convert the timestamps into Date objects and collect everything
    ++-- else into one field
    ++local Date = require 'pl.Date'
    ++
    ++local function date_convert (ds)
    ++    return Date(tonumber(ds))
    ++end
    ++
    ++local d = data.read(f,{convert={[1]=date_convert},last_field_collect=true})
    ++
    ++asserteq(#d[1],2)
    ++asserteq(d[2][1]:year(),2010)
    ++
    ++d = {{1,2,3},{10,20,30}}
    ++out = stringio.create()
    ++data.write(d,out,{'A','B','C'},',')
    ++asserteq(out:value(),
    ++[[
    ++A,B,C
    ++1,2,3
    ++10,20,30
    ++]])
    ++
    ++out = stringio.create()
    ++d.fieldnames = {'A','B','C'}
    ++data.write(d,out)
    ++
    ++asserteq(out:value(),
    ++[[
    ++A	B	C
    ++1	2	3
    ++10	20	30
    ++]])
    ++
    ++
    ++d = data.read(stringio.open 'One,Two\n1,\n,20\n',{csv=true})
    ++asserteq(d,{
    ++    {1,0},{0,20},
    ++    original_fieldnames={"One","Two"},fieldnames={"One","Two"},delim=","
    ++})
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/test-listcallbacks.lua.html b/extra/penlight/docs/examples/test-listcallbacks.lua.html +new file mode 100644 +index 0000000..b565bf2 +--- /dev/null ++++ b/extra/penlight/docs/examples/test-listcallbacks.lua.html +@@ -0,0 +1,138 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    test-listcallbacks.lua

    ++
    ++-- demonstrates how to use a list of callbacks
    ++local List = require 'pl.List'
    ++local utils = require 'pl.utils'
    ++local actions = List()
    ++local L = utils.string_lambda
    ++
    ++actions:append(function() print 'hello' end)
    ++actions:append(L '|| print "yay"')
    ++
    ++-- '()' is a shortcut for operator.call or function(x) return x() end
    ++actions:foreach '()'
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/test-pretty.lua.html b/extra/penlight/docs/examples/test-pretty.lua.html +new file mode 100644 +index 0000000..ff0a776 +--- /dev/null ++++ b/extra/penlight/docs/examples/test-pretty.lua.html +@@ -0,0 +1,140 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    test-pretty.lua

    ++
    ++local pretty = require 'pl.pretty'
    ++
    ++local tb = {
    ++    'one','two','three',{1,2,3},
    ++    alpha=1,beta=2,gamma=3,['&']=true,[0]=false,
    ++    _fred = {true,true},
    ++    s = [[
    ++hello dolly
    ++you're so fine
    ++]]
    ++}
    ++
    ++print(pretty.write(tb))
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/test-symbols.lua.html b/extra/penlight/docs/examples/test-symbols.lua.html +new file mode 100644 +index 0000000..fb4b7c1 +--- /dev/null ++++ b/extra/penlight/docs/examples/test-symbols.lua.html +@@ -0,0 +1,209 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    test-symbols.lua

    ++
    ++require 'pl'
    ++-- force us to look in the script's directory when requiring...
    ++app.require_here()
    ++require 'symbols'
    ++
    ++local MT = getmetatable(_1)
    ++
    ++add = MT.__add
    ++mul = MT.__mul
    ++pow = MT.__pow
    ++
    ++
    ++function testeq (e1,e2)
    ++    if not equals(e1,e2) then
    ++        print ('Not equal',repr(e1),repr(e2))
    ++    end
    ++end
    ++
    ++sin = register(math.sin,'sin')
    ++
    ++f = register(function(x,y,z) end)
    ++
    ++--[[
    ++testeq (_1,_1)
    ++testeq (_1+_2,_1+_2)
    ++testeq (_1 + 3*_2,_1 + 3*_2)
    ++testeq (_2+_1,_1+_2)
    ++testeq (sin(_1),sin(_1))
    ++testeq (1+f(10,20,'ok'),f(10,20,'ok')+1)
    ++--]]
    ++
    ++
    ++function testexpand (e)
    ++    print(repr(fold(expand(e)))) --fold
    ++end
    ++
    ++--[[
    ++testexpand (a*(a+1))
    ++
    ++testexpand ((x+2)*(b+1))
    ++]]--
    ++
    ++function testfold (e)
    ++    print(repr(fold(e)))
    ++end
    ++
    ++a,b,c,x,y = Var 'a,b,c,x,y'
    ++
    ++--~ testfold(_1 + _2)
    ++--~ testfold(add(10,20))
    ++--~ testfold(add(mul(2,_1),mul(3,_2)))
    ++--[[
    ++testfold(sin(a))
    ++e = a^(b+2)
    ++testfold(e)
    ++bindval(b,1)
    ++testfold(e)
    ++bindval(a,2)
    ++testfold(e)
    ++
    ++bindval(a)
    ++bindval(b)
    ++]]
    ++
    ++
    ++
    ++function testdiff (e)
    ++    balance(e)
    ++    e = diff(e,x)
    ++    balance(e)
    ++    print('+ ',e)
    ++    e = fold(e)
    ++    print('- ',e)
    ++end
    ++
    ++
    ++testdiff(x^2+1)
    ++testdiff(3*x^2)
    ++testdiff(x^2 + 2*x^3)
    ++testdiff(x^2 + 2*a*x^3 + x^4)
    ++testdiff(2*a*x^3)
    ++testdiff(x*x*x)
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testapp.lua.html b/extra/penlight/docs/examples/testapp.lua.html +new file mode 100644 +index 0000000..56ae5a3 +--- /dev/null ++++ b/extra/penlight/docs/examples/testapp.lua.html +@@ -0,0 +1,133 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testapp.lua

    ++
    ++-- shows how a script can get a private file path
    ++-- the output on my Windows machine is:
    ++-- C:\Documents and Settings\steve\.testapp\test.txt
    ++local app = require 'pl.app'
    ++print(app.appfile 'test.txt')
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.4.6 ++Last updated 2018-11-23 21:07:42 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testclone.lua.html b/extra/penlight/docs/examples/testclone.lua.html +new file mode 100644 +index 0000000..6f17069 +--- /dev/null ++++ b/extra/penlight/docs/examples/testclone.lua.html +@@ -0,0 +1,165 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testclone.lua

    ++
    ++--cloning a directory tree.
    ++local lfs = require 'lfs'
    ++local path = require 'pl.path'
    ++local dir = require 'pl.dir'
    ++
    ++local p1 = [[examples]]
    ++local p2 = [[copy/of/examples]]
    ++
    ++if not path.isfile 'examples/testclone.lua' then
    ++	return print 'please run this in the penlight folder (below examples)'
    ++end
    ++
    ++-- make a copy of the examples folder
    ++dir.clonetree(p1,p2,dir.copyfile)
    ++
    ++assert(path.isdir 'copy')
    ++
    ++print '---'
    ++local t = os.time()
    ++print(lfs.touch('examples/testclone.lua',t,t+10))
    ++
    ++-- this should only update this file
    ++dir.clonetree(p1,p2,
    ++function(f1,f2)
    ++  local t1 = path.getmtime(f1)
    ++  local t2 = path.getmtime(f2)
    ++  --print(f1,t1,f2,t2)
    ++  if t1 > t2 then
    ++	dir.copyfile(f1,f2)
    ++	print(f1,f2,t1,t2)
    ++  end
    ++  return true
    ++end)
    ++
    ++-- and get rid of the whole copy directory, with subdirs
    ++dir.rmtree 'copy'
    ++
    ++assert(not path.exists 'copy')
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testconfig.lua.html b/extra/penlight/docs/examples/testconfig.lua.html +new file mode 100644 +index 0000000..ca9a336 +--- /dev/null ++++ b/extra/penlight/docs/examples/testconfig.lua.html +@@ -0,0 +1,176 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testconfig.lua

    ++
    ++local stringio = require 'pl.stringio'
    ++local config = require 'pl.config'
    ++
    ++local function dump(t,indent)
    ++    if type(t) == 'table' then
    ++        io.write(indent,'{\n')
    ++        local newindent = indent..'  '
    ++        for k,v in pairs(t) do
    ++            io.write(newindent,k,'=')
    ++            dump(v,indent)
    ++            io.write('\n')
    ++        end
    ++        io.write(newindent,'},\n')
    ++    else
    ++        io.write(indent,t,'(',type(t),')')
    ++    end
    ++end
    ++
    ++
    ++local function testconfig(test)
    ++    local f = stringio.open(test)
    ++    local c = config.read(f)
    ++    f:close()
    ++    dump(c,'  ')
    ++    print '-----'
    ++end
    ++
    ++testconfig [[
    ++ ; comment 2 (an ini file)
    ++[section!]
    ++bonzo.dog=20,30
    ++config_parm=here we go again
    ++depth = 2
    ++[another]
    ++felix="cat"
    ++]]
    ++
    ++testconfig [[
    ++# this is a more Unix-y config file
    ++fred = 1
    ++alice = 2
    ++home = /bonzo/dog/etc
    ++]]
    ++
    ++testconfig [[
    ++# this is just a set of comma-separated values
    ++1000,444,222
    ++44,555,224
    ++]]
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testglobal.lua.html b/extra/penlight/docs/examples/testglobal.lua.html +new file mode 100644 +index 0000000..0f8c191 +--- /dev/null ++++ b/extra/penlight/docs/examples/testglobal.lua.html +@@ -0,0 +1,153 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testglobal.lua

    ++
    ++-- very simple lexer program which looks at all identifiers in a Lua
    ++-- file and checks whether they're in the global namespace.
    ++-- At the end, we dump out the result of count_map, which will give us
    ++-- unique identifiers with their usage count.
    ++-- (an example of a program which itself needs to be careful about what
    ++-- goes into the global namespace)
    ++
    ++local utils = require 'pl.utils'
    ++local file = require 'pl.file'
    ++local lexer = require 'pl.lexer'
    ++local List = require 'pl.List'
    ++local pretty = require 'pl.pretty'
    ++local seq = require 'pl.seq'
    ++local path = require 'pl.path'
    ++
    ++utils.on_error 'quit'
    ++
    ++local txt = file.read(arg[1] or path.normpath('examples/testglobal.lua'))
    ++local globals = List()
    ++for t,v in lexer.lua(txt) do
    ++	if t == 'iden' and rawget(_G,v) then
    ++		globals:append(v)
    ++	end
    ++end
    ++
    ++pretty.dump(seq.count_map(globals))
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testinputfields.lua.html b/extra/penlight/docs/examples/testinputfields.lua.html +new file mode 100644 +index 0000000..99158d2 +--- /dev/null ++++ b/extra/penlight/docs/examples/testinputfields.lua.html +@@ -0,0 +1,140 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testinputfields.lua

    ++
    ++local input = require 'pl.input'
    ++local sum = 0.0
    ++local count = 0
    ++local text = [[
    ++    981124001	2.0	18988.4	10047.1	4149.7
    ++    981125001	0.8	19104.0	9970.4	5088.7
    ++    981127003	0.5	19012.5	9946.9	3831.2
    ++]]
    ++for id,magn,x in input.fields(3,' ',text) do
    ++  sum = sum + x
    ++  count = count + 1
    ++end
    ++print('average x coord is ',sum/count)
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testinputfields2.lua.html b/extra/penlight/docs/examples/testinputfields2.lua.html +new file mode 100644 +index 0000000..192dae4 +--- /dev/null ++++ b/extra/penlight/docs/examples/testinputfields2.lua.html +@@ -0,0 +1,136 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testinputfields2.lua

    ++
    ++local input = require 'pl.input'
    ++local seq = require 'pl.seq'
    ++local text = [[
    ++    981124001	2.0	18988.4	10047.1	4149.7
    ++    981125001	0.8	19104.0	9970.4	5088.7
    ++    981127003	0.5	19012.5	9946.9	3831.2
    ++]]
    ++local sum,count = seq.sum(input.fields ({3},' ',text))
    ++print(sum/count)
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/testxml.lua.html b/extra/penlight/docs/examples/testxml.lua.html +new file mode 100644 +index 0000000..58e7bcf +--- /dev/null ++++ b/extra/penlight/docs/examples/testxml.lua.html +@@ -0,0 +1,209 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    testxml.lua

    ++
    ++-- an example showing 'pl.lexer' doing some serious work.
    ++-- The resulting Lua table is in the same LOM format used by luaexpat.
    ++-- This is (clearly) not a professional XML parser, so don't use it
    ++-- on your homework!
    ++
    ++local lexer = require 'pl.lexer'
    ++local pretty = require 'pl.pretty'
    ++
    ++local append = table.insert
    ++local skipws,expecting = lexer.skipws,lexer.expecting
    ++
    ++local function parse_element (tok,tag)
    ++	local tbl,t,v,attrib
    ++	tbl = {}
    ++	tbl.tag = tag  -- LOM 'tag' is the element tag
    ++	t,v = skipws(tok)
    ++	while v ~= '/' and v ~= '>' do
    ++		if t ~= 'iden' then error('expecting attribute identifier') end
    ++		attrib = v
    ++		expecting(tok,'=')
    ++		v = expecting(tok,'string')
    ++		-- LOM: 'attr' subtable contains attrib/value pairs and an ordered list of attribs
    ++		if not tbl.attr then tbl.attr = {} end
    ++		tbl.attr[attrib] = v
    ++		append(tbl.attr,attrib)
    ++		t,v = skipws(tok)
    ++	end
    ++	if v == '/' then
    ++		expecting(tok,'>')
    ++		return tbl
    ++	end
    ++	-- pick up element data
    ++	t,v = tok()
    ++	while true do
    ++		if t == '<' then
    ++			t,v = skipws(tok)
    ++			if t == '/' then -- element end tag
    ++				t,v = tok()
    ++				if t == '>' then return tbl end
    ++				if t == 'iden' and v == tag then
    ++					if tok() == '>' then return tbl end
    ++				end
    ++				error('expecting end tag '..tag)
    ++			else
    ++				append(tbl,parse_element(tok,v)) -- LOM: child elements added to table
    ++				t,v = skipws(tok)
    ++			end
    ++		else
    ++			append(tbl,v) -- LOM: text added to table
    ++			t,v = skipws(tok)
    ++		end
    ++	end
    ++end
    ++
    ++local function parse_xml (tok)
    ++	local t = skipws(tok)
    ++	local v
    ++	while t == '<' do
    ++		t,v = tok()
    ++		if t == '?' or t == '!' then
    ++			-- skip meta stuff and commentary
    ++			repeat t = tok() until t == '>'
    ++			t = expecting(tok,'<')
    ++		else
    ++			return parse_element(tok,v)
    ++		end
    ++	end
    ++end
    ++
    ++local s = [[
    ++<?xml version="1.0" encoding="UTF-8"?>
    ++<sensor name="closure-meter-2" id="7D7D0600006F0D00" loc="100,100,0" device="closure-meter" init="true">
    ++<detector name="closure-meter" phenomenon="closure" units="mm" id="1"
    ++    vmin="0" vmax="5000" device="closure-meter" calib="0,0;5000,5000"
    ++    sampling_interval="25000" measurement_interval="600000"
    ++/>
    ++</sensor>
    ++]]
    ++
    ++local tok = lexer.scan(s,nil,{space=false},{string=true})
    ++local res = parse_xml(tok)
    ++print(pretty.write(res))
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/examples/which.lua.html b/extra/penlight/docs/examples/which.lua.html +new file mode 100644 +index 0000000..6d07770 +--- /dev/null ++++ b/extra/penlight/docs/examples/which.lua.html +@@ -0,0 +1,155 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    which.lua

    ++
    ++-- a simple implementation of the which command. This looks for
    ++-- the given file on the path. On windows, it will assume an extension
    ++-- of .exe if no extension is given.
    ++local List = require 'pl.List'
    ++local path = require 'pl.path'
    ++local app = require 'pl.app'
    ++
    ++local pathl = List.split(os.getenv 'PATH',path.dirsep)
    ++
    ++local function which (file)
    ++    local res = pathl:map(path.join,file)
    ++    res = res:filter(path.exists)
    ++    if res then return res[1] end
    ++end
    ++
    ++local _,lua = app.lua()
    ++local file = arg[1] or lua -- i.e. location of lua executable
    ++local try
    ++
    ++if not file then return print 'must provide a filename' end
    ++
    ++if path.extension(file) == '' and path.is_windows then
    ++    try = which(file..'.exe')
    ++else
    ++    try = which(file)
    ++end
    ++
    ++if try then print(try) else print 'cannot find on path' end
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/index.html b/extra/penlight/docs/index.html +new file mode 100644 +index 0000000..2a11195 +--- /dev/null ++++ b/extra/penlight/docs/index.html +@@ -0,0 +1,392 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Penlight Lua Libraries 1.14.0

    ++

    Penlight is a set of pure Lua libraries for making it easier to work with common tasks like iterating over directories, reading configuration files and the like. Provides functional operations on tables and sequences. Visit the GitHub project to review the code or file issues. Skip to the introduction.

    ++ ++

    Libraries

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    plEntry point for loading all PL libraries only on demand, into the global space.
    pl.appApplication support functions.
    pl.array2dOperations on two-dimensional arrays.
    pl.classProvides a reusable and convenient framework for creating classes in Lua.
    pl.compatLua 5.1/5.2/5.3 compatibility.
    pl.comprehensionList comprehensions implemented in Lua.
    pl.configReads configuration files into a Lua table.
    pl.dataReading and querying simple tabular data.
    pl.dirListing files in directories and creating/removing directory paths.
    pl.fileFile manipulation functions: reading, writing, moving and copying.
    pl.funcFunctional helpers like composition, binding and placeholder expressions.
    pl.import_intoPL loader, for loading all PL libraries, only on demand.
    pl.inputIterators for extracting words or numbers from an input source.
    pl.lappSimple command-line parsing using human-readable specification.
    pl.lexerLexical scanner for creating a sequence of tokens from text.
    pl.luabalancedExtract delimited Lua sequences from strings.
    pl.operatorLua operators available as functions.
    pl.pathPath manipulation and file queries.
    pl.permutePermutation operations.
    pl.prettyPretty-printing Lua tables.
    pl.seqManipulating iterators as sequences.
    pl.sipSimple Input Patterns (SIP).
    pl.strictChecks uses of undeclared global variables.
    pl.stringioReading and writing strings using file-like objects.
    pl.stringxPython-style extended string library.
    pl.tablexExtended operations on Lua tables.
    pl.templateA template preprocessor.
    pl.testUseful test utilities.
    pl.textText processing utilities.
    pl.typesDealing with Detailed Type Information
    pl.urlPython-style URL quoting library.
    pl.utilsGenerally useful routines.
    pl.xmlXML LOM Utilities.
    ++

    Classes

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pl.DateDate and Date Format classes.
    pl.ListPython-style list class.
    pl.MapA Map class.
    pl.MultiMapMultiMap, a Map which has multiple values per key.
    pl.OrderedMapOrderedMap, a map which preserves ordering.
    pl.SetA Set class.
    ++

    Manual

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    01-introduction.md
    02-arrays.md
    03-strings.md
    04-paths.md
    05-dates.md
    06-data.md
    07-functional.md
    08-additional.md
    09-discussion.md
    ++

    Examples

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    seesubst.lua
    sipscan.lua
    symbols.lua
    test-cmp.lua
    test-data.lua
    test-listcallbacks.lua
    test-pretty.lua
    test-symbols.lua
    testclone.lua
    testconfig.lua
    testglobal.lua
    testinputfields.lua
    testinputfields2.lua
    testxml.lua
    which.lua
    ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/ldoc_fixed.css b/extra/penlight/docs/ldoc_fixed.css +new file mode 100644 +index 0000000..d7c7a6e +--- /dev/null ++++ b/extra/penlight/docs/ldoc_fixed.css +@@ -0,0 +1,312 @@ ++/* BEGIN RESET ++ ++Copyright (c) 2010, Yahoo! Inc. All rights reserved. ++Code licensed under the BSD License: ++http://developer.yahoo.com/yui/license.html ++version: 2.8.2r1 ++*/ ++html { ++ color: #000; ++ background: #FFF; ++} ++body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { ++ margin: 0; ++ padding: 0; ++} ++table { ++ border-collapse: collapse; ++ border-spacing: 0; ++} ++fieldset,img { ++ border: 0; ++} ++address,caption,cite,code,dfn,em,strong,th,var,optgroup { ++ font-style: inherit; ++ font-weight: inherit; ++} ++del,ins { ++ text-decoration: none; ++} ++li { ++ margin-left: 20px; ++} ++caption,th { ++ text-align: left; ++} ++h1,h2,h3,h4,h5,h6 { ++ font-size: 100%; ++ font-weight: bold; ++} ++q:before,q:after { ++ content: ''; ++} ++abbr,acronym { ++ border: 0; ++ font-variant: normal; ++} ++sup { ++ vertical-align: baseline; ++} ++sub { ++ vertical-align: baseline; ++} ++legend { ++ color: #000; ++} ++input,button,textarea,select,optgroup,option { ++ font-family: inherit; ++ font-size: inherit; ++ font-style: inherit; ++ font-weight: inherit; ++} ++input,button,textarea,select {*font-size:100%; ++} ++/* END RESET */ ++ ++body { ++ margin-left: 1em; ++ margin-right: 1em; ++ font-family: arial, helvetica, geneva, sans-serif; ++ background-color: #ffffff; margin: 0px; ++} ++ ++code, tt { font-family: monospace; font-size: 1.1em; } ++span.parameter { font-family:monospace; } ++span.parameter:after { content:":"; } ++span.types:before { content:"("; } ++span.types:after { content:")"; } ++.type { font-weight: bold; font-style:italic } ++ ++body, p, td, th { font-size: .95em; line-height: 1.2em;} ++ ++p, ul { margin: 10px 0 0 0px;} ++ ++strong { font-weight: bold;} ++ ++em { font-style: italic;} ++ ++h1 { ++ font-size: 1.5em; ++ margin: 0 0 20px 0; ++} ++h2, h3, h4 { margin: 15px 0 10px 0; } ++h2 { font-size: 1.25em; } ++h3 { font-size: 1.15em; } ++h4 { font-size: 1.06em; } ++ ++a:link { font-weight: bold; color: #004080; text-decoration: none; } ++a:visited { font-weight: bold; color: #006699; text-decoration: none; } ++a:link:hover { text-decoration: underline; } ++ ++hr { ++ color:#cccccc; ++ background: #00007f; ++ height: 1px; ++} ++ ++blockquote { margin-left: 3em; } ++ ++ul { list-style-type: disc; } ++ ++p.name { ++ font-family: "Andale Mono", monospace; ++ padding-top: 1em; ++} ++ ++pre { ++ background-color: rgb(245, 245, 245); ++ border: 1px solid #C0C0C0; /* silver */ ++ padding: 10px; ++ margin: 10px 0 10px 0; ++ overflow: auto; ++ font-family: "Andale Mono", monospace; ++} ++ ++pre.example { ++ font-size: .85em; ++} ++ ++table.index { border: 1px #00007f; } ++table.index td { text-align: left; vertical-align: top; } ++ ++#container { ++ margin-left: 1em; ++ margin-right: 1em; ++ background-color: #ffffff; ++} ++ ++#product { ++ text-align: center; ++ border-bottom: 1px solid #cccccc; ++ background-color: #ffffff; ++} ++ ++#product big { ++ font-size: 2em; ++} ++ ++#main { ++ background-color:#FFFFFF; // #f0f0f0; ++ border-left: 1px solid #cccccc; ++} ++ ++#navigation { ++ position: fixed; ++ top: 0; ++ left: 0; ++ float: left; ++ width: 14em; ++ vertical-align: top; ++ background-color:#FFFFFF; // #f0f0f0; ++ border-right: 2px solid #cccccc; ++ overflow: visible; ++ overflow-y: scroll; ++ height: 100%; ++ padding-left: 1em; ++} ++ ++#navigation h2 { ++ background-color:#FFFFFF;//:#e7e7e7; ++ font-size:1.1em; ++ color:#000000; ++ text-align: left; ++ padding:0.2em; ++ border-bottom:1px solid #dddddd; ++} ++ ++#navigation ul ++{ ++ font-size:1em; ++ list-style-type: none; ++ margin: 1px 1px 10px 1px; ++} ++ ++#navigation li { ++ text-indent: -1em; ++ display: block; ++ margin: 3px 0px 0px 22px; ++} ++ ++#navigation li li a { ++ margin: 0px 3px 0px -1em; ++} ++ ++#content { ++ margin-left: 14em; ++ padding: 1em; ++ padding-left: 2em; ++ width: 700px; ++ border-left: 2px solid #cccccc; ++ // border-right: 2px solid #cccccc; ++ background-color: #ffffff; ++} ++ ++#about { ++ clear: both; ++ padding-left: 1em; ++ margin-left: 14em; // avoid the damn sidebar! ++ border-top: 2px solid #cccccc; ++ border-left: 2px solid #cccccc; ++ background-color: #ffffff; ++} ++ ++@media print { ++ body { ++ font: 12pt "Times New Roman", "TimeNR", Times, serif; ++ } ++ a { font-weight: bold; color: #004080; text-decoration: underline; } ++ ++ #main { ++ background-color: #ffffff; ++ border-left: 0px; ++ } ++ ++ #container { ++ margin-left: 2%; ++ margin-right: 2%; ++ background-color: #ffffff; ++ } ++ ++ #content { ++ padding: 1em; ++ background-color: #ffffff; ++ } ++ ++ #navigation { ++ display: none; ++ } ++ pre.example { ++ font-family: "Andale Mono", monospace; ++ font-size: 10pt; ++ page-break-inside: avoid; ++ } ++} ++ ++table.module_list { ++ border-width: 1px; ++ border-style: solid; ++ border-color: #cccccc; ++ border-collapse: collapse; ++} ++table.module_list td { ++ border-width: 1px; ++ padding: 3px; ++ border-style: solid; ++ border-color: #cccccc; ++} ++table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } ++table.module_list td.summary { width: 100%; } ++ ++table.function_list { ++ border-width: 1px; ++ border-style: solid; ++ border-color: #cccccc; ++ border-collapse: collapse; ++} ++table.function_list td { ++ border-width: 1px; ++ padding: 3px; ++ border-style: solid; ++ border-color: #cccccc; ++} ++table.function_list td.name { background-color: #f6f6ff; ; min-width: 200px; } ++table.function_list td.summary { width: 100%; } ++ ++dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} ++dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} ++dl.table h3, dl.function h3 {font-size: .95em;} ++ ++ul.nowrap { ++ overflow:auto; ++ whitespace:nowrap; ++} ++ ++/* stop sublists from having initial vertical space */ ++ul ul { margin-top: 0px; } ++ol ul { margin-top: 0px; } ++ol ol { margin-top: 0px; } ++ul ol { margin-top: 0px; } ++ ++/* make the target distinct; helps when we're navigating to a function */ ++a:target + * { ++ background-color: #FF9; ++} ++ ++ ++/* styles for prettification of source */ ++pre .comment { color: #558817; } ++pre .constant { color: #a8660d; } ++pre .escape { color: #844631; } ++pre .keyword { color: #aa5050; font-weight: bold; } ++pre .library { color: #0e7c6b; } ++pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } ++pre .string { color: #8080ff; } ++pre .number { color: #f8660d; } ++pre .function-name { color: #60447f; } ++pre .operator { color: #2239a8; font-weight: bold; } ++pre .preprocessor, pre .prepro { color: #a33243; } ++pre .global { color: #800080; } ++pre .user-keyword { color: #800080; } ++pre .prompt { color: #558817; } ++pre .url { color: #272fc2; text-decoration: underline; } ++ +diff --git a/extra/penlight/docs/libraries/pl.Set.html b/extra/penlight/docs/libraries/pl.Set.html +new file mode 100644 +index 0000000..8eda41e +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.Set.html +@@ -0,0 +1,650 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.Set

    ++

    A Set class.

    ++

    ++ ++

    ++> Set = require 'pl.Set'
    ++> = Set{'one','two'} == Set{'two','one'}
    ++true
    ++> fruit = Set{'apple','banana','orange'}
    ++> = fruit['banana']
    ++true
    ++> = fruit['hazelnut']
    ++nil
    ++> colours = Set{'red','orange','green','blue'}
    ++> = fruit,colours
    ++[apple,orange,banana]   [blue,green,orange,red]
    ++> = fruit+colours
    ++[blue,green,apple,red,orange,banana]
    ++[orange]
    ++> more_fruits = fruit + 'apricot'
    ++> = fruit*colours
    ++ =  more_fruits, fruit
    ++banana,apricot,apple,orange]    [banana,apple,orange]
    ++
    ++ ++ ++

    Dependencies: pl.utils, pl.tablex, pl.class, pl.Map, (pl.List if __tostring is used)

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Set (t)create a set.
    values (self)get a list of the values in a set.
    map (self, fn, ...)map a function over the values of a set.
    union (self, set)union of two sets (also +).
    intersection (self, set)intersection of two sets (also *).
    difference (self, set)new set with elements in the set that are not in the other (also -).
    issubset (self, set)is the first set a subset of the second (also <)?.
    isempty (self)is the set empty?.
    isdisjoint (s1, s2)are the sets disjoint?
    len (s)size of this set (also # for 5.2).
    ++

    metamethods

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    __tostring ()string representation of a set.
    __add ()union of sets.
    __mul ()intersection of sets.
    __sub ()difference of sets.
    __pow ()symmetric difference of sets.
    __lt ()first set subset of second?
    __len ()cardinality of set (5.2).
    __eq (s1, s2)equality between sets.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ Set (t) ++
    ++
    ++ create a set.
    ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ may be a Set, Map or list-like table. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ values (self) ++
    ++
    ++ get a list of the values in a set. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ map (self, fn, ...) ++
    ++
    ++ map a function over the values of a set. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • fn ++ a function ++
    • ++
    • ... ++ extra arguments to pass to the function. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ union (self, set) ++
    ++
    ++ union of two sets (also +). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ intersection (self, set) ++
    ++
    ++ intersection of two sets (also *). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      > s = Set{10,20,30}
      ++> t = Set{20,30,40}
      ++> = t
      ++[20,30,40]
      ++> = Set.intersection(s,t)
      ++[30,20]
      ++> = s*t
      ++[30,20]
      ++
    ++ ++
    ++
    ++ ++ difference (self, set) ++
    ++
    ++ new set with elements in the set that are not in the other (also -). ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ issubset (self, set) ++
    ++
    ++ is the first set a subset of the second (also <)?. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    • set ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ isempty (self) ++
    ++
    ++ is the set empty?. ++ ++ ++

    Parameters:

    ++
      ++
    • self ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ isdisjoint (s1, s2) ++
    ++
    ++ are the sets disjoint? (no elements in common). ++ Uses naive definition, i.e. that intersection is empty ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ a Set ++
    • ++
    • s2 ++ another set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ len (s) ++
    ++
    ++ size of this set (also # for 5.2). ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ a Set ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ size ++
    ++ ++ ++ ++ ++
    ++
    ++

    metamethods

    ++ ++
    ++
    ++ ++ __tostring () ++
    ++
    ++ string representation of a set. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __add () ++
    ++
    ++ union of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __mul () ++
    ++
    ++ intersection of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __sub () ++
    ++
    ++ difference of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __pow () ++
    ++
    ++ symmetric difference of sets. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __lt () ++
    ++
    ++ first set subset of second? ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __len () ++
    ++
    ++ cardinality of set (5.2). ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ __eq (s1, s2) ++
    ++
    ++ equality between sets. ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ ++
    • ++
    • s2 ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.4.6 ++Last updated 2018-11-23 21:07:42 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.app.html b/extra/penlight/docs/libraries/pl.app.html +new file mode 100644 +index 0000000..4201d44 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.app.html +@@ -0,0 +1,401 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.app

    ++

    Application support functions.

    ++

    See the Guide

    ++ ++

    Dependencies: pl.utils, pl.path

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    script_name ()return the name of the current script running.
    require_here (base, nofollow)prefixes the current script's path to the Lua module path.
    appfile (file)return a suitable path for files private to this application.
    platform ()return string indicating operating system.
    lua ()return the full command-line used to invoke this script.
    parse_args (args, flags_with_values, flags_valid)parse command-line arguments into flags and parameters.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ script_name () ++
    ++
    ++ return the name of the current script running. ++ The name will be the name as passed on the command line ++ ++ ++ ++

    Returns:

    ++
      ++ ++ string filename ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ require_here (base, nofollow) ++
    ++
    ++ prefixes the current script's path to the Lua module path. ++ Applies to both the source and the binary module paths. It makes it easy for ++ the main file of a multi-file program to access its modules in the same directory. ++ base allows these modules to be put in a specified subdirectory, to allow for ++ cleaner deployment and resolve potential conflicts between a script name and its ++ library directory.

    ++ ++

    Note: the path is prefixed, so it is searched first when requiring modules. ++ ++ ++

    Parameters:

    ++
      ++
    • base ++ string ++ optional base directory (absolute, or relative path). ++
    • ++
    • nofollow ++ boolean ++ always use the invocation's directory, even if the invoked file is a symlink ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ the current script's path with a trailing slash ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ appfile (file) ++
    ++
    ++ return a suitable path for files private to this application. ++ These will look like '~/.SNAME/file', with '~' as with expanduser and ++ SNAME is the name of the script without .lua extension. ++ If the directory does not exist, it will be created. ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ string ++ a filename (w/out path) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a full pathname, or nil
    2. ++
    3. ++ cannot create directory error
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      -- when run from a script called 'testapp' (on Windows):
      ++local app = require 'pl.app'
      ++print(app.appfile 'test.txt')
      ++-- C:\Documents and Settings\steve\.testapp\test.txt
      ++
    ++ ++
    ++
    ++ ++ platform () ++
    ++
    ++ return string indicating operating system. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ 'Windows','OSX' or whatever uname returns (e.g. 'Linux') ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ lua () ++
    ++
    ++ return the full command-line used to invoke this script. ++ It will not include the scriptname itself, see app.script_name. ++ ++ ++ ++

    Returns:

    ++
      ++
    1. ++ command-line
    2. ++
    3. ++ name of Lua program used
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      -- execute:  lua -lluacov -e 'print(_VERSION)' myscript.lua
      ++
      ++-- myscript.lua
      ++print(require("pl.app").lua())  --> "lua -lluacov -e 'print(_VERSION)'", "lua"
      ++
    ++ ++
    ++
    ++ ++ parse_args (args, flags_with_values, flags_valid) ++
    ++
    ++ parse command-line arguments into flags and parameters. ++ Understands GNU-style command-line flags; short (-f) and long (--flag).

    ++ ++

    These may be given a value with either '=' or ':' (-k:2,--alpha=3.2,-n2), ++ a number value can be given without a space. If the flag is marked ++ as having a value, then a space-separated value is also accepted (-i hello), ++ see the flags_with_values argument).

    ++ ++

    Multiple short args can be combined like so: ( -abcd).

    ++ ++

    When specifying the flags_valid parameter, its contents can also contain ++ aliases, to convert short/long flags to the same output name. See the ++ example below.

    ++ ++

    Note: if a flag is repeated, the last value wins. ++ ++ ++

    Parameters:

    ++
      ++
    • args ++ {string} ++ an array of strings (default is the global arg) ++
    • ++
    • flags_with_values ++ table ++ any flags that take values, either list or hash ++ table e.g. { out=true } or { "out" }. ++
    • ++
    • flags_valid ++ table ++ (optional) flags that are valid, either list or hashtable. ++ If not given, everything ++ will be accepted(everything in flags_with_values will automatically be allowed) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a table of flags (flag=value pairs)
    2. ++
    3. ++ an array of parameters
    4. ++
    ++ ++

    Raises:

    ++ if args is nil, then the global args must be available! ++ ++ ++

    Usage:

    ++
      ++
      -- Simple form:
      ++local flags, params = app.parse_args(nil,
      ++     { "hello", "world" },  -- list of flags taking values
      ++     { "l", "a", "b"})      -- list of allowed flags (value ones will be added)
      ++
      ++-- More complex example using aliases:
      ++local valid = {
      ++    long = "l",           -- if 'l' is specified, it is reported as 'long'
      ++    new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new'
      ++}
      ++local values = {
      ++    "value",   -- will automatically be added to the allowed set of flags
      ++    "new",     -- will mark 'n' and 'old' as requiring a value as well
      ++}
      ++local flags, params = app.parse_args(nil, values, valid)
      ++
      ++-- command:  myapp.lua -l --old:hello --value world param1 param2
      ++-- will yield:
      ++flags = {
      ++    long = true,     -- input from 'l'
      ++    new = "hello",   -- input from 'old'
      ++    value = "world", -- allowed because it was in 'values', note: space separated!
      ++}
      ++params = {
      ++    [1] = "param1"
      ++    [2] = "param2"
      ++}
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.array2d.html b/extra/penlight/docs/libraries/pl.array2d.html +new file mode 100644 +index 0000000..c798f4d +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.array2d.html +@@ -0,0 +1,1368 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.array2d

    ++

    Operations on two-dimensional arrays.

    ++

    See The Guide

    ++ ++

    The size of the arrays is determined by using the length operator # hence ++ the module is not nil safe, and the usual precautions apply.

    ++ ++

    Note: all functions taking i1,j1,i2,j2 as arguments will normalize the ++ arguments using default_range.

    ++ ++

    Dependencies: pl.utils, pl.tablex, pl.types

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    size (a)return the row and column size.
    column (a, j)extract a column from the 2D array.
    row (a, i)extract a row from the 2D array.
    map (f, a, arg)map a function over a 2D array
    reduce_rows (f, a)reduce the rows using a function.
    reduce_cols (f, a)reduce the columns using a function.
    reduce2 (opc, opr, a)reduce a 2D array into a scalar, using two operations.
    map2 (f, ad, bd, a, b, arg)map a function over two arrays.
    product (f, t1, t2)cartesian product of two 1d arrays.
    flatten (t)flatten a 2D array.
    reshape (t, nrows, co)reshape a 2D array.
    transpose (t)transpose a 2D array.
    swap_rows (t, i1, i2)swap two rows of an array.
    swap_cols (t, j1, j2)swap two columns of an array.
    extract_rows (t, ridx)extract the specified rows.
    extract_cols (t, cidx)extract the specified columns.
    remove_row (t, i)remove a row from an array.
    remove_col (t, j)remove a column from an array.
    parse_range (s)parse a spreadsheet range or cell.
    range (...)get a slice of a 2D array.
    default_range (t[, i1=1[, j1=1[, i2=N[, j2=M]]]])normalizes coordinates to valid positive entries and defaults.
    slice (t[, i1=1[, j1=1[, i2=N[, j2=M]]]])get a slice of a 2D array.
    set (t, value[, i1=1[, j1=1[, i2=N[, j2=M]]]])set a specified range of an array to a value.
    write (t, f, fmt[, i1=1[, j1=1[, i2=N[, j2=M]]]])write a 2D array to a file.
    forall (t, row_op, end_row_op[, i1=1[, j1=1[, i2=N[, j2=M]]]])perform an operation for all values in a 2D array.
    move (dest, di, dj, src[, i1=1[, j1=1[, i2=N[, j2=M]]]])move a block from the destination to the source.
    iter (a, indices[, i1=1[, j1=1[, i2=N[, j2=M]]]])iterate over all elements in a 2D array, with optional indices.
    columns (a)iterate over all columns.
    rows (a)iterate over all rows.
    new (rows, cols, val)new array of specified dimensions
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ size (a) ++
    ++
    ++ return the row and column size. ++ Size is calculated using the Lua length operator #, so usual precautions ++ regarding nil values apply. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ a 2d array ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ int ++ number of rows (#a)
    2. ++
    3. ++ int ++ number of cols (#a[1])
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ column (a, j) ++
    ++
    ++ extract a column from the 2D array. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ 2d array ++
    • ++
    • j ++ column index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 1d array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ row (a, i) ++
    ++
    ++ extract a row from the 2D array. ++ Added in line with column, for read-only purposes directly ++ accessing a[i] is more performant. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ 2d array ++
    • ++
    • i ++ row index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 1d array (copy of the row) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ map (f, a, arg) ++
    ++
    ++ map a function over a 2D array ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ a function of at least one argument ++
    • ++
    • a ++ array ++ 2d array ++
    • ++
    • arg ++ an optional extra argument to be passed to the function. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 2d array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ reduce_rows (f, a) ++
    ++
    ++ reduce the rows using a function. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ a binary function ++
    • ++
    • a ++ array ++ 2d array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 1d array ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ reduce_cols (f, a) ++
    ++
    ++ reduce the columns using a function. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ a binary function ++
    • ++
    • a ++ array ++ 2d array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 1d array ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ reduce2 (opc, opr, a) ++
    ++
    ++ reduce a 2D array into a scalar, using two operations. ++ ++ ++

    Parameters:

    ++
      ++
    • opc ++ function ++ operation to reduce the final result ++
    • ++
    • opr ++ function ++ operation to reduce the rows ++
    • ++
    • a ++ 2D array ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ map2 (f, ad, bd, a, b, arg) ++
    ++
    ++ map a function over two arrays. ++ They can be both or either 2D arrays ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ function of at least two arguments ++
    • ++
    • ad ++ integer ++ order of first array (1 if a is a list/array, 2 if it is a 2d array) ++
    • ++
    • bd ++ integer ++ order of second array (1 if b is a list/array, 2 if it is a 2d array) ++
    • ++
    • a ++ table ++ 1d or 2d array ++
    • ++
    • b ++ table ++ 1d or 2d array ++
    • ++
    • arg ++ optional extra argument to pass to function ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 2D array, unless both arrays are 1D ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ product (f, t1, t2) ++
    ++
    ++ cartesian product of two 1d arrays. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ a function of 2 arguments ++
    • ++
    • t1 ++ array ++ a 1d table ++
    • ++
    • t2 ++ array ++ a 1d table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ 2d table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
      ++
    ++ ++
    ++
    ++ ++ flatten (t) ++
    ++
    ++ flatten a 2D array. ++ (this goes over columns first.) ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2d table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a 1d table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
      ++
    ++ ++
    ++
    ++ ++ reshape (t, nrows, co) ++
    ++
    ++ reshape a 2D array. Reshape the array by specifying a new nr of rows. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2d array ++
    • ++
    • nrows ++ integer ++ new number of rows ++
    • ++
    • co ++ boolean ++ use column-order (Fortran-style) (default false) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new 2d array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ transpose (t) ++
    ++
    ++ transpose a 2D array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2d array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new 2d array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ swap_rows (t, i1, i2) ++
    ++
    ++ swap two rows of an array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2d array ++
    • ++
    • i1 ++ integer ++ a row index ++
    • ++
    • i2 ++ integer ++ a row index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ t (same, modified 2d array) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ swap_cols (t, j1, j2) ++
    ++
    ++ swap two columns of an array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2d array ++
    • ++
    • j1 ++ integer ++ a column index ++
    • ++
    • j2 ++ integer ++ a column index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ t (same, modified 2d array) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ extract_rows (t, ridx) ++
    ++
    ++ extract the specified rows. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2d array ++
    • ++
    • ridx ++ {int} ++ a table of row indices ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new 2d array with the extracted rows ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ extract_cols (t, cidx) ++
    ++
    ++ extract the specified columns. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2d array ++
    • ++
    • cidx ++ {int} ++ a table of column indices ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new 2d array with the extracted columns ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ remove_row (t, i) ++
    ++
    ++ remove a row from an array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2d array ++
    • ++
    • i ++ integer ++ a row index ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ remove_col (t, j) ++
    ++
    ++ remove a column from an array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2d array ++
    • ++
    • j ++ integer ++ a column index ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ parse_range (s) ++
    ++
    ++ parse a spreadsheet range or cell. ++ The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for ++ single cells as 'A1' or 'R1C1'. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a range (case insensitive). ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ int ++ start row
    2. ++
    3. ++ int ++ start col
    4. ++
    5. ++ int ++ end row (or nil if the range was a single cell)
    6. ++
    7. ++ int ++ end col (or nil if the range was a single cell)
    8. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ range (...) ++
    ++
    ++ get a slice of a 2D array. ++ Same as slice. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ ++ ++ ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ default_range (t[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ normalizes coordinates to valid positive entries and defaults. ++ Negative indices will be counted from the end, too low, or too high ++ will be limited by the array sizes. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2D array ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ i1, j1, i2, j2 ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ slice (t[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ get a slice of a 2D array. Note that if the specified range has ++ a 1D result, the rank of the result will be 1. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2D array ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an array, 2D in general but 1D in special cases. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ set (t, value[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ set a specified range of an array to a value. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2D array ++
    • ++
    • value ++ the value (may be a function, called as val(i,j)) ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ write (t, f, fmt[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ write a 2D array to a file. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a 2D array ++
    • ++
    • f ++ a file object (default stdout) ++
    • ++
    • fmt ++ string ++ a format string (default is just to use tostring) ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ forall (t, row_op, end_row_op[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ perform an operation for all values in a 2D array. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ 2D array ++
    • ++
    • row_op ++ function ++ function to call on each value; row_op(row,j) ++
    • ++
    • end_row_op ++ function ++ function to call at end of each row; end_row_op(i) ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ move (dest, di, dj, src[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ move a block from the destination to the source. ++ ++ ++

    Parameters:

    ++
      ++
    • dest ++ array ++ a 2D array ++
    • ++
    • di ++ integer ++ start row in dest ++
    • ++
    • dj ++ integer ++ start col in dest ++
    • ++
    • src ++ array ++ a 2D array ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ iter (a, indices[, i1=1[, j1=1[, i2=N[, j2=M]]]]) ++
    ++
    ++ iterate over all elements in a 2D array, with optional indices. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ 2D array ++
    • ++
    • indices ++ boolean ++ with indices (default false) ++
    • ++
    • i1 ++ int or string ++ start row or spreadsheet range passed to parse_range ++ (default 1) ++
    • ++
    • j1 ++ int ++ start col ++ (default 1) ++
    • ++
    • i2 ++ int ++ end row ++ (default N) ++
    • ++
    • j2 ++ int ++ end col ++ (default M) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ either value or i,j,value depending on the value of indices ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ columns (a) ++
    ++
    ++ iterate over all columns. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ a 2D array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ column, column-index ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ rows (a) ++
    ++
    ++ iterate over all rows. ++ Returns a copy of the row, for read-only purposes directly iterating ++ is more performant; ipairs(a) ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ array ++ a 2D array ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ row, row-index ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ new (rows, cols, val) ++
    ++
    ++ new array of specified dimensions ++ ++ ++

    Parameters:

    ++
      ++
    • rows ++ integer ++ number of rows ++
    • ++
    • cols ++ integer ++ number of cols ++
    • ++
    • val ++ initial value; if it's a function then use val(i,j) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ new 2d array ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.class.html b/extra/penlight/docs/libraries/pl.class.html +new file mode 100644 +index 0000000..cd0f014 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.class.html +@@ -0,0 +1,332 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.class

    ++

    Provides a reusable and convenient framework for creating classes in Lua.

    ++

    Two possible notations:

    ++ ++ ++
    ++B = class(A)
    ++class.B(A)
    ++
    ++ ++

    The latter form creates a named class within the current environment. Note ++ that this implicitly brings in pl.utils as a dependency.

    ++ ++

    See the Guide for further discussion

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    _init (...)initializes an instance upon creation.
    instance:is_a (some_class)checks whether an instance is derived from some class.
    some_class:class_of (some_instance)checks whether an instance is derived from some class.
    some_class:cast (some_instance)cast an object to another class.
    class (base, c_arg, c)create a new class, derived from a given base class.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ _init (...) ++
    ++
    ++ initializes an instance upon creation. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ parameters passed to the constructor ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      local Cat = class()
      ++function Cat:_init(name)
      ++  --self:super(name)   -- call the ancestor initializer if needed
      ++  self.name = name
      ++end
      ++
      ++local pussycat = Cat("pussycat")
      ++print(pussycat.name)  --> pussycat
      ++
    ++ ++
    ++
    ++ ++ instance:is_a (some_class) ++
    ++
    ++ checks whether an instance is derived from some class. ++ Works the other way around as class_of. It has two ways of using; ++ 1) call with a class to check against, 2) call without params. ++ ++ ++

    Parameters:

    ++
      ++
    • some_class ++ class to check against, or nil to return the class ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if instance is derived from some_class, or if some_class == nil then ++ it returns the class table of the instance ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local pussycat = Lion()  -- assuming Lion derives from Cat
      ++if pussycat:is_a(Cat) then
      ++  -- it's true, it is a Lion, but also a Cat
      ++end
      ++
      ++if pussycat:is_a() == Lion then
      ++  -- It's true
      ++end
      ++
    ++ ++
    ++
    ++ ++ some_class:class_of (some_instance) ++
    ++
    ++ checks whether an instance is derived from some class. ++ Works the other way around as is_a. ++ ++ ++

    Parameters:

    ++
      ++
    • some_instance ++ instance to check against ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if some_instance is derived from some_class ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local pussycat = Lion()  -- assuming Lion derives from Cat
      ++if Cat:class_of(pussycat) then
      ++  -- it's true
      ++end
      ++
    ++ ++
    ++
    ++ ++ some_class:cast (some_instance) ++
    ++
    ++ cast an object to another class. ++ It is not clever (or safe!) so use carefully. ++ ++ ++

    Parameters:

    ++
      ++
    • some_instance ++ the object to be changed ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ class (base, c_arg, c) ++
    ++
    ++ create a new class, derived from a given base class. ++ Supporting two class creation syntaxes: ++ either Name = class(base) or class.Name(base). ++ The first form returns the class directly and does not set its _name. ++ The second form creates a variable Name in the current environment set ++ to the class, and also sets _name. ++ ++ ++

    Parameters:

    ++
      ++
    • base ++ optional base class ++
    • ++
    • c_arg ++ optional parameter to class constructor ++
    • ++
    • c ++ optional table to be used as class ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.compat.html b/extra/penlight/docs/libraries/pl.compat.html +new file mode 100644 +index 0000000..fc83841 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.compat.html +@@ -0,0 +1,579 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.compat

    ++

    Lua 5.1/5.2/5.3 compatibility.

    ++

    Injects table.pack, table.unpack, and package.searchpath in the global ++ environment, to make sure they are available for Lua 5.1 and LuaJIT.

    ++ ++

    All other functions are exported as usual in the returned module table.

    ++ ++

    NOTE: everything in this module is also available in pl.utils.

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    execute (cmd)execute a shell command, in a compatible and platform independent way.
    load (ld[, source[, mode[, env]]])Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way).
    getfenv (f)Get environment of a function (in a Lua 5.1 compatible way).
    setfenv (f, env)Set environment of a function (in a Lua 5.1 compatible way).
    ++

    Fields

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    lua51boolean flag this is Lua 5.1 (or LuaJIT).
    jitboolean flag this is LuaJIT.
    jit52boolean flag this is LuaJIT with 5.2 compatibility compiled in.
    dir_separatorthe directory separator character for the current platform.
    is_windowsboolean flag this is a Windows platform.
    ++

    Global exported functions (for Lua 5.1 & LuaJIT)

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    table.pack (...)pack an argument list into a table.
    table.unpack (t[, i[, j]])unpack a table and return the elements.
    package.searchpath (name, path[, sep[, rep]])return the full path where a file name would be matched.
    ++

    Global exported functions (for Lua < 5.4)

    ++ ++ ++ ++ ++ ++
    warn (...)raise a warning message.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ execute (cmd) ++
    ++
    ++ execute a shell command, in a compatible and platform independent way. ++ This is a compatibility function that returns the same for Lua 5.1 and ++ Lua 5.2+.

    ++ ++

    NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems ++ only use exitcodes 0-255, anything else is undefined.

    ++ ++

    NOTE2: In Lua 5.2 and 5.3 a Windows exitcode of -1 would not properly be ++ returned, this function will return it properly for all versions. ++ ++ ++

    Parameters:

    ++
      ++
    • cmd ++ a shell command ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ true if successful
    2. ++
    3. ++ actual return code
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ load (ld[, source[, mode[, env]]]) ++
    ++
    ++ Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way). ++ ++ ++

    Parameters:

    ++
      ++
    • ld ++ code string or loader ++
    • ++
    • source ++ name of chunk for errors ++ (optional) ++
    • ++
    • mode ++ 'b', 't' or 'bt' ++ (optional) ++
    • ++
    • env ++ environment to load the chunk in ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ getfenv (f) ++
    ++
    ++ Get environment of a function (in a Lua 5.1 compatible way). ++ Not 100% compatible, so with Lua 5.2 it may return nil for a function with no ++ global references! ++ Based on code by Sergey Rozhenko ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ a function or a call stack reference ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ setfenv (f, env) ++
    ++
    ++ Set environment of a function (in a Lua 5.1 compatible way). ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ a function or a call stack reference ++
    • ++
    • env ++ a table that becomes the new environment of f ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Fields

    ++ ++
    ++
    ++ ++ lua51 ++
    ++
    ++ boolean flag this is Lua 5.1 (or LuaJIT). ++ ++ ++
      ++
    • lua51 ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ jit ++
    ++
    ++ boolean flag this is LuaJIT. ++ ++ ++
      ++
    • jit ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ jit52 ++
    ++
    ++ boolean flag this is LuaJIT with 5.2 compatibility compiled in. ++ ++ ++
      ++
    • jit52 ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ dir_separator ++
    ++
    ++ the directory separator character for the current platform. ++ ++ ++
      ++
    • dir_separator ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ is_windows ++
    ++
    ++ boolean flag this is a Windows platform. ++ ++ ++
      ++
    • is_windows ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Global exported functions (for Lua 5.1 & LuaJIT)

    ++ ++
    ++
    ++ ++ table.pack (...) ++
    ++
    ++ pack an argument list into a table. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ any arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table with field n set to the length ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ table.unpack (t[, i[, j]]) ++
    ++
    ++ unpack a table and return the elements.

    ++ ++

    NOTE: this version does NOT honor the n field, and hence it is not nil-safe. ++ See utils.unpack for a version that is nil-safe. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table to unpack ++
    • ++
    • i ++ index from which to start unpacking, defaults to 1 ++ (optional) ++
    • ++
    • j ++ index of the last element to unpack, defaults to #t ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ multiple return values from the table ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ package.searchpath (name, path[, sep[, rep]]) ++
    ++
    ++ return the full path where a file name would be matched. ++ This function was introduced in Lua 5.2, so this compatibility version ++ will be injected in Lua 5.1 engines. ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ file name, possibly dotted ++
    • ++
    • path ++ string ++ a path-template in the same form as package.path or package.cpath ++
    • ++
    • sep ++ string ++ template separate character to be replaced by path separator. Default: "." ++ (optional) ++
    • ++
    • rep ++ string ++ the path separator to use, defaults to system separator. Default; "/" on Unixes, "\" on Windows. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ on success: path of the file
    2. ++
    3. ++ on failure: nil, error string listing paths tried
    4. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++

    Global exported functions (for Lua < 5.4)

    ++ ++
    ++
    ++ ++ warn (...) ++
    ++
    ++ raise a warning message. ++ This functions mimics the warn function added in Lua 5.4. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ any arguments ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.comprehension.html b/extra/penlight/docs/libraries/pl.comprehension.html +new file mode 100644 +index 0000000..4733ba8 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.comprehension.html +@@ -0,0 +1,165 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.comprehension

    ++

    List comprehensions implemented in Lua.

    ++

    See the wiki page

    ++ ++ ++
    ++local C= require 'pl.comprehension' . new()
    ++
    ++C ('x for x=1,10') ()
    ++==> {1,2,3,4,5,6,7,8,9,10}
    ++C 'x^2 for x=1,4' ()
    ++==> {1,4,9,16}
    ++C '{x,x^2} for x=1,4' ()
    ++==> {{1,1},{2,4},{3,9},{4,16}}
    ++C '2*x for x' {1,2,3}
    ++==> {2,4,6}
    ++dbl = C '2*x for x'
    ++dbl {10,20,30}
    ++==> {20,40,60}
    ++C 'x for x if x % 2 == 0' {1,2,3,4,5}
    ++==> {2,4}
    ++C '{x,y} for x = 1,2 for y = 1,2' ()
    ++==> {{1,1},{1,2},{2,1},{2,2}}
    ++C '{x,y} for x for y' ({1,2},{10,20})
    ++==> {{1,10},{1,20},{2,10},{2,20}}
    ++assert(C 'sum(x^2 for x)' {2,3,4} == 2^2+3^2+4^2)
    ++
    ++ ++

    (c) 2008 David Manura. Licensed under the same terms as Lua (MIT license).

    ++ ++

    Dependencies: pl.utils, pl.luabalanced

    ++ ++

    See the Guide

    ++ ++ ++ ++
    ++
    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.config.html b/extra/penlight/docs/libraries/pl.config.html +new file mode 100644 +index 0000000..70f4622 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.config.html +@@ -0,0 +1,259 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.config

    ++

    Reads configuration files into a Lua table.

    ++

    ++ ++

    Understands INI files, classic Unix config files, and simple ++ delimited columns of values. See the Guide

    ++ ++ ++
    ++# test.config
    ++# Read timeout in seconds
    ++read.timeout=10
    ++# Write timeout in seconds
    ++write.timeout=5
    ++#acceptable ports
    ++ports = 1002,1003,1004
    ++
    ++-- readconfig.lua
    ++local config = require 'config'
    ++local t = config.read 'test.config'
    ++print(pretty.write(t))
    ++
    ++### output #####
    ++{
    ++  ports = {
    ++    1002,
    ++    1003,
    ++    1004
    ++  },
    ++  write_timeout = 5,
    ++  read_timeout = 10
    ++}
    ++
    ++ ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    lines (file)like io.lines, but allows for lines to be continued with '\'.
    read (file[, cnfg])read a configuration file into a table
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ lines (file) ++
    ++
    ++ like io.lines, but allows for lines to be continued with '\'. ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ a file-like object (anything where read() returns the next line) or a filename. ++ Defaults to standard input. ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ an iterator over the lines, or nil
    2. ++
    3. ++ error 'not a file-like object' or 'file is nil'
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ read (file[, cnfg]) ++
    ++
    ++ read a configuration file into a table ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ either a file-like object or a string, which must be a filename ++
    • ++
    • cnfg ++ table ++ ++

      a configuration table that may contain these fields:

      ++ ++
        ++
      • smart try to deduce what kind of config file we have (default false)
      • ++
      • variabilize make names into valid Lua identifiers (default true)
      • ++
      • convert_numbers try to convert values into numbers (default true)
      • ++
      • trim_space ensure that there is no starting or trailing whitespace with values (default true)
      • ++
      • trim_quotes remove quotes from strings (default false)
      • ++
      • list_delim delimiter to use when separating columns (default ',')
      • ++
      • keysep separator between key and value pairs (default '=')
      • ++
      ++ ++ ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a table containing items, or nil
    2. ++
    3. ++ error message (same as config.lines
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.data.html b/extra/penlight/docs/libraries/pl.data.html +new file mode 100644 +index 0000000..3c770ac +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.data.html +@@ -0,0 +1,571 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.data

    ++

    Reading and querying simple tabular data.

    ++

    ++ ++ ++ ++

    ++data.read 'test.txt'
    ++==> {{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
    ++
    ++ ++

    Provides a way of creating basic SQL-like queries.

    ++ ++ ++
    ++require 'pl'
    ++local d = data.read('xyz.txt')
    ++local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
    ++for x,y,z in q do
    ++    print(x,y,z)
    ++end
    ++
    ++ ++

    See the Guide

    ++ ++

    Dependencies: pl.utils, pl.array2d (fallback methods)

    ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Data.column_by_name (name)return a particular column as a list of values (method).
    Data.select (condn)return a query iterator on this data (method).
    Data.select_row (condn)return a row iterator on this data (method).
    Data.copy_select (condn)return a new data object based on this query (method).
    Data.column_names ()return the field names of this data object (method).
    Data.write_row (f)write out a row (method).
    Data.write (f)write data out to file (method).
    read (file, cnfg)read a delimited file in a Lua table.
    write (data, file[, fieldnames[, delim='\t']])write 2D data to a file.
    new (d[, fieldnames])create a new dataset from a table of rows.
    query (data, condn, context, return_row)create a query iterator from a select string.
    filter (Q, infile, outfile, dont_fail)Filter input using a query.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ Data.column_by_name (name) ++
    ++
    ++ return a particular column as a list of values (method). ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ either name of column, or numerical index. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Data.select (condn) ++
    ++
    ++ return a query iterator on this data (method). ++ ++ ++

    Parameters:

    ++
      ++
    • condn ++ string ++ the query expression ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ Data.select_row (condn) ++
    ++
    ++ return a row iterator on this data (method). ++ ++ ++

    Parameters:

    ++
      ++
    • condn ++ string ++ the query expression ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Data.copy_select (condn) ++
    ++
    ++ return a new data object based on this query (method). ++ ++ ++

    Parameters:

    ++
      ++
    • condn ++ string ++ the query expression ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Data.column_names () ++
    ++
    ++ return the field names of this data object (method). ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Data.write_row (f) ++
    ++
    ++ write out a row (method). ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ file-like object ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Data.write (f) ++
    ++
    ++ write data out to file (method). ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ file-like object ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ read (file, cnfg) ++
    ++
    ++ read a delimited file in a Lua table. ++ By default, attempts to treat first line as separated list of fieldnames. ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ a filename or a file-like object ++
    • ++
    • cnfg parsing options ++
        ++
      • delim ++ string ++ a string pattern to split fields ++
      • ++
      • fieldnames ++ array ++ (i.e. don't read from first line) ++
      • ++
      • no_convert ++ boolean ++ (default is to try conversion on first data line) ++
      • ++
      • convert ++ table ++ table of custom conversion functions with column keys ++
      • ++
      • numfields ++ integer ++ indices of columns known to be numbers ++
      • ++
      • last_field_collect ++ boolean ++ only split as many fields as fieldnames. ++
      • ++
      • thousands_dot ++ integer ++ thousands separator in Excel CSV is '.' ++
      • ++
      • csv ++ boolean ++ fields may be double-quoted and contain commas; ++ Also, empty fields are considered to be equivalent to zero. ++
      • ++
      ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ data object, or nil
    2. ++
    3. ++ error message. May be a file error, 'not a file-like object' ++ or a conversion error
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ write (data, file[, fieldnames[, delim='\t']]) ++
    ++
    ++ write 2D data to a file. ++ Does not assume that the data has actually been ++ generated with new or read. ++ ++ ++

    Parameters:

    ++
      ++
    • data ++ 2D array ++
    • ++
    • file ++ filename or file-like object ++
    • ++
    • fieldnames ++ {string} ++ list of fields (optional) ++ (optional) ++
    • ++
    • delim ++ string ++ delimiter (default tab) ++ (default '\t') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or nil, error ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ new (d[, fieldnames]) ++
    ++
    ++ create a new dataset from a table of rows. ++ Can specify the fieldnames, else the table must have a field called ++ 'fieldnames', which is either a string of delimiter-separated names, ++ or a table of names.
    ++ If the table does not have a field called 'delim', then an attempt will be ++ made to guess it from the fieldnames string, defaults otherwise to tab. ++ ++ ++

    Parameters:

    ++
      ++
    • d ++ the table. ++
    • ++
    • fieldnames ++ {string} ++ optional fieldnames ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the table. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ query (data, condn, context, return_row) ++
    ++
    ++ create a query iterator from a select string. ++ Select string has this format:
    ++ FIELDLIST [ where LUA-CONDN [ sort by FIELD] ]
    ++ FIELDLIST is a comma-separated list of valid fields, or '*'.

    ++ The condition can also be a table, with fields 'fields' (comma-sep string or ++ table), 'sort_by' (string) and 'where' (Lua expression string or function) ++ ++ ++

    Parameters:

    ++
      ++
    • data ++ table produced by read ++
    • ++
    • condn ++ select string or table ++
    • ++
    • context ++ a list of tables to be searched when resolving functions ++
    • ++
    • return_row ++ if true, wrap the results in a row table ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ an iterator over the specified fields, or nil
    2. ++
    3. ++ an error message
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ filter (Q, infile, outfile, dont_fail) ++
    ++
    ++ Filter input using a query. ++ ++ ++

    Parameters:

    ++
      ++
    • Q ++ string ++ a query string ++
    • ++
    • infile ++ filename or file-like object ++
    • ++
    • outfile ++ filename or file-like object ++
    • ++
    • dont_fail ++ boolean ++ true if you want to return an error, not just fail ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.dir.html b/extra/penlight/docs/libraries/pl.dir.html +new file mode 100644 +index 0000000..66371e3 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.dir.html +@@ -0,0 +1,620 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.dir

    ++

    Listing files in directories and creating/removing directory paths.

    ++

    Dependencies: pl.utils, pl.path

    ++ ++

    Soft Dependencies: alien, ffi (either are used on Windows for copying/moving files)

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    fnmatch (filename, pattern)Test whether a file name matches a shell pattern.
    filter (filenames, pattern)Return a list of all file names within an array which match a pattern.
    getfiles ([dirname='.'[, mask]])return a list of all files in a directory which match a shell pattern.
    getdirectories ([dirname='.'])return a list of all subdirectories of the directory.
    copyfile (src, dest, flag)copy a file.
    movefile (src, dest)move a file.
    walk (root, bottom_up, follow_links)return an iterator which walks through a directory tree starting at root.
    rmtree (fullpath)remove a whole directory tree.
    makepath (p)create a directory path.
    clonetree (path1, path2, file_fun, verbose)clone a directory tree.
    dirtree (d)return an iterator over all entries in a directory tree
    getallfiles ([start_path='.'[, shell_pattern='*']])Recursively returns all the file starting at 'path'.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ fnmatch (filename, pattern) ++
    ++
    ++ Test whether a file name matches a shell pattern. ++ Both parameters are case-normalized if operating system is ++ case-insensitive. ++ ++ ++

    Parameters:

    ++
      ++
    • filename ++ string ++ A file name. ++
    • ++
    • pattern ++ string ++ A shell pattern. The only special characters are ++ '*' and '?': '*' matches any sequence of characters and ++ '?' matches any single character. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ bool ++ ++ ++ ++
    ++ ++

    Raises:

    ++ dir and mask must be strings ++ ++ ++ ++
    ++
    ++ ++ filter (filenames, pattern) ++
    ++
    ++ Return a list of all file names within an array which match a pattern. ++ ++ ++

    Parameters:

    ++
      ++
    • filenames ++ table ++ An array containing file names. ++
    • ++
    • pattern ++ string ++ A shell pattern (see fnmatch). ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ List(string) ++ List of matching file names. ++
    ++ ++

    Raises:

    ++ dir and mask must be strings ++ ++ ++ ++
    ++
    ++ ++ getfiles ([dirname='.'[, mask]]) ++
    ++
    ++ return a list of all files in a directory which match a shell pattern. ++ ++ ++

    Parameters:

    ++
      ++
    • dirname ++ string ++ A directory. ++ (default '.') ++
    • ++
    • mask ++ string ++ A shell pattern (see fnmatch). If not given, all files are returned. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ {string} ++ list of files ++
    ++ ++

    Raises:

    ++ dirname and mask must be strings ++ ++ ++ ++
    ++
    ++ ++ getdirectories ([dirname='.']) ++
    ++
    ++ return a list of all subdirectories of the directory. ++ ++ ++

    Parameters:

    ++
      ++
    • dirname ++ string ++ A directory. ++ (default '.') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ {string} ++ a list of directories ++
    ++ ++

    Raises:

    ++ dir must be a valid directory ++ ++ ++ ++
    ++
    ++ ++ copyfile (src, dest, flag) ++
    ++
    ++ copy a file. ++ ++ ++

    Parameters:

    ++
      ++
    • src ++ string ++ source file ++
    • ++
    • dest ++ string ++ destination file or directory ++
    • ++
    • flag ++ boolean ++ true if you want to force the copy (default) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ bool ++ operation succeeded ++
    ++ ++

    Raises:

    ++ src and dest must be strings ++ ++ ++ ++
    ++
    ++ ++ movefile (src, dest) ++
    ++
    ++ move a file. ++ ++ ++

    Parameters:

    ++
      ++
    • src ++ string ++ source file ++
    • ++
    • dest ++ string ++ destination file or directory ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ bool ++ operation succeeded ++
    ++ ++

    Raises:

    ++ src and dest must be strings ++ ++ ++ ++
    ++
    ++ ++ walk (root, bottom_up, follow_links) ++
    ++
    ++ return an iterator which walks through a directory tree starting at root. ++ The iterator returns (root,dirs,files) ++ Note that dirs and files are lists of names (i.e. you must say path.join(root,d) ++ to get the actual full path) ++ If bottom_up is false (or not present), then the entries at the current level are returned ++ before we go deeper. This means that you can modify the returned list of directories before ++ continuing. ++ This is a clone of os.walk from the Python libraries. ++ ++ ++

    Parameters:

    ++
      ++
    • root ++ string ++ A starting directory ++
    • ++
    • bottom_up ++ boolean ++ False if we start listing entries immediately. ++
    • ++
    • follow_links ++ boolean ++ follow symbolic links ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator returning root,dirs,files ++
    ++ ++

    Raises:

    ++ root must be a directory ++ ++ ++ ++
    ++
    ++ ++ rmtree (fullpath) ++
    ++
    ++ remove a whole directory tree. ++ Symlinks in the tree will be deleted without following them. ++ ++ ++

    Parameters:

    ++
      ++
    • fullpath ++ string ++ A directory path (must be an actual directory, not a symlink) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ true or nil
    2. ++
    3. ++ error if failed
    4. ++
    ++ ++

    Raises:

    ++ fullpath must be a string ++ ++ ++ ++
    ++
    ++ ++ makepath (p) ++
    ++
    ++ create a directory path. ++ This will create subdirectories as necessary! ++ ++ ++

    Parameters:

    ++
      ++
    • p ++ string ++ A directory path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true on success, nil + errormsg on failure ++
    ++ ++

    Raises:

    ++ failure to create ++ ++ ++ ++
    ++
    ++ ++ clonetree (path1, path2, file_fun, verbose) ++
    ++
    ++ clone a directory tree. Will always try to create a new directory structure ++ if necessary. ++ ++ ++

    Parameters:

    ++
      ++
    • path1 ++ string ++ the base path of the source tree ++
    • ++
    • path2 ++ string ++ the new base path for the destination ++
    • ++
    • file_fun ++ function ++ an optional function to apply on all files ++
    • ++
    • verbose ++ boolean ++ an optional boolean to control the verbosity of the output. ++ It can also be a logging function that behaves like print() ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ true, or nil
    2. ++
    3. ++ error message, or list of failed directory creations
    4. ++
    5. ++ list of failed file operations
    6. ++
    ++ ++

    Raises:

    ++ path1 and path2 must be strings ++ ++ ++

    Usage:

    ++
      ++
      clonetree('.','../backup',copyfile)
      ++
    ++ ++
    ++
    ++ ++ dirtree (d) ++
    ++
    ++ return an iterator over all entries in a directory tree ++ ++ ++

    Parameters:

    ++
      ++
    • d ++ string ++ a directory ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator giving pathname and mode (true for dir, false otherwise) ++
    ++ ++

    Raises:

    ++ d must be a non-empty string ++ ++ ++ ++
    ++
    ++ ++ getallfiles ([start_path='.'[, shell_pattern='*']]) ++
    ++
    ++ Recursively returns all the file starting at 'path'. It can optionally take a shell pattern and ++ only returns files that match 'shell_pattern'. If a pattern is given it will do a case insensitive search. ++ ++ ++

    Parameters:

    ++
      ++
    • start_path ++ string ++ A directory. ++ (default '.') ++
    • ++
    • shell_pattern ++ string ++ A shell pattern (see fnmatch). ++ (default '*') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ List(string) ++ containing all the files found recursively starting at 'path' and filtered by 'shell_pattern'. ++
    ++ ++

    Raises:

    ++ start_path must be a directory ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.file.html b/extra/penlight/docs/libraries/pl.file.html +new file mode 100644 +index 0000000..391ece4 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.file.html +@@ -0,0 +1,301 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.file

    ++

    File manipulation functions: reading, writing, moving and copying.

    ++

    This module wraps a number of functions from other modules into a ++ file related module for convenience.

    ++ ++

    Dependencies: pl.utils, pl.dir, pl.path

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    read ()return the contents of a file as a string.
    write ()write a string to a file.
    copy ()copy a file.
    move ()move a file.
    access_time ()Return the time of last access as the number of seconds since the epoch.
    creation_time ()Return when the file was created.
    modified_time ()Return the time of last modification.
    delete ()Delete a file.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ read () ++
    ++
    ++ return the contents of a file as a string. ++ This function is a copy of utils.readfile. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ write () ++
    ++
    ++ write a string to a file. ++ This function is a copy of utils.writefile. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ copy () ++
    ++
    ++ copy a file. ++ This function is a copy of dir.copyfile. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ move () ++
    ++
    ++ move a file. ++ This function is a copy of dir.movefile. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ access_time () ++
    ++
    ++ Return the time of last access as the number of seconds since the epoch. ++ This function is a copy of path.getatime. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ creation_time () ++
    ++
    ++ Return when the file was created. ++ This function is a copy of path.getctime. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ modified_time () ++
    ++
    ++ Return the time of last modification. ++ This function is a copy of path.getmtime. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ delete () ++
    ++
    ++ Delete a file. ++ This function is a copy of os.remove. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.func.html b/extra/penlight/docs/libraries/pl.func.html +new file mode 100644 +index 0000000..5ee0ade +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.func.html +@@ -0,0 +1,464 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.func

    ++

    Functional helpers like composition, binding and placeholder expressions.

    ++

    Placeholder expressions are useful for short anonymous functions, and were ++ inspired by the Boost Lambda library.

    ++ ++ ++
    ++> utils.import 'pl.func'
    ++> ls = List{10,20,30}
    ++> = ls:map(_1+1)
    ++{11,21,31}
    ++
    ++ ++

    They can also be used to bind particular arguments of a function.

    ++ ++ ++
    ++> p = bind(print,'start>',_0)
    ++> p(10,20,30)
    ++> start>   10   20  30
    ++
    ++ ++

    See the Guide

    ++ ++

    Dependencies: pl.utils, pl.tablex

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    import (tname, context)wrap a table of functions.
    register (fun[, name])register a function for use in placeholder expressions.
    tail (ls)all elements of a table except the first.
    repr (e, lastpred)create a string representation of a placeholder expression.
    instantiate (e)instantiate a PE into an actual function.
    I (e)instantiate a PE unless it has already been done.
    bind1 (fn, p)bind the first parameter of the function to a value.
    compose (f, g, ...)create a function which chains multiple functions.
    bind (fn, ...)bind the arguments of a function to given values.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ import (tname, context) ++
    ++
    ++ wrap a table of functions. This makes them available for use in ++ placeholder expressions. ++ ++ ++

    Parameters:

    ++
      ++
    • tname ++ string ++ a table name ++
    • ++
    • context ++ table ++ context to put results, defaults to environment of caller ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ register (fun[, name]) ++
    ++
    ++ register a function for use in placeholder expressions. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function ++
    • ++
    • name ++ string ++ an optional name ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a placeholder functiond ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ tail (ls) ++
    ++
    ++ all elements of a table except the first. ++ ++ ++

    Parameters:

    ++
      ++
    • ls ++ table ++ a list-like table. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ repr (e, lastpred) ++
    ++
    ++ create a string representation of a placeholder expression. ++ ++ ++

    Parameters:

    ++
      ++
    • e ++ a placeholder expression ++
    • ++
    • lastpred ++ not used ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ instantiate (e) ++
    ++
    ++ instantiate a PE into an actual function. First we find the largest placeholder used, ++ e.g. 2; from this a list of the formal parameters can be build. Then we collect and replace ++ any non-PE values from the PE, and build up a constant binding list. ++ Finally, the expression can be compiled, and e.PEfunction is set. ++ ++ ++

    Parameters:

    ++
      ++
    • e ++ a placeholder expression ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ I (e) ++
    ++
    ++ instantiate a PE unless it has already been done. ++ ++ ++

    Parameters:

    ++
      ++
    • e ++ a placeholder expression ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the function ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ bind1 (fn, p) ++
    ++
    ++ bind the first parameter of the function to a value. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ function ++ a function of one or more arguments ++
    • ++
    • p ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function of one less argument ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      (bind1(math.max,10))(20) == math.max(10,20)
      ++
    ++ ++
    ++
    ++ ++ compose (f, g, ...) ++
    ++
    ++ create a function which chains multiple functions. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ function ++ a function of at least one argument ++
    • ++
    • g ++ function ++ a function of at least one argument ++
    • ++
    • ... ++ additional functions to compose ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • printf = compose(io.write, string.format)
    • ++
    • printf = compose(io.write, string.lower, string.format)
    • ++
    ++ ++
    ++
    ++ ++ bind (fn, ...) ++
    ++
    ++ bind the arguments of a function to given values. ++ bind(fn,v,_2) is equivalent to bind1(fn,v). ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ function ++ a function of at least one argument ++
    • ++
    • ... ++ values or placeholder variables ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • (bind(f,_1,a))(b) == f(a,b)
    • ++
    • (bind(f,_2,_1))(a,b) == f(b,a)
    • ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.html b/extra/penlight/docs/libraries/pl.html +new file mode 100644 +index 0000000..24644ec +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.html +@@ -0,0 +1,139 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl

    ++

    Entry point for loading all PL libraries only on demand, into the global space.

    ++

    Requiring 'pl' means that whenever a module is implicitly accessed ++ (e.g. utils.split) ++ then that module is dynamically loaded. The submodules are all brought into ++ the global space. ++Updated to use pl.import_into

    ++ ++ ++ ++
    ++
    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.import_into.html b/extra/penlight/docs/libraries/pl.import_into.html +new file mode 100644 +index 0000000..d3b329e +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.import_into.html +@@ -0,0 +1,142 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.import_into

    ++

    PL loader, for loading all PL libraries, only on demand.

    ++

    Whenever a module is implicitly accessed, the table will have the module automatically injected. ++ (e.g. _ENV.tablex) ++ then that module is dynamically loaded. The submodules are all brought into ++ the table that is provided as the argument, or returned in a new table. ++ If a table is provided, that table's metatable is clobbered, but the values are not. ++ This module returns a single function, which is passed the environment. ++ If this is true, then return a 'shadow table' as the module ++ See the Guide

    ++ ++ ++ ++
    ++
    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.input.html b/extra/penlight/docs/libraries/pl.input.html +new file mode 100644 +index 0000000..2f55157 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.input.html +@@ -0,0 +1,336 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.input

    ++

    Iterators for extracting words or numbers from an input source.

    ++

    ++ ++ ++ ++

    ++require 'pl'
    ++local total,n = seq.sum(input.numbers())
    ++print('average',total/n)
    ++
    ++ ++

    source is defined as a string or a file-like object (i.e. has a read() method which returns the next line)

    ++ ++

    See here

    ++ ++

    Dependencies: pl.utils

    ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    alltokens (getter, pattern[, fn])create an iterator over all tokens.
    create_getter (f)create a function which grabs the next value from a source.
    numbers (f)generate a sequence of numbers from a source.
    words (f)generate a sequence of words from a source.
    fields (ids, delim, f, opts)parse an input source into fields.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ alltokens (getter, pattern[, fn]) ++
    ++
    ++ create an iterator over all tokens. ++ based on allwords from PiL, 7.1 ++ ++ ++

    Parameters:

    ++
      ++
    • getter ++ function ++ any function that returns a line of text ++
    • ++
    • pattern ++ string ++ ++ ++ ++
    • ++
    • fn ++ string ++ Optionally can pass a function to process each token as it's found. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ create_getter (f) ++
    ++
    ++ create a function which grabs the next value from a source. If the source is a string, then the getter ++ will return the string and thereafter return nil. If not specified then the source is assumed to be stdin. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ a string or a file-like object (i.e. has a read() method which returns the next line) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a getter function ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ numbers (f) ++
    ++
    ++ generate a sequence of numbers from a source. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ A source ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ An iterator ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ words (f) ++
    ++
    ++ generate a sequence of words from a source. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ A source ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ An iterator ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ fields (ids, delim, f, opts) ++
    ++
    ++ parse an input source into fields. ++ By default, will fail if it cannot convert a field to a number. ++ ++ ++

    Parameters:

    ++
      ++
    • ids ++ a list of field indices, or a maximum field index ++
    • ++
    • delim ++ string ++ delimiter to parse fields (default space) ++
    • ++
    • f ++ a source @see create_getter ++
    • ++
    • opts ++ table ++ option table, {no_fail=true} ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator with the field values ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      for x,y in fields {2,3} do print(x,y) end -- 2nd and 3rd fields from stdin
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.lapp.html b/extra/penlight/docs/libraries/pl.lapp.html +new file mode 100644 +index 0000000..af5cee5 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.lapp.html +@@ -0,0 +1,382 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.lapp

    ++

    Simple command-line parsing using human-readable specification.

    ++

    Supports GNU-style parameters.

    ++ ++ ++
    ++lapp = require 'pl.lapp'
    ++local args = lapp [[
    ++Does some calculations
    ++  -o,--offset (default 0.0)  Offset to add to scaled number
    ++  -s,--scale  (number)  Scaling factor
    ++  <number> (number) Number to be scaled
    ++]]
    ++
    ++print(args.offset + args.scale * args.number)
    ++
    ++ ++

    Lines beginning with '-' are flags; there may be a short and a long name; ++ lines beginning with '<var>' are arguments. Anything in parens after ++ the flag/argument is either a default, a type name or a range constraint.

    ++ ++

    See the Guide

    ++ ++

    Dependencies: pl.sip

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    quit (msg, no_usage)quit this script immediately.
    error (msg, no_usage)print an error to stderr and quit.
    open (file[, opt])open a file.
    assert (condn, msg)quit if the condition is false.
    add_type (name, converter[, constraint])add a new type to Lapp.
    process_options_string (str, args)process a Lapp options string.
    ++

    Fields

    ++ ++ ++ ++ ++ ++
    show_usage_errorcontrols whether to dump usage on error.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ quit (msg, no_usage) ++
    ++
    ++ quit this script immediately. ++ ++ ++

    Parameters:

    ++
      ++
    • msg ++ string ++ optional message ++
    • ++
    • no_usage ++ boolean ++ suppress 'usage' display ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ error (msg, no_usage) ++
    ++
    ++ print an error to stderr and quit. ++ ++ ++

    Parameters:

    ++
      ++
    • msg ++ string ++ a message ++
    • ++
    • no_usage ++ boolean ++ suppress 'usage' display ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ open (file[, opt]) ++
    ++
    ++ open a file. ++ This will quit on error, and keep a list of file objects for later cleanup. ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ string ++ filename ++
    • ++
    • opt ++ string ++ same as second parameter of io.open ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ assert (condn, msg) ++
    ++
    ++ quit if the condition is false. ++ ++ ++

    Parameters:

    ++
      ++
    • condn ++ boolean ++ a condition ++
    • ++
    • msg ++ string ++ message text ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ add_type (name, converter[, constraint]) ++
    ++
    ++ add a new type to Lapp. These appear in parens after the value like ++ a range constraint, e.g. ' (integer) Process PID' ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ name of type ++
    • ++
    • converter ++ either a function to convert values, or a Lua type name. ++
    • ++
    • constraint ++ function ++ optional function to verify values, should use lapp.error ++ if failed. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ process_options_string (str, args) ++
    ++
    ++ process a Lapp options string. ++ Usually called as lapp(). ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ the options text ++
    • ++
    • args ++ {string} ++ a table of arguments (default is _G.arg) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table with parameter-value pairs ++
    ++ ++ ++ ++ ++
    ++
    ++

    Fields

    ++ ++
    ++
    ++ ++ show_usage_error ++
    ++
    ++ controls whether to dump usage on error. ++ Defaults to true ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.lexer.html b/extra/penlight/docs/libraries/pl.lexer.html +new file mode 100644 +index 0000000..dc3102a +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.lexer.html +@@ -0,0 +1,524 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.lexer

    ++

    Lexical scanner for creating a sequence of tokens from text.

    ++

    lexer.scan(s) returns an iterator over all tokens found in the ++ string s. This iterator returns two values, a token type string ++ (such as 'string' for quoted string, 'iden' for identifier) and the value of the ++ token.

    ++ ++

    Versions specialized for Lua and C are available; these also handle block comments ++ and classify keywords as 'keyword' tokens. For example:

    ++ ++ ++
    ++> s = 'for i=1,n do'
    ++> for t,v in lexer.lua(s)  do print(t,v) end
    ++keyword for
    ++iden    i
    ++=       =
    ++number  1
    ++,       ,
    ++iden    n
    ++keyword do
    ++
    ++ ++

    See the Guide for further discussion

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    scan (s, matches[, filter[, options]])create a plain token iterator from a string or file-like object.
    insert (tok, a1, a2)insert tokens into a stream.
    getline (tok)get everything in a stream upto a newline.
    lineno (tok)get current line number.
    getrest (tok)get the rest of the stream.
    get_keywords ()get the Lua keywords as a set-like table.
    lua (s[, filter[, options]])create a Lua token iterator from a string or file-like object.
    cpp (s[, filter[, options]])create a C/C++ token iterator from a string or file-like object.
    get_separated_list (tok[, endtoken=')'[, delim=']])get a list of parameters separated by a delimiter from a stream.
    skipws (tok)get the next non-space token from the stream.
    expecting (tok, expected_type, no_skip_ws)get the next token, which must be of the expected type.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ scan (s, matches[, filter[, options]]) ++
    ++
    ++ create a plain token iterator from a string or file-like object. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string or file ++ a string or a file-like object with :read() method returning lines. ++
    • ++
    • matches ++ table ++ an optional match table - array of token descriptions. ++ A token is described by a {pattern, action} pair, where pattern should match ++ token body and action is a function called when a token of described type is found. ++
    • ++
    • filter ++ table ++ a table of token types to exclude, by default {space=true} ++ (optional) ++
    • ++
    • options ++ table ++ a table of options; by default, {number=true,string=true}, ++ which means convert numbers and strip string quotes. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ insert (tok, a1, a2) ++
    ++
    ++ insert tokens into a stream. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ a token stream ++
    • ++
    • a1 ++ a string is the type, a table is a token list and ++ a function is assumed to be a token-like iterator (returns type & value) ++
    • ++
    • a2 ++ string ++ a string is the value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ getline (tok) ++
    ++
    ++ get everything in a stream upto a newline. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ a token stream ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ lineno (tok) ++
    ++
    ++ get current line number. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ a token stream ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the line number. ++ if the input source is a file-like object, ++ also return the column. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ getrest (tok) ++
    ++
    ++ get the rest of the stream. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ a token stream ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ get_keywords () ++
    ++
    ++ get the Lua keywords as a set-like table. ++ So res["and"] etc would be true. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ a table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ lua (s[, filter[, options]]) ++
    ++
    ++ create a Lua token iterator from a string or file-like object. ++ Will return the token type and value. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • filter ++ table ++ a table of token types to exclude, by default {space=true,comments=true} ++ (optional) ++
    • ++
    • options ++ table ++ a table of options; by default, {number=true,string=true}, ++ which means convert numbers and strip string quotes. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ cpp (s[, filter[, options]]) ++
    ++
    ++ create a C/C++ token iterator from a string or file-like object. ++ Will return the token type type and value. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • filter ++ table ++ a table of token types to exclude, by default {space=true,comments=true} ++ (optional) ++
    • ++
    • options ++ table ++ a table of options; by default, {number=true,string=true}, ++ which means convert numbers and strip string quotes. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ get_separated_list (tok[, endtoken=')'[, delim=']]) ++
    ++
    ++ get a list of parameters separated by a delimiter from a stream. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ the token stream ++
    • ++
    • endtoken ++ string ++ end of list. Can be '\n' ++ (default ')') ++
    • ++
    • delim ++ string ++ separator ++ (default ') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list of token lists. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ skipws (tok) ++
    ++
    ++ get the next non-space token from the stream. ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ the token stream. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ expecting (tok, expected_type, no_skip_ws) ++
    ++
    ++ get the next token, which must be of the expected type. ++ Throws an error if this type does not match! ++ ++ ++

    Parameters:

    ++
      ++
    • tok ++ the token stream ++
    • ++
    • expected_type ++ string ++ the token type ++
    • ++
    • no_skip_ws ++ boolean ++ whether we should skip whitespace ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.luabalanced.html b/extra/penlight/docs/libraries/pl.luabalanced.html +new file mode 100644 +index 0000000..ab475c9 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.luabalanced.html +@@ -0,0 +1,149 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.luabalanced

    ++

    Extract delimited Lua sequences from strings.

    ++

    Inspired by Damian Conway's Text::Balanced in Perl.
    ++

      ++
    • [1] Lua Wiki Page
    • ++
    • [2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm
    • ++

    ++
    ++ local lb = require "pl.luabalanced"
    ++ --Extract Lua expression starting at position 4.
    ++  print(lb.match_expression("if x^2 + x > 5 then print(x) end", 4))
    ++  --> x^2 + x > 5     16
    ++ --Extract Lua string starting at (default) position 1.
    ++ print(lb.match_string([["test\"123" .. "more"]]))
    ++ --> "test\"123"     12
    ++ 
    ++ (c) 2008, David Manura, Licensed under the same terms as Lua (MIT license).

    ++ ++ ++ ++
    ++
    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.operator.html b/extra/penlight/docs/libraries/pl.operator.html +new file mode 100644 +index 0000000..958678e +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.operator.html +@@ -0,0 +1,819 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.operator

    ++

    Lua operators available as functions.

    ++

    (similar to the Python module of the same name)

    ++ ++

    There is a module field optable which maps the operator strings ++ onto these functions, e.g. operator.optable['()']==operator.call

    ++ ++

    Operator strings like '>' and '{}' can be passed to most Penlight functions ++ expecting a function argument.

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    call (fn, ...)apply function to some arguments ()
    index (t, k)get the indexed value from a table []
    eq (a, b)returns true if arguments are equal ==
    neq (a, b)returns true if arguments are not equal ~=
    lt (a, b)returns true if a is less than b <
    le (a, b)returns true if a is less or equal to b <=
    gt (a, b)returns true if a is greater than b >
    ge (a, b)returns true if a is greater or equal to b >=
    len (a)returns length of string or table #
    add (a, b)add two values +
    sub (a, b)subtract b from a -
    mul (a, b)multiply two values *
    div (a, b)divide first value by second /
    pow (a, b)raise first to the power of second ^
    mod (a, b)modulo; remainder of a divided by b %
    concat (a, b)concatenate two values (either strings or __concat defined) ..
    unm (a)return the negative of a value -
    lnot (a)false if value evaluates as true not
    land (a, b)true if both values evaluate as true and
    lor (a, b)true if either value evaluate as true or
    table (...)make a table from the arguments {}
    match (a, b)match two strings ~.
    nop (...)the null operation.
    ++

    Tables

    ++ ++ ++ ++ ++ ++
    optableMap from operator symbol to function.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ call (fn, ...) ++
    ++
    ++ apply function to some arguments () ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ a function or callable object ++
    • ++
    • ... ++ arguments ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ index (t, k) ++
    ++
    ++ get the indexed value from a table [] ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a table or any indexable object ++
    • ++
    • k ++ the key ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ eq (a, b) ++
    ++
    ++ returns true if arguments are equal == ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ neq (a, b) ++
    ++
    ++ returns true if arguments are not equal ~= ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ lt (a, b) ++
    ++
    ++ returns true if a is less than b < ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ le (a, b) ++
    ++
    ++ returns true if a is less or equal to b <= ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ gt (a, b) ++
    ++
    ++ returns true if a is greater than b > ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ge (a, b) ++
    ++
    ++ returns true if a is greater or equal to b >= ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ len (a) ++
    ++
    ++ returns length of string or table # ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ a string or a table ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ add (a, b) ++
    ++
    ++ add two values + ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ sub (a, b) ++
    ++
    ++ subtract b from a - ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ mul (a, b) ++
    ++
    ++ multiply two values * ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ div (a, b) ++
    ++
    ++ divide first value by second / ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pow (a, b) ++
    ++
    ++ raise first to the power of second ^ ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ mod (a, b) ++
    ++
    ++ modulo; remainder of a divided by b % ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ concat (a, b) ++
    ++
    ++ concatenate two values (either strings or __concat defined) .. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ unm (a) ++
    ++
    ++ return the negative of a value - ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ lnot (a) ++
    ++
    ++ false if value evaluates as true not ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ land (a, b) ++
    ++
    ++ true if both values evaluate as true and ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ lor (a, b) ++
    ++
    ++ true if either value evaluate as true or ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ value ++
    • ++
    • b ++ value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ table (...) ++
    ++
    ++ make a table from the arguments {} ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ non-nil arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ match (a, b) ++
    ++
    ++ match two strings ~. ++ uses string.find ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ ++ ++ ++
    • ++
    • b ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ nop (...) ++
    ++
    ++ the null operation. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the arguments ++
    ++ ++ ++ ++ ++
    ++
    ++

    Tables

    ++ ++
    ++
    ++ ++ optable ++
    ++
    ++ ++

    Map from operator symbol to function. ++ Most of these map directly from operators; ++ But note these extras

    ++ ++ ++ ++ ++ ++ ++

    Fields:

    ++
      ++
    • operator ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.path.html b/extra/penlight/docs/libraries/pl.path.html +new file mode 100644 +index 0000000..74fea5e +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.path.html +@@ -0,0 +1,1087 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.path

    ++

    Path manipulation and file queries.

    ++

    This is modelled after Python's os.path library (10.1); see the Guide.

    ++ ++

    NOTE: the functions assume the paths being dealt with to originate ++ from the OS the application is running on. Windows drive letters are not ++ to be used when running on a Unix system for example. The one exception ++ is Windows paths to allow both forward and backward slashes (since Lua ++ also accepts those)

    ++ ++

    Dependencies: pl.utils, lfs

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    dir ()Lua iterator over the entries of a given directory.
    mkdir ()Creates a directory.
    rmdir ()Removes a directory.
    attrib ()Gets attributes.
    currentdir ()Get the working directory.
    link_attrib ()Gets symlink attributes.
    chdir ()Changes the working directory.
    isdir (P)is this a directory?
    isfile (P)is this a file?
    getsize (P)return size of a file.
    exists (P)does a path exist?
    getatime (P)Return the time of last access as the number of seconds since the epoch.
    getmtime (P)Return the time of last modification as the number of seconds since the epoch.
    getctime (P)Return the system's ctime as the number of seconds since the epoch.
    splitpath (P)given a path, return the directory part and a file part.
    abspath (P[, pwd])return an absolute path.
    splitext (P)given a path, return the root part and the extension part.
    dirname (P)return the directory part of a path
    basename (P)return the file part of a path
    extension (P)get the extension part of a path.
    isabs (P)is this an absolute path?
    join (p1, p2, ...)return the path resulting from combining the individual paths.
    normcase (P)normalize the case of a pathname.
    normpath (P)normalize a path name.
    relpath (P[, start])relative path from current directory or optional start point
    expanduser (P)Replace a starting '~' with the user's home directory.
    tmpname ()Return a suitable full path to a new temporary file name.
    common_prefix (path1, path2)return the largest common prefix path of two paths.
    package_path (mod)return the full path where a particular Lua module would be found.
    ++

    Fields

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    is_windowsare we running Windows?
    seppath separator for this platform.
    dirsepseparator for PATH for this platform
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ dir () ++
    ++
    ++ Lua iterator over the entries of a given directory. ++ Implicit link to luafilesystem.dir ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ mkdir () ++
    ++
    ++ Creates a directory. ++ Implicit link to luafilesystem.mkdir ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ rmdir () ++
    ++
    ++ Removes a directory. ++ Implicit link to luafilesystem.rmdir ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ attrib () ++
    ++
    ++ Gets attributes. ++ Implicit link to luafilesystem.attributes ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ currentdir () ++
    ++
    ++ Get the working directory. ++ Implicit link to luafilesystem.currentdir ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ link_attrib () ++
    ++
    ++ Gets symlink attributes. ++ Implicit link to luafilesystem.symlinkattributes ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ chdir () ++
    ++
    ++ Changes the working directory. ++ On Windows, if a drive is specified, it also changes the current drive. If ++ only specifying the drive, it will only switch drive, but not modify the path. ++ Implicit link to luafilesystem.chdir ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isdir (P) ++
    ++
    ++ is this a directory? ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isfile (P) ++
    ++
    ++ is this a file? ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ getsize (P) ++
    ++
    ++ return size of a file. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ exists (P) ++
    ++
    ++ does a path exist? ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the file path if it exists (either as file, directory, socket, etc), nil otherwise ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ getatime (P) ++
    ++
    ++ Return the time of last access as the number of seconds since the epoch. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ getmtime (P) ++
    ++
    ++ Return the time of last modification as the number of seconds since the epoch. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ getctime (P) ++
    ++
    ++ Return the system's ctime as the number of seconds since the epoch. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ splitpath (P) ++
    ++
    ++ given a path, return the directory part and a file part. ++ if there's no directory part, the first value will be empty ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ directory part
    2. ++
    3. ++ file part
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local dir, file = path.splitpath("some/dir/myfile.txt")
      ++assert(dir == "some/dir")
      ++assert(file == "myfile.txt")
      ++
      ++local dir, file = path.splitpath("some/dir/")
      ++assert(dir == "some/dir")
      ++assert(file == "")
      ++
      ++local dir, file = path.splitpath("some_dir")
      ++assert(dir == "")
      ++assert(file == "some_dir")
      ++
    ++ ++
    ++
    ++ ++ abspath (P[, pwd]) ++
    ++
    ++ return an absolute path. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    • pwd ++ string ++ optional start path to use (default is current dir) ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ splitext (P) ++
    ++
    ++ given a path, return the root part and the extension part. ++ if there's no extension part, the second value will be empty ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ string ++ root part (everything upto the "."", maybe empty)
    2. ++
    3. ++ string ++ extension part (including the ".", maybe empty)
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local file_path, ext = path.splitext("/bonzo/dog_stuff/cat.txt")
      ++assert(file_path == "/bonzo/dog_stuff/cat")
      ++assert(ext == ".txt")
      ++
      ++local file_path, ext = path.splitext("")
      ++assert(file_path == "")
      ++assert(ext == "")
      ++
    ++ ++
    ++
    ++ ++ dirname (P) ++
    ++
    ++ return the directory part of a path ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ everything before the last dir-separator ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      path.dirname("/some/path/file.txt")   -- "/some/path"
      ++path.dirname("file.txt")              -- "" (empty string)
      ++
    ++ ++
    ++
    ++ ++ basename (P) ++
    ++
    ++ return the file part of a path ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ ++ ++ ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      path.basename("/some/path/file.txt")  -- "file.txt"
      ++path.basename("/some/path/file/")     -- "" (empty string)
      ++
    ++ ++
    ++
    ++ ++ extension (P) ++
    ++
    ++ get the extension part of a path. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ ++ ++ ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      path.extension("/some/path/file.txt") -- ".txt"
      ++path.extension("/some/path/file_txt") -- "" (empty string)
      ++
    ++ ++
    ++
    ++ ++ isabs (P) ++
    ++
    ++ is this an absolute path? ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      path.isabs("hello/path")    -- false
      ++path.isabs("/hello/path")   -- true
      ++-- Windows;
      ++path.isabs("hello\path")    -- false
      ++path.isabs("\hello\path")   -- true
      ++path.isabs("C:\hello\path") -- true
      ++path.isabs("C:hello\path")  -- false
      ++
    ++ ++
    ++
    ++ ++ join (p1, p2, ...) ++
    ++
    ++ return the path resulting from combining the individual paths. ++ if the second (or later) path is absolute, we return the last absolute path (joined with any non-absolute paths following). ++ empty elements (except the last) will be ignored. ++ ++ ++

    Parameters:

    ++
      ++
    • p1 ++ string ++ A file path ++
    • ++
    • p2 ++ string ++ A file path ++
    • ++
    • ... ++ string ++ more file paths ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ the combined path ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      path.join("/first","second","third")   -- "/first/second/third"
      ++path.join("first","second/third")      -- "first/second/third"
      ++path.join("/first","/second","third")  -- "/second/third"
      ++
    ++ ++
    ++
    ++ ++ normcase (P) ++
    ++
    ++ ++

    normalize the case of a pathname. On Unix, this returns the path unchanged, ++ for Windows it converts;

    ++ ++
      ++
    • the path to lowercase
    • ++
    • forward slashes to backward slashes
    • ++
    ++ ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      path.normcase("/Some/Path/File.txt")
      ++-- Windows: "\some\path\file.txt"
      ++-- Others : "/Some/Path/File.txt"
      ++
    ++ ++
    ++
    ++ ++ normpath (P) ++
    ++
    ++ normalize a path name. ++ A//B, A/./B, and A/foo/../B all become A/B.

    ++ ++

    An empty path results in '.'. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ a file path ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ relpath (P[, start]) ++
    ++
    ++ relative path from current directory or optional start point ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ a path ++
    • ++
    • start ++ string ++ optional start point (default current directory) ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ expanduser (P) ++
    ++
    ++ Replace a starting '~' with the user's home directory. ++ In windows, if HOME isn't set, then USERPROFILE is used in preference to ++ HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. ++ ++ ++

    Parameters:

    ++
      ++
    • P ++ string ++ A file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string ++ The file path with the ~ prefix substituted, or the input path if it had no prefix. ++
    ++

    Or

    ++
      ++
    1. ++ nil ++ ++ ++
    2. ++
    3. ++ string ++ Error message if the environment variables were unavailable.
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ tmpname () ++
    ++
    ++ Return a suitable full path to a new temporary file name. ++ unlike os.tmpname(), it always gives you a writeable path (uses TEMP environment variable on Windows) ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ common_prefix (path1, path2) ++
    ++
    ++ return the largest common prefix path of two paths. ++ ++ ++

    Parameters:

    ++
      ++
    • path1 ++ string ++ a file path ++
    • ++
    • path2 ++ string ++ a file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the common prefix (Windows: separators will be normalized, casing will be original) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ package_path (mod) ++
    ++
    ++ return the full path where a particular Lua module would be found. ++ Both package.path and package.cpath is searched, so the result may ++ either be a Lua file or a shared library. ++ ++ ++

    Parameters:

    ++
      ++
    • mod ++ string ++ name of the module ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ on success: path of module, lua or binary
    2. ++
    3. ++ on error: nil, error string listing paths tried
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++

    Fields

    ++ ++
    ++
    ++ ++ is_windows ++
    ++
    ++ are we running Windows? ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ sep ++
    ++
    ++ path separator for this platform. ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ dirsep ++
    ++
    ++ separator for PATH for this platform ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.permute.html b/extra/penlight/docs/libraries/pl.permute.html +new file mode 100644 +index 0000000..4f6fb4b +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.permute.html +@@ -0,0 +1,354 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.permute

    ++

    Permutation operations.

    ++

    Dependencies: pl.utils, pl.tablex

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    order_iter (a)an iterator over all order-permutations of the elements of a list.
    order_table (a)construct a table containing all the order-permutations of a list.
    list_iter (...)an iterator over all permutations of the elements of the given lists.
    list_table (...)construct a table containing all the permutations of a set of lists.
    iter (...)deprecated.
    table (...)deprecated.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ order_iter (a) ++
    ++
    ++ an iterator over all order-permutations of the elements of a list. ++ Please note that the same list is returned each time, so do not keep references! ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ list-like table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator which provides the next permutation as a list ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ order_table (a) ++
    ++
    ++ construct a table containing all the order-permutations of a list. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ list-like table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table of tables ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      permute.order_table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}}
      ++
    ++ ++
    ++
    ++ ++ list_iter (...) ++
    ++
    ++ an iterator over all permutations of the elements of the given lists. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ list-like tables, they are nil-safe if a length-field n is provided (see utils.pack) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator which provides the next permutation as return values in the same order as the provided lists, preceded by an index ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local strs = utils.pack("one", nil, "three")  -- adds an 'n' field for nil-safety
      ++local bools = utils.pack(true, false)
      ++local iter = permute.list_iter(strs, bools)
      ++
      ++print(iter())    --> 1, one, true
      ++print(iter())    --> 2, nil, true
      ++print(iter())    --> 3, three, true
      ++print(iter())    --> 4, one, false
      ++print(iter())    --> 5, nil, false
      ++print(iter())    --> 6, three, false
      ++
    ++ ++
    ++
    ++ ++ list_table (...) ++
    ++
    ++ construct a table containing all the permutations of a set of lists. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ list-like tables, they are nil-safe if a length-field n is provided ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list of lists, the sub-lists have an 'n' field for nil-safety ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local strs = utils.pack("one", nil, "three")  -- adds an 'n' field for nil-safety
      ++local bools = utils.pack(true, false)
      ++local results = permute.list_table(strs, bools)
      ++-- results = {
      ++--   { "one, true, n = 2 }
      ++--   { nil, true, n = 2 },
      ++--   { "three, true, n = 2 },
      ++--   { "one, false, n = 2 },
      ++--   { nil, false, n = 2 },
      ++--   { "three", false, n = 2 },
      ++-- }
      ++
    ++ ++
    ++
    ++ ++ iter (...) ++
    ++
    ++ deprecated. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ ++ ++ ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ table (...) ++
    ++
    ++ deprecated. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ ++ ++ ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.pretty.html b/extra/penlight/docs/libraries/pl.pretty.html +new file mode 100644 +index 0000000..35a5fdd +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.pretty.html +@@ -0,0 +1,402 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.pretty

    ++

    Pretty-printing Lua tables.

    ++

    Also provides a sandboxed Lua table reader and ++ a function to present large numbers in human-friendly format.

    ++ ++

    Dependencies: pl.utils, pl.lexer, pl.stringx, debug

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    read (s)Read a string representation of a Lua table.
    load (s[, env[, paranoid]])Read a Lua chunk.
    write (tbl[, space[, not_clever]])Create a string representation of a Lua table.
    dump (t[, filename])Dump a Lua table out to a file or stdout.
    debug (...)Dump a series of arguments to stdout for debug purposes.
    number (num[, kind[, prec]])Format large numbers nicely for human consumption.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ read (s) ++
    ++
    ++ Read a string representation of a Lua table. ++ This function loads and runs the string as Lua code, but bails out ++ if it contains a function definition. ++ Loaded string is executed in an empty environment. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ string to read in {...} format, possibly with some whitespace ++ before or after the curly braces. A single line comment may be present ++ at the beginning. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table in case of success. ++ If loading the string failed, return nil and error message. ++ If executing loaded string failed, return nil and the error it raised. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ load (s[, env[, paranoid]]) ++
    ++
    ++ Read a Lua chunk. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ Lua code. ++
    • ++
    • env ++ table ++ environment used to run the code, empty by default. ++ (optional) ++
    • ++
    • paranoid ++ boolean ++ abort loading if any looping constructs a found in the code ++ and disable string methods. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the environment in case of success or nil and syntax or runtime error ++ if something went wrong. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ write (tbl[, space[, not_clever]]) ++
    ++
    ++ Create a string representation of a Lua table. ++ This function never fails, but may complain by returning an ++ extra value. Normally puts out one item per line, using ++ the provided indent; set the second parameter to an empty string ++ if you want output on one line.

    ++ ++

    NOTE: this is NOT a serialization function, not a full blown ++ debug function. Checkout out respectively the ++ serpent ++ or inspect ++ Lua modules for that if you need them. ++ ++ ++

    Parameters:

    ++
      ++
    • tbl ++ table ++ Table to serialize to a string. ++
    • ++
    • space ++ string ++ The indent to use. ++ Defaults to two spaces; pass an empty string for no indentation. ++ (optional) ++
    • ++
    • not_clever ++ boolean ++ Pass true for plain output, e.g {['key']=1}. ++ Defaults to false. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a string
    2. ++
    3. ++ an optional error message
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ dump (t[, filename]) ++
    ++
    ++ Dump a Lua table out to a file or stdout. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ The table to write to a file or stdout. ++
    • ++
    • filename ++ string ++ File name to write too. Defaults to writing ++ to stdout. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ debug (...) ++
    ++
    ++ Dump a series of arguments to stdout for debug purposes. ++ This function is attached to the module table __call method, to make it ++ extra easy to access. So the full:

    ++ ++
     print(require("pl.pretty").write({...}))
    ++
    ++ ++

    Can be shortened to:

    ++ ++
     require"pl.pretty" (...)
    ++
    ++ ++

    Any nil entries will be printed as "<nil>" to make them explicit. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ the parameters to dump to stdout. ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      -- example debug output
      ++require"pl.pretty" ("hello", nil, "world", { bye = "world", true} )
      ++
      ++-- output:
      ++{
      ++  ["arg 1"] = "hello",
      ++  ["arg 2"] = "<nil>",
      ++  ["arg 3"] = "world",
      ++  ["arg 4"] = {
      ++    true,
      ++    bye = "world"
      ++  }
      ++}
      ++
    ++ ++
    ++
    ++ ++ number (num[, kind[, prec]]) ++
    ++
    ++ Format large numbers nicely for human consumption. ++ ++ ++

    Parameters:

    ++
      ++
    • num ++ number ++ a number. ++
    • ++
    • kind ++ string ++ one of 'M' (memory in KiB, MiB, etc.), ++ 'N' (postfixes are 'K', 'M' and 'B'), ++ or 'T' (use commas as thousands separator), 'N' by default. ++ (optional) ++
    • ++
    • prec ++ integer ++ number of digits to use for 'M' and 'N', 1 by default. ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.seq.html b/extra/penlight/docs/libraries/pl.seq.html +new file mode 100644 +index 0000000..48e8a2c +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.seq.html +@@ -0,0 +1,888 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.seq

    ++

    Manipulating iterators as sequences.

    ++

    See The Guide

    ++ ++

    Dependencies: pl.utils, pl.types, debug

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    matching (s)given a string, return a function(y) which matches y against the string.
    list (t)sequence adaptor for a table.
    keys (t)return the keys of the table.
    range (start, finish)create an iterator over a numerical range.
    minmax (iter)return the minimum and the maximum value of the sequence.
    sum (iter, fn)return the sum and element count of the sequence.
    copy (iter)create a table from the sequence.
    copy2 (iter, i1, i2)create a table of pairs from the double-valued sequence.
    copy_tuples (iter)create a table of 'tuples' from a multi-valued sequence.
    random (n, l, u)return an iterator of random numbers.
    sort (iter, comp)return an iterator to the sorted elements of a sequence.
    zip (iter1, iter2)return an iterator which returns elements of two sequences.
    count_map (iter)Makes a table where the key/values are the values and value counts of the sequence.
    printall (iter, sep, nfields, fmt)print out a sequence iter with a separator.
    map (fn, iter, arg)return a sequence where every element of a sequence has been transformed ++ by a function.
    filter (iter, pred, arg)filter a sequence using a predicate function.
    reduce (fn, iter, initval)'reduce' a sequence using a binary function.
    take (iter, n)take the first n values from the sequence.
    skip (iter, n)skip the first n values of a sequence
    enum (iter)a sequence with a sequence count and the original value.
    mapmethod (iter, name, arg1, arg2)map using a named method over a sequence.
    last (iter)a sequence of (last,current) values from another sequence.
    foreach (iter, fn)call the function on each element of the sequence.
    lines (f, ...)create a wrapped iterator over all lines in the file.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ matching (s) ++
    ++
    ++ given a string, return a function(y) which matches y against the string. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ list (t) ++
    ++
    ++ sequence adaptor for a table. Note that if any generic function is ++ passed a table, it will automatically use seq.list() ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a list-like table ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • sum(list(t)) is the sum of all elements of t
    • ++
    • for x in list(t) do...end
    • ++
    ++ ++
    ++
    ++ ++ keys (t) ++
    ++
    ++ return the keys of the table. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ an arbitrary table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ iterator over keys ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ range (start, finish) ++
    ++
    ++ create an iterator over a numerical range. Like the standard Python function xrange. ++ ++ ++

    Parameters:

    ++
      ++
    • start ++ a number ++
    • ++
    • finish ++ a number greater than start ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ minmax (iter) ++
    ++
    ++ return the minimum and the maximum value of the sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ minimum value
    2. ++
    3. ++ maximum value
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ sum (iter, fn) ++
    ++
    ++ return the sum and element count of the sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    • fn ++ an optional function to apply to the values ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ copy (iter) ++
    ++
    ++ create a table from the sequence. (This will make the result a List.) ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a List ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • copy(list(ls)) is equal to ls
    • ++
    • copy(list {1,2,3}) == List{1,2,3}
    • ++
    ++ ++
    ++
    ++ ++ copy2 (iter, i1, i2) ++
    ++
    ++ create a table of pairs from the double-valued sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a double-valued sequence ++
    • ++
    • i1 ++ used to capture extra iterator values ++
    • ++
    • i2 ++ as with pairs & ipairs ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list-like table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      copy2(ipairs{10,20,30}) == {{1,10},{2,20},{3,30}}
      ++
    ++ ++
    ++
    ++ ++ copy_tuples (iter) ++
    ++
    ++ create a table of 'tuples' from a multi-valued sequence. ++ A generalization of copy2 above ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a multiple-valued sequence ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list-like table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ random (n, l, u) ++
    ++
    ++ return an iterator of random numbers. ++ ++ ++

    Parameters:

    ++
      ++
    • n ++ the length of the sequence ++
    • ++
    • l ++ same as the first optional argument to math.random ++
    • ++
    • u ++ same as the second optional argument to math.random ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a sequence ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ sort (iter, comp) ++
    ++
    ++ return an iterator to the sorted elements of a sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    • comp ++ an optional comparison function (comp(x,y) is true if x < y) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ zip (iter1, iter2) ++
    ++
    ++ return an iterator which returns elements of two sequences. ++ ++ ++

    Parameters:

    ++
      ++
    • iter1 ++ a sequence ++
    • ++
    • iter2 ++ a sequence ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      for x,y in seq.zip(ls1,ls2) do....end
      ++
    ++ ++
    ++
    ++ ++ count_map (iter) ++
    ++
    ++ Makes a table where the key/values are the values and value counts of the sequence. ++ This version works with 'hashable' values like strings and numbers. ++ pl.tablex.count_map is more general. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a map-like table
    2. ++
    3. ++ a table
    4. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ printall (iter, sep, nfields, fmt) ++
    ++
    ++ print out a sequence iter with a separator. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    • sep ++ the separator (default space) ++
    • ++
    • nfields ++ maximum number of values per line (default 7) ++
    • ++
    • fmt ++ optional format function for each value ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ map (fn, iter, arg) ++
    ++
    ++ return a sequence where every element of a sequence has been transformed ++ by a function. If you don't supply an argument, then the function will ++ receive both values of a double-valued sequence, otherwise behaves rather like ++ tablex.map. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ a function to apply to elements; may take two arguments ++
    • ++
    • iter ++ a sequence of one or two values ++
    • ++
    • arg ++ optional argument to pass to function. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ filter (iter, pred, arg) ++
    ++
    ++ filter a sequence using a predicate function. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence of one or two values ++
    • ++
    • pred ++ a boolean function; may take two arguments ++
    • ++
    • arg ++ optional argument to pass to function. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ reduce (fn, iter, initval) ++
    ++
    ++ 'reduce' a sequence using a binary function. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ function ++ a function of two arguments ++
    • ++
    • iter ++ a sequence ++
    • ++
    • initval ++ optional initial value ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • seq.reduce(operator.add,seq.list{1,2,3,4}) == 10
    • ++
    • seq.reduce('-',{1,2,3,4,5}) == -13
    • ++
    ++ ++
    ++
    ++ ++ take (iter, n) ++
    ++
    ++ take the first n values from the sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence of one or two values ++
    • ++
    • n ++ number of items to take ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a sequence of at most n items ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ skip (iter, n) ++
    ++
    ++ skip the first n values of a sequence ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence of one or more values ++
    • ++
    • n ++ number of items to skip ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ enum (iter) ++
    ++
    ++ a sequence with a sequence count and the original value. ++ enum(copy(ls)) is a roundabout way of saying ipairs(ls). ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a single or double valued sequence ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ sequence of (i,v), i = 1..n and v is from iter. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ mapmethod (iter, name, arg1, arg2) ++
    ++
    ++ map using a named method over a sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    • name ++ the method name ++
    • ++
    • arg1 ++ optional first extra argument ++
    • ++
    • arg2 ++ optional second extra argument ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ last (iter) ++
    ++
    ++ a sequence of (last,current) values from another sequence. ++ This will return S(i-1),S(i) if given S(i) ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ foreach (iter, fn) ++
    ++
    ++ call the function on each element of the sequence. ++ ++ ++

    Parameters:

    ++
      ++
    • iter ++ a sequence with up to 3 values ++
    • ++
    • fn ++ a function ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ lines (f, ...) ++
    ++
    ++ create a wrapped iterator over all lines in the file. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ either a filename, file-like object, or 'STDIN' (for standard input) ++
    • ++
    • ... ++ for Lua 5.2 only, optional format specifiers, as in io.read. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a sequence wrapper ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.sip.html b/extra/penlight/docs/libraries/pl.sip.html +new file mode 100644 +index 0000000..c1812ea +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.sip.html +@@ -0,0 +1,399 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.sip

    ++

    Simple Input Patterns (SIP).

    ++

    SIP patterns start with '$', then a ++ one-letter type, and then an optional variable in curly braces.

    ++ ++ ++
    ++sip.match('$v=$q','name="dolly"',res)
    ++==> res=={'name','dolly'}
    ++sip.match('($q{first},$q{second})','("john","smith")',res)
    ++==> res=={second='smith',first='john'}
    ++
    ++ ++

    Type names:

    ++ ++ ++
    ++v     identifier
    ++i     integer
    ++f     floating-point
    ++q     quoted string
    ++([{<  match up to closing bracket
    ++
    ++ ++

    See the Guide

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    create_pattern (spec, options)convert a SIP pattern into the equivalent Lua string pattern.
    compile (spec, options)convert a SIP pattern into a matching function.
    match (spec, line, res, options)match a SIP pattern against a string.
    match_at_start (spec, line, res)match a SIP pattern against the start of a string.
    fields (spec, f)given a pattern and a file object, return an iterator over the results
    pattern (spec, fun)register a match which will be used in the read function.
    read (f, matches)enter a loop which applies all registered matches to the input file.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ create_pattern (spec, options) ++
    ++
    ++ convert a SIP pattern into the equivalent Lua string pattern. ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ a SIP pattern ++
    • ++
    • options ++ a table; only the at_start field is ++ currently meaningful and ensures that the pattern is anchored ++ at the start of the string. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a Lua string pattern. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ compile (spec, options) ++
    ++
    ++ convert a SIP pattern into a matching function. ++ The returned function takes two arguments, the line and an empty table. ++ If the line matched the pattern, then this function returns true ++ and the table is filled with field-value pairs. ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ a SIP pattern ++
    • ++
    • options ++ optional table; {at_start=true} ensures that the pattern ++ is anchored at the start of the string. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function if successful, or nil,error ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ match (spec, line, res, options) ++
    ++
    ++ match a SIP pattern against a string. ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ a SIP pattern ++
    • ++
    • line ++ a string ++
    • ++
    • res ++ a table to receive values ++
    • ++
    • options ++ (optional) option table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ match_at_start (spec, line, res) ++
    ++
    ++ match a SIP pattern against the start of a string. ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ a SIP pattern ++
    • ++
    • line ++ a string ++
    • ++
    • res ++ a table to receive values ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ fields (spec, f) ++
    ++
    ++ given a pattern and a file object, return an iterator over the results ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ a SIP pattern ++
    • ++
    • f ++ a file-like object. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ pattern (spec, fun) ++
    ++
    ++ register a match which will be used in the read function. ++ ++ ++

    Parameters:

    ++
      ++
    • spec ++ string ++ a SIP pattern ++
    • ++
    • fun ++ function ++ a function to be called with the results of the match ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ read (f, matches) ++
    ++
    ++ enter a loop which applies all registered matches to the input file. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ a file-like object ++
    • ++
    • matches ++ array ++ optional list of {spec,fun} pairs, as for pattern above. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.strict.html b/extra/penlight/docs/libraries/pl.strict.html +new file mode 100644 +index 0000000..33a2c11 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.strict.html +@@ -0,0 +1,270 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.strict

    ++

    Checks uses of undeclared global variables.

    ++

    All global variables must be 'declared' through a regular assignment ++ (even assigning nil will do) in a main chunk before being used ++ anywhere or assigned to inside a function. Existing metatables __newindex and __index ++ metamethods are respected.

    ++ ++

    You can set any table to have strict behaviour using strict.module. Creating a new ++ module with strict.closed_module makes the module immune to monkey-patching, if ++ you don't wish to encourage monkey business.

    ++ ++

    If the global PENLIGHT_NO_GLOBAL_STRICT is defined, then this module won't make the ++ global environment strict - if you just want to explicitly set table strictness.

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    module ([name[, mod[, predeclared]]])make an existing table strict.
    make_all_strict (T)make all tables in a table strict.
    closed_module (mod, name)make a new module table which is closed to further changes.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ module ([name[, mod[, predeclared]]]) ++
    ++
    ++ make an existing table strict. ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ name of table ++ (optional) ++
    • ++
    • mod ++ table ++ the table to protect - if nil then we'll return a new table ++ (optional) ++
    • ++
    • predeclared ++ table ++ ++
        ++
      • table of variables that are to be considered predeclared.
      • ++
      ++ ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the given table, or a new table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local M = { hello = "world" }
      ++strict.module ("Awesome_Module", M, {
      ++  Lua = true,  -- defines allowed keys
      ++})
      ++
      ++assert(M.hello == "world")
      ++assert(M.Lua == nil)       -- access allowed, but has no value yet
      ++M.Lua = "Rocks"
      ++assert(M.Lua == "Rocks")
      ++M.not_allowed = "bad boy"  -- throws an error
      ++
    ++ ++
    ++
    ++ ++ make_all_strict (T) ++
    ++
    ++ make all tables in a table strict. ++ So strict.make_all_strict(_G) prevents monkey-patching ++ of any global table ++ ++ ++

    Parameters:

    ++
      ++
    • T ++ table ++ the table containing the tables to protect. Table T itself will NOT be protected. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ closed_module (mod, name) ++
    ++
    ++ make a new module table which is closed to further changes. ++ ++ ++

    Parameters:

    ++
      ++
    • mod ++ table ++ module table ++
    • ++
    • name ++ string ++ module name ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.stringio.html b/extra/penlight/docs/libraries/pl.stringio.html +new file mode 100644 +index 0000000..84e3011 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.stringio.html +@@ -0,0 +1,215 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.stringio

    ++

    Reading and writing strings using file-like objects.

    ++


    ++ ++ ++
    ++f = stringio.open(text)
    ++l1 = f:read()  -- read first line
    ++n,m = f:read ('*n','*n') -- read two numbers
    ++for line in f:lines() do print(line) end -- iterate over all lines
    ++f = stringio.create()
    ++f:write('hello')
    ++f:write('dolly')
    ++assert(f:value(),'hellodolly')
    ++
    ++ ++

    See the Guide.

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    create ()create a file-like object which can be used to construct a string.
    open (s)create a file-like object for reading from a given string.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ create () ++
    ++
    ++ create a file-like object which can be used to construct a string. ++ The resulting object has an extra value() method for ++ retrieving the string value. Implements file:write, file:seek, file:lines, ++ plus an extra writef method which works like utils.printf. ++ ++ ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      f = create(); f:write('hello, dolly\n'); print(f:value())
      ++
    ++ ++
    ++
    ++ ++ open (s) ++
    ++
    ++ create a file-like object for reading from a given string. ++ Implements file:read. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ The input string. ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      fs = open '20 10'; x,y = f:read ('*n','*n'); assert(x == 20 and y == 10)
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.stringx.html b/extra/penlight/docs/libraries/pl.stringx.html +new file mode 100644 +index 0000000..b00666f +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.stringx.html +@@ -0,0 +1,1630 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.stringx

    ++

    Python-style extended string library.

    ++

    see 3.6.1 of the Python reference. ++ If you want to make these available as string methods, then say ++ stringx.import() to bring them into the standard string table.

    ++ ++

    See the Guide

    ++ ++

    Dependencies: pl.utils, pl.types

    ++ ++ ++

    String Predicates

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    isalpha (s)does s only contain alphabetic characters?
    isdigit (s)does s only contain digits?
    isalnum (s)does s only contain alphanumeric characters?
    isspace (s)does s only contain whitespace?
    islower (s)does s only contain lower case characters?
    isupper (s)does s only contain upper case characters?
    startswith (s, prefix)does s start with prefix or one of prefixes?
    endswith (s, suffix)does s end with suffix or one of suffixes?
    ++

    Strings and Lists

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    join (s, seq)concatenate the strings using this string as a delimiter.
    splitlines (s[, keep_ends])Split a string into a list of lines.
    split (s[, re[, n]])split a string into a list of strings using a delimiter.
    expandtabs (s, tabsize)replace all tabs in s with tabsize spaces.
    ++

    Finding and Replacing

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    lfind (s, sub[, first[, last]])find index of first instance of sub in s from the left.
    rfind (s, sub[, first[, last]])find index of first instance of sub in s from the right.
    replace (s, old, new[, n])replace up to n instances of old by new in the string s.
    count (s, sub[, allow_overlap])count all instances of substring in string.
    ++

    Stripping and Justifying

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ljust (s, w[, ch=' '])left-justify s with width w.
    rjust (s, w[, ch=' '])right-justify s with width w.
    center (s, w[, ch=' '])center-justify s with width w.
    lstrip (s[, chrs='%s'])trim any characters on the left of s.
    rstrip (s[, chrs='%s'])trim any characters on the right of s.
    strip (s[, chrs='%s'])trim any characters on both left and right of s.
    ++

    Partitioning Strings

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    splitv (s[, re='%s'])split a string using a pattern.
    partition (s, ch)partition the string using first occurrence of a delimiter
    rpartition (s, ch)partition the string p using last occurrence of a delimiter
    at (s, idx)return the 'character' at the index.
    ++

    Text handling

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    indent (s, n[, ch=' '])indent a multiline string.
    dedent (s)dedent a multiline string by removing any initial indent.
    wrap (s[, width=70[, breaklong=false]])format a paragraph into lines so that they fit into a line width.
    fill (s[, width=70[, breaklong=false]])format a paragraph so that it fits into a line width.
    ++

    Template

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Template (tmpl)Creates a new Template class.
    Template:substitute (tbl)substitute values into a template, throwing an error.
    Template:safe_substitute (tbl)substitute values into a template.
    Template:indent_substitute (tbl)substitute values into a template, preserving indentation.
    ++

    Miscellaneous

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    lines (s)return an iterator over all lines in a string
    title (s)initial word letters uppercase ('title case').
    shorten (s, w, tail)Return a shortened version of a string.
    quote_string (s)Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
    format_operator ()Python-style formatting operator.
    import ()import the stringx functions into the global string (meta)table
    ++ ++
    ++
    ++ ++ ++

    String Predicates

    ++ ++
    ++
    ++ ++ isalpha (s) ++
    ++
    ++ does s only contain alphabetic characters? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isdigit (s) ++
    ++
    ++ does s only contain digits? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isalnum (s) ++
    ++
    ++ does s only contain alphanumeric characters? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isspace (s) ++
    ++
    ++ does s only contain whitespace? ++ Matches on pattern '%s' so matches space, newline, tabs, etc. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ islower (s) ++
    ++
    ++ does s only contain lower case characters? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ isupper (s) ++
    ++
    ++ does s only contain upper case characters? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ startswith (s, prefix) ++
    ++
    ++ does s start with prefix or one of prefixes? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    • prefix ++ a string or an array of strings ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ endswith (s, suffix) ++
    ++
    ++ does s end with suffix or one of suffixes? ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ a string ++
    • ++
    • suffix ++ a string or an array of strings ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Strings and Lists

    ++ ++
    ++
    ++ ++ join (s, seq) ++
    ++
    ++ concatenate the strings using this string as a delimiter. ++ Note that the arguments are reversed from string.concat. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • seq ++ a table of strings or numbers ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.join(' ', {1,2,3}) == '1 2 3'
      ++
    ++ ++
    ++
    ++ ++ splitlines (s[, keep_ends]) ++
    ++
    ++ Split a string into a list of lines. ++ "\r", "\n", and "\r\n" are considered line ends. ++ They are not included in the lines unless keepends is passed. ++ Terminal line end does not produce an extra line. ++ Splitting an empty string results in an empty list. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string. ++
    • ++
    • keep_ends ++ boolean ++ include line ends. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ List of lines ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ split (s[, re[, n]]) ++
    ++
    ++ split a string into a list of strings using a delimiter. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • re ++ string ++ a delimiter (defaults to whitespace) ++ (optional) ++
    • ++
    • n ++ integer ++ maximum number of results ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ List ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • #(stringx.split('one two')) == 2
    • ++
    • stringx.split('one,two,three', ',') == List{'one','two','three'}
    • ++
    • stringx.split('one,two,three', ',', 2) == List{'one','two,three'}
    • ++
    ++ ++
    ++
    ++ ++ expandtabs (s, tabsize) ++
    ++
    ++ replace all tabs in s with tabsize spaces. If not specified, tabsize defaults to 8. ++ Tab stops will be honored. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • tabsize ++ integer ++ [opt=8] number of spaces to expand each tab ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ expanded string ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • stringx.expandtabs('\tone,two,three', 4)   == '    one,two,three'
    • ++
    • stringx.expandtabs('  \tone,two,three', 4) == '    one,two,three'
    • ++
    ++ ++
    ++
    ++

    Finding and Replacing

    ++ ++
    ++
    ++ ++ lfind (s, sub[, first[, last]]) ++
    ++
    ++ find index of first instance of sub in s from the left. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • sub ++ string ++ substring ++
    • ++
    • first ++ integer ++ first index ++ (optional) ++
    • ++
    • last ++ integer ++ last index ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ start index, or nil if not found ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ rfind (s, sub[, first[, last]]) ++
    ++
    ++ find index of first instance of sub in s from the right. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • sub ++ string ++ substring ++
    • ++
    • first ++ integer ++ first index ++ (optional) ++
    • ++
    • last ++ integer ++ last index ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ start index, or nil if not found ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ replace (s, old, new[, n]) ++
    ++
    ++ replace up to n instances of old by new in the string s. ++ If n is not present, replace all instances. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • old ++ string ++ the target substring ++
    • ++
    • new ++ string ++ the substitution ++
    • ++
    • n ++ integer ++ optional maximum number of substitutions ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ result string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ count (s, sub[, allow_overlap]) ++
    ++
    ++ count all instances of substring in string. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • sub ++ string ++ substring ++
    • ++
    • allow_overlap ++ boolean ++ allow matches to overlap ++ (optional) ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      assert(stringx.count('banana', 'ana') == 1)
      ++assert(stringx.count('banana', 'ana', true) == 2)
      ++
    ++ ++
    ++
    ++

    Stripping and Justifying

    ++ ++
    ++
    ++ ++ ljust (s, w[, ch=' ']) ++
    ++
    ++ left-justify s with width w. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • w ++ integer ++ width of justification ++
    • ++
    • ch ++ string ++ padding character ++ (default ' ') ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.ljust('hello', 10, '*') == '*****hello'
      ++
    ++ ++
    ++
    ++ ++ rjust (s, w[, ch=' ']) ++
    ++
    ++ right-justify s with width w. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • w ++ integer ++ width of justification ++
    • ++
    • ch ++ string ++ padding character ++ (default ' ') ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.rjust('hello', 10, '*') == 'hello*****'
      ++
    ++ ++
    ++
    ++ ++ center (s, w[, ch=' ']) ++
    ++
    ++ center-justify s with width w. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • w ++ integer ++ width of justification ++
    • ++
    • ch ++ string ++ padding character ++ (default ' ') ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.center('hello', 10, '*') == '**hello***'
      ++
    ++ ++
    ++
    ++ ++ lstrip (s[, chrs='%s']) ++
    ++
    ++ trim any characters on the left of s. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • chrs ++ string ++ default any whitespace character, ++ but can be a string of characters to be trimmed ++ (default '%s') ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ rstrip (s[, chrs='%s']) ++
    ++
    ++ trim any characters on the right of s. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • chrs ++ string ++ default any whitespace character, ++ but can be a string of characters to be trimmed ++ (default '%s') ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ strip (s[, chrs='%s']) ++
    ++
    ++ trim any characters on both left and right of s. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • chrs ++ string ++ default any whitespace character, ++ but can be a string of characters to be trimmed ++ (default '%s') ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.strip('  --== Hello ==--  ', "- =")  --> 'Hello'
      ++
    ++ ++
    ++
    ++

    Partitioning Strings

    ++ ++
    ++
    ++ ++ splitv (s[, re='%s']) ++
    ++
    ++ split a string using a pattern. Note that at least one value will be returned! ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • re ++ string ++ a Lua string pattern (defaults to whitespace) ++ (default '%s') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the parts of the string ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      a,b = line:splitv('=')
      ++
    ++ ++
    ++
    ++ ++ partition (s, ch) ++
    ++
    ++ partition the string using first occurrence of a delimiter ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • ch ++ string ++ delimiter (match as plain string, no patterns) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ part before ch
    2. ++
    3. ++ ch
    4. ++
    5. ++ part after ch
    6. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • {stringx.partition('a,b,c', ','))} == {'a', ',', 'b,c'}
    • ++
    • {stringx.partition('abc', 'x'))} == {'abc', '', ''}
    • ++
    ++ ++
    ++
    ++ ++ rpartition (s, ch) ++
    ++
    ++ partition the string p using last occurrence of a delimiter ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • ch ++ string ++ delimiter (match as plain string, no patterns) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ part before ch
    2. ++
    3. ++ ch
    4. ++
    5. ++ part after ch
    6. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • {stringx.rpartition('a,b,c', ','))} == {'a,b', ',', 'c'}
    • ++
    • {stringx.rpartition('abc', 'x'))} == {'', '', 'abc'}
    • ++
    ++ ++
    ++
    ++ ++ at (s, idx) ++
    ++
    ++ return the 'character' at the index. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • idx ++ integer ++ an index (can be negative) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a substring of length 1 if successful, empty string otherwise. ++
    ++ ++ ++ ++ ++
    ++
    ++

    Text handling

    ++ ++
    ++
    ++ ++ indent (s, n[, ch=' ']) ++
    ++
    ++ indent a multiline string. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the (multiline) string ++
    • ++
    • n ++ integer ++ the size of the indent ++
    • ++
    • ch ++ string ++ the character to use when indenting ++ (default ' ') ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ indented string ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ dedent (s) ++
    ++
    ++ dedent a multiline string by removing any initial indent. ++ useful when working with [[..]] strings. ++ Empty lines are ignored. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the (multiline) string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string with initial indent zero. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local s = dedent [[
      ++         One
      ++
      ++       Two
      ++
      ++     Three
      ++]]
      ++assert(s == [[
      ++    One
      ++
      ++  Two
      ++
      ++Three
      ++]])
      ++
    ++ ++
    ++
    ++ ++ wrap (s[, width=70[, breaklong=false]]) ++
    ++
    ++ format a paragraph into lines so that they fit into a line width. ++ It will not break long words by default, so lines can be over the length ++ to that extent. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string to format ++
    • ++
    • width ++ integer ++ the margin width ++ (default 70) ++
    • ++
    • breaklong ++ boolean ++ if truthy, words longer than the width given will be forced split. ++ (default false) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list of lines (List object), use fill to return a string instead of a List. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ fill (s[, width=70[, breaklong=false]]) ++
    ++
    ++ format a paragraph so that it fits into a line width. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string to format ++
    • ++
    • width ++ integer ++ the margin width ++ (default 70) ++
    • ++
    • breaklong ++ boolean ++ if truthy, words longer than the width given will be forced split. ++ (default false) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string, use wrap to return a list of lines instead of a string. ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++

    Template

    ++ ++
    ++
    ++ ++ Template (tmpl) ++
    ++
    ++ Creates a new Template class. ++ This is a shortcut to Template.new(tmpl). ++ ++ ++

    Parameters:

    ++
      ++
    • tmpl ++ string ++ the template string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ Template ++ ++ ++ ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Template:substitute (tbl) ++
    ++
    ++ substitute values into a template, throwing an error. ++ This will throw an error if no name is found. ++ ++ ++

    Parameters:

    ++
      ++
    • tbl ++ table ++ a table of name-value pairs. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string with place holders substituted ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Template:safe_substitute (tbl) ++
    ++
    ++ substitute values into a template. ++ This version just passes unknown names through. ++ ++ ++

    Parameters:

    ++
      ++
    • tbl ++ table ++ a table of name-value pairs. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string with place holders substituted ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Template:indent_substitute (tbl) ++
    ++
    ++ substitute values into a template, preserving indentation.
    ++ If the value is a multiline string or a template, it will insert ++ the lines at the correct indentation.
    ++ Furthermore, if a template, then that template will be substituted ++ using the same table. ++ ++ ++

    Parameters:

    ++
      ++
    • tbl ++ table ++ a table of name-value pairs. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ string with place holders substituted ++
    ++ ++ ++ ++ ++
    ++
    ++

    Miscellaneous

    ++ ++
    ++
    ++ ++ lines (s) ++
    ++
    ++ return an iterator over all lines in a string ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local line_no = 1
      ++for line in stringx.lines(some_text) do
      ++  print(line_no, line)
      ++  line_no = line_no + 1
      ++end
      ++
    ++ ++
    ++
    ++ ++ title (s) ++
    ++
    ++ initial word letters uppercase ('title case'). ++ Here 'words' mean chunks of non-space characters. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string with each word's first letter uppercase ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      stringx.title("hello world") == "Hello World")
      ++
    ++ ++
    ++
    ++ ++ shorten (s, w, tail) ++
    ++
    ++ Return a shortened version of a string. ++ Fits string within w characters. Removed characters are marked with ellipsis. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • w ++ integer ++ the maximum size allowed ++
    • ++
    • tail ++ boolean ++ true if we want to show the end of the string (head otherwise) ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • ('1234567890'):shorten(8) == '12345...'
    • ++
    • ('1234567890'):shorten(8, true) == '...67890'
    • ++
    • ('1234567890'):shorten(20) == '1234567890'
    • ++
    ++ ++
    ++
    ++ ++ quote_string (s) ++
    ++
    ++ Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ The string to be quoted. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ The quoted string. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ format_operator () ++
    ++
    ++ Python-style formatting operator. ++ Calling text.format_operator() overloads the % operator for strings to give ++ Python/Ruby style formatted output. ++ This is extended to also do template-like substitution for map-like data.

    ++ ++

    Note this goes further than the original, and will allow these cases:

    ++ ++
      ++
    1. a single value
    2. ++
    3. a list of values
    4. ++
    5. a map of var=value pairs
    6. ++
    7. a function, as in gsub
    8. ++
    ++ ++

    For the second two cases, it uses $-variable substitution.

    ++ ++

    When called, this function will monkey-patch the global string metatable by ++ adding a __mod method.

    ++ ++

    See the lua-users wiki ++ ++ ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      require 'pl.text'.format_operator()
      ++local out1 = '%s = %5.3f' % {'PI',math.pi}                   --> 'PI = 3.142'
      ++local out2 = '$name = $value' % {name='dog',value='Pluto'}   --> 'dog = Pluto'
      ++
    ++ ++
    ++
    ++ ++ import () ++
    ++
    ++ import the stringx functions into the global string (meta)table ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.tablex.html b/extra/penlight/docs/libraries/pl.tablex.html +new file mode 100644 +index 0000000..48eea54 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.tablex.html +@@ -0,0 +1,1980 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.tablex

    ++

    Extended operations on Lua tables.

    ++

    See the Guide

    ++ ++

    Dependencies: pl.utils, pl.types

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    size (t)total number of elements in this table.
    index_by (tbl, idx)return a list of all values in a table indexed by another list.
    transform (fun, t, ...)apply a function to all values of a table, in-place.
    range (start, finish[, step=1])generate a table of all numbers in a range.
    reduce (fun, t, memo)'reduce' a list using a binary function.
    index_map (t)create an index map from a list-like table.
    makeset (t)create a set from a list-like table.
    union (t1, t2)the union of two map-like tables.
    intersection (t1, t2)the intersection of two map-like tables.
    count_map (t, cmp)A table where the key/values are the values and value counts of the table.
    set (t, val[, i1=1[, i2=#t]])set an array range to a value.
    new (n, val)create a new array of specified size with initial value.
    clear (t, istart)clear out the contents of a table.
    removevalues (t, i1, i2)remove a range of values from a table.
    readonly (t)modifies a table to be read only.
    ++

    Copying

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    update (t1, t2)copy a table into another, in-place.
    copy (t)make a shallow copy of a table
    deepcopy (t)make a deep copy of a table, recursively copying all the keys and fields.
    icopy (dest, src[, idest=1[, isrc=1[, nsrc=#src]]])copy an array into another one, clearing dest after idest+nsrc, if necessary.
    move (dest, src[, idest=1[, isrc=1[, nsrc=#src]]])copy an array into another one.
    insertvalues (t[, position], values)insert values into a table.
    ++

    Comparing

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    deepcompare (t1, t2[, ignore_mt[, eps]])compare two values.
    compare (t1, t2, cmp)compare two arrays using a predicate.
    compare_no_order (t1, t2, cmp)compare two list-like tables using an optional predicate, without regard for element order.
    ++

    Finding

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    find (t, val, idx)return the index of a value in a list.
    rfind (t, val, idx)return the index of a value in a list, searching from the end.
    find_if (t, cmp, arg)return the index (or key) of a value in a table using a comparison function.
    search (t, value[, exclude])find a value in a table by recursive search.
    ++

    MappingAndFiltering

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    map (fun, t, ...)apply a function to all values of a table.
    imap (fun, t, ...)apply a function to all values of a list.
    map_named_method (name, t, ...)apply a named method to values from a table.
    map2 (fun, t1, t2, ...)apply a function to values from two tables.
    imap2 (fun, t1, t2, ...)apply a function to values from two arrays.
    mapn (fun, ..., fun)Apply a function to a number of tables.
    pairmap (fun, t, ...)call the function with the key and value pairs from a table.
    filter (t, pred, arg)filter an array's values using a predicate function
    ++

    Iterating

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    foreach (t, fun, ...)apply a function to all elements of a table.
    foreachi (t, fun, ...)apply a function to all elements of a list-like table in order.
    sort (t, f)return an iterator to a table sorted by its keys
    sortv (t, f)return an iterator to a table sorted by its values
    ++

    Extraction

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    keys (t)return all the keys of a table in arbitrary order.
    values (t)return all the values of the table in arbitrary order
    sub (t, first, last)Extract a range from a table, like 'string.sub'.
    ++

    Merging

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    merge (t1, t2, dup)combine two tables, either as union or intersection.
    difference (s1, s2, symm)a new table which is the difference of two tables.
    zip (...)return a table where each element is a table of the ith values of an arbitrary ++ number of tables.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ size (t) ++
    ++
    ++ total number of elements in this table. ++ Note that this is distinct from #t, which is the number ++ of values in the array part; this value will always ++ be greater or equal. The difference gives the size of ++ the hash part, for practical purposes. Works for any ++ object with a __pairs metamethod. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ a table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the size ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ index_by (tbl, idx) ++
    ++
    ++ return a list of all values in a table indexed by another list. ++ ++ ++

    Parameters:

    ++
      ++
    • tbl ++ table ++ a table ++
    • ++
    • idx ++ array ++ an index table (a list of keys) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list-like table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • index_by({10,20,30,40},{2,4}) == {20,40}
    • ++
    • index_by({one=1,two=2,three=3},{'one','three'}) == {1,3}
    • ++
    ++ ++
    ++
    ++ ++ transform (fun, t, ...) ++
    ++
    ++ apply a function to all values of a table, in-place. ++ Any extra arguments are passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ A function that takes at least one argument ++
    • ++
    • t ++ table ++ a table ++
    • ++
    • ... ++ extra arguments passed to fun ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ range (start, finish[, step=1]) ++
    ++
    ++ generate a table of all numbers in a range. ++ This is consistent with a numerical for loop. ++ ++ ++

    Parameters:

    ++
      ++
    • start ++ integer ++ number ++
    • ++
    • finish ++ integer ++ number ++
    • ++
    • step ++ integer ++ make this negative for start < finish ++ (default 1) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ reduce (fun, t, memo) ++
    ++
    ++ 'reduce' a list using a binary function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of two arguments ++
    • ++
    • t ++ array ++ a list-like table ++
    • ++
    • memo ++ array ++ optional initial memo value. Defaults to first value in table. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the result of the function ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      reduce('+',{1,2,3,4}) == 10
      ++
    ++ ++
    ++
    ++ ++ index_map (t) ++
    ++
    ++ create an index map from a list-like table. The original values become keys, ++ and the associated values are the indices into the original list. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a map-like table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ makeset (t) ++
    ++
    ++ create a set from a list-like table. A set is a table where the original values ++ become keys, and the associated values are all true. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a set (a map-like table) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ union (t1, t2) ++
    ++
    ++ the union of two map-like tables. ++ If there are duplicate keys, the second table wins. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ table ++ a table ++
    • ++
    • t2 ++ table ++ a table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ tab ++ ++ ++ ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ intersection (t1, t2) ++
    ++
    ++ the intersection of two map-like tables. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ table ++ a table ++
    • ++
    • t2 ++ table ++ a table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ tab ++ ++ ++ ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ count_map (t, cmp) ++
    ++
    ++ A table where the key/values are the values and value counts of the table. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    • cmp ++ function ++ a function that defines equality (otherwise uses ==) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a map-like table ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ set (t, val[, i1=1[, i2=#t]]) ++
    ++
    ++ set an array range to a value. If it's a function we use the result ++ of applying it to the indices. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    • val ++ a value ++
    • ++
    • i1 ++ integer ++ start range ++ (default 1) ++
    • ++
    • i2 ++ integer ++ end range ++ (default #t) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ new (n, val) ++
    ++
    ++ create a new array of specified size with initial value. ++ ++ ++

    Parameters:

    ++
      ++
    • n ++ integer ++ size ++
    • ++
    • val ++ initial value (can be nil, but don't expect # to work!) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ clear (t, istart) ++
    ++
    ++ clear out the contents of a table. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list ++
    • ++
    • istart ++ optional start position ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ removevalues (t, i1, i2) ++
    ++
    ++ remove a range of values from a table. ++ End of range may be negative. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    • i1 ++ integer ++ start index ++
    • ++
    • i2 ++ integer ++ end index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ readonly (t) ++
    ++
    ++ modifies a table to be read only. ++ This only offers weak protection. Tables can still be modified with ++ table.insert and rawset.

    ++ ++

    NOTE: for Lua 5.1 length, pairs and ipairs will not work, since the ++ equivalent metamethods are only available in Lua 5.2 and newer. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the table read only (a proxy). ++
    ++ ++ ++ ++ ++
    ++
    ++

    Copying

    ++ ++
    ++
    ++ ++ update (t1, t2) ++
    ++
    ++ copy a table into another, in-place. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ table ++ destination table ++
    • ++
    • t2 ++ table ++ source (actually any iterable object) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ first table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ copy (t) ++
    ++
    ++ make a shallow copy of a table ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ an iterable source ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ new table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ deepcopy (t) ++
    ++
    ++ make a deep copy of a table, recursively copying all the keys and fields. ++ This supports cycles in tables; cycles will be reproduced in the copy. ++ This will also set the copied table's metatable to that of the original. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ A table ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ new table ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ icopy (dest, src[, idest=1[, isrc=1[, nsrc=#src]]]) ++
    ++
    ++ copy an array into another one, clearing dest after idest+nsrc, if necessary. ++ ++ ++

    Parameters:

    ++
      ++
    • dest ++ array ++ a list-like table ++
    • ++
    • src ++ array ++ a list-like table ++
    • ++
    • idest ++ integer ++ where to start copying values into destination ++ (default 1) ++
    • ++
    • isrc ++ integer ++ where to start copying values from source ++ (default 1) ++
    • ++
    • nsrc ++ integer ++ number of elements to copy from source ++ (default #src) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ move (dest, src[, idest=1[, isrc=1[, nsrc=#src]]]) ++
    ++
    ++ copy an array into another one. ++ ++ ++

    Parameters:

    ++
      ++
    • dest ++ array ++ a list-like table ++
    • ++
    • src ++ array ++ a list-like table ++
    • ++
    • idest ++ integer ++ where to start copying values into destination ++ (default 1) ++
    • ++
    • isrc ++ integer ++ where to start copying values from source ++ (default 1) ++
    • ++
    • nsrc ++ integer ++ number of elements to copy from source ++ (default #src) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ insertvalues (t[, position], values) ++
    ++
    ++ insert values into a table. ++ similar to table.insert but inserts values from given table values, ++ not the object itself, into table t at position pos. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ the list ++
    • ++
    • position ++ integer ++ (default is at end) ++ (optional) ++
    • ++
    • values ++ array ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Comparing

    ++ ++
    ++
    ++ ++ deepcompare (t1, t2[, ignore_mt[, eps]]) ++
    ++
    ++ compare two values. ++ if they are tables, then compare their keys and fields recursively. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ A value ++
    • ++
    • t2 ++ A value ++
    • ++
    • ignore_mt ++ boolean ++ if true, ignore __eq metamethod (default false) ++ (optional) ++
    • ++
    • eps ++ number ++ if defined, then used for any number comparisons ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ compare (t1, t2, cmp) ++
    ++
    ++ compare two arrays using a predicate. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ array ++ an array ++
    • ++
    • t2 ++ array ++ an array ++
    • ++
    • cmp ++ function ++ A comparison function; bool = cmp(t1_value, t2_value) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true or false ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      assert(tablex.compare({ 1, 2, 3 }, { 1, 2, 3 }, "=="))
      ++
      ++assert(tablex.compare(
      ++   {1,2,3, hello = "world"},  -- fields are not compared!
      ++   {1,2,3}, function(v1, v2) return v1 == v2 end)
      ++
    ++ ++
    ++
    ++ ++ compare_no_order (t1, t2, cmp) ++
    ++
    ++ compare two list-like tables using an optional predicate, without regard for element order. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ array ++ a list-like table ++
    • ++
    • t2 ++ array ++ a list-like table ++
    • ++
    • cmp ++ A comparison function (may be nil) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Finding

    ++ ++
    ++
    ++ ++ find (t, val, idx) ++
    ++
    ++ return the index of a value in a list. ++ Like string.find, there is an optional index to start searching, ++ which can be negative. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ A list-like table ++
    • ++
    • val ++ A value ++
    • ++
    • idx ++ integer ++ index to start; -1 means last element,etc (default 1) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ index of value or nil if not found ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • find({10,20,30},20) == 2
    • ++
    • find({'a','b','a','c'},'a',2) == 3
    • ++
    ++ ++
    ++
    ++ ++ rfind (t, val, idx) ++
    ++
    ++ return the index of a value in a list, searching from the end. ++ Like string.find, there is an optional index to start searching, ++ which can be negative. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ A list-like table ++
    • ++
    • val ++ A value ++
    • ++
    • idx ++ index to start; -1 means last element,etc (default #t) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ index of value or nil if not found ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      rfind({10,10,10},10) == 3
      ++
    ++ ++
    ++
    ++ ++ find_if (t, cmp, arg) ++
    ++
    ++ return the index (or key) of a value in a table using a comparison function.

    ++ ++

    NOTE: the 2nd return value of this function, the value returned ++ by the comparison function, has a limitation that it cannot be false. ++ Because if it is, then it indicates the comparison failed, and the ++ function will continue the search. See examples. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ A table ++
    • ++
    • cmp ++ function ++ A comparison function ++
    • ++
    • arg ++ an optional second argument to the function ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ index of value, or nil if not found
    2. ++
    3. ++ value returned by comparison function (cannot be false!)
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      -- using an operator
      ++local lst = { "Rudolph", true, false, 15 }
      ++local idx, cmp_result = tablex.rfind(lst, "==", "Rudolph")
      ++assert(idx == 1)
      ++assert(cmp_result == true)
      ++
      ++local idx, cmp_result = tablex.rfind(lst, "==", false)
      ++assert(idx == 3)
      ++assert(cmp_result == true)       -- looking up 'false' works!
      ++
      ++-- using a function returning the value looked up
      ++local cmp = function(v1, v2) return v1 == v2 and v2 end
      ++local idx, cmp_result = tablex.rfind(lst, cmp, "Rudolph")
      ++assert(idx == 1)
      ++assert(cmp_result == "Rudolph")  -- the value is returned
      ++
      ++-- NOTE: this fails, since 'false' cannot be returned!
      ++local idx, cmp_result = tablex.rfind(lst, cmp, false)
      ++assert(idx == nil)               -- looking up 'false' failed!
      ++assert(cmp_result == nil)
      ++
    ++ ++
    ++
    ++ ++ search (t, value[, exclude]) ++
    ++
    ++ find a value in a table by recursive search. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table ++
    • ++
    • value ++ the value ++
    • ++
    • exclude ++ array ++ any tables to avoid searching ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a fieldspec, e.g. 'a.b' or 'math.sin' ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      search(_G,math.sin,{package.path}) == 'math.sin'
      ++
    ++ ++
    ++
    ++

    MappingAndFiltering

    ++ ++
    ++
    ++ ++ map (fun, t, ...) ++
    ++
    ++ apply a function to all values of a table. ++ This returns a table of the results. ++ Any extra arguments are passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ A function that takes at least one argument ++
    • ++
    • t ++ table ++ A table ++
    • ++
    • ... ++ optional arguments ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4}
      ++
    ++ ++
    ++
    ++ ++ imap (fun, t, ...) ++
    ++
    ++ apply a function to all values of a list. ++ This returns a table of the results. ++ Any extra arguments are passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ A function that takes at least one argument ++
    • ++
    • t ++ array ++ a table (applies to array part) ++
    • ++
    • ... ++ optional arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list-like table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900}
      ++
    ++ ++
    ++
    ++ ++ map_named_method (name, t, ...) ++
    ++
    ++ apply a named method to values from a table. ++ ++ ++

    Parameters:

    ++
      ++
    • name ++ string ++ the method name ++
    • ++
    • t ++ array ++ a list-like table ++
    • ++
    • ... ++ any extra arguments to the method ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a List with the results of the method (1st result only) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local Car = {}
      ++Car.__index = Car
      ++function Car.new(car)
      ++  return setmetatable(car or {}, Car)
      ++end
      ++Car.speed = 0
      ++function Car:faster(increase)
      ++  self.speed = self.speed + increase
      ++  return self.speed
      ++end
      ++
      ++local ferrari = Car.new{ name = "Ferrari" }
      ++local lamborghini = Car.new{ name = "Lamborghini", speed = 50 }
      ++local cars = { ferrari, lamborghini }
      ++
      ++assert(ferrari.speed == 0)
      ++assert(lamborghini.speed == 50)
      ++tablex.map_named_method("faster", cars, 10)
      ++assert(ferrari.speed == 10)
      ++assert(lamborghini.speed == 60)
      ++
    ++ ++
    ++
    ++ ++ map2 (fun, t1, t2, ...) ++
    ++
    ++ apply a function to values from two tables. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of at least two arguments ++
    • ++
    • t1 ++ table ++ a table ++
    • ++
    • t2 ++ table ++ a table ++
    • ++
    • ... ++ extra arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44}
      ++
    ++ ++
    ++
    ++ ++ imap2 (fun, t1, t2, ...) ++
    ++
    ++ apply a function to values from two arrays. ++ The result will be the length of the shortest array. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ a function of at least two arguments ++
    • ++
    • t1 ++ array ++ a list-like table ++
    • ++
    • t2 ++ array ++ a list-like table ++
    • ++
    • ... ++ extra arguments ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23}
      ++
    ++ ++
    ++
    ++ ++ mapn (fun, ..., fun) ++
    ++
    ++ Apply a function to a number of tables. ++ A more general version of map ++ The result is a table containing the result of applying that function to the ++ ith value of each table. Length of output list is the minimum length of all the lists ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ A function that takes as many arguments as there are tables ++
    • ++
    • ... ++ table ++ n tables ++
    • ++
    • fun ++ A function that takes as many arguments as there are tables ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333}
    • ++
    • mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is    {100,200,300}
    • ++
    ++ ++
    ++
    ++ ++ pairmap (fun, t, ...) ++
    ++
    ++ call the function with the key and value pairs from a table. ++ The function can return a value and a key (note the order!). If both ++ are not nil, then this pair is inserted into the result: if the key already exists, we convert the value for that ++ key into a table and append into it. If only value is not nil, then it is appended to the result. ++ ++ ++

    Parameters:

    ++
      ++
    • fun ++ function ++ A function which will be passed each key and value as arguments, plus any extra arguments to pairmap. ++
    • ++
    • t ++ table ++ A table ++
    • ++
    • ... ++ optional arguments ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
    • pairmap(function(k,v) return v end,{fred=10,bonzo=20}) is {10,20} _or_ {20,10}
    • ++
    • pairmap(function(k,v) return {k,v},k end,{one=1,two=2}) is {one={'one',1},two={'two',2}}
    • ++
    ++ ++
    ++
    ++ ++ filter (t, pred, arg) ++
    ++
    ++ filter an array's values using a predicate function ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    • pred ++ function ++ a boolean function ++
    • ++
    • arg ++ optional argument to be passed as second argument of the predicate ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Iterating

    ++ ++
    ++
    ++ ++ foreach (t, fun, ...) ++
    ++
    ++ apply a function to all elements of a table. ++ The arguments to the function will be the value, ++ the key and finally any extra arguments passed to this function. ++ Note that the Lua 5.0 function table.foreach passed the key first. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ a table ++
    • ++
    • fun ++ function ++ a function on the elements; function(value, key, ...) ++
    • ++
    • ... ++ extra arguments passed to fun ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ foreachi (t, fun, ...) ++
    ++
    ++ apply a function to all elements of a list-like table in order. ++ The arguments to the function will be the value, ++ the index and finally any extra arguments passed to this function ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a table ++
    • ++
    • fun ++ function ++ a function with at least one argument ++
    • ++
    • ... ++ optional arguments ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ sort (t, f) ++
    ++
    ++ return an iterator to a table sorted by its keys ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table ++
    • ++
    • f ++ function ++ an optional comparison function (f(x,y) is true if x < y) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator to traverse elements sorted by the keys ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      for k,v in tablex.sort(t) do print(k,v) end
      ++
    ++ ++
    ++
    ++ ++ sortv (t, f) ++
    ++
    ++ return an iterator to a table sorted by its values ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table ++
    • ++
    • f ++ function ++ an optional comparison function (f(x,y) is true if x < y) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an iterator to traverse elements sorted by the values ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      for k,v in tablex.sortv(t) do print(k,v) end
      ++
    ++ ++
    ++
    ++

    Extraction

    ++ ++
    ++
    ++ ++ keys (t) ++
    ++
    ++ return all the keys of a table in arbitrary order. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ A list-like table where the values are the keys of the input table ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ values (t) ++
    ++
    ++ return all the values of the table in arbitrary order ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ A list-like table where the values are the values of the input table ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ sub (t, first, last) ++
    ++
    ++ Extract a range from a table, like 'string.sub'. ++ If first or last are negative then they are relative to the end of the list ++ eg. sub(t,-2) gives last 2 entries in a list, and ++ sub(t,-4,-2) gives from -4th to -2nd ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ array ++ a list-like table ++
    • ++
    • first ++ integer ++ An index ++
    • ++
    • last ++ integer ++ An index ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a new List ++
    ++ ++ ++ ++ ++
    ++
    ++

    Merging

    ++ ++
    ++
    ++ ++ merge (t1, t2, dup) ++
    ++
    ++ combine two tables, either as union or intersection. Corresponds to ++ set operations for sets () but more general. Not particularly ++ useful for list-like tables. ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ table ++ a table ++
    • ++
    • t2 ++ table ++ a table ++
    • ++
    • dup ++ boolean ++ true for a union, false for an intersection. ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
    • merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34}
    • ++
    • merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23}
    • ++
    ++ ++
    ++
    ++ ++ difference (s1, s2, symm) ++
    ++
    ++ a new table which is the difference of two tables. ++ With sets (where the values are all true) this is set difference and ++ symmetric difference depending on the third parameter. ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ table ++ a map-like table or set ++
    • ++
    • s2 ++ table ++ a map-like table or set ++
    • ++
    • symm ++ boolean ++ symmetric difference (default false) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a map-like table or set ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ zip (...) ++
    ++
    ++ return a table where each element is a table of the ith values of an arbitrary ++ number of tables. It is equivalent to a matrix transpose. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ array ++ arrays to be zipped ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}}
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.template.html b/extra/penlight/docs/libraries/pl.template.html +new file mode 100644 +index 0000000..73658aa +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.template.html +@@ -0,0 +1,427 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.template

    ++

    A template preprocessor.

    ++

    Originally by Ricki Lake

    ++ ++

    There are two rules:

    ++ ++
      ++
    • lines starting with # are Lua
    • ++
    • otherwise, $(expr) is the result of evaluating expr
    • ++
    ++ ++

    Example:

    ++ ++ ++
    ++#  for i = 1,3 do
    ++   $(i) Hello, Word!
    ++#  end
    ++===>
    ++1 Hello, Word!
    ++2 Hello, Word!
    ++3 Hello, Word!
    ++
    ++ ++

    Other escape characters can be used, when the defaults conflict ++ with the output language.

    ++ ++ ++
    ++> for _,n in pairs{'one','two','three'} do
    ++static int l_${n} (luaState *state);
    ++> end
    ++
    ++ ++

    See the Guide.

    ++ ++

    Dependencies: pl.utils

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    substitute (str[, env])expand the template using the specified environment.
    ct:render ([env[, parent[, db]]])executes the previously compiled template and renders it.
    compile (str[, opts])compiles the template.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ substitute (str[, env]) ++
    ++
    ++ expand the template using the specified environment. ++ This function will compile and render the template. For more performant ++ recurring usage use the two step approach by using compile and ct:render. ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ the template string ++
    • ++
    • env the environment. This table has the following special fields: ++
        ++
      • _parent ++ table ++ continue looking up in this table (e.g. _parent=_G). ++ (default nil) ++
      • ++
      • _brackets ++ string ++ bracket pair that wraps inline Lua expressions. ++ (default "()") ++
      • ++
      • _escape ++ string ++ character marking Lua lines. ++ (default "#") ++
      • ++
      • _inline_escape ++ string ++ character marking inline Lua expression. ++ (default "$") ++
      • ++
      • _chunk_name ++ string ++ chunk name for loaded templates, used if there ++ is an error in Lua code. ++ (default "TMP") ++
      • ++
      • _debug ++ boolean ++ if truthy, the generated code will be printed upon a render error. ++ (default false) ++
      • ++
      ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ string ++ render result
    2. ++
    3. ++ nil ++ ++ ++
    4. ++
    5. ++ string ++ source_code (only if 'env._debug' was truthy).
    6. ++
    ++

    Or

    ++
      ++
    1. ++ nil ++ ++ ++
    2. ++
    3. ++ string ++ error message
    4. ++
    5. ++ string ++ source_code (only if 'env._debug' was truthy).
    6. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ct:render ([env[, parent[, db]]]) ++
    ++
    ++ executes the previously compiled template and renders it. ++ ++ ++

    Parameters:

    ++
      ++
    • env ++ table ++ the environment. ++ (optional) ++
    • ++
    • parent ++ table ++ continue looking up in this table (e.g. parent=_G). ++ (optional) ++
    • ++
    • db ++ boolean ++ if thruthy, it will print the code upon a render error ++ (provided the template was compiled with the debug option). ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ string ++ render result
    2. ++
    3. ++ nil ++ ++ ++
    4. ++
    5. ++ string ++ source_code (only if 'env._debug' was truthy).
    6. ++
    ++

    Or

    ++
      ++
    1. ++ nil ++ ++ ++
    2. ++
    3. ++ string ++ error message
    4. ++
    5. ++ string ++ source_code (only if 'env._debug' was truthy).
    6. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local ct, err = template.compile(my_template)
      ++local rendered , err = ct:render(my_env, parent)
      ++
    ++ ++
    ++
    ++ ++ compile (str[, opts]) ++
    ++
    ++ compiles the template. ++ Returns an object that can repeatedly be rendered without parsing/compiling ++ the template again. Preserves the line layout of the template so that line ++ numbers in error messages should point to the correct lines in the source ++ string. ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ the template string ++
    • ++
    • opts the compilation options to use. This table supports the following options: ++
        ++
      • chunk_name ++ string ++ chunk name for loaded templates, used if there ++ is an error in Lua code. ++ (default "TMP") ++
      • ++
      • escape ++ string ++ character marking Lua lines. ++ (default "#") ++
      • ++
      • inline_escape ++ string ++ character marking inline Lua expression. ++ (default "$") ++
      • ++
      • inline_brackets ++ string ++ bracket pair that wraps inline Lua expressions. ++ (default "()") ++
      • ++
      • newline ++ boolean ++ if truthy, newlines will be stripped from text in the template. ++ (default false) ++
      • ++
      • debug ++ boolean ++ if truthy, the generated code will be printed upon a render error. ++ (default false) ++
      • ++
      ++
    ++ ++

    Returns:

    ++
      ++ ++ ct ++ compiled template object ++
    ++

    Or

    ++
      ++
    1. ++ nil ++ ++ ++
    2. ++
    3. ++ string ++ error message
    4. ++
    5. ++ string ++ source_code
    6. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local ct, err = template.compile(my_template)
      ++local rendered , err = ct:render(my_env, parent)
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.test.html b/extra/penlight/docs/libraries/pl.test.html +new file mode 100644 +index 0000000..64497e6 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.test.html +@@ -0,0 +1,445 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.test

    ++

    Useful test utilities.

    ++

    ++ ++ ++ ++

    ++test.asserteq({1,2},{1,2}) -- can compare tables
    ++test.asserteq(1.2,1.19,0.02) -- compare FP numbers within precision
    ++T = test.tuple -- used for comparing multiple results
    ++test.asserteq(T(string.find(" me","me")),T(2,3))
    ++
    ++ ++

    Dependencies: pl.utils, pl.tablex, pl.pretty, pl.path, debug

    ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    error_handler (file, line, got_text, needed_text, msg)error handling for test results.
    complain (x, y, msg, where)general test complain message.
    asserteq (x, y, eps, where)like assert, except takes two arguments that must be equal and can be tables.
    assertmatch (s1, s2, where)assert that the first string matches the second.
    assertraise (fn, e, where)assert that the function raises a particular error.
    asserteq2 (x1, x2, y1, y2, where)a version of asserteq that takes two pairs of values.
    tuple (...)encode an arbitrary argument list as a tuple.
    timer (msg, n, fun, ...)Time a function.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ error_handler (file, line, got_text, needed_text, msg) ++
    ++
    ++ error handling for test results. ++ By default, this writes to stderr and exits the program. ++ Re-define this function to raise an error and/or redirect output ++ ++ ++

    Parameters:

    ++
      ++
    • file ++ ++ ++ ++
    • ++
    • line ++ ++ ++ ++
    • ++
    • got_text ++ ++ ++ ++
    • ++
    • needed_text ++ ++ ++ ++
    • ++
    • msg ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ complain (x, y, msg, where) ++
    ++
    ++ general test complain message. ++ Useful for composing new test functions (see tests/tablex.lua for an example) ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ a value ++
    • ++
    • y ++ value to compare first value against ++
    • ++
    • msg ++ message ++
    • ++
    • where ++ extra level offset for errors ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ asserteq (x, y, eps, where) ++
    ++
    ++ like assert, except takes two arguments that must be equal and can be tables. ++ If they are plain tables, it will use tablex.deepcompare. ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ any value ++
    • ++
    • y ++ a value equal to x ++
    • ++
    • eps ++ an optional tolerance for numerical comparisons ++
    • ++
    • where ++ extra level offset ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ assertmatch (s1, s2, where) ++
    ++
    ++ assert that the first string matches the second. ++ ++ ++

    Parameters:

    ++
      ++
    • s1 ++ a string ++
    • ++
    • s2 ++ a string ++
    • ++
    • where ++ extra level offset ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ assertraise (fn, e, where) ++
    ++
    ++ assert that the function raises a particular error. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ a function or a table of the form {function,arg1,...} ++
    • ++
    • e ++ a string to match the error against ++
    • ++
    • where ++ extra level offset ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ asserteq2 (x1, x2, y1, y2, where) ++
    ++
    ++ a version of asserteq that takes two pairs of values. ++ x1==y1 and x2==y2 must be true. Useful for functions that naturally ++ return two values. ++ ++ ++

    Parameters:

    ++
      ++
    • x1 ++ any value ++
    • ++
    • x2 ++ any value ++
    • ++
    • y1 ++ any value ++
    • ++
    • y2 ++ any value ++
    • ++
    • where ++ extra level offset ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ tuple (...) ++
    ++
    ++ encode an arbitrary argument list as a tuple. ++ This can be used to compare to other argument lists, which is ++ very useful for testing functions which return a number of values. ++ Unlike regular array-like tables ('sequences') they may contain nils. ++ Tuples understand equality and know how to print themselves out. ++ The # operator is defined to be the size, irrespecive of any nils, ++ and there is an unpack method. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++

    Usage:

    ++
      ++
      asserteq(tuple( ('ab'):find 'a'), tuple(1,1))
      ++
    ++ ++
    ++
    ++ ++ timer (msg, n, fun, ...) ++
    ++
    ++ Time a function. Call the function a given number of times, and report the number of seconds taken, ++ together with a message. Any extra arguments will be passed to the function. ++ ++ ++

    Parameters:

    ++
      ++
    • msg ++ string ++ a descriptive message ++
    • ++
    • n ++ integer ++ number of times to call the function ++
    • ++
    • fun ++ function ++ the function ++
    • ++
    • ... ++ optional arguments to fun ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.text.html b/extra/penlight/docs/libraries/pl.text.html +new file mode 100644 +index 0000000..a15af4c +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.text.html +@@ -0,0 +1,145 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.text

    ++

    Text processing utilities.

    ++

    This provides a Template class (modeled after the same from the Python ++ libraries, see string.Template). It also provides similar functions to those ++ found in the textwrap module.

    ++ ++

    IMPORTANT: this module has been deprecated and will be removed in a future ++ version (2.0). The contents of this module have moved to the pl.stringx ++ module.

    ++ ++

    See the Guide.

    ++ ++

    Dependencies: pl.stringx, pl.utils

    ++ ++ ++ ++
    ++
    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.types.html b/extra/penlight/docs/libraries/pl.types.html +new file mode 100644 +index 0000000..b880da3 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.types.html +@@ -0,0 +1,475 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.types

    ++

    Dealing with Detailed Type Information

    ++

    ++ ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    is_callable (obj)is the object either a function or a callable object?.
    is_type (obj, tp)is the object of the specified type?.
    type (obj)a string representation of a type.
    is_integer (x)is this number an integer?
    is_empty (o, ignore_spaces)Check if the object is "empty".
    is_indexable (val)is an object 'array-like'?
    is_iterable (val)can an object be iterated over with pairs?
    is_writeable (val)can an object accept new key/pair values?
    to_bool (o[, true_strs[, check_objs]])Convert to a boolean value.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ is_callable (obj) ++
    ++
    ++ is the object either a function or a callable object?. ++ ++ ++

    Parameters:

    ++
      ++
    • obj ++ Object to check. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ is_type (obj, tp) ++
    ++
    ++ is the object of the specified type?. ++ If the type is a string, then use type, otherwise compare with metatable.

    ++ ++

    NOTE: this function is imported from utils.is_type. ++ ++ ++

    Parameters:

    ++
      ++
    • obj ++ An object to check ++
    • ++
    • tp ++ The expected type ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ type (obj) ++
    ++
    ++ a string representation of a type. ++ For tables and userdata with metatables, we assume that the metatable has a _name ++ field. If the field is not present it will return 'unknown table' or ++ 'unknown userdata'. ++ Lua file objects return the type 'file'. ++ ++ ++

    Parameters:

    ++
      ++
    • obj ++ an object ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string like 'number', 'table', 'file' or 'List' ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_integer (x) ++
    ++
    ++ is this number an integer? ++ ++ ++

    Parameters:

    ++
      ++
    • x ++ a number ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ boolean ++
    ++ ++

    Raises:

    ++ error if x is not a number ++ ++ ++ ++
    ++
    ++ ++ is_empty (o, ignore_spaces) ++
    ++
    ++ ++

    Check if the object is "empty". ++ An object is considered empty if it is:

    ++ ++
      ++
    • nil
    • ++
    • a table without any items (key-value pairs or indexes)
    • ++
    • a string with no content ("")
    • ++
    • not a nil/table/string
    • ++
    ++ ++ ++ ++

    Parameters:

    ++
      ++
    • o ++ The object to check if it is empty. ++
    • ++
    • ignore_spaces ++ If the object is a string and this is true the string is ++ considered empty if it only contains spaces. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the object is empty, otherwise a falsy value. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_indexable (val) ++
    ++
    ++ is an object 'array-like'? ++ An object is array like if:

    ++ ++
      ++
    • it is a table, or
    • ++
    • it has a metatable with __len and __index methods
    • ++
    ++ ++

    NOTE: since __len is 5.2+, on 5.1 is usually returns false for userdata ++ ++ ++

    Parameters:

    ++
      ++
    • val ++ any value. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the object is array-like, otherwise a falsy value. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_iterable (val) ++
    ++
    ++ can an object be iterated over with pairs? ++ An object is iterable if:

    ++ ++
      ++
    • it is a table, or
    • ++
    • it has a metatable with a __pairs meta method
    • ++
    ++ ++

    NOTE: since __pairs is 5.2+, on 5.1 is usually returns false for userdata ++ ++ ++

    Parameters:

    ++
      ++
    • val ++ any value. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the object is iterable, otherwise a falsy value. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_writeable (val) ++
    ++
    ++ ++

    can an object accept new key/pair values? ++ An object is iterable if:

    ++ ++
      ++
    • it is a table, or
    • ++
    • it has a metatable with a __newindex meta method
    • ++
    ++ ++ ++ ++ ++

    Parameters:

    ++
      ++
    • val ++ any value. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the object is writeable, otherwise a falsy value. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ to_bool (o[, true_strs[, check_objs]]) ++
    ++
    ++ ++

    Convert to a boolean value. ++ True values are:

    ++ ++
      ++
    • boolean: true.
    • ++
    • string: 'yes', 'y', 'true', 't', '1' or additional strings specified by true_strs.
    • ++
    • number: Any non-zero value.
    • ++
    • table: Is not empty and check_objs is true.
    • ++
    • everything else: Is not nil and check_objs is true.
    • ++
    ++ ++ ++ ++ ++

    Parameters:

    ++
      ++
    • o ++ The object to evaluate. ++
    • ++
    • true_strs ++ optional Additional strings that when matched should evaluate to true. Comparison is case insensitive. ++ This should be a List of strings. E.g. "ja" to support German. ++ (optional) ++
    • ++
    • check_objs ++ True if objects should be evaluated. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ true if the input evaluates to true, otherwise false. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.url.html b/extra/penlight/docs/libraries/pl.url.html +new file mode 100644 +index 0000000..c102dc1 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.url.html +@@ -0,0 +1,212 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.url

    ++

    Python-style URL quoting library.

    ++

    ++ ++

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    quote (s, quote_plus)Quote the url, replacing special characters using the '%xx' escape.
    unquote (s)Unquote the url, replacing '%xx' escapes and plus signs.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ quote (s, quote_plus) ++
    ++
    ++ Quote the url, replacing special characters using the '%xx' escape. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    • quote_plus ++ boolean ++ Also escape slashes and replace spaces by plus signs. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ The quoted string, or if s wasn't a string, just plain unaltered s. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ unquote (s) ++
    ++
    ++ Unquote the url, replacing '%xx' escapes and plus signs. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ string ++ the string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ The unquoted string, or if s wasn't a string, just plain unaltered s. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.utils.html b/extra/penlight/docs/libraries/pl.utils.html +new file mode 100644 +index 0000000..7457815 +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.utils.html +@@ -0,0 +1,1604 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.utils

    ++

    Generally useful routines.

    ++

    See the Guide.

    ++ ++

    Dependencies: pl.compat, all exported fields and functions from ++ pl.compat are also available in this module.

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    pack (...)pack an argument list into a table.
    unpack (t[, i[, j]])unpack a table and return its contents.
    printf (fmt, ...)print an arbitrary number of arguments using a format.
    fprintf (f, fmt, ...)write an arbitrary number of arguments to a file using a format.
    import (t, T)take a table and 'inject' it into the local namespace.
    choose (cond, value1, value2)return either of two values, depending on a condition.
    array_tostring (t[, temp[, tostr]])convert an array of values to strings.
    is_type (obj, tp)is the object of the specified type?
    npairs (t[, i_start=1[, i_end=t.n or #t[, step=1]]])an iterator with indices, similar to ipairs, but with a range.
    kpairs (t)an iterator over all non-integer keys (inverse of ipairs).
    ++

    Tables

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    patternsSome standard patterns
    stdmtStandard meta-tables as used by other Penlight modules
    ++

    Error handling

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    assert_arg (n, val, tp, verify, msg, lev)assert that the given argument is in fact of the correct type.
    enum (...)creates an Enum or constants lookup table for improved error handling.
    function_arg (idx, f, msg)process a function argument.
    assert_string (n, val)assert the common case that the argument is a string.
    on_error (mode)control the error strategy used by Penlight.
    raise (err)used by Penlight functions to return errors.
    ++

    File handling

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    readfile (filename, is_bin)return the contents of a file as a string
    writefile (filename, str, is_bin)write a string to a file
    readlines (filename)return the contents of a file as a list of lines
    ++

    OS functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    executeex (cmd, bin)execute a shell command and return the output.
    quote_arg (argument)Quote and escape an argument of a command.
    quit ([code], msg, ...)error out of this program gracefully.
    ++

    String functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    escape (s)escape any Lua 'magic' characters in a string
    split (s, re, plain, n)split a string into a list of strings separated by a delimiter.
    splitv (s, re, plain, n)split a string into a number of return values.
    ++

    Functional

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    memoize (func)'memoize' a function (cache returned value for next call).
    add_function_factory (mt, fun)associate a function factory with a type.
    string_lambda (lf)an anonymous function as a string.
    bind1 (fn, p)bind the first argument of the function to a value.
    bind2 (fn, p)bind the second argument of the function to a value.
    ++

    Deprecation

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    set_deprecation_func (func)Sets a deprecation warning function.
    raise_deprecation (opts)raises a deprecation warning.
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ pack (...) ++
    ++
    ++ pack an argument list into a table. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ any arguments ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a table with field n set to the length ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ unpack (t[, i[, j]]) ++
    ++
    ++ unpack a table and return its contents.

    ++ ++

    NOTE: this implementation differs from the Lua implementation in the way ++ that this one DOES honor the n field in the table t, such that it is 'nil-safe'. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table to unpack ++
    • ++
    • i ++ index from which to start unpacking, defaults to 1 ++ (optional) ++
    • ++
    • j ++ index of the last element to unpack, defaults to t.n or else #t ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ multiple return values from the table ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local t = table.pack(nil, nil, nil, 4)
      ++local a, b, c, d = table.unpack(t)   -- this unpack is NOT nil-safe, so d == nil
      ++
      ++local a, b, c, d = utils.unpack(t)   -- this is nil-safe, so d == 4
      ++
    ++ ++
    ++
    ++ ++ printf (fmt, ...) ++
    ++
    ++ print an arbitrary number of arguments using a format. ++ Output will be sent to stdout. ++ ++ ++

    Parameters:

    ++
      ++
    • fmt ++ The format (see string.format) ++
    • ++
    • ... ++ Extra arguments for format ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ fprintf (f, fmt, ...) ++
    ++
    ++ write an arbitrary number of arguments to a file using a format. ++ ++ ++

    Parameters:

    ++
      ++
    • f ++ File handle to write to. ++
    • ++
    • fmt ++ The format (see string.format). ++
    • ++
    • ... ++ Extra arguments for format ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ import (t, T) ++
    ++
    ++ take a table and 'inject' it into the local namespace. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ The table (table), or module name (string), defaults to this utils module table ++
    • ++
    • T ++ An optional destination table (defaults to callers environment) ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ choose (cond, value1, value2) ++
    ++
    ++ return either of two values, depending on a condition. ++ ++ ++

    Parameters:

    ++
      ++
    • cond ++ A condition ++
    • ++
    • value1 ++ Value returned if cond is truthy ++
    • ++
    • value2 ++ Value returned if cond is falsy ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ array_tostring (t[, temp[, tostr]]) ++
    ++
    ++ convert an array of values to strings. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ a list-like table ++
    • ++
    • temp ++ (table) buffer to use, otherwise allocate ++ (optional) ++
    • ++
    • tostr ++ custom tostring function, called with (value,index). Defaults to tostring. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the converted buffer ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_type (obj, tp) ++
    ++
    ++ is the object of the specified type? ++ If the type is a string, then use type, otherwise compare with metatable ++ ++ ++

    Parameters:

    ++
      ++
    • obj ++ An object to check ++
    • ++
    • tp ++ String of what type it should be ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ boolean ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      utils.is_type("hello world", "string")   --> true
      ++-- or check metatable
      ++local my_mt = {}
      ++local my_obj = setmetatable(my_obj, my_mt)
      ++utils.is_type(my_obj, my_mt)  --> true
      ++
    ++ ++
    ++
    ++ ++ npairs (t[, i_start=1[, i_end=t.n or #t[, step=1]]]) ++
    ++
    ++ an iterator with indices, similar to ipairs, but with a range. ++ This is a nil-safe index based iterator that will return nil when there ++ is a hole in a list. To be safe ensure that table t.n contains the length. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table to iterate over ++
    • ++
    • i_start ++ integer ++ start index ++ (default 1) ++
    • ++
    • i_end ++ integer ++ end index ++ (default t.n or #t) ++
    • ++
    • step ++ integer ++ step size ++ (default 1) ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ integer ++ index
    2. ++
    3. ++ any ++ value at index (which can be nil!)
    4. ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local t = utils.pack(nil, 123, nil)  -- adds an n field when packing
      ++
      ++for i, v in utils.npairs(t, 2) do  -- start at index 2
      ++  t[i] = tostring(t[i])
      ++end
      ++
      ++-- t = { n = 3, [2] = "123", [3] = "nil" }
      ++
    ++ ++
    ++
    ++ ++ kpairs (t) ++
    ++
    ++ an iterator over all non-integer keys (inverse of ipairs). ++ It will skip any key that is an integer number, so negative indices or an ++ array with holes will not return those either (so it returns slightly less than ++ 'the inverse of ipairs').

    ++ ++

    This uses pairs under the hood, so any value that is iterable using pairs ++ will work with this function. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ the table to iterate over ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ key ++ ++ ++
    2. ++
    3. ++ value ++ ++ ++
    4. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local t = {
      ++  "hello",
      ++  "world",
      ++  hello = "hallo",
      ++  world = "Welt",
      ++}
      ++
      ++for k, v in utils.kpairs(t) do
      ++  print("German: ", v)
      ++end
      ++
      ++-- output;
      ++-- German: hallo
      ++-- German: Welt
      ++
    ++ ++
    ++
    ++

    Tables

    ++ ++
    ++
    ++ ++ patterns ++
    ++
    ++ Some standard patterns ++ ++ ++

    Fields:

    ++
      ++
    • FLOAT ++ floating point number ++
    • ++
    • INTEGER ++ integer number ++
    • ++
    • IDEN ++ identifier ++
    • ++
    • FILE ++ file ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ stdmt ++
    ++
    ++ Standard meta-tables as used by other Penlight modules ++ ++ ++

    Fields:

    ++
      ++
    • List ++ the List metatable ++
    • ++
    • Map ++ the Map metatable ++
    • ++
    • Set ++ the Set metatable ++
    • ++
    • MultiMap ++ the MultiMap metatable ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++

    Error handling

    ++ ++
    ++
    ++ ++ assert_arg (n, val, tp, verify, msg, lev) ++
    ++
    ++ assert that the given argument is in fact of the correct type. ++ ++ ++

    Parameters:

    ++
      ++
    • n ++ argument index ++
    • ++
    • val ++ the value ++
    • ++
    • tp ++ the type ++
    • ++
    • verify ++ an optional verification function ++
    • ++
    • msg ++ an optional custom message ++
    • ++
    • lev ++ optional stack position for trace, default 2 ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the validated value ++
    ++ ++

    Raises:

    ++ if val is not the correct type ++ ++ ++

    Usage:

    ++
      ++
      local param1 = assert_arg(1,"hello",'table')  --> error: argument 1 expected a 'table', got a 'string'
      ++local param4 = assert_arg(4,'!@#$%^&*','string',path.isdir,'not a directory')
      ++     --> error: argument 4: '!@#$%^&*' not a directory
      ++
    ++ ++
    ++
    ++ ++ enum (...) ++
    ++
    ++ creates an Enum or constants lookup table for improved error handling. ++ This helps prevent magic strings in code by throwing errors for accessing ++ non-existing values, and/or converting strings/identifiers to other values.

    ++ ++

    Calling on the object does the same, but returns a soft error; nil + err, if ++ the call is successful (the key exists), it will return the value.

    ++ ++

    When calling with varargs or an array the values will be equal to the keys. ++ The enum object is read-only. ++ ++ ++

    Parameters:

    ++
      ++
    • ... ++ table or vararg ++ the input for the Enum. If varargs or an array then the ++ values in the Enum will be equal to the names (must be strings), if a hash-table ++ then values remain (any type), and the keys must be strings. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ Enum object (read-only table/object) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
    • -- Enum access at runtime
      ++local obj = {}
      ++obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT")
      ++
      ++if current_movement == obj.MOVEMENT.FORWARD then
      ++  -- do something
      ++
      ++elseif current_movement == obj.MOVEMENT.REVERES then
      ++  -- throws error due to typo 'REVERES', so a silent mistake becomes a hard error
      ++  -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')"
      ++
      ++end
    • ++
    • -- standardized error codes
      ++local obj = {
      ++  ERR = utils.enum {
      ++    NOT_FOUND = "the item was not found",
      ++    OUT_OF_BOUNDS = "the index is outside the allowed range"
      ++  },
      ++
      ++  some_method = function(self)
      ++    return nil, self.ERR.OUT_OF_BOUNDS
      ++  end,
      ++}
      ++
      ++local result, err = obj:some_method()
      ++if not result then
      ++  if err == obj.ERR.NOT_FOUND then
      ++    -- check on error code, not magic strings
      ++
      ++  else
      ++    -- return the error description, contained in the constant
      ++    return nil, "error: "..err  -- "error: the index is outside the allowed range"
      ++  end
      ++end
    • ++
    • -- validating/converting user-input
      ++local color = "purple"
      ++local ansi_colors = utils.enum {
      ++  black     = 30,
      ++  red       = 31,
      ++  green     = 32,
      ++}
      ++local color_code, err = ansi_colors(color) -- calling on the object, returns the value from the enum
      ++if not color_code then
      ++  print("bad 'color', " .. err)
      ++  -- "bad 'color', 'purple' is not a valid value (expected one of: 'black', 'red', 'green')"
      ++  os.exit(1)
      ++end
    • ++
    ++ ++
    ++
    ++ ++ function_arg (idx, f, msg) ++
    ++
    ++ process a function argument. ++ This is used throughout Penlight and defines what is meant by a function: ++ Something that is callable, or an operator string as defined by pl.operator, ++ such as '>' or '#'. If a function factory has been registered for the type, it will ++ be called to get the function. ++ ++ ++

    Parameters:

    ++
      ++
    • idx ++ argument index ++
    • ++
    • f ++ a function, operator string, or callable object ++
    • ++
    • msg ++ optional error message ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a callable ++
    ++ ++

    Raises:

    ++ if idx is not a number or if f is not callable ++ ++ ++ ++
    ++
    ++ ++ assert_string (n, val) ++
    ++
    ++ assert the common case that the argument is a string. ++ ++ ++

    Parameters:

    ++
      ++
    • n ++ argument index ++
    • ++
    • val ++ a value that must be a string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the validated value ++
    ++ ++

    Raises:

    ++ val must be a string ++ ++ ++

    Usage:

    ++
      ++
      local val = 42
      ++local param2 = utils.assert_string(2, val) --> error: argument 2 expected a 'string', got a 'number'
      ++
    ++ ++
    ++
    ++ ++ on_error (mode) ++
    ++
    ++ ++

    control the error strategy used by Penlight. ++ This is a global setting that controls how utils.raise behaves:

    ++ ++
      ++
    • 'default': return nil + error (this is the default)
    • ++
    • 'error': throw a Lua error
    • ++
    • 'quit': exit the program
    • ++
    ++ ++ ++ ++ ++

    Parameters:

    ++
      ++
    • mode ++ either 'default', 'quit' or 'error' ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ raise (err) ++
    ++
    ++ used by Penlight functions to return errors. Its global behaviour is controlled ++ by utils.on_error. ++ To use this function you MUST use it in conjunction with return, since it might ++ return nil + error. ++ ++ ++

    Parameters:

    ++
      ++
    • err ++ the error string. ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      if some_condition then
      ++  return utils.raise("some condition was not met")  -- MUST use 'return'!
      ++end
      ++
    ++ ++
    ++
    ++

    File handling

    ++ ++
    ++
    ++ ++ readfile (filename, is_bin) ++
    ++
    ++ return the contents of a file as a string ++ ++ ++

    Parameters:

    ++
      ++
    • filename ++ The file path ++
    • ++
    • is_bin ++ open in binary mode ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ file contents ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ writefile (filename, str, is_bin) ++
    ++
    ++ write a string to a file ++ ++ ++

    Parameters:

    ++
      ++
    • filename ++ The file path ++
    • ++
    • str ++ The string ++
    • ++
    • is_bin ++ open in binary mode ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ true or nil
    2. ++
    3. ++ error message
    4. ++
    ++ ++

    Raises:

    ++ error if filename or str aren't strings ++ ++ ++ ++
    ++
    ++ ++ readlines (filename) ++
    ++
    ++ return the contents of a file as a list of lines ++ ++ ++

    Parameters:

    ++
      ++
    • filename ++ The file path ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ file contents as a table ++
    ++ ++

    Raises:

    ++ error if filename is not a string ++ ++ ++ ++
    ++
    ++

    OS functions

    ++ ++
    ++
    ++ ++ executeex (cmd, bin) ++
    ++
    ++ execute a shell command and return the output. ++ This function redirects the output to tempfiles and returns the content of those files. ++ ++ ++

    Parameters:

    ++
      ++
    • cmd ++ a shell command ++
    • ++
    • bin ++ boolean, if true, read output as binary file ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ true if successful
    2. ++
    3. ++ actual return code
    4. ++
    5. ++ stdout output (string)
    6. ++
    7. ++ errout output (string)
    8. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ quote_arg (argument) ++
    ++
    ++ Quote and escape an argument of a command. ++ Quotes a single (or list of) argument(s) of a command to be passed ++ to os.execute, pl.utils.execute or pl.utils.executeex. ++ ++ ++

    Parameters:

    ++
      ++
    • argument ++ (string or table/list) the argument to quote. If a list then ++ all arguments in the list will be returned as a single string quoted. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ quoted and escaped argument. ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local options = utils.quote_arg {
      ++    "-lluacov",
      ++    "-e",
      ++    "utils = print(require('pl.utils')._VERSION",
      ++}
      ++-- returns: -lluacov -e 'utils = print(require('\''pl.utils'\'')._VERSION'
      ++
    ++ ++
    ++
    ++ ++ quit ([code], msg, ...) ++
    ++
    ++ error out of this program gracefully. ++ ++ ++

    Parameters:

    ++
      ++
    • code ++ The exit code, defaults to -1 if omitted ++ (optional) ++
    • ++
    • msg ++ The exit message will be sent to stderr (will be formatted with the extra parameters) ++
    • ++
    • ... ++ extra arguments for message's format' ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      utils.quit(-1, "Error '%s' happened", "42")
      ++-- is equivalent to
      ++utils.quit("Error '%s' happened", "42")  --> Error '42' happened
      ++
    ++ ++
    ++
    ++

    String functions

    ++ ++
    ++
    ++ ++ escape (s) ++
    ++
    ++ escape any Lua 'magic' characters in a string ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ The input string ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ split (s, re, plain, n) ++
    ++
    ++ split a string into a list of strings separated by a delimiter. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ The input string ++
    • ++
    • re ++ optional A Lua string pattern; defaults to '%s+' ++
    • ++
    • plain ++ optional If truthy don't use Lua patterns ++
    • ++
    • n ++ optional maximum number of elements (if there are more, the last will remain un-split) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list-like table ++
    ++ ++

    Raises:

    ++ error if s is not a string ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ splitv (s, re, plain, n) ++
    ++
    ++ split a string into a number of return values. ++ Identical to split but returns multiple sub-strings instead of ++ a single list of sub-strings. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ the string ++
    • ++
    • re ++ A Lua string pattern; defaults to '%s+' ++
    • ++
    • plain ++ don't use Lua patterns ++
    • ++
    • n ++ optional maximum number of splits ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ n values ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      first,next = splitv('user=jane=doe','=', false, 2)
      ++assert(first == "user")
      ++assert(next == "jane=doe")
      ++
    ++ ++
    ++
    ++

    Functional

    ++ ++
    ++
    ++ ++ memoize (func) ++
    ++
    ++ 'memoize' a function (cache returned value for next call). ++ This is useful if you have a function which is relatively expensive, ++ but you don't know in advance what values will be required, so ++ building a table upfront is wasteful/impossible. ++ ++ ++

    Parameters:

    ++
      ++
    • func ++ a function that takes exactly one argument (which later serves as the cache key) and returns a single value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function taking one argument and returning a single value either from the cache or by running the original input function ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ add_function_factory (mt, fun) ++
    ++
    ++ associate a function factory with a type. ++ A function factory takes an object of the given type and ++ returns a function for evaluating it ++ ++ ++

    Parameters:

    ++
      ++
    • mt ++ table ++ metatable ++
    • ++
    • fun ++ function ++ a callable that returns a function ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ string_lambda (lf) ++
    ++
    ++ an anonymous function as a string. This string is either of the form ++ '|args| expression' or is a function of one argument, '_' ++ ++ ++

    Parameters:

    ++
      ++
    • lf ++ function as a string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      string_lambda '|x|x+1' (2) == 3
      ++string_lambda '_+1' (2) == 3
      ++
    ++ ++
    ++
    ++ ++ bind1 (fn, p) ++
    ++
    ++ bind the first argument of the function to a value. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ a function of at least two values (may be an operator string) ++
    • ++
    • p ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function such that f(x) is fn(p,x) ++
    ++ ++

    Raises:

    ++ same as function_arg ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local function f(msg, name)
      ++  print(msg .. " " .. name)
      ++end
      ++
      ++local hello = utils.bind1(f, "Hello")
      ++
      ++print(hello("world"))     --> "Hello world"
      ++print(hello("sunshine"))  --> "Hello sunshine"
      ++
    ++ ++
    ++
    ++ ++ bind2 (fn, p) ++
    ++
    ++ bind the second argument of the function to a value. ++ ++ ++

    Parameters:

    ++
      ++
    • fn ++ a function of at least two values (may be an operator string) ++
    • ++
    • p ++ a value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a function such that f(x) is fn(x,p) ++
    ++ ++

    Raises:

    ++ same as function_arg ++ ++ ++

    Usage:

    ++
      ++
      local function f(a, b, c)
      ++  print(a .. " " .. b .. " " .. c)
      ++end
      ++
      ++local hello = utils.bind1(f, "world")
      ++
      ++print(hello("Hello", "!"))  --> "Hello world !"
      ++print(hello("Bye", "?"))    --> "Bye world ?"
      ++
    ++ ++
    ++
    ++

    Deprecation

    ++ ++
    ++
    ++ ++ set_deprecation_func (func) ++
    ++
    ++ Sets a deprecation warning function. ++ An application can override this function to support proper output of ++ deprecation warnings. The warnings can be generated from libraries or ++ functions by calling utils.raise_deprecation. The default function ++ will write to the 'warn' system (introduced in Lua 5.4, or the compatibility ++ function from the compat module for earlier versions).

    ++ ++

    Note: only applications should set/change this function, libraries should not. ++ ++ ++

    Parameters:

    ++
      ++
    • func ++ a callback with signature: function(msg, trace) both arguments are strings, the latter being optional. ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      -- write to the Nginx logs with OpenResty
      ++utils.set_deprecation_func(function(msg, trace)
      ++  ngx.log(ngx.WARN, msg, (trace and (" " .. trace) or nil))
      ++end)
      ++
      ++-- disable deprecation warnings
      ++utils.set_deprecation_func()
      ++
    ++ ++
    ++
    ++ ++ raise_deprecation (opts) ++
    ++
    ++ raises a deprecation warning. ++ For options see the usage example below.

    ++ ++

    Note: the opts.deprecated_after field is the last version in which ++ a feature or option was NOT YET deprecated! Because when writing the code it ++ is quite often not known in what version the code will land. But the last ++ released version is usually known. ++ ++ ++

    Parameters:

    ++
      ++
    • opts ++ options table ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      warn("@on")   -- enable Lua warnings, they are usually off by default
      ++
      ++function stringx.islower(str)
      ++  raise_deprecation {
      ++    source = "Penlight " .. utils._VERSION,                   -- optional
      ++    message = "function 'islower' was renamed to 'is_lower'", -- required
      ++    version_removed = "2.0.0",                                -- optional
      ++    deprecated_after = "1.2.3",                               -- optional
      ++    no_trace = true,                                          -- optional
      ++  }
      ++  return stringx.is_lower(str)
      ++end
      ++-- output: "[Penlight 1.9.2] function 'islower' was renamed to 'is_lower' (deprecated after 1.2.3, scheduled for removal in 2.0.0)"
      ++
    ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/libraries/pl.xml.html b/extra/penlight/docs/libraries/pl.xml.html +new file mode 100644 +index 0000000..58c98ef +--- /dev/null ++++ b/extra/penlight/docs/libraries/pl.xml.html +@@ -0,0 +1,1356 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++

    Module pl.xml

    ++

    XML LOM Utilities.

    ++

    This implements some useful things on LOM documents, such as returned by lxp.lom.parse. ++ In particular, it can convert LOM back into XML text, with optional pretty-printing control. ++ It is based on stanza.lua from Prosody

    ++ ++ ++
    ++> d = xml.parse "<nodes><node id='1'>alice</node></nodes>"
    ++> = d
    ++<nodes><node id='1'>alice</node></nodes>
    ++> = xml.tostring(d,'','  ')
    ++<nodes>
    ++   <node id='1'>alice</node>
    ++</nodes>
    ++
    ++ ++

    Can be used as a lightweight one-stop-shop for simple XML processing; a simple XML parser is included ++ but the default is to use lxp.lom if it can be found. ++

    ++ Prosody IM
    ++ Copyright (C) 2008-2010 Matthew Wild
    ++ Copyright (C) 2008-2010 Waqas Hussain--
    ++ classic Lua XML parser by Roberto Ierusalimschy.
    ++ modified to output LOM format.
    ++ http://lua-users.org/wiki/LuaXml
    ++ 
    ++ See the Guide

    ++ ++

    Dependencies: pl.utils

    ++ ++

    Soft Dependencies: lxp.lom (fallback is to use basic Lua parser)

    ++ ++ ++

    Functions

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    new (tag[, attr={}])create a new document node.
    parse (text_or_filename, is_file, use_basic)parse an XML document.
    elem (tag, items)Create a Node with a set of children (text or Nodes) and attributes.
    tags (list)given a list of names, return a number of element constructors.
    Doc:addtag (tag[, attrs={}])Adds a document Node, at current position.
    Doc:text (text)Adds a text node, at current position.
    Doc:up ()Moves current position up one level.
    Doc:reset ()Resets current position to top level.
    Doc:add_direct_child (child)Append a child to the current Node (ignoring current position).
    Doc:add_child (child)Append a child at the current position (without changing position).
    Doc:set_attribs (t)Set attributes of a document node.
    Doc:set_attrib (a, v)Set a single attribute of a document node.
    Doc:get_attribs ()Gets the attributes of a document node.
    Doc.subst (template, data)create a substituted copy of a document,
    Doc:child_with_name (tag)Return the first child with a given tag name (non-recursive).
    Doc:get_elements_with_name (tag[, dont_recurse=false])Returns all elements in a document that have a given tag.
    Doc:children ()Iterator over all children of a document node, including text nodes.
    Doc:first_childtag ()Return the first child element of a node, if it exists.
    Doc:matching_tags ([tag=nil[, xmlns=nil]])Iterator that matches tag names, and a namespace (non-recursive).
    Doc:childtags ()Iterator over all child tags of a document node.
    Doc:maptags (callback)Visit child Nodes of a node and call a function, possibly modifying the document.
    xml_escape (str)Escapes a string for safe use in xml.
    xml_unescape (str)Unescapes a string from xml.
    tostring (doc[, b_ind[, t_ind[, a_ind[, xml_preface]]]])Function to pretty-print an XML document.
    Doc:tostring ([b_ind[, t_ind[, a_ind[, xml_preface="<?xml version='1.0'?>"]]]])Method to pretty-print an XML document.
    Doc:get_text ()get the full text value of an element.
    clone (doc[, strsubst])Returns a copy of a document.
    Doc:filter ([strsubst])Returns a copy of a document.
    compare (t1, t2)Compare two documents or elements.
    is_tag (d)is this value a document element?
    walk (doc, depth_first, operation)Calls a function recursively over Nodes in the document.
    parsehtml (s)Parse a well-formed HTML file as a string.
    basic_parse (s, all_text, html)Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version.
    Doc:match (pat)does something...
    ++ ++
    ++
    ++ ++ ++

    Functions

    ++ ++
    ++
    ++ ++ new (tag[, attr={}]) ++
    ++
    ++ create a new document node. ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ string ++ the tag name ++
    • ++
    • attr ++ table ++ attributes (table of name-value pairs) ++ (default {}) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the Node object ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main", { hello = "world", answer = "42" })
      ++print(doc)  -->  <main hello='world' answer='42'/>
      ++
    ++ ++
    ++
    ++ ++ parse (text_or_filename, is_file, use_basic) ++
    ++
    ++ parse an XML document. By default, this uses lxp.lom.parse, but ++ falls back to basic_parse, or if use_basic is truthy ++ ++ ++

    Parameters:

    ++
      ++
    • text_or_filename ++ file or string representation ++
    • ++
    • is_file ++ whether textorfile is a file name or not ++
    • ++
    • use_basic ++ do a basic parse ++
    • ++
    ++ ++

    Returns:

    ++
      ++
    1. ++ a parsed LOM document with the document metatatables set
    2. ++
    3. ++ nil, error the error can either be a file error or a parse error
    4. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ elem (tag, items) ++
    ++
    ++ Create a Node with a set of children (text or Nodes) and attributes. ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ string ++ a tag name ++
    • ++
    • items ++ table or string ++ either a single child (text or Node), or a table where the hash ++ part is the attributes and the list part is the children (text or Nodes). ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the new Node ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.elem("top", "hello world")                -- <top>hello world</top>
      ++local doc = xml.elem("main", xml.new("child"))            -- <main><child/></main>
      ++local doc = xml.elem("main", { "this ", "is ", "nice" })  -- <main>this is nice</main>
      ++local doc = xml.elem("main", { xml.new "this",
      ++                               xml.new "is",
      ++                               xml.new "nice" })          -- <main><this/><is/><nice/></main>
      ++local doc = xml.elem("main", { hello = "world" })         -- <main hello='world'/>
      ++local doc = xml.elem("main", {
      ++  "prefix",
      ++  xml.elem("child", { "this ", "is ", "nice"}),
      ++  "postfix",
      ++  attrib = "value"
      ++})   -- <main attrib='value'>prefix<child>this is nice</child>postfix</main>"
      ++
    ++ ++
    ++
    ++ ++ tags (list) ++
    ++
    ++ given a list of names, return a number of element constructors. ++ If passing a comma-separated string, then whitespace surrounding the values ++ will be stripped.

    ++ ++

    The returned constructor functions are a shortcut to xml.elem where you ++ no longer provide the tag-name, but only the items table. ++ ++ ++

    Parameters:

    ++
      ++
    • list ++ string or table ++ a list of names, or a comma-separated string. ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ (multiple) constructor functions; function(items). For the items ++ parameter see xml.elem. ++
    ++ ++ ++

    See also:

    ++ ++ ++

    Usage:

    ++
      ++
      local new_parent, new_child = xml.tags 'mom, kid'
      ++doc = new_parent {new_child 'Bob', new_child 'Annie'}
      ++-- <mom><kid>Bob</kid><kid>Annie</kid></mom>
      ++
    ++ ++
    ++
    ++ ++ Doc:addtag (tag[, attrs={}]) ++
    ++
    ++ Adds a document Node, at current position. ++ This updates the last inserted position to the new Node. ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ string ++ the tag name ++
    • ++
    • attrs ++ table ++ attributes (table of name-value pairs) ++ (default {}) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main")
      ++doc:addtag("penlight", { hello = "world"})
      ++doc:addtag("expat")  -- added to 'penlight' since position moved
      ++print(doc)  -->  <main><penlight hello='world'><expat/></penlight></main>
      ++
    ++ ++
    ++
    ++ ++ Doc:text (text) ++
    ++
    ++ Adds a text node, at current position. ++ ++ ++

    Parameters:

    ++
      ++
    • text ++ string ++ a string ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main")
      ++doc:text("penlight")
      ++doc:text("expat")
      ++print(doc)  -->  <main><penlightexpat</main>
      ++
    ++ ++
    ++
    ++ ++ Doc:up () ++
    ++
    ++ Moves current position up one level. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:reset () ++
    ++
    ++ Resets current position to top level. ++ Resets to the self node. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:add_direct_child (child) ++
    ++
    ++ Append a child to the current Node (ignoring current position). ++ ++ ++

    Parameters:

    ++
      ++
    • child ++ a child node (either text or a document) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main")
      ++doc:add_direct_child("dog")
      ++doc:add_direct_child(xml.new("child"))
      ++doc:add_direct_child("cat")
      ++print(doc)  -->  <main>dog<child/>cat</main>
      ++
    ++ ++
    ++
    ++ ++ Doc:add_child (child) ++
    ++
    ++ Append a child at the current position (without changing position). ++ ++ ++

    Parameters:

    ++
      ++
    • child ++ a child node (either text or a document) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main")
      ++doc:addtag("one")
      ++doc:add_child(xml.new("item1"))
      ++doc:add_child(xml.new("item2"))
      ++doc:add_child(xml.new("item3"))
      ++print(doc)  -->  <main><one><item1/><item2/><item3/></one></main>
      ++
    ++ ++
    ++
    ++ ++ Doc:set_attribs (t) ++
    ++
    ++ Set attributes of a document node. ++ Will add/overwrite values, but will not remove existing ones. ++ Operates on the Node itself, will not take position into account. ++ ++ ++

    Parameters:

    ++
      ++
    • t ++ table ++ a table containing attribute/value pairs ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:set_attrib (a, v) ++
    ++
    ++ Set a single attribute of a document node. ++ Operates on the Node itself, will not take position into account. ++ ++ ++

    Parameters:

    ++
      ++
    • a ++ attribute ++
    • ++
    • v ++ its value, pass in nil to delete the attribute ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the current node (self) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:get_attribs () ++
    ++
    ++ Gets the attributes of a document node. ++ Operates on the Node itself, will not take position into account. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ table with attributes (attribute/value pairs) ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc.subst (template, data) ++
    ++
    ++ create a substituted copy of a document, ++ ++ ++

    Parameters:

    ++
      ++
    • template ++ may be a document or a string representation which will be parsed and cached ++
    • ++
    • data ++ a table of name-value pairs or a list of such tables ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ an XML document ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:child_with_name (tag) ++
    ++
    ++ Return the first child with a given tag name (non-recursive). ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ the tag name ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ the child Node found or nil if not found ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:get_elements_with_name (tag[, dont_recurse=false]) ++
    ++
    ++ Returns all elements in a document that have a given tag. ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ string ++ a tag name ++
    • ++
    • dont_recurse ++ boolean ++ optionally only return the immediate children with this tag name ++ (default false) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a list of elements found, list will be empty if none was found. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:children () ++
    ++
    ++ Iterator over all children of a document node, including text nodes. ++ This function is not recursive, so returns only direct child nodes. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ iterator that returns a single Node per iteration. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:first_childtag () ++
    ++
    ++ Return the first child element of a node, if it exists. ++ This will skip text nodes. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ first child Node or nil if there is none. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:matching_tags ([tag=nil[, xmlns=nil]]) ++
    ++
    ++ Iterator that matches tag names, and a namespace (non-recursive). ++ ++ ++

    Parameters:

    ++
      ++
    • tag ++ string ++ tag names to return. Returns all tags if not provided. ++ (default nil) ++
    • ++
    • xmlns ++ string ++ the namespace value ('xmlns' attribute) to return. If not ++ provided will match all namespaces. ++ (default nil) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ iterator that returns a single Node per iteration. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:childtags () ++
    ++
    ++ Iterator over all child tags of a document node. This will skip over ++ text nodes. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ iterator that returns a single Node per iteration. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:maptags (callback) ++
    ++
    ++ Visit child Nodes of a node and call a function, possibly modifying the document. ++ Text elements will be skipped. ++ This is not recursive, so only direct children will be passed. ++ ++ ++

    Parameters:

    ++
      ++
    • callback ++ function ++ a function with signature function(node), passed the node. ++ The element will be updated with the returned value, or deleted if it returns nil. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ xml_escape (str) ++
    ++
    ++ Escapes a string for safe use in xml. ++ Handles quotes(single+double), less-than, greater-than, and ampersand. ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ string value to escape ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ escaped string ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local esc = xml.xml_escape([["'<>&]])  --> "&quot;&apos;&lt;&gt;&amp;"
      ++
    ++ ++
    ++
    ++ ++ xml_unescape (str) ++
    ++
    ++ Unescapes a string from xml. ++ Handles quotes(single+double), less-than, greater-than, and ampersand. ++ ++ ++

    Parameters:

    ++
      ++
    • str ++ string ++ string value to unescape ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ unescaped string ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local unesc = xml.xml_escape("&quot;&apos;&lt;&gt;&amp;")  --> [["'<>&]]
      ++
    ++ ++
    ++
    ++ ++ tostring (doc[, b_ind[, t_ind[, a_ind[, xml_preface]]]]) ++
    ++
    ++ Function to pretty-print an XML document. ++ ++ ++

    Parameters:

    ++
      ++
    • doc ++ an XML document ++
    • ++
    • b_ind ++ string or int ++ an initial block-indent (required when t_ind is set) ++ (optional) ++
    • ++
    • t_ind ++ string or int ++ an tag-indent for each level (required when a_ind is set) ++ (optional) ++
    • ++
    • a_ind ++ string or int ++ if given, indent each attribute pair and put on a separate line ++ (optional) ++
    • ++
    • xml_preface ++ string or bool ++ force prefacing with default or custom , if truthy then &lt;?xml version='1.0'?&gt; will be used as default. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string representation ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ Doc:tostring ([b_ind[, t_ind[, a_ind[, xml_preface="<?xml version='1.0'?>"]]]]) ++
    ++
    ++ Method to pretty-print an XML document. ++ Invokes xml.tostring. ++ ++ ++

    Parameters:

    ++
      ++
    • b_ind ++ string or int ++ an initial indent (required when t_ind is set) ++ (optional) ++
    • ++
    • t_ind ++ string or int ++ an indent for each level (required when a_ind is set) ++ (optional) ++
    • ++
    • a_ind ++ string or int ++ if given, indent each attribute pair and put on a separate line ++ (optional) ++
    • ++
    • xml_preface ++ string ++ force prefacing with default or custom ++ (default "<?xml version='1.0'?>") ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ a string representation ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ Doc:get_text () ++
    ++
    ++ get the full text value of an element. ++ ++ ++ ++

    Returns:

    ++
      ++ ++ a single string with all text elements concatenated ++
    ++ ++ ++ ++

    Usage:

    ++
      ++
      local doc = xml.new("main")
      ++doc:text("one")
      ++doc:add_child(xml.elem "two")
      ++doc:text("three")
      ++
      ++local t = doc:get_text()    -->  "onethree"
      ++
    ++ ++
    ++
    ++ ++ clone (doc[, strsubst]) ++
    ++
    ++ ++

    Returns a copy of a document. ++ The strsubst parameter is a callback with signature function(object, kind, parent).

    ++ ++

    Param kind has the following values, and parameters:

    ++ ++
      ++
    • "*TAG": object is the tag-name, parent is the Node object. Returns the new tag name.

    • ++
    • "*TEXT": object is the text-element, parent is the Node object. Returns the new text value.

    • ++
    • other strings not prefixed with *: kind is the attribute name, object is the ++ attribute value, parent is the Node object. Returns the new attribute value.

    • ++
    ++ ++ ++ ++ ++

    Parameters:

    ++
      ++
    • doc ++ Node or string ++ a Node object or string (text node) ++
    • ++
    • strsubst ++ function ++ an optional function for handling string copying ++ which could do substitution, etc. ++ (optional) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ copy of the document ++
    ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ Doc:filter ([strsubst]) ++
    ++
    ++ Returns a copy of a document. ++ This is the method version of xml.clone. ++ ++ ++

    Parameters:

    ++
      ++
    • strsubst ++ function ++ an optional function for handling string copying ++ (optional) ++
    • ++
    ++ ++ ++ ++

    See also:

    ++ ++ ++ ++
    ++
    ++ ++ compare (t1, t2) ++
    ++
    ++ Compare two documents or elements. ++ Equality is based on tag, child nodes (text and tags), attributes and order ++ of those (order only fails if both are given, and not equal). ++ ++ ++

    Parameters:

    ++
      ++
    • t1 ++ Node or string ++ a Node object or string (text node) ++
    • ++
    • t2 ++ Node or string ++ a Node object or string (text node) ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ boolean ++ true when the Nodes are equal. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ is_tag (d) ++
    ++
    ++ is this value a document element? ++ ++ ++

    Parameters:

    ++
      ++
    • d ++ any value ++
    • ++
    ++ ++

    Returns:

    ++
      ++ ++ boolean ++ true if it is a table with property tag being a string value. ++
    ++ ++ ++ ++ ++
    ++
    ++ ++ walk (doc, depth_first, operation) ++
    ++
    ++ Calls a function recursively over Nodes in the document. ++ Will only call on tags, it will skip text nodes. ++ The function signature for operation is function(tag_name, Node). ++ ++ ++

    Parameters:

    ++
      ++
    • doc ++ Node or string ++ a Node object or string (text node) ++
    • ++
    • depth_first ++ boolean ++ visit child nodes first, then the current node ++
    • ++
    • operation ++ function ++ a function which will receive the current tag name and current node. ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ parsehtml (s) ++
    ++
    ++ Parse a well-formed HTML file as a string. ++ Tags are case-insensitive, DOCTYPE is ignored, and empty elements can be .. empty. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ the HTML ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ basic_parse (s, all_text, html) ++
    ++
    ++ Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version. ++ ++ ++

    Parameters:

    ++
      ++
    • s ++ the XML document to be parsed. ++
    • ++
    • all_text ++ if true, preserves all whitespace. Otherwise only text containing non-whitespace is included. ++
    • ++
    • html ++ if true, uses relaxed HTML rules for parsing ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ Doc:match (pat) ++
    ++
    ++ does something... ++ ++ ++

    Parameters:

    ++
      ++
    • pat ++ ++ ++ ++
    • ++
    ++ ++ ++ ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/01-introduction.md.html b/extra/penlight/docs/manual/01-introduction.md.html +new file mode 100644 +index 0000000..8ff64eb +--- /dev/null ++++ b/extra/penlight/docs/manual/01-introduction.md.html +@@ -0,0 +1,843 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Introduction

    ++ ++

    ++

    Purpose

    ++ ++

    It is often said of Lua that it does not include batteries. That is because the ++goal of Lua is to produce a lean expressive language that will be used on all ++sorts of machines, (some of which don't even have hierarchical filesystems). The ++Lua language is the equivalent of an operating system kernel; the creators of Lua ++do not see it as their responsibility to create a full software ecosystem around ++the language. That is the role of the community.

    ++ ++

    A principle of software design is to recognize common patterns and reuse them. If ++you find yourself writing things like `io.write(string.format('the answer is %d ++',42))` more than a number of times then it becomes useful just to define a ++function printf. This is good, not just because repeated code is harder to ++maintain, but because such code is easier to read, once people understand your ++libraries.

    ++ ++

    Penlight captures many such code patterns, so that the intent of your code ++becomes clearer. For instance, a Lua idiom to copy a table is {unpack(t)}, but ++this will only work for 'small' tables (for a given value of 'small') so it is ++not very robust. Also, the intent is not clear. So tablex.deepcopy is provided, ++which will also copy nested tables and and associated metatables, so it can be ++used to clone complex objects.

    ++ ++

    The default error handling policy follows that of the Lua standard libraries: if ++a argument is the wrong type, then an error will be thrown, but otherwise we ++return nil,message if there is a problem. There are some exceptions; functions ++like input.fields default to shutting down the program immediately with a ++useful message. This is more appropriate behaviour for a script than providing ++a stack trace. (However, this default can be changed.) The lexer functions always ++throw errors, to simplify coding, and so should be wrapped in pcall.

    ++ ++

    If you are used to Python conventions, please note that all indices consistently ++start at 1.

    ++ ++

    The Lua function table.foreach has been deprecated in favour of the for in ++statement, but such an operation becomes particularly useful with the ++higher-order function support in Penlight. Note that tablex.foreach reverses ++the order, so that the function is passed the value and then the key. Although ++perverse, this matches the intended use better.

    ++ ++

    The only important external dependence of Penlight is ++LuaFileSystem ++(lfs), and if you want dir.copyfile to work cleanly on Windows, you will need ++either alien or be using ++LuaJIT as well. (The fallback is to call the equivalent ++shell commands.)

    ++ ++

    ++

    To Inject or not to Inject?

    ++ ++

    It was realized a long time ago that large programs needed a way to keep names ++distinct by putting them into tables (Lua), namespaces (C++) or modules ++(Python). It is obviously impossible to run a company where everyone is called ++'Bruce', except in Monty Python skits. These 'namespace clashes' are more of a ++problem in a simple language like Lua than in C++, because C++ does more ++complicated lookup over 'injected namespaces'. However, in a small group of ++friends, 'Bruce' is usually unique, so in particular situations it's useful to ++drop the formality and not use last names. It depends entirely on what kind of ++program you are writing, whether it is a ten line script or a ten thousand line ++program.

    ++ ++

    So the Penlight library provides the formal way and the informal way, without ++imposing any preference. You can do it formally like:

    ++ ++ ++
    ++local utils = require 'pl.utils'
    ++utils.printf("%s\n","hello, world!")
    ++
    ++ ++

    or informally like:

    ++ ++ ++
    ++require 'pl'
    ++utils.printf("%s\n","That feels better")
    ++
    ++ ++

    require 'pl' makes all the separate Penlight modules available, without needing ++to require them each individually.

    ++ ++

    Generally, the formal way is better when writing modules, since then there are no ++global side-effects and the dependencies of your module are made explicit.

    ++ ++

    Andrew Starks has contributed another way, which balances nicely between the ++formal need to keep the global table uncluttered and the informal need for ++convenience. require'pl.import_into' returns a function, which accepts a table ++for injecting Penlight into, or if no table is given, it passes back a new one.

    ++ ++ ++
    ++local pl = require'pl.import_into'()
    ++
    ++ ++

    The table pl is a 'lazy table' which loads modules as needed, so we can then ++use pl.utils.printf and so forth, without an explicit `require' or harming any ++globals.

    ++ ++

    If you are using _ENV with Lua 5.2 to define modules, then here is a way to ++make Penlight available within a module:

    ++ ++ ++
    ++local _ENV,M = require 'pl.import_into' ()
    ++
    ++function answer ()
    ++    -- all the Penlight modules are available!
    ++    return pretty.write(utils.split '10 20  30', '')
    ++end
    ++
    ++return M
    ++
    ++ ++

    The default is to put Penlight into \_ENV, which has the unintended effect of ++making it available from the module (much as module(...,package.seeall) does). ++To satisfy both convenience and safety, you may pass true to this function, and ++then the module M is not the same as \_ENV, but only contains the exported ++functions.

    ++ ++

    Otherwise, Penlight will not bring in functions into the global table, or ++clobber standard tables like 'io'. require('pl') will bring tables like ++'utils','tablex',etc into the global table if they are used. This ++'load-on-demand' strategy ensures that the whole kitchen sink is not loaded up ++front, so this method is as efficient as explicitly loading required modules.

    ++ ++

    You have an option to bring the pl.stringx methods into the standard string ++table. All strings have a metatable that allows for automatic lookup in string, ++so we can say s:upper(). Importing stringx allows for its functions to also ++be called as methods: s:strip(),etc:

    ++ ++ ++
    ++require 'pl'
    ++stringx.import()
    ++
    ++ ++

    or, more explicitly:

    ++ ++ ++
    ++require('pl.stringx').import()
    ++
    ++ ++

    A more delicate operation is importing tables into the local environment. This is ++convenient when the context makes the meaning of a name very clear:

    ++ ++ ++
    ++> require 'pl'
    ++> utils.import(math)
    ++> = sin(1.2)
    ++0.93203908596723
    ++
    ++ ++

    utils.import can also be passed a module name as a string, which is first ++required and then imported. If used in a module, import will bring the symbols ++into the module context.

    ++ ++

    Keeping the global scope simple is very necessary with dynamic languages. Using ++global variables in a big program is always asking for trouble, especially since ++you do not have the spell-checking provided by a compiler. The pl.strict ++module enforces a simple rule: globals must be 'declared'. This means that they ++must be assigned before use; assigning to nil is sufficient.

    ++ ++ ++
    ++> require 'pl.strict'
    ++> print(x)
    ++stdin:1: variable 'x' is not declared
    ++> x = nil
    ++> print(x)
    ++nil
    ++
    ++ ++

    The strict module provided by Penlight is compatible with the 'load-on-demand' ++scheme used by require 'pl.

    ++ ++

    strict also disallows assignment to global variables, except in the main ++program. Generally, modules have no business messing with global scope; if you ++must do it, then use a call to rawset. Similarly, if you have to check for the ++existence of a global, use rawget.

    ++ ++

    If you wish to enforce strictness globally, then just add require 'pl.strict' ++at the end of pl/init.lua, otherwise call it from your main program.

    ++ ++

    As from 1.1.0, this module provides a strict.module function which creates (or ++modifies) modules so that accessing an unknown function or field causes an error.

    ++ ++

    For example,

    ++ ++ ++
    ++-- mymod.lua
    ++local strict = require 'pl.strict'
    ++local M = strict.module (...)
    ++
    ++function M.answer ()
    ++    return 42
    ++end
    ++
    ++return M
    ++
    ++ ++

    If you were to accidentally type mymod.Answer(), then you would get a runtime ++error: "variable 'Answer' is not declared in 'mymod'".

    ++ ++

    This can be applied to existing modules. You may desire to have the same level ++of checking for the Lua standard libraries:

    ++ ++ ++
    ++strict.make_all_strict(_G)
    ++
    ++ ++

    Thereafter a typo such as math.cosine will give you an explicit error, rather ++than merely returning a nil that will cause problems later.

    ++ ++

    ++

    What are function arguments in Penlight?

    ++ ++

    Many functions in Penlight themselves take function arguments, like map which ++applies a function to a list, element by element. You can use existing ++functions, like math.max, anonymous functions (like `function(x,y) return x > y ++end ), or operations by name (e.g '*' or '..'). The module pl.operator` exports ++all the standard Lua operations, like the Python module of the same name. ++Penlight allows these to be referred to by name, so operator.gt can be more ++concisely expressed as '>'.

    ++ ++

    Note that the map functions pass any extra arguments to the function, so we can ++have ls:filter('>',0), which is a shortcut for ++ls:filter(function(x) return x > 0 end).

    ++ ++

    Finally, pl.func supports placeholder expressions in the Boost lambda style, ++so that an anonymous function to multiply the two arguments can be expressed as ++\1*\2.

    ++ ++

    To use them directly, note that all function arguments in Penlight go through ++utils.function_arg. pl.func registers itself with this function, so that you ++can directly use placeholder expressions with standard methods:

    ++ ++ ++
    ++> _1 = func._1
    ++> = List{10,20,30}:map(_1+1)
    ++{11,21,31}
    ++
    ++ ++

    Another option for short anonymous functions is provided by ++utils.string_lambda; this is invoked automatically:

    ++ ++ ++
    ++> = List{10,20,30}:map '|x| x + 1'
    ++{11,21,31}
    ++
    ++ ++

    ++

    Pros and Cons of Loopless Programming

    ++ ++

    The standard loops-and-ifs 'imperative' style of programming is dominant, and ++often seems to be the 'natural' way of telling a machine what to do. It is in ++fact very much how the machine does things, but we need to take a step back and ++find ways of expressing solutions in a higher-level way. For instance, applying ++a function to all elements of a list is a common operation:

    ++ ++ ++
    ++local res = {}
    ++for i = 1,#ls do
    ++    res[i] = fun(ls[i])
    ++end
    ++
    ++ ++

    This can be efficiently and succinctly expressed as ls:map(fun). Not only is ++there less typing but the intention of the code is clearer. If readers of your ++code spend too much time trying to guess your intention by analyzing your loops, ++then you have failed to express yourself clearly. Similarly, ls:filter('>',0) ++will give you all the values in a list greater than zero. (Of course, if you ++don't feel like using List, or have non-list-like tables, then pl.tablex ++offers the same facilities. In fact, the List methods are implemented using ++tablex functions.)

    ++ ++

    A common observation is that loopless programming is less efficient, particularly ++in the way it uses memory. ls1:map2('*',ls2):reduce '+' will give you the dot ++product of two lists, but an unnecessary temporary list is created. But ++efficiency is relative to the actual situation, it may turn out to be fast ++enough, or may not appear in any crucial inner loops, etc.

    ++ ++

    Writing loops is 'error-prone and tedious', as Stroustrup says. But any ++half-decent editor can be taught to do much of that typing for you. The question ++should actually be: is it tedious to read loops? As with natural language, ++programmers tend to read chunks at a time. A for-loop causes no surprise, and ++probably little brain activity. One argument for loopless programming is the ++loops that you do write stand out more, and signal 'something different ++happening here'. It should not be an all-or-nothing thing, since most programs ++require a mixture of idioms that suit the problem. Some languages (like APL) do ++nearly everything with map and reduce operations on arrays, and so solutions can ++sometimes seem forced. Wisdom is knowing when a particular idiom makes a ++particular problem easy to solve and the solution easy to explain afterwards.

    ++ ++

    ++

    Generally useful functions.

    ++ ++

    The function printf discussed earlier is included in pl.utils because it ++makes properly formatted output easier. (There is an equivalent fprintf which ++also takes a file object parameter, just like the C function.)

    ++ ++

    Splitting a string using a delimiter is a fairly common operation, hence split.

    ++ ++

    Utility functions like is_type help with identifying what ++kind of animal you are dealing with. ++The Lua type function handles the basic types, but can't distinguish between ++different kinds of objects, which are all tables. So is_type handles both ++cases, like is_type(s,"string") and is_type(ls,List).

    ++ ++

    A common pattern when working with Lua varargs is capturing all the arguments in ++a table:

    ++ ++ ++
    ++function t(...)
    ++    local args = {...}
    ++    ...
    ++end
    ++
    ++ ++

    But this will bite you someday when nil is one of the arguments, since this ++will put a 'hole' in your table. In particular, #ls will only give you the size ++upto the nil value. Hence the need for table.pack - this is a new Lua 5.2 ++function which Penlight defines also for Lua 5.1.

    ++ ++ ++
    ++function t(...)
    ++    local args,n = table.pack(...)
    ++    for i = 1,n do
    ++      ...
    ++    end
    ++end
    ++
    ++ ++

    The 'memoize' pattern occurs when you have a function which is expensive to call, ++but will always return the same value subsequently. utils.memoize is given a ++function, and returns another function. This calls the function the first time, ++saves the value for that argument, and thereafter for that argument returns the ++saved value. This is a more flexible alternative to building a table of values ++upfront, since in general you won't know what values are needed.

    ++ ++ ++
    ++sum = utils.memoize(function(n)
    ++    local sum = 0
    ++    for i = 1,n do sum = sum + i end
    ++    return sum
    ++end)
    ++...
    ++s = sum(1e8) --takes time!
    ++...
    ++s = sum(1e8) --returned saved value!
    ++
    ++ ++

    Penlight is fully compatible with Lua 5.1, 5.2 and LuaJIT 2. To ensure this, ++utils also defines the global Lua 5.2 ++load function as utils.load

    ++ ++
      ++
    • the input (either a string or a function)
    • ++
    • the source name used in debug information
    • ++
    • the mode is a string that can have either or both of 'b' or 't', depending on ++ whether the source is a binary chunk or text code (default is 'bt')
    • ++
    • the environment for the compiled chunk
    • ++
    ++ ++

    Using utils.load should reduce the need to call the deprecated function setfenv, ++and make your Lua 5.1 code 5.2-friendly.

    ++ ++

    The utils module exports getfenv and setfenv for ++Lua 5.2 as well, based on code by Sergey Rozhenko. Note that these functions can fail ++for functions which don't access any globals.

    ++ ++

    ++

    Application Support

    ++ ++

    app.parse_args is a simple command-line argument parser. If called without any ++arguments, it tries to use the global arg array. It returns the flags ++(options beginning with '-') as a table of name/value pairs, and the arguments ++as an array. It knows about long GNU-style flag names, e.g. --value, and ++groups of short flags are understood, so that -ab is short for -a -b. The ++flags result would then look like {value=true,a=true,b=true}.

    ++ ++

    Flags may take values. The command-line --value=open -n10 would result in ++{value='open',n='10'}; generally you can use '=' or ':' to separate the flag ++from its value, except in the special case where a short flag is followed by an ++integer. Or you may specify upfront that some flags have associated values, and ++then the values will follow the flag.

    ++ ++ ++
    ++> require 'pl'
    ++> flags,args = app.parse_args({'-o','fred','-n10','fred.txt'},{o=true})
    ++> pretty.dump(flags)
    ++{o='fred',n='10'}
    ++
    ++ ++

    parse_args is not intelligent or psychic; it will not convert any flag values ++or arguments for you, or raise errors. For that, have a look at ++Lapp.

    ++ ++

    An application which consists of several files usually cannot use require to ++load files in the same directory as the main script. app.require_here() ++ensures that the Lua module path is modified so that files found locally are ++found first. In the examples directory, test-symbols.lua uses this function ++to ensure that it can find symbols.lua even if it is not run from this directory.

    ++ ++

    app.appfile will create a filename that your application can use to store its ++private data, based on the script name. For example, app.appfile "test.txt" ++from a script called testapp.lua produces the following file on my Windows ++machine:

    ++ ++
    C:\Documents and Settings\SJDonova\.testapp\test.txt
    ++
    ++ ++ ++

    and the equivalent on my Linux machine:

    ++ ++
    /home/sdonovan/.testapp/test.txt
    ++
    ++ ++ ++

    If .testapp does not exist, it will be created.

    ++ ++

    Penlight makes it convenient to save application data in Lua format. You can use ++pretty.dump(t,file) to write a Lua table in a human-readable form to a file, ++and pretty.read(file.read(file)) to generate the table again, using the ++pretty module.

    ++ ++ ++

    ++

    Simplifying Object-Oriented Programming in Lua

    ++ ++

    Lua is similar to JavaScript in that the concept of class is not directly ++supported by the language. In fact, Lua has a very general mechanism for ++extending the behaviour of tables which makes it straightforward to implement ++classes. A table's behaviour is controlled by its metatable. If that metatable ++has a \\index function or table, this will handle looking up anything which is ++not found in the original table. A class is just a table with an __index key ++pointing to itself. Creating an object involves making a table and setting its ++metatable to the class; then when handling obj.fun, Lua first looks up fun in ++the table obj, and if not found it looks it up in the class. obj:fun(a) is ++just short for obj.fun(obj,a). So with the metatable mechanism and this bit of ++syntactic sugar, it is straightforward to implement classic object orientation.

    ++ ++ ++
    ++-- animal.lua
    ++
    ++class = require 'pl.class'
    ++
    ++class.Animal()
    ++
    ++function Animal:_init(name)
    ++    self.name = name
    ++end
    ++
    ++function Animal:__tostring()
    ++  return self.name..': '..self:speak()
    ++end
    ++
    ++class.Dog(Animal)
    ++
    ++function Dog:speak()
    ++  return 'bark'
    ++end
    ++
    ++class.Cat(Animal)
    ++
    ++function Cat:_init(name,breed)
    ++    self:super(name)  -- must init base!
    ++    self.breed = breed
    ++end
    ++
    ++function Cat:speak()
    ++  return 'meow'
    ++end
    ++
    ++class.Lion(Cat)
    ++
    ++function Lion:speak()
    ++  return 'roar'
    ++end
    ++
    ++fido = Dog('Fido')
    ++felix = Cat('Felix','Tabby')
    ++leo = Lion('Leo','African')
    ++
    ++$ lua -i animal.lua
    ++> = fido,felix,leo
    ++Fido: bark      Felix: meow     Leo: roar
    ++> = leo:is_a(Animal)
    ++true
    ++> = leo:is_a(Dog)
    ++false
    ++> = leo:is_a(Cat)
    ++true
    ++
    ++ ++

    All Animal does is define \\tostring, which Lua will use whenever a string ++representation is needed of the object. In turn, this relies on speak, which is ++not defined. So it's what C++ people would call an abstract base class; the ++specific derived classes like Dog define speak. Please note that if derived ++classes have their own constructors, they must explicitly call the base ++constructor for their base class; this is conveniently available as the super ++method.

    ++ ++

    Note that (as always) there are multiple ways to implement OOP in Lua; this method ++uses the classic 'a class is the __index of its objects' but does 'fat inheritance'; ++methods from the base class are copied into the new class. The advantage of this is ++that you are not penalized for long inheritance chains, for the price of larger classes, ++but generally objects outnumber classes! (If not, something odd is going on with your design.)

    ++ ++

    All such objects will have a is_a method, which looks up the inheritance chain ++to find a match. Another form is class_of, which can be safely called on all ++objects, so instead of leo:is_a(Animal) one can say Animal:class_of(leo).

    ++ ++

    There are two ways to define a class, either class.Name() or Name = class(); ++both work identically, except that the first form will always put the class in ++the current environment (whether global or module); the second form provides more ++flexibility about where to store the class. The first form does name the class ++by setting the _name field, which can be useful in identifying the objects of ++this type later. This session illustrates the usefulness of having named classes, ++if no __tostring method is explicitly defined.

    ++ ++ ++
    ++> class.Fred()
    ++> a = Fred()
    ++> = a
    ++Fred: 00459330
    ++> Alice = class()
    ++> b = Alice()
    ++> = b
    ++table: 00459AE8
    ++> Alice._name = 'Alice'
    ++> = b
    ++Alice: 00459AE8
    ++
    ++ ++

    So Alice = class(); Alice._name = 'Alice' is exactly the same as class.Alice().

    ++ ++

    This useful notation is borrowed from Hugo Etchegoyen's ++classlib which further ++extends this concept to allow for multiple inheritance. Notice that the ++more convenient form puts the class name in the current environment! That is, ++you may use it safely within modules using the old-fashioned module() ++or the new _ENV mechanism.

    ++ ++

    There is always more than one way of doing things in Lua; some may prefer this ++style for creating classes:

    ++ ++ ++
    ++local class = require 'pl.class'
    ++
    ++class.Named {
    ++    _init = function(self,name)
    ++        self.name = name
    ++    end;
    ++
    ++    __tostring = function(self)
    ++        return 'boo '..self.name
    ++    end;
    ++}
    ++
    ++b = Named 'dog'
    ++print(b)
    ++--> boo dog
    ++
    ++ ++

    Note that you have to explicitly declare self and end each function definition ++with a semi-colon or comma, since this is a Lua table. To inherit from a base class, ++set the special field _base to the class in this table.

    ++ ++

    Penlight provides a number of useful classes; there is List, which is a Lua ++clone of the standard Python list object, and Set which represents sets. There ++are three kinds of map defined: Map, MultiMap (where a key may have ++multiple values) and OrderedMap (where the order of insertion is remembered.). ++There is nothing special about these classes and you may inherit from them.

    ++ ++

    A powerful thing about dynamic languages is that you can redefine existing classes ++and functions, which is often called 'monkey patching' It's entertaining and convenient, ++but ultimately anti-social; you may modify List but then any other modules using ++this shared resource can no longer be sure about its behaviour. (This is why you ++must say stringx.import() explicitly if you want the extended string methods - it ++would be a bad default.) Lua is particularly open to modification but the ++community is not as tolerant of monkey-patching as the Ruby community, say. You may ++wish to add some new methods to List? Cool, but that's what subclassing is for.

    ++ ++ ++
    ++class.Strings(List)
    ++
    ++function Strings:my_method()
    ++...
    ++end
    ++
    ++ ++

    It's definitely more useful to define exactly how your objects behave ++in unknown conditions. All classes have a catch method you can use to set ++a handler for unknown lookups; the function you pass looks exactly like the ++__index metamethod.

    ++ ++ ++
    ++Strings:catch(function(self,name)
    ++    return function() error("no such method "..name,2) end
    ++end)
    ++
    ++ ++

    In this case we're just customizing the error message, but ++creative things can be done. Consider this code from test-vector.lua:

    ++ ++ ++
    ++Strings:catch(List.default_map_with(string))
    ++
    ++ls = Strings{'one','two','three'}
    ++asserteq(ls:upper(),{'ONE','TWO','THREE'})
    ++asserteq(ls:sub(1,2),{'on','tw','th'})
    ++
    ++ ++

    So we've converted a unknown method invocation into a map using the function of ++that name found in string. So for a Vector (which is a specialization of List ++for numbers) it makes sense to make math the default map so that v:sin() makes ++sense.

    ++ ++

    Note that map operations return a object of the same type - this is often called ++covariance. So ls:upper() itself returns a Strings object.

    ++ ++

    This is not always what you want, but objects can always be cast to the desired type. ++(cast doesn't create a new object, but returns the object passed.)

    ++ ++ ++
    ++local sizes = ls:map '#'
    ++asserteq(sizes, {3,3,5})
    ++asserteq(utils.type(sizes),'Strings')
    ++asserteq(sizes:is_a(Strings),true)
    ++sizes = Vector:cast(sizes)
    ++asserteq(utils.type(sizes),'Vector')
    ++asserteq(sizes+1,{4,4,6})
    ++
    ++ ++

    About utils.type: it can only return a string for a class type if that class does ++in fact have a _name field.

    ++ ++ ++

    Properties are a useful object-oriented pattern. We wish to control access to a ++field, but don't wish to force the user of the class to say obj:get_field() ++etc. This excerpt from tests/test-class.lua shows how it is done:

    ++ ++ ++ ++
    ++local MyProps = class(class.properties)
    ++local setted_a, got_b
    ++
    ++function MyProps:_init ()
    ++    self._a = 1
    ++    self._b = 2
    ++end
    ++
    ++function MyProps:set_a (v)
    ++    setted_a = true
    ++    self._a = v
    ++end
    ++
    ++function MyProps:get_b ()
    ++    got_b = true
    ++    return self._b
    ++end
    ++
    ++local mp = MyProps()
    ++
    ++mp.a = 10
    ++
    ++asserteq(mp.a,10)
    ++asserteq(mp.b,2)
    ++asserteq(setted_a and got_b, true)
    ++
    ++ ++

    The convention is that the internal field name is prefixed with an underscore; ++when reading mp.a, first a check for an explicit getter get_a and then only ++look for _a. Simularly, writing mp.a causes the setter set_a to be used.

    ++ ++

    This is cool behaviour, but like much Lua metaprogramming, it is not free. Method ++lookup on such objects goes through \\index as before, but now \\index is a ++function which has to explicitly look up methods in the class, before doing any ++property indexing, which is not going to be as fast as field lookup. If however, ++your accessors actually do non-trivial things, then the extra overhead could be ++worth it.

    ++ ++

    This is not really intended for access control because external code can write ++to mp._a directly. It is possible to have this kind of control in Lua, but it ++again comes with run-time costs.

    ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/02-arrays.md.html b/extra/penlight/docs/manual/02-arrays.md.html +new file mode 100644 +index 0000000..daa493d +--- /dev/null ++++ b/extra/penlight/docs/manual/02-arrays.md.html +@@ -0,0 +1,914 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Tables and Arrays

    ++ ++

    ++ ++

    ++

    Python-style Lists

    ++ ++

    One of the elegant things about Lua is that tables do the job of both lists and ++dicts (as called in Python) or vectors and maps, (as called in C++), and they do ++it efficiently. However, if we are dealing with 'tables with numerical indices' ++we may as well call them lists and look for operations which particularly make ++sense for lists. The Penlight List class was originally written by Nick Trout ++for Lua 5.0, and translated to 5.1 and extended by myself. It seemed that ++borrowing from Python was a good idea, and this eventually grew into Penlight.

    ++ ++

    Here is an example showing List in action; it redefines __tostring, so that ++it can print itself out more sensibly:

    ++ ++ ++
    ++> List = require 'pl.List'  --> automatic with require 'pl' <---
    ++> l = List()
    ++> l:append(10)
    ++> l:append(20)
    ++> = l
    ++{10,20}
    ++> l:extend {30,40}
    ++{10,20,30,40}
    ++> l:insert(1,5)
    ++{5,10,20,30,40}
    ++> = l:pop()
    ++40
    ++> = l
    ++{5,10,20,30}
    ++> = l:index(30)
    ++4
    ++> = l:contains(30)
    ++true
    ++> = l:reverse()  ---> note: doesn't make a copy!
    ++{30,20,10,5}
    ++
    ++ ++

    Although methods like sort and reverse operate in-place and change the list, ++they do return the original list. This makes it possible to do method chaining, ++like ls = ls:append(10):append(20):reverse():append(1). But (and this is an ++important but) no extra copy is made, so ls does not change identity. List ++objects (like tables) are mutable, unlike strings. If you want a copy of a ++list, then List(ls) will do the job, i.e. it acts like a copy constructor. ++However, if passed any other table, List will just set the metatable of the ++table and not make a copy.

    ++ ++

    A particular feature of Python lists is slicing. This is fully supported in ++this version of List, except we use 1-based indexing. So List.slice works ++rather like string.sub:

    ++ ++ ++
    ++> l = List {10,20,30,40}
    ++> = l:slice(1,1)  ---> note: creates a new list!
    ++{10}
    ++> = l:slice(2,2)
    ++{20}
    ++> = l:slice(2,3)
    ++{20,30}
    ++> = l:slice(2,-2)
    ++{20,30}
    ++> = l:slice_assign(2,2,{21,22,23})
    ++{10,21,22,23,30,40}
    ++> = l:chop(1,1)
    ++{21,22,23,30,40}
    ++
    ++ ++

    Functions like slice_assign and chop modify the list; the first is equivalent ++to Pythonl[i1:i2] = seq and the second to del l[i1:i2].

    ++ ++

    List objects are ultimately just Lua 'list-like' tables, but they have extra ++operations defined on them, such as equality and concatention. For regular ++tables, equality is only true if the two tables are identical objects, whereas ++two lists are equal if they have the same contents, i.e. that l1[i]==l2[i] for ++all elements.

    ++ ++ ++
    ++> l1 = List {1,2,3}
    ++> l2 = List {1,2,3}
    ++> = l1 == l2
    ++true
    ++> = l1..l2
    ++{1,2,3,1,2,3}
    ++
    ++ ++

    The List constructor can be passed a function. If so, it's assumed that this is ++an iterator function that can be repeatedly called to generate a sequence. One ++such function is io.lines; the following short, intense little script counts ++the number of lines in standard input:

    ++ ++ ++
    ++-- linecount.lua
    ++require 'pl'
    ++ls = List(io.lines())
    ++print(#ls)
    ++
    ++ ++

    List.iterate captures what List considers a sequence. In particular, it can ++also iterate over all 'characters' in a string:

    ++ ++ ++
    ++> for ch in List.iterate 'help' do io.write(ch,' ') end
    ++h e l p >
    ++
    ++ ++

    Since the function iterate is used internally by the List constructor, ++strings can be made into lists of character strings very easily.

    ++ ++

    There are a number of operations that go beyond the standard Python methods. For ++instance, you can partition a list into a table of sublists using a function. ++In the simplest form, you use a predicate (a function returning a boolean value) ++to partition the list into two lists, one of elements matching and another of ++elements not matching. But you can use any function; if we use type then the ++keys will be the standard Lua type names.

    ++ ++ ++
    ++> ls = List{1,2,3,4}
    ++> ops = require 'pl.operator'
    ++> ls:partition(function(x) return x > 2 end)
    ++{false={1,2},true={3,4}}
    ++> ls = List{'one',math.sin,List{1},10,20,List{1,2}}
    ++> ls:partition(type)
    ++{function={function: 00369110},string={one},number={10,20},table={{1},{1,2}}}
    ++
    ++ ++

    This is one List method which returns a table which is not a List. Bear in ++mind that you can always call a List method on a plain table argument, so ++List.partition(t,type) works as expected. But these functions will only operate ++on the array part of the table.

    ++ ++

    The 'nominal' type of the returned table is pl.Multimap, which describes a mapping ++between keys and multiple values. This does not mean that pl.Multimap is automatically ++loaded whenever you use partition (or List for that matter); this is one of the ++standard metatables which are only filled out when the appropriate module is loaded. ++This allows tables to be tagged appropriately without causing excessive coupling.

    ++ ++

    Stacks occur everywhere in computing. List supports stack-like operations; ++there is already pop (remove and return last value) and append acts like ++push (add a value to the end). push is provided as an alias for append, and ++the other stack operation (size) is simply the size operator #. Queues can ++also be implemented; you use pop to take values out of the queue, and put to ++insert a value at the beginning.

    ++ ++

    You may derive classes from List, and since the list-returning methods ++are covariant, the result of slice etc will return lists of the derived type, ++not List. For instance, consider the specialization of a List type that contains ++numbers in tests/test-list.lua:

    ++ ++ ++
    ++n1 = NA{10,20,30}
    ++n2 = NA{1,2,3}
    ++ns = n1 + 2*n2
    ++asserteq(ns,{12,24,36})
    ++min,max = ns:slice(1,2):minmax()
    ++asserteq(T(min,max),T(12,24))
    ++asserteq(n1:normalize():sum(),1,1e-8)
    ++
    ++ ++

    ++

    Map and Set classes

    ++ ++

    The Map class exposes what Python would call a 'dict' interface, and accesses ++the hash part of the table. The name 'Map' is used to emphasize the interface, ++not the implementation; it is an object which maps keys onto values; m['alice'] ++or the equivalent m.alice is the access operation. This class also provides ++explicit set and get methods, which are trivial for regular maps but get ++interesting when Map is subclassed. The other operation is update, which ++extends a map by copying the keys and values from another table, perhaps ++overwriting existing keys:

    ++ ++ ++
    ++> Map = require 'pl.Map'
    ++> m = Map{one=1,two=2}
    ++> m:update {three=3,four=4,two=20}
    ++> = m == M{one=1,two=20,three=3,four=4}
    ++true
    ++
    ++ ++

    The method values returns a list of the values, and keys returns a list of ++the keys; there is no guarantee of order. getvalues is given a list of keys and ++returns a list of values associated with these keys:

    ++ ++ ++
    ++> m = Map{one=1,two=2,three=3}
    ++> = m:getvalues {'one','three'}
    ++{1,3}
    ++> = m:getvalues(m:keys()) == m:values()
    ++true
    ++
    ++ ++

    When querying the value of a Map, it is best to use the get method:

    ++ ++ ++
    ++> print(m:get 'one', m:get 'two')
    ++1     2
    ++
    ++ ++

    The reason is that m[key] can be ambiguous; due to the current implementation, ++m["get"] will always succeed, because if a value is not present in the map, it ++will be looked up in the Map metatable, which contains a method get. There is ++currently no simple solution to this annoying restriction.

    ++ ++

    There are some useful classes which inherit from Map. An OrderedMap behaves ++like a Map but keeps its keys in order if you use its set method to add keys ++and values. Like all the 'container' classes in Penlight, it defines an iter ++method for iterating over its values; this will return the keys and values in the ++order of insertion; the keys and values methods likewise.

    ++ ++

    A MultiMap allows multiple values to be associated with a given key. So set ++(as before) takes a key and a value, but calling it with the same key and a ++different value does not overwrite but adds a new value. get (or using []) ++will return a list of values.

    ++ ++

    A Set can be seen as a special kind of Map, where all the values are true, ++the keys are the values, and the order is not important. So in this case ++Set.values is defined to return a list of the keys. Sets can display ++themselves, and the basic operations like union (+) and intersection (*) ++are defined.

    ++ ++ ++
    ++> Set = require 'pl.Set'
    ++> = Set{'one','two'} == Set{'two','one'}
    ++true
    ++> fruit = Set{'apple','banana','orange'}
    ++> = fruit['banana']
    ++true
    ++> = fruit['hazelnut']
    ++nil
    ++> = fruit:values()
    ++{apple,orange,banana}
    ++> colours = Set{'red','orange','green','blue'}
    ++> = fruit,colours
    ++[apple,orange,banana]   [blue,green,orange,red]
    ++> = fruit+colours
    ++[blue,green,apple,red,orange,banana]
    ++> = fruit*colours
    ++[orange]
    ++
    ++ ++

    There are also the functions Set.difference and Set.symmetric_difference. The ++first answers the question 'what fruits are not colours?' and the second 'what ++are fruits and colours but not both?'

    ++ ++ ++
    ++> = fruit - colours
    ++[apple,banana]
    ++> = fruit ^ colours
    ++[blue,green,apple,red,banana]
    ++
    ++ ++

    Adding elements to a set is simply fruit['peach'] = true and removing is ++fruit['apple'] = nil . To make this simplicity work properly, the Set class has no ++methods - either you use the operator forms or explicitly use Set.intersect ++etc. In this way we avoid the ambiguity that plagues Map.

    ++ ++ ++

    (See pl.Map and pl.Set)

    ++ ++

    ++

    Useful Operations on Tables

    ++ ++ ++

    Some notes on terminology: Lua tables are usually list-like (like an array) or ++map-like (like an associative array or dict); they can of course have a ++list-like and a map-like part. Some of the table operations only make sense for ++list-like tables, and some only for map-like tables. (The usual Lua terminology ++is the array part and the hash part of the table, which reflects the actual ++implementation used; it is more accurate to say that a Lua table is an ++associative map which happens to be particularly efficient at acting like an ++array.)

    ++ ++

    The functions provided in table provide all the basic manipulations on Lua ++tables, but as we saw with the List class, it is useful to build higher-level ++operations on top of those functions. For instance, to copy a table involves this ++kind of loop:

    ++ ++ ++
    ++local res = {}
    ++for k,v in pairs(T) do
    ++    res[k] = v
    ++end
    ++return res
    ++
    ++ ++

    The tablex module provides this as copy, which does a shallow copy of a ++table. There is also deepcopy which goes further than a simple loop in two ++ways; first, it also gives the copy the same metatable as the original (so it can ++copy objects like List above) and any nested tables will also be copied, to ++arbitrary depth. There is also icopy which operates on list-like tables, where ++you can set optionally set the start index of the source and destination as well. ++It ensures that any left-over elements will be deleted:

    ++ ++ ++
    ++asserteq(icopy({1,2,3,4,5,6},{20,30}),{20,30})   -- start at 1
    ++asserteq(icopy({1,2,3,4,5,6},{20,30},2),{1,20,30}) -- start at 2
    ++asserteq(icopy({1,2,3,4,5,6},{20,30},2,2),{1,30}) -- start at 2, copy from 2
    ++
    ++ ++

    (This code from the tablex test module shows the use of pl.test.asserteq)

    ++ ++

    Whereas, move overwrites but does not delete the rest of the destination:

    ++ ++ ++
    ++asserteq(move({1,2,3,4,5,6},{20,30}),{20,30,3,4,5,6})
    ++asserteq(move({1,2,3,4,5,6},{20,30},2),{1,20,30,4,5,6})
    ++asserteq(move({1,2,3,4,5,6},{20,30},2,2),{1,30,3,4,5,6})
    ++
    ++ ++

    (The difference is somewhat like that between C's strcpy and memmove.)

    ++ ++

    To summarize, use copy or deepcopy to make a copy of an arbitrary table. To ++copy into a map-like table, use update; to copy into a list-like table use ++icopy, and move if you are updating a range in the destination.

    ++ ++

    To complete this set of operations, there is insertvalues which works like ++table.insert except that one provides a table of values to be inserted, and ++removevalues which removes a range of values.

    ++ ++ ++
    ++asserteq(insertvalues({1,2,3,4},2,{20,30}),{1,20,30,2,3,4})
    ++asserteq(insertvalues({1,2},{3,4}),{1,2,3,4})
    ++
    ++ ++

    Another example:

    ++ ++ ++
    ++> T = require 'pl.tablex'
    ++> t = {10,20,30,40}
    ++> = T.removevalues(t,2,3)
    ++{10,40}
    ++> = T.insertvalues(t,2,{20,30})
    ++{10,20,30,40}
    ++
    ++ ++

    In a similar spirit to deepcopy, deepcompare will take two tables and return ++true only if they have exactly the same values and structure.

    ++ ++ ++
    ++> t1 = {1,{2,3},4}
    ++> t2 = deepcopy(t1)
    ++> = t1 == t2
    ++false
    ++> = deepcompare(t1,t2)
    ++true
    ++
    ++ ++

    find will return the index of a given value in a list-like table. Note that ++like string.find you can specify an index to start searching, so that all ++instances can be found. There is an optional fourth argument, which makes the ++search start at the end and go backwards, so we could define rfind like so:

    ++ ++ ++
    ++function rfind(t,val,istart)
    ++    return tablex.find(t,val,istart,true)
    ++end
    ++
    ++ ++

    find does a linear search, so it can slow down code that depends on it. If ++efficiency is required for large tables, consider using an index map. ++index_map will return a table where the keys are the original values of the ++list, and the associated values are the indices. (It is almost exactly the ++representation needed for a set.)

    ++ ++ ++
    ++> t = {'one','two','three'}
    ++> = tablex.find(t,'two')
    ++2
    ++> = tablex.find(t,'four')
    ++nil
    ++> il = tablex.index_map(t)
    ++> = il['two']
    ++2
    ++> = il.two
    ++2
    ++
    ++ ++

    A version of index_map called makeset is also provided, where the values are ++just true. This is useful because two such sets can be compared for equality ++using deepcompare:

    ++ ++ ++
    ++> = deepcompare(makeset {1,2,3},makeset {2,1,3})
    ++true
    ++
    ++ ++

    Consider the problem of determining the new employees that have joined in a ++period. Assume we have two files of employee names:

    ++ ++ ++
    ++(last-month.txt)
    ++smith,john
    ++brady,maureen
    ++mongale,thabo
    ++
    ++(this-month.txt)
    ++smith,john
    ++smit,johan
    ++brady,maureen
    ++mogale,thabo
    ++van der Merwe,Piet
    ++
    ++ ++

    To find out differences, just make the employee lists into sets, like so:

    ++ ++ ++
    ++require 'pl'
    ++
    ++function read_employees(file)
    ++  local ls = List(io.lines(file)) -- a list of employees
    ++  return tablex.makeset(ls)
    ++end
    ++
    ++last = read_employees 'last-month.txt'
    ++this = read_employees 'this-month.txt'
    ++
    ++-- who is in this but not in last?
    ++diff = tablex.difference(this,last)
    ++
    ++-- in a set, the keys are the values...
    ++for e in pairs(diff) do print(e) end
    ++
    ++--  *output*
    ++-- van der Merwe,Piet
    ++-- smit,johan
    ++
    ++ ++

    The difference operation is easy to write and read:

    ++ ++ ++
    ++for e in pairs(this) do
    ++  if not last[e] then
    ++    print(e)
    ++  end
    ++end
    ++
    ++ ++

    Using difference here is not that it is a tricky thing to code, it is that you ++are stating your intentions clearly to other readers of your code. (And naturally ++to your future self, in six months time.)

    ++ ++

    find_if will search a table using a function. The optional third argument is a ++value which will be passed as a second argument to the function. pl.operator ++provides the Lua operators conveniently wrapped as functions, so the basic ++comparison functions are available:

    ++ ++ ++
    ++> ops = require 'pl.operator'
    ++> = tablex.find_if({10,20,30,40},ops.gt,20)
    ++3       true
    ++
    ++ ++

    Note that find_if will also return the actual value returned by the function, ++which of course is usually just true for a boolean function, but any value ++which is not nil and not false can be usefully passed back.

    ++ ++

    deepcompare does a thorough recursive comparison, but otherwise using the ++default equality operator. compare allows you to specify exactly what function ++to use when comparing two list-like tables, and compare_no_order is true if ++they contain exactly the same elements. Do note that the latter does not need an ++explicit comparison function - in this case the implementation is actually to ++compare the two sets, as above:

    ++ ++ ++
    ++> = compare_no_order({1,2,3},{2,1,3})
    ++true
    ++> = compare_no_order({1,2,3},{2,1,3},'==')
    ++true
    ++
    ++ ++

    (Note the special string '==' above; instead of saying ops.gt or ops.eq we ++can use the strings '>' or '==' respectively.)

    ++ ++

    sort and sortv return iterators that will iterate through the ++sorted elements of a table. sort iterates by sorted key order, and ++sortv iterates by sorted value order. For example, given a table ++with names and ages, it is trivial to iterate over the elements:

    ++ ++ ++
    ++> t = {john=27,jane=31,mary=24}
    ++> for name,age in tablex.sort(t) do print(name,age) end
    ++jane    31
    ++john    27
    ++mary    24
    ++> for name,age in tablex.sortv(t) do print(name,age) end
    ++mary    24
    ++john    27
    ++jane    31
    ++
    ++ ++

    There are several ways to merge tables in PL. If they are list-like, then see the ++operations defined by pl.List, like concatenation. If they are map-like, then ++merge provides two basic operations. If the third arg is false, then the result ++only contains the keys that are in common between the two tables, and if true, ++then the result contains all the keys of both tables. These are in fact ++generalized set union and intersection operations:

    ++ ++ ++
    ++> S1 = {john=27,jane=31,mary=24}
    ++> S2 = {jane=31,jones=50}
    ++> = tablex.merge(S1, S2, false)
    ++{jane=31}
    ++> = tablex.merge(S1, S2, true)
    ++{mary=24,jane=31,john=27,jones=50}
    ++
    ++ ++

    When working with tables, you will often find yourself writing loops like in the ++first example. Loops are second nature to programmers, but they are often not the ++most elegant and self-describing way of expressing an operation. Consider the ++map function, which creates a new table by applying a function to each element ++of the original:

    ++ ++ ++
    ++> = map(math.sin, {1,2,3,4})
    ++{  0.84,  0.91,  0.14, -0.76}
    ++> = map(function(x) return x*x end, {1,2,3,4})
    ++{1,4,9,16}
    ++
    ++ ++

    map saves you from writing a loop, and the resulting code is often clearer, as ++well as being shorter. This is not to say that 'loops are bad' (although you will ++hear that from some extremists), just that it's good to capture standard ++patterns. Then the loops you do write will stand out and acquire more significance.

    ++ ++

    pairmap is interesting, because the function works with both the key and the ++value.

    ++ ++ ++
    ++> t = {fred=10,bonzo=20,alice=4}
    ++> = pairmap(function(k,v) return v end, t)
    ++{4,10,20}
    ++> = pairmap(function(k,v) return k end, t)
    ++{'alice','fred','bonzo'}
    ++
    ++ ++

    (These are common enough operations that the first is defined as values and the ++second as keys.) If the function returns two values, then the second value is ++considered to be the new key:

    ++ ++ ++
    ++> = pairmap(t,function(k,v) return v+10, k:upper() end)
    ++{BONZO=30,FRED=20,ALICE=14}
    ++
    ++ ++

    map2 applies a function to two tables:

    ++ ++ ++
    ++> map2(ops.add,{1,2},{10,20})
    ++{11,22}
    ++> map2('*',{1,2},{10,20})
    ++{10,40}
    ++
    ++ ++

    The various map operations generate tables; reduce applies a function of two ++arguments over a table and returns the result as a scalar:

    ++ ++ ++
    ++> reduce ('+', {1,2,3})
    ++6
    ++> reduce ('..', {'one','two','three'})
    ++'onetwothree'
    ++
    ++ ++

    Finally, zip sews different tables together:

    ++ ++ ++
    ++> = zip({1,2,3},{10,20,30})
    ++{{1,10},{2,20},{3,30}}
    ++
    ++ ++

    Browsing through the documentation, you will find that tablex and List share ++methods. For instance, tablex.imap and List.map are basically the same ++function; they both operate over the array-part of the table and generate another ++table. This can also be expressed as a list comprehension C 'f(x) for x' (t) ++which makes the operation more explicit. So why are there different ways to do ++the same thing? The main reason is that not all tables are Lists: the expression ++ls:map('#') will return a list of the lengths of any elements of ls. A list ++is a thin wrapper around a table, provided by the metatable List. Sometimes you ++may wish to work with ordinary Lua tables; the List interface is not a ++compulsory way to use Penlight table operations.

    ++ ++

    ++

    Operations on two-dimensional tables

    ++ ++ ++

    Two-dimensional tables are of course easy to represent in Lua, for instance ++{{1,2},{3,4}} where we store rows as subtables and index like so A[col][row]. ++This is the common representation used by matrix libraries like ++LuaMatrix. pl.array2d does not provide ++matrix operations, since that is the job for a specialized library, but rather ++provides generalizations of the higher-level operations provided by pl.tablex ++for one-dimensional arrays.

    ++ ++

    iter is a useful generalization of ipairs. (The extra parameter determines ++whether you want the indices as well.)

    ++ ++ ++
    ++> a = {{1,2},{3,4}}
    ++> for i,j,v in array2d.iter(a,true) do print(i,j,v) end
    ++1       1       1
    ++1       2       2
    ++2       1       3
    ++2       2       4
    ++
    ++ ++

    Note that you can always convert an arbitrary 2D array into a 'list of lists' ++with List(tablex.map(List,a))

    ++ ++

    map will apply a function over all elements (notice that extra arguments can be ++provided, so this operation is in effect function(x) return x-1 end)

    ++ ++ ++
    ++> array2d.map('-',a,1)
    ++{{0,1},{2,3}}
    ++
    ++ ++

    2D arrays are stored as an array of rows, but columns can be extracted:

    ++ ++ ++
    ++> array2d.column(a,1)
    ++{1,3}
    ++
    ++ ++

    There are three equivalents to tablex.reduce. You can either reduce along the ++rows (which is the most efficient) or reduce along the columns. Either one will ++give you a 1D array. And reduce2 will apply two operations: the first one ++reduces the rows, and the second reduces the result.

    ++ ++ ++
    ++> array2d.reduce_rows('+',a)
    ++{3,7}
    ++> array2d.reduce_cols('+',a)
    ++{4,6}
    ++> -- same as tablex.reduce('*',array.reduce_rows('+',a))
    ++> array2d.reduce2('*','+',a)
    ++21    `
    ++
    ++ ++

    tablex.map2 applies an operation to two tables, giving another table. ++array2d.map2 does this for 2D arrays. Note that you have to provide the rank ++of the arrays involved, since it's hard to always correctly deduce this from the ++data:

    ++ ++ ++
    ++> b = {{10,20},{30,40}}
    ++> a = {{1,2},{3,4}}
    ++> = array2d.map2('+',2,2,a,b)  -- two 2D arrays
    ++{{11,22},{33,44}}
    ++> = array2d.map2('+',1,2,{10,100},a)  -- 1D, 2D
    ++{{11,102},{13,104}}
    ++> = array2d.map2('*',2,1,a,{1,-1})  -- 2D, 1D
    ++{{1,-2},{3,-4}}
    ++
    ++ ++

    Of course, you are not limited to simple arithmetic. Say we have a 2D array of ++strings, and wish to print it out with proper right justification. The first step ++is to create all the string lengths by mapping string.len over the array, the ++second is to reduce this along the columns using math.max to get maximum column ++widths, and last, apply stringx.rjust with these widths.

    ++ ++ ++
    ++maxlens = reduce_cols(math.max,map('#',lines))
    ++lines = map2(stringx.rjust,2,1,lines,maxlens)
    ++
    ++ ++

    There is product which returns the Cartesian product of two 1D arrays. The ++result is a 2D array formed from applying the function to all possible pairs from ++the two arrays.

    ++ ++ ++
    ++> array2d.product('{}',{1,2},{'a','b'})
    ++{{{1,'b'},{2,'a'}},{{1,'a'},{2,'b'}}}
    ++
    ++ ++

    There is a set of operations which work in-place on 2D arrays. You can ++swap_rows and swap_cols; the first really is a simple one-liner, but the idea ++here is to give the operation a name. remove_row and remove_col are ++generalizations of table.remove. Likewise, extract_rows and extract_cols ++are given arrays of indices and discard anything else. So, for instance, ++extract_cols(A,{2,4}) will leave just columns 2 and 4 in the array.

    ++ ++

    List.slice is often useful on 1D arrays; slice does the same thing, but is ++generally given a start (row,column) and a end (row,column).

    ++ ++ ++
    ++> A = {{1,2,3},{4,5,6},{7,8,9}}
    ++> B = slice(A,1,1,2,2)
    ++> write(B)
    ++ 1 2
    ++ 4 5
    ++> B = slice(A,2,2)
    ++> write(B,nil,'%4.1f')
    ++ 5.0 6.0
    ++ 8.0 9.0
    ++
    ++ ++

    Here write is used to print out an array nicely; the second parameter is nil, ++which is the default (stdout) but can be any file object and the third parameter ++is an optional format (as used in string.format).

    ++ ++

    parse_range will take a spreadsheet range like 'A1:B2' or 'R1C1:R2C2' and ++return the range as four numbers, which can be passed to slice. The rule is ++that slice will return an array of the appropriate shape depending on the ++range; if a range represents a row or a column, the result is 1D, otherwise 2D.

    ++ ++

    This applies to iter as well, which can also optionally be given a range:

    ++ ++ ++ ++
    ++> for i,j,v in iter(A,true,2,2) do print(i,j,v) end
    ++2       2       5
    ++2       3       6
    ++3       2       8
    ++3       3       9
    ++
    ++ ++

    new will construct a new 2D array with the given dimensions. You provide an ++initial value for the elements, which is interpreted as a function if it's ++callable. With L being utils.string_lambda we then have the following way to ++make an identity matrix:

    ++ ++ ++
    ++asserteq(
    ++    array.new(3,3,L'|i,j| i==j and 1 or 0'),
    ++    {{1,0,0},{0,1,0},{0,0,1}}
    ++)
    ++
    ++ ++

    Please note that most functions in array2d are covariant, that is, they ++return an array of the same type as they receive. In particular, any objects ++created with data.new or matrix.new will remain data or matrix objects when ++reshaped or sliced, etc. Data objects have the array2d functions available as ++methods.

    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/03-strings.md.html b/extra/penlight/docs/manual/03-strings.md.html +new file mode 100644 +index 0000000..7aa6756 +--- /dev/null ++++ b/extra/penlight/docs/manual/03-strings.md.html +@@ -0,0 +1,397 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Strings. Higher-level operations on strings.

    ++ ++

    ++

    Extra String Methods

    ++ ++ ++

    These are convenient borrowings from Python, as described in 3.6.1 of the Python ++reference, but note that indices in Lua always begin at one. stringx defines ++functions like isalpha and isdigit, which return true if s is only composed ++of letters or digits respectively. startswith and endswith are convenient ++ways to find substrings. (endswith works as in Python 2.5, so that `f:endswith ++{'.bat','.exe','.cmd'}` will be true for any filename which ends with these ++extensions.) There are justify methods and whitespace trimming functions like ++strip.

    ++ ++ ++
    ++> stringx.import()
    ++> ('bonzo.dog'):endswith {'.dog','.cat'}
    ++true
    ++> ('bonzo.txt'):endswith {'.dog','.cat'}
    ++false
    ++> ('bonzo.cat'):endswith {'.dog','.cat'}
    ++true
    ++> (' stuff'):ljust(20,'+')
    ++'++++++++++++++ stuff'
    ++> ('  stuff '):lstrip()
    ++'stuff '
    ++> ('  stuff '):rstrip()
    ++'  stuff'
    ++> ('  stuff '):strip()
    ++'stuff'
    ++> for s in ('one\ntwo\nthree\n'):lines() do print(s) end
    ++one
    ++two
    ++three
    ++
    ++ ++

    Most of these can be fairly easily implemented using the Lua string library, ++which is more general and powerful. But they are convenient operations to have ++easily at hand. Note that can be injected into the string table if you use ++stringx.import, but a simple alias like local stringx = require 'pl.stringx' ++is preferable. This is the recommended practice when writing modules for ++consumption by other people, since it is bad manners to change the global state ++of the rest of the system. Magic may be used for convenience, but there is always ++a price.

    ++ ++ ++

    ++

    String Templates

    ++ ++ ++

    Another borrowing from Python, string templates allow you to substitute values ++looked up in a table:

    ++ ++ ++
    ++local Template = require ('pl.text').Template
    ++t = Template('${here} is the $answer')
    ++print(t:substitute {here = 'Lua', answer = 'best'})
    ++==>
    ++Lua is the best
    ++
    ++ ++

    '$ variables' can optionally have curly braces; this form is useful if you are ++glueing text together to make variables, e.g ${prefix}_name_${postfix}. The ++substitute method will throw an error if a $ variable is not found in the ++table, and the safe_substitute method will not.

    ++ ++

    The Lua implementation has an extra method, indent_substitute which is very ++useful for inserting blocks of text, because it adjusts indentation. Consider ++this example:

    ++ ++ ++
    ++-- testtemplate.lua
    ++local Template = require ('pl.text').Template
    ++
    ++t = Template [[
    ++    for i = 1,#$t do
    ++        $body
    ++    end
    ++]]
    ++
    ++body = Template [[
    ++local row = $t[i]
    ++for j = 1,#row do
    ++    fun(row[j])
    ++end
    ++]]
    ++
    ++print(t:indent_substitute {body=body,t='tbl'})
    ++
    ++ ++

    And the output is:

    ++ ++ ++
    ++for i = 1,#tbl do
    ++    local row = tbl[i]
    ++    for j = 1,#row do
    ++        fun(row[j])
    ++    end
    ++end
    ++
    ++ ++

    indent_substitute can substitute templates, and in which case they themselves ++will be substituted using the given table. So in this case, $t was substituted ++twice.

    ++ ++

    pl.text also has a number of useful functions like dedent, which strips all ++the initial indentation from a multiline string. As in Python, this is useful for ++preprocessing multiline strings if you like indenting them with your code. The ++function wrap is passed a long string (a paragraph) and returns a list of ++lines that fit into a desired line width. As an extension, there is also indent ++for indenting multiline strings.

    ++ ++

    New in Penlight with the 0.9 series is text.format_operator. Calling this ++enables Python-style string formatting using the modulo operator %:

    ++ ++ ++
    ++> text.format_operator()
    ++> = '%s[%d]' % {'dog',1}
    ++dog[1]
    ++
    ++ ++

    So in its simplest form it saves the typing involved with string.format; it ++will also expand $ variables using named fields:

    ++ ++ ++
    ++> = '$animal[$num]' % {animal='dog',num=1}
    ++dog[1]
    ++
    ++ ++

    As with stringx.import you have to do this explicitly, since all strings share the same ++metatable. But in your own scripts you can feel free to do this.

    ++ ++

    ++

    Another Style of Template

    ++ ++

    A new module is template, which is a version of Rici Lake's Lua ++Preprocessor. This ++allows you to mix Lua code with your templates in a straightforward way. There ++are only two rules:

    ++ ++
      ++
    • Lines beginning with # are Lua
    • ++
    • Otherwise, anything inside $() is a Lua expression.
    • ++
    ++ ++

    So a template generating an HTML list would look like this:

    ++ ++ ++
    ++<ul>
    ++# for i,val in ipairs(T) do
    ++<li>$(i) = $(val:upper())</li>
    ++# end
    ++</ul>
    ++
    ++ ++

    Assume the text is inside tmpl, then the template can be expanded using:

    ++ ++ ++
    ++local template = require 'pl.template'
    ++local my_env = {
    ++  ipairs = ipairs,
    ++  T = {'one','two','three'}
    ++}
    ++res = template.substitute(tmpl, my_env)
    ++
    ++ ++

    and we get

    ++ ++ ++
    ++<ul>
    ++<li>1 = ONE</li>
    ++<li>2 = TWO</li>
    ++<li>3 = THREE</li>
    ++</ul>
    ++
    ++ ++

    There is a single function, template.substitute which is passed a template ++string and an environment table. This table may contain some special fields, ++like \_parent which can be set to a table representing a 'fallback' environment ++in case a symbol was not found. \_brackets is usually '()' and \_escape is ++usually '#' but it's sometimes necessary to redefine these if the defaults ++interfere with the target language - for instance, $(V) has another meaning in ++Make, and # means a preprocessor line in C/C++.

    ++ ++

    Finally, if something goes wrong, passing _debug will cause the intermediate ++Lua code to be dumped if there's a problem.

    ++ ++

    Here is a C code generation example; something that could easily be extended to ++be a minimal Lua extension skeleton generator.

    ++ ++ ++
    ++local subst = require 'pl.template'.substitute
    ++
    ++local templ = [[
    ++#include <lua.h>
    ++#include <lauxlib.h>
    ++#include <lualib.h>
    ++
    ++> for _,f in ipairs(mod) do
    ++static int l_$(f.name) (lua_State *L) {
    ++
    ++}
    ++> end
    ++
    ++static const luaL_reg $(mod.name)[] = {
    ++> for _,f in ipairs(mod) do
    ++    {"$(f.name)",l_$(f.name)},
    ++> end
    ++    {NULL,NULL}
    ++};
    ++
    ++int luaopen_$(mod.name) {
    ++   luaL_register (L, "$(mod.name)", $(mod.name));
    ++    return 1;
    ++}
    ++]]
    ++
    ++print(subst(templ,{
    ++    _escape = '>',
    ++    ipairs = ipairs,
    ++    mod = {
    ++        name = 'baggins';
    ++        {name='frodo'},
    ++        {name='bilbo'}
    ++    }
    ++}))
    ++
    ++ ++

    ++

    File-style I/O on Strings

    ++ ++

    pl.stringio provides just three functions; stringio.open is passed a string, ++and returns a file-like object for reading. It supports a read method, which ++takes the same arguments as standard file objects:

    ++ ++ ++
    ++> f = stringio.open 'first line\n10 20 30\n'
    ++> = f:read()
    ++first line
    ++> = f:read('*n','*n','*n')
    ++10    20    30
    ++
    ++ ++

    lines and seek are also supported.

    ++ ++

    stringio.lines is a useful short-cut for iterating over all the lines in a ++string.

    ++ ++

    stringio.create creates a writeable file-like object. You then use write to ++this stream, and finally extract the built string using value. This 'string ++builder' pattern is useful for efficiently creating large strings.

    ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/04-paths.md.html b/extra/penlight/docs/manual/04-paths.md.html +new file mode 100644 +index 0000000..03295a9 +--- /dev/null ++++ b/extra/penlight/docs/manual/04-paths.md.html +@@ -0,0 +1,329 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Paths and Directories

    ++ ++

    ++

    Working with Paths

    ++ ++

    Programs should not depend on quirks of your operating system. They will be ++harder to read, and need to be ported for other systems. The worst of course is ++hardcoding paths like 'c:\' in programs, and wondering why Vista complains so ++much. But even something like dir..'\'..file is a problem, since Unix can't ++understand backslashes in this way. dir..'/'..file is usually portable, but ++it's best to put this all into a simple function, path.join. If you ++consistently use path.join, then it's much easier to write cross-platform code, ++since it handles the directory separator for you.

    ++ ++

    pl.path provides the same functionality as Python's os.path module (11.1).

    ++ ++ ++
    ++> p = 'c:\\bonzo\\DOG.txt'
    ++> = path.normcase (p)  ---> only makes sense on Windows
    ++c:\bonzo\dog.txt
    ++> = path.splitext (p)
    ++c:\bonzo\DOG    .txt
    ++> = path.extension (p)
    ++.txt
    ++> = path.basename (p)
    ++DOG.txt
    ++> = path.exists(p)
    ++false
    ++> = path.join ('fred','alice.txt')
    ++fred\alice.txt
    ++> = path.exists 'pretty.lua'
    ++true
    ++> = path.getsize 'pretty.lua'
    ++2125
    ++> = path.isfile 'pretty.lua'
    ++true
    ++> = path.isdir 'pretty.lua'
    ++false
    ++
    ++ ++

    It is very important for all programmers, not just on Unix, to only write to ++where they are allowed to write. path.expanduser will expand '~' (tilde) into ++the home directory. Depending on your OS, this will be a guaranteed place where ++you can create files:

    ++ ++ ++
    ++> = path.expanduser '~/mydata.txt'
    ++'C:\Documents and Settings\SJDonova/mydata.txt'
    ++
    ++> = path.expanduser '~/mydata.txt'
    ++/home/sdonovan/mydata.txt
    ++
    ++ ++

    Under Windows, os.tmpname returns a path which leads to your drive root full of ++temporary files. (And increasingly, you do not have access to this root folder.) ++This is corrected by path.tmpname, which uses the environment variable TMP:

    ++ ++ ++
    ++> os.tmpname()  -- not a good place to put temporary files!
    ++'\s25g.'
    ++> path.tmpname()
    ++'C:\DOCUME~1\SJDonova\LOCALS~1\Temp\s25g.1'
    ++
    ++ ++

    A useful extra function is pl.path.package_path, which will tell you the path ++of a particular Lua module. So on my system, package_path('pl.path') returns ++'C:\Program Files\Lua\5.1\lualibs\pl\path.lua', and package_path('ifs') returns ++'C:\Program Files\Lua\5.1\clibs\lfs.dll'. It is implemented in terms of ++package.searchpath, which is a new function in Lua 5.2 which has been ++implemented for Lua 5.1 in Penlight.

    ++ ++

    ++

    File Operations

    ++ ++

    pl.file is a new module that provides more sensible names for common file ++operations. For instance, file.read and file.write are aliases for ++utils.readfile and utils.writefile.

    ++ ++

    Smaller files can be efficiently read and written in one operation. file.read ++is passed a filename and returns the contents as a string, if successful; if not, ++then it returns nil and the actual error message. There is an optional boolean ++parameter if you want the file to be read in binary mode (this makes no ++difference on Unix but remains important with Windows.)

    ++ ++

    In previous versions of Penlight, utils.readfile would read standard input if ++the file was not specified, but this can lead to nasty bugs; use io.read '*a' ++to grab all of standard input.

    ++ ++

    Similarly, file.write takes a filename and a string which will be written to ++that file.

    ++ ++

    For example, this little script converts a file into upper case:

    ++ ++ ++
    ++require 'pl'
    ++assert(#arg == 2, 'supply two filenames')
    ++text = assert(file.read(arg[1]))
    ++assert(file.write(arg[2],text:upper()))
    ++
    ++ ++

    Copying files is surprisingly tricky. file.copy and file.move attempt to use ++the best implementation possible. On Windows, they link to the API functions ++CopyFile and MoveFile, but only if the alien package is installed (this is ++true for Lua for Windows.) Otherwise, the system copy command is used. This can ++be ugly when writing Windows GUI applications, because of the dreaded flashing ++black-box problem with launching processes.

    ++ ++

    ++

    Directory Operations

    ++ ++

    pl.dir provides some useful functions for working with directories. fnmatch ++will match a filename against a shell pattern, and filter will return any files ++in the supplied list which match the given pattern, which correspond to the ++functions in the Python fnmatch module. getdirectories will return all ++directories contained in a directory, and getfiles will return all files in a ++directory which match a shell pattern. These functions return the files as a ++table, unlike lfs.dir which returns an iterator.)

    ++ ++

    dir.makepath can create a full path, creating subdirectories as necessary; ++rmtree is the Nuclear Option of file deleting functions, since it will ++recursively clear out and delete all directories found beginning at a path (there ++is a similar function with this name in the Python shutils module.)

    ++ ++ ++
    ++> = dir.makepath 't\\temp\\bonzo'
    ++> = path.isdir 't\\temp\\bonzo'
    ++true
    ++> = dir.rmtree 't'
    ++
    ++ ++

    dir.rmtree depends on dir.walk, which is a powerful tool for scanning a whole ++directory tree. Here is the implementation of dir.rmtree:

    ++ ++ ++
    ++--- remove a whole directory tree.
    ++-- @param path A directory path
    ++function dir.rmtree(fullpath)
    ++    for root,dirs,files in dir.walk(fullpath) do
    ++        for i,f in ipairs(files) do
    ++            os.remove(path.join(root,f))
    ++        end
    ++        lfs.rmdir(root)
    ++    end
    ++end
    ++
    ++ ++

    dir.clonetree clones directory trees. The first argument is a path that must ++exist, and the second path is the path to be cloned. (Note that this path cannot ++be inside the first path, since this leads to madness.) By default, it will ++then just recreate the directory structure. You can in addition provide a ++function, which will be applied for all files found.

    ++ ++ ++
    ++-- make a copy of my libs folder
    ++require 'pl'
    ++p1 = [[d:\dev\lua\libs]]
    ++p2 = [[D:\dev\lua\libs\..\tests]]
    ++dir.clonetree(p1,p2,dir.copyfile)
    ++
    ++ ++

    A more sophisticated version, which only copies files which have been modified:

    ++ ++ ++
    ++-- p1 and p2 as before, or from arg[1] and arg[2]
    ++dir.clonetree(p1,p2,function(f1,f2)
    ++  local res
    ++  local t1,t2 = path.getmtime(f1),path.getmtime(f2)
    ++  -- f2 might not exist, so be careful about t2
    ++  if not t2 or t1 > t2 then
    ++    res = dir.copyfile(f1,f2)
    ++  end
    ++  return res -- indicates successful operation
    ++end)
    ++
    ++ ++

    dir.clonetree uses path.common_prefix. With p1 and p2 defined above, the ++common path is 'd:\dev\lua'. So 'd:\dev\lua\libs\testfunc.lua' is copied to ++'d:\dev\lua\test\testfunc.lua', etc.

    ++ ++

    If you need to find the common path of list of files, then tablex.reduce will ++do the job:

    ++ ++ ++
    ++> p3 = [[d:\dev]]
    ++> = tablex.reduce(path.common_prefix,{p1,p2,p3})
    ++'d:\dev'
    ++
    ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/05-dates.md.html b/extra/penlight/docs/manual/05-dates.md.html +new file mode 100644 +index 0000000..946e08c +--- /dev/null ++++ b/extra/penlight/docs/manual/05-dates.md.html +@@ -0,0 +1,269 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Date and Time

    ++ ++

    ++ ++

    NOTE: the Date module is deprecated

    ++ ++

    ++

    Creating and Displaying Dates

    ++ ++

    The Date class provides a simplified way to work with date and ++time in Lua; it leans heavily on the functions ++os.date and os.time.

    ++ ++

    A Date object can be constructed from a table, just like with os.time. ++Methods are provided to get and set the various parts of the date.

    ++ ++ ++
    ++> d = Date {year = 2011, month = 3, day = 2 }
    ++> = d
    ++2011-03-02 12:00:00
    ++> = d:month(),d:year(),d:day()
    ++3    2011    2
    ++> d:month(4)
    ++> = d
    ++2011-04-02 12:00:00
    ++> d:add {day=1}
    ++> = d
    ++2011-04-03 12:00:00
    ++
    ++ ++

    add takes a table containing one of the date table fields.

    ++ ++ ++
    ++> = d:weekday_name()
    ++Sun
    ++> = d:last_day()
    ++2011-04-30 12:00:00
    ++> = d:month_name(true)
    ++April
    ++
    ++ ++

    There is a default conversion to text for date objects, but Date.Format gives ++you full control of the format for both parsing and displaying dates:

    ++ ++ ++
    ++> iso = Date.Format 'yyyy-mm-dd'
    ++> d = iso:parse '2010-04-10'
    ++> amer = Date.Format 'mm/dd/yyyy'
    ++> = amer:tostring(d)
    ++04/10/2010
    ++
    ++ ++

    With the 0.9.7 release, the Date constructor has become more flexible. You may ++omit any of the 'year', 'month' or 'day' fields:

    ++ ++ ++
    ++> = Date { year = 2008 }
    ++2008-01-01 12:00:00
    ++> = Date { month = 3 }
    ++2011-03-01 12:00:00
    ++> = Date { day = 20 }
    ++2011-10-20 12:00:00
    ++> = Date { hour = 14, min = 30 }
    ++2011-10-13 14:30:00
    ++
    ++ ++

    If 'year' is omitted, then the current year is assumed, and likewise for 'month'.

    ++ ++

    To set the time on such a partial date, you can use the fact that the 'setter' ++methods return the date object and so you can 'chain' these methods.

    ++ ++ ++
    ++> d = Date { day = 03 }
    ++> = d:hour(18):min(30)
    ++2011-10-03 18:30:00
    ++
    ++ ++

    Finally, Date also now accepts positional arguments:

    ++ ++ ++
    ++> = Date(2011,10,3)
    ++2011-10-03 12:00:00
    ++> = Date(2011,10,3,18,30,23)
    ++2011-10-03 18:30:23
    ++
    ++ ++

    Date.format has been extended. If you construct an instance without a pattern, ++then it will try to match against a set of known formats. This is useful for ++human-input dates since keeping to a strict format is not one of the strong ++points of users. It assumes that there will be a date, and then a date.

    ++ ++ ++
    ++> df = Date.Format()
    ++> = df:parse '5.30pm'
    ++2011-10-13 17:30:00
    ++> = df:parse '1730'
    ++nil     day out of range: 1730 is not between 1 and 31
    ++> = df:parse '17.30'
    ++2011-10-13 17:30:00
    ++> = df:parse 'mar'
    ++2011-03-01 12:00:00
    ++> = df:parse '3 March'
    ++2011-03-03 12:00:00
    ++> = df:parse '15 March'
    ++2011-03-15 12:00:00
    ++> = df:parse '15 March 2008'
    ++2008-03-15 12:00:00
    ++> = df:parse '15 March 2008 1.30pm'
    ++2008-03-15 13:30:00
    ++> = df:parse '2008-10-03 15:30:23'
    ++2008-10-03 15:30:23
    ++
    ++ ++

    ISO date format is of course a good idea if you need to deal with users from ++different countries. Here is the default behaviour for 'short' dates:

    ++ ++ ++
    ++> = df:parse '24/02/12'
    ++2012-02-24 12:00:00
    ++
    ++ ++

    That's not what Americans expect! It's tricky to work out in a cross-platform way ++exactly what the expected format is, so there is an explicit flag:

    ++ ++ ++
    ++> df:US_order(true)
    ++> = df:parse '9/11/01'
    ++2001-11-09 12:00:00
    ++
    ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/06-data.md.html b/extra/penlight/docs/manual/06-data.md.html +new file mode 100644 +index 0000000..62c775f +--- /dev/null ++++ b/extra/penlight/docs/manual/06-data.md.html +@@ -0,0 +1,1633 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Data

    ++ ++

    ++

    Reading Data Files

    ++ ++

    The first thing to consider is this: do you actually need to write a custom file ++reader? And if the answer is yes, the next question is: can you write the reader ++in as clear a way as possible? Correctness, Robustness, and Speed; pick the first ++two and the third can be sorted out later, if necessary.

    ++ ++

    A common sort of data file is the configuration file format commonly used on Unix ++systems. This format is often called a property file in the Java world.

    ++ ++ ++
    ++# Read timeout in seconds
    ++read.timeout=10
    ++
    ++# Write timeout in seconds
    ++write.timeout=10
    ++
    ++ ++

    Here is a simple Lua implementation:

    ++ ++ ++
    ++-- property file parsing with Lua string patterns
    ++props = []
    ++for line in io.lines() do
    ++    if line:find('#',1,true) ~= 1 and not line:find('^%s*$') then
    ++        local var,value = line:match('([^=]+)=(.*)')
    ++        props[var] = value
    ++    end
    ++end
    ++
    ++ ++

    Very compact, but it suffers from a similar disease in equivalent Perl programs; ++it uses odd string patterns which are 'lexically noisy'. Noisy code like this ++slows the casual reader down. (For an even more direct way of doing this, see the ++next section, 'Reading Configuration Files')

    ++ ++

    Another implementation, using the Penlight libraries:

    ++ ++ ++
    ++-- property file parsing with extended string functions
    ++require 'pl'
    ++stringx.import()
    ++props = []
    ++for line in io.lines() do
    ++    if not line:startswith('#') and not line:isspace() then
    ++        local var,value = line:splitv('=')
    ++        props[var] = value
    ++    end
    ++end
    ++
    ++ ++

    This is more self-documenting; it is generally better to make the code express ++the intention, rather than having to scatter comments everywhere - comments are ++necessary, of course, but mostly to give the higher view of your intention that ++cannot be expressed in code. It is slightly slower, true, but in practice the ++speed of this script is determined by I/O, so further optimization is unnecessary.

    ++ ++

    ++

    Reading Unstructured Text Data

    ++ ++

    Text data is sometimes unstructured, for example a file containing words. The ++pl.input module has a number of functions which makes processing such files ++easier. For example, a script to count the number of words in standard input ++using import.words:

    ++ ++ ++
    ++-- countwords.lua
    ++require 'pl'
    ++local k = 1
    ++for w in input.words(io.stdin) do
    ++    k = k + 1
    ++end
    ++print('count',k)
    ++
    ++ ++

    Or this script to calculate the average of a set of numbers using input.numbers:

    ++ ++ ++
    ++-- average.lua
    ++require 'pl'
    ++local k = 1
    ++local sum = 0
    ++for n in input.numbers(io.stdin) do
    ++    sum = sum + n
    ++    k = k + 1
    ++end
    ++print('average',sum/k)
    ++
    ++ ++

    These scripts can be improved further by eliminating loops In the last case, ++there is a perfectly good function seq.sum which can already take a sequence of ++numbers and calculate these numbers for us:

    ++ ++ ++
    ++-- average2.lua
    ++require 'pl'
    ++local total,n = seq.sum(input.numbers())
    ++print('average',total/n)
    ++
    ++ ++

    A further simplification here is that if numbers or words are not passed an ++argument, they will grab their input from standard input. The first script can ++be rewritten:

    ++ ++ ++
    ++-- countwords2.lua
    ++require 'pl'
    ++print('count',seq.count(input.words()))
    ++
    ++ ++

    A useful feature of a sequence generator like numbers is that it can read from ++a string source. Here is a script to calculate the sums of the numbers on each ++line in a file:

    ++ ++ ++
    ++-- sums.lua
    ++for line in io.lines() do
    ++    print(seq.sum(input.numbers(line))
    ++end
    ++
    ++ ++

    ++

    Reading Columnar Data

    ++ ++

    It is very common to find data in columnar form, either space or comma-separated, ++perhaps with an initial set of column headers. Here is a typical example:

    ++ ++ ++
    ++EventID    Magnitude    LocationX    LocationY    LocationZ
    ++981124001    2.0    18988.4    10047.1    4149.7
    ++981125001    0.8    19104.0    9970.4    5088.7
    ++981127003    0.5    19012.5    9946.9    3831.2
    ++...
    ++
    ++ ++

    input.fields is designed to extract several columns, given some delimiter ++(default to whitespace). Here is a script to calculate the average X location of ++all the events:

    ++ ++ ++
    ++-- avg-x.lua
    ++require 'pl'
    ++io.read() -- skip the header line
    ++local sum,count = seq.sum(input.fields {3})
    ++print(sum/count)
    ++
    ++ ++

    input.fields is passed either a field count, or a list of column indices, ++starting at one as usual. So in this case we're only interested in column 3. If ++you pass it a field count, then you get every field up to that count:

    ++ ++ ++
    ++for id,mag,locX,locY,locZ in input.fields (5) do
    ++....
    ++end
    ++
    ++ ++

    input.fields by default tries to convert each field to a number. It will skip ++lines which clearly don't match the pattern, but will abort the script if there ++are any fields which cannot be converted to numbers.

    ++ ++

    The second parameter is a delimiter, by default spaces. ' ' is understood to mean ++'any number of spaces', i.e. '%s+'. Any Lua string pattern can be used.

    ++ ++

    The third parameter is a data source, by default standard input (defined by ++input.create_getter.) It assumes that the data source has a read method which ++brings in the next line, i.e. it is a 'file-like' object. As a special case, a ++string will be split into its lines:

    ++ ++ ++
    ++> for x,y in input.fields(2,' ','10 20\n30 40\n') do print(x,y) end
    ++10      20
    ++30      40
    ++
    ++ ++

    Note the default behaviour for bad fields, which is to show the offending line ++number:

    ++ ++ ++
    ++> for x,y in input.fields(2,' ','10 20\n30 40x\n') do print(x,y) end
    ++10      20
    ++line 2: cannot convert '40x' to number
    ++
    ++ ++

    This behaviour of input.fields is appropriate for a script which you want to ++fail immediately with an appropriate user error message if conversion fails. ++The fourth optional parameter is an options table: {no_fail=true} means that ++conversion is attempted but if it fails it just returns the string, rather as AWK ++would operate. You are then responsible for checking the type of the returned ++field. {no_convert=true} switches off conversion altogether and all fields are ++returned as strings.

    ++ ++ ++

    Sometimes it is useful to bring a whole dataset into memory, for operations such ++as extracting columns. Penlight provides a flexible reader specifically for ++reading this kind of data, using the data module. Given a file looking like this:

    ++ ++ ++
    ++x,y
    ++10,20
    ++2,5
    ++40,50
    ++
    ++ ++

    Then data.read will create a table like this, with each row represented by a ++sublist:

    ++ ++ ++
    ++> t = data.read 'test.txt'
    ++> pretty.dump(t)
    ++{{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
    ++
    ++ ++

    You can now analyze this returned table using the supplied methods. For instance, ++the method column_by_name returns a table of all the values of that column.

    ++ ++ ++
    ++-- testdata.lua
    ++require 'pl'
    ++d = data.read('fev.txt')
    ++for _,name in ipairs(d.fieldnames) do
    ++    local col = d:column_by_name(name)
    ++    if type(col[1]) == 'number' then
    ++        local total,n = seq.sum(col)
    ++        utils.printf("Average for %s is %f\n",name,total/n)
    ++    end
    ++end
    ++
    ++ ++

    data.read tries to be clever when given data; by default it expects a first ++line of column names, unless any of them are numbers. It tries to deduce the ++column delimiter by looking at the first line. Sometimes it guesses wrong; these ++things can be specified explicitly. The second optional parameter is an options ++table: can override delim (a string pattern), fieldnames (a list or ++comma-separated string), specify no_convert (default is to convert), numfields ++(indices of columns known to be numbers, as a list) and thousands_dot (when the ++thousands separator in Excel CSV is '.')

    ++ ++

    A very powerful feature is a way to execute SQL-like queries on such data:

    ++ ++ ++
    ++-- queries on tabular data
    ++require 'pl'
    ++local d = data.read('xyz.txt')
    ++local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
    ++for x,y,z in q do
    ++    print(x,y,z)
    ++end
    ++
    ++ ++

    Please note that the format of queries is restricted to the following syntax:

    ++ ++ ++
    ++FIELDLIST [ 'where' CONDITION ] [ 'sort by' FIELD [asc|desc]]
    ++
    ++ ++

    Any valid Lua code can appear in CONDITION; remember it is not SQL and you ++have to use == (this warning comes from experience.)

    ++ ++

    For this to work, field names must be Lua identifiers. So read will massage ++fieldnames so that all non-alphanumeric chars are replaced with underscores. ++However, the original_fieldnames field always contains the original un-massaged ++fieldnames.

    ++ ++

    read can handle standard CSV files fine, although doesn't try to be a ++full-blown CSV parser. With the csv=true option, it's possible to have ++double-quoted fields, which may contain commas; then trailing commas become ++significant as well.

    ++ ++

    Spreadsheet programs are not always the best tool to ++process such data, strange as this might seem to some people. This is a toy CSV ++file; to appreciate the problem, imagine thousands of rows and dozens of columns ++like this:

    ++ ++ ++
    ++Department Name,Employee ID,Project,Hours Booked
    ++sales,1231,overhead,4
    ++sales,1255,overhead,3
    ++engineering,1501,development,5
    ++engineering,1501,maintenance,3
    ++engineering,1433,maintenance,10
    ++
    ++ ++

    The task is to reduce the dataset to a relevant set of rows and columns, perhaps ++do some processing on row data, and write the result out to a new CSV file. The ++write_row method uses the delimiter to write the row to a file; ++Data.select_row is like Data.select, except it iterates over rows, not ++fields; this is necessary if we are dealing with a lot of columns!

    ++ ++ ++
    ++names = {[1501]='don',[1433]='dilbert'}
    ++keepcols = {'Employee_ID','Hours_Booked'}
    ++t:write_row (outf,{'Employee','Hours_Booked'})
    ++q = t:select_row {
    ++    fields=keepcols,
    ++    where=function(row) return row[1]=='engineering' end
    ++}
    ++for row in q do
    ++    row[1] = names[row[1]]
    ++    t:write_row(outf,row)
    ++end
    ++
    ++ ++

    Data.select_row and Data.select can be passed a table specifying the query; a ++list of field names, a function defining the condition and an optional parameter ++sort_by. It isn't really necessary here, but if we had a more complicated row ++condition (such as belonging to a specified set) then it is not generally ++possible to express such a condition as a query string, without resorting to ++hackery such as global variables.

    ++ ++

    With 1.0.3, you can specify explicit conversion functions for selected columns. ++For instance, this is a log file with a Unix date stamp:

    ++ ++ ++
    ++Time Message
    ++1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00
    ++1266840760 closure data 0.000000 1972 1972 0
    ++1266840760 ++ 1266840760 EE 1
    ++1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00
    ++1266840764 closure data 0.000000 1972 1972 0
    ++
    ++ ++

    We would like the first column as an actual date object, so the convert ++field sets an explicit conversion for column 1. (Note that we have to explicitly ++convert the string to a number first.)

    ++ ++ ++
    ++Date = require 'pl.Date'
    ++
    ++function date_convert (ds)
    ++    return Date(tonumber(ds))
    ++end
    ++
    ++d = data.read(f,{convert={[1]=date_convert},last_field_collect=true})
    ++
    ++ ++

    This gives us a two-column dataset, where the first column contains Date objects ++and the second column contains the rest of the line. Queries can then easily ++pick out events on a day of the week:

    ++ ++ ++
    ++q = d:select "Time,Message where Time:weekday_name()=='Sun'"
    ++
    ++ ++

    Data does not have to come from files, nor does it necessarily come from the lab ++or the accounts department. On Linux, ps aux gives you a full listing of all ++processes running on your machine. It is straightforward to feed the output of ++this command into data.read and perform useful queries on it. Notice that ++non-identifier characters like '%' get converted into underscores:

    ++ ++ ++
    ++require 'pl'
    ++f = io.popen 'ps aux'
    ++s = data.read (f,{last_field_collect=true})
    ++f:close()
    ++print(s.fieldnames)
    ++print(s:column_by_name 'USER')
    ++qs = 'COMMAND,_MEM where _MEM > 5 and USER=="steve"'
    ++for name,mem in s:select(qs) do
    ++    print(mem,name)
    ++end
    ++
    ++ ++

    I've always been an admirer of the AWK programming language; with filter you ++can get Lua programs which are just as compact:

    ++ ++ ++
    ++-- printxy.lua
    ++require 'pl'
    ++data.filter 'x,y where x > 3'
    ++
    ++ ++

    It is common enough to have data files without headers of field names. ++data.read makes a special exception for such files if all fields are numeric. ++Since there are no column names to use in query expressions, you can use AWK-like ++column indexes, e.g. '$1,$2 where $1 > 3'. I have a little executable script on ++my system called lf which looks like this:

    ++ ++ ++
    ++#!/usr/bin/env lua
    ++require 'pl.data'.filter(arg[1])
    ++
    ++ ++

    And it can be used generally as a filter command to extract columns from data. ++(The column specifications may be expressions or even constants.)

    ++ ++ ++
    ++$ lf '$1,$5/10' < test.dat
    ++
    ++ ++

    (As with AWK, please note the single-quotes used in this command; this prevents ++the shell trying to expand the column indexes. If you are on Windows, then you ++must quote the expression in double-quotes so ++it is passed as one argument to your batch file.)

    ++ ++

    As a tutorial resource, have a look at test-data.lua in the PL tests directory ++for other examples of use, plus comments.

    ++ ++

    The data returned by read or constructed by Data.copy_select from a query is ++basically just an array of rows: {{1,2},{3,4}}. So you may use read to pull ++in any array-like dataset, and process with any function that expects such a ++implementation. In particular, the functions in array2d will work fine with ++this data. In fact, these functions are available as methods; e.g. ++array2d.flatten can be called directly like so to give us a one-dimensional list:

    ++ ++ ++
    ++v = data.read('dat.txt'):flatten()
    ++
    ++ ++

    The data is also in exactly the right shape to be treated as matrices by ++LuaMatrix:

    ++ ++ ++
    ++> matrix = require 'matrix'
    ++> m = matrix(data.read 'mat.txt')
    ++> = m
    ++1       0.2     0.3
    ++0.2     1       0.1
    ++0.1     0.2     1
    ++> = m^2  -- same as m*m
    ++1.07    0.46    0.62
    ++0.41    1.06    0.26
    ++0.24    0.42    1.05
    ++
    ++ ++

    write will write matrices back to files for you.

    ++ ++

    Finally, for the curious, the global variable _DEBUG can be used to print out ++the actual iterator function which a query generates and dynamically compiles. By ++using code generation, we can get pretty much optimal performance out of ++arbitrary queries.

    ++ ++ ++
    ++> lua -lpl -e "_DEBUG=true" -e "data.filter 'x,y where x > 4 sort by x'" < test.txt
    ++return function (t)
    ++        local i = 0
    ++        local v
    ++        local ls = {}
    ++        for i,v in ipairs(t) do
    ++            if v[1] > 4  then
    ++                    ls[#ls+1] = v
    ++            end
    ++        end
    ++        table.sort(ls,function(v1,v2)
    ++            return v1[1] < v2[1]
    ++        end)
    ++        local n = #ls
    ++        return function()
    ++            i = i + 1
    ++            v = ls[i]
    ++            if i > n then return end
    ++            return v[1],v[2]
    ++        end
    ++end
    ++
    ++10,20
    ++40,50
    ++
    ++ ++

    ++

    Reading Configuration Files

    ++ ++

    The config module provides a simple way to convert several kinds of ++configuration files into a Lua table. Consider the simple example:

    ++ ++ ++
    ++# test.config
    ++# Read timeout in seconds
    ++read.timeout=10
    ++
    ++# Write timeout in seconds
    ++write.timeout=5
    ++
    ++#acceptable ports
    ++ports = 1002,1003,1004
    ++
    ++ ++

    This can be easily brought in using config.read and the result shown using ++pretty.write:

    ++ ++ ++
    ++-- readconfig.lua
    ++local config = require 'pl.config'
    ++local pretty= require 'pl.pretty'
    ++
    ++local t = config.read(arg[1])
    ++print(pretty.write(t))
    ++
    ++ ++

    and the output of lua readconfig.lua test.config is:

    ++ ++ ++
    ++{
    ++  ports = {
    ++    1002,
    ++    1003,
    ++    1004
    ++  },
    ++  write_timeout = 5,
    ++  read_timeout = 10
    ++}
    ++
    ++ ++

    That is, config.read will bring in all key/value pairs, ignore # comments, and ++ensure that the key names are proper Lua identifiers by replacing non-identifier ++characters with '_'. If the values are numbers, then they will be converted. (So ++the value of t.write_timeout is the number 5). In addition, any values which ++are separated by commas will be converted likewise into an array.

    ++ ++

    Any line can be continued with a backslash. So this will all be considered one ++line:

    ++ ++ ++
    ++names=one,two,three, \
    ++four,five,six,seven, \
    ++eight,nine,ten
    ++
    ++ ++

    Windows-style INI files are also supported. The section structure of INI files ++translates naturally to nested tables in Lua:

    ++ ++ ++
    ++; test.ini
    ++[timeouts]
    ++read=10 ; Read timeout in seconds
    ++write=5 ; Write timeout in seconds
    ++[portinfo]
    ++ports = 1002,1003,1004
    ++
    ++ ++

    The output is:

    ++ ++ ++
    ++{
    ++  portinfo = {
    ++    ports = {
    ++      1002,
    ++      1003,
    ++      1004
    ++    }
    ++  },
    ++  timeouts = {
    ++    write = 5,
    ++    read = 10
    ++  }
    ++}
    ++
    ++ ++

    You can now refer to the write timeout as t.timeouts.write.

    ++ ++

    As a final example of the flexibility of config.read, if passed this simple ++comma-delimited file

    ++ ++ ++
    ++one,two,three
    ++10,20,30
    ++40,50,60
    ++1,2,3
    ++
    ++ ++

    it will produce the following table:

    ++ ++ ++
    ++{
    ++  { "one", "two", "three" },
    ++  { 10, 20, 30 },
    ++  { 40, 50, 60  },
    ++  { 1, 2, 3 }
    ++}
    ++
    ++ ++

    config.read isn't designed to read all CSV files in general, but intended to ++support some Unix configuration files not structured as key-value pairs, such as ++'/etc/passwd'.

    ++ ++

    This function is intended to be a Swiss Army Knife of configuration readers, but ++it does have to make assumptions, and you may not like them. So there is an ++optional extra parameter which allows some control, which is table that may have ++the following fields:

    ++ ++ ++
    ++{
    ++   variablilize = true,
    ++   convert_numbers = tonumber,
    ++   trim_space = true,
    ++   list_delim = ',',
    ++   trim_quotes = true,
    ++   ignore_assign = false,
    ++   keysep = '=',
    ++   smart = false,
    ++}
    ++
    ++ ++

    variablilize is the option that converted write.timeout in the first example ++to the valid Lua identifier write_timeout. If convert_numbers is true, then ++an attempt is made to convert any string that starts like a number. You can ++specify your own function (say one that will convert a string like '5224 kb' into ++a number.)

    ++ ++

    trim_space ensures that there is no starting or trailing whitespace with ++values, and list_delim is the character that will be used to decide whether to ++split a value up into a list (it may be a Lua string pattern such as '%s+'.)

    ++ ++

    For instance, the password file in Unix is colon-delimited:

    ++ ++ ++
    ++t = config.read('/etc/passwd',{list_delim=':'})
    ++
    ++ ++

    This produces the following output on my system (only last two lines shown):

    ++ ++ ++
    ++{
    ++  ...
    ++  {
    ++    "user",
    ++    "x",
    ++    "1000",
    ++    "1000",
    ++    "user,,,",
    ++    "/home/user",
    ++    "/bin/bash"
    ++  },
    ++  {
    ++    "sdonovan",
    ++    "x",
    ++    "1001",
    ++    "1001",
    ++    "steve donovan,28,,",
    ++    "/home/sdonovan",
    ++    "/bin/bash"
    ++  }
    ++}
    ++
    ++ ++

    You can get this into a more sensible format, where the usernames are the keys, ++with this (the tablex.pairmap function must return value, key!)

    ++ ++ ++
    ++t = tablex.pairmap(function(k,v) return v,v[1] end,t)
    ++
    ++ ++

    and you get:

    ++ ++ ++
    ++{ ...
    ++  sdonovan = {
    ++    "sdonovan",
    ++    "x",
    ++    "1001",
    ++    "1001",
    ++    "steve donovan,28,,",
    ++    "/home/sdonovan",
    ++    "/bin/bash"
    ++  }
    ++...
    ++}
    ++
    ++ ++

    Many common Unix configuration files can be read by tweaking these parameters. ++For /etc/fstab, the options {list_delim='%s+',ignore_assign=true} will ++correctly separate the columns. It's common to find 'KEY VALUE' assignments in ++files such as /etc/ssh/ssh_config; the options {keysep=' '} make ++config.read return a table where each KEY has a value VALUE.

    ++ ++

    Files in the Linux procfs usually use ':` as the field delimiter:

    ++ ++ ++
    ++> t = config.read('/proc/meminfo',{keysep=':'})
    ++> = t.MemFree
    ++220140 kB
    ++
    ++ ++

    That result is a string, since tonumber doesn't like it, but defining the ++convert_numbers option as `function(s) return tonumber((s:gsub(' kB$',''))) ++end` will get the memory figures as actual numbers in the result. (The extra ++parentheses are necessary so that tonumber only gets the first result from ++gsub). From `tests/test-config.lua':

    ++ ++ ++
    ++testconfig([[
    ++MemTotal:        1024748 kB
    ++MemFree:          220292 kB
    ++]],
    ++{ MemTotal = 1024748, MemFree = 220292 },
    ++{
    ++ keysep = ':',
    ++ convert_numbers = function(s)
    ++    s = s:gsub(' kB$','')
    ++    return tonumber(s)
    ++  end
    ++ }
    ++)
    ++
    ++ ++

    The smart option lets config.read make a reasonable guess for you; there ++are examples in tests/test-config.lua, but basically these common file ++formats (and those following the same pattern) can be processed directly in ++smart mode: 'etc/fstab', '/proc/XXXX/status', 'ssh_config' and 'pdatedb.conf'.

    ++ ++

    Please note that config.read can be passed a file-like object; if it's not a ++string and supports the read method, then that will be used. For instance, to ++read a configuration from a string, use stringio.open.

    ++ ++ ++

    ++ ++

    ++

    Lexical Scanning

    ++ ++

    Although Lua's string pattern matching is very powerful, there are times when ++something more powerful is needed. pl.lexer.scan provides lexical scanners ++which tokenize a string, classifying tokens into numbers, strings, etc.

    ++ ++ ++
    ++> lua -lpl
    ++Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
    ++> tok = lexer.scan 'alpha = sin(1.5)'
    ++> = tok()
    ++iden    alpha
    ++> = tok()
    ++=       =
    ++> = tok()
    ++iden    sin
    ++> = tok()
    ++(       (
    ++> = tok()
    ++number  1.5
    ++> = tok()
    ++)       )
    ++> = tok()
    ++(nil)
    ++
    ++ ++

    The scanner is a function, which is repeatedly called and returns the type and ++value of the token. Recognized basic types are 'iden','string','number', and ++'space'. and everything else is represented by itself. Note that by default the ++scanner will skip any 'space' tokens.

    ++ ++

    'comment' and 'keyword' aren't applicable to the plain scanner, which is not ++language-specific, but a scanner which understands Lua is available. It ++recognizes the Lua keywords, and understands both short and long comments and ++strings.

    ++ ++ ++
    ++> for t,v in lexer.lua 'for i=1,n do' do print(t,v) end
    ++keyword for
    ++iden    i
    ++=       =
    ++number  1
    ++,       ,
    ++iden    n
    ++keyword do
    ++
    ++ ++

    A lexical scanner is useful where you have highly-structured data which is not ++nicely delimited by newlines. For example, here is a snippet of a in-house file ++format which it was my task to maintain:

    ++ ++ ++
    ++points
    ++    (818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1)
    ++    ,(818327.4,-20388,-0.1),(818322,-20387.7,-0.1),(818316.3,-20388.6,-0.1)
    ++    ,(818309.7,-20389.4,-0.1),(818303.5,-20390.6,-0.1),(818295.8,-20388.3,-0.1)
    ++    ,(818290.5,-20386.9,-0.1),(818285.2,-20386.1,-0.1),(818279.3,-20383.6,-0.1)
    ++    ,(818274,-20381.2,-0.1),(818274,-20380.7,-0.1);
    ++
    ++ ++

    Here is code to extract the points using pl.lexer:

    ++ ++ ++
    ++-- assume 's' contains the text above...
    ++local lexer = require 'pl.lexer'
    ++local expecting = lexer.expecting
    ++local append = table.insert
    ++
    ++local tok = lexer.scan(s)
    ++
    ++local points = {}
    ++local t,v = tok() -- should be 'iden','points'
    ++
    ++while t ~= ';' do
    ++    c = {}
    ++    expecting(tok,'(')
    ++    c.x = expecting(tok,'number')
    ++    expecting(tok,',')
    ++    c.y = expecting(tok,'number')
    ++    expecting(tok,',')
    ++    c.z = expecting(tok,'number')
    ++    expecting(tok,')')
    ++    t,v = tok()  -- either ',' or ';'
    ++    append(points,c)
    ++end
    ++
    ++ ++

    The expecting function grabs the next token and if the type doesn't match, it ++throws an error. (pl.lexer, unlike other PL libraries, raises errors if ++something goes wrong, so you should wrap your code in pcall to catch the error ++gracefully.)

    ++ ++

    The scanners all have a second optional argument, which is a table which controls ++whether you want to exclude spaces and/or comments. The default for lexer.lua ++is {space=true,comments=true}. There is a third optional argument which ++determines how string and number tokens are to be processed.

    ++ ++

    The ultimate highly-structured data is of course, program source. Here is a ++snippet from 'text-lexer.lua':

    ++ ++ ++
    ++require 'pl'
    ++
    ++lines = [[
    ++for k,v in pairs(t) do
    ++    if type(k) == 'number' then
    ++        print(v) -- array-like case
    ++    else
    ++        print(k,v)
    ++    end
    ++end
    ++]]
    ++
    ++ls = List()
    ++for tp,val in lexer.lua(lines,{space=true,comments=true}) do
    ++    assert(tp ~= 'space' and tp ~= 'comment')
    ++    if tp == 'keyword' then ls:append(val) end
    ++end
    ++test.asserteq(ls,List{'for','in','do','if','then','else','end','end'})
    ++
    ++ ++

    Here is a useful little utility that identifies all common global variables found ++in a lua module (ignoring those declared locally for the moment):

    ++ ++ ++
    ++-- testglobal.lua
    ++require 'pl'
    ++
    ++local txt,err = utils.readfile(arg[1])
    ++if not txt then return print(err) end
    ++
    ++local globals = List()
    ++for t,v in lexer.lua(txt) do
    ++    if t == 'iden' and _G[v] then
    ++        globals:append(v)
    ++    end
    ++end
    ++pretty.dump(seq.count_map(globals))
    ++
    ++ ++

    Rather then dumping the whole list, with its duplicates, we pass it through ++seq.count_map which turns the list into a table where the keys are the values, ++and the associated values are the number of times those values occur in the ++sequence. Typical output looks like this:

    ++ ++ ++
    ++{
    ++  type = 2,
    ++  pairs = 2,
    ++  table = 2,
    ++  print = 3,
    ++  tostring = 2,
    ++  require = 1,
    ++  ipairs = 4
    ++}
    ++
    ++ ++

    You could further pass this through tablex.keys to get a unique list of ++symbols. This can be useful when writing 'strict' Lua modules, where all global ++symbols must be defined as locals at the top of the file.

    ++ ++

    For a more detailed use of lexer.scan, please look at testxml.lua in the ++examples directory.

    ++ ++

    ++

    XML

    ++ ++

    New in the 0.9.7 release is some support for XML. This is a large topic, and ++Penlight does not provide a full XML stack, which is properly the task of a more ++specialized library.

    ++ ++

    Parsing and Pretty-Printing

    ++ ++

    The semi-standard XML parser in the Lua universe is lua-expat. ++In particular, ++it has a function called lxp.lom.parse which will parse XML into the Lua Object ++Model (LOM) format. However, it does not provide a way to convert this data back ++into XML text. xml.parse will use this function, if lua-expat is ++available, and otherwise switches back to a pure Lua parser originally written by ++Roberto Ierusalimschy.

    ++ ++

    The resulting document object knows how to render itself as a string, which is ++useful for debugging:

    ++ ++ ++
    ++> d = xml.parse "<nodes><node id='1'>alice</node></nodes>"
    ++> = d
    ++<nodes><node id='1'>alice</node></nodes>
    ++> pretty.dump (d)
    ++{
    ++  {
    ++    "alice",
    ++    attr = {
    ++      "id",
    ++      id = "1"
    ++    },
    ++    tag = "node"
    ++  },
    ++  attr = {
    ++  },
    ++  tag = "nodes"
    ++}
    ++
    ++ ++

    Looking at the actual shape of the data reveals the structure of LOM:

    ++ ++
      ++
    • every element has a tag field with its name
    • ++
    • plus a attr field which is a table containing the attributes as fields, and ++ also as an array. It is always present.
    • ++
    • the children of the element are the array part of the element, so d[1] is ++ the first child of d, etc.
    • ++
    ++ ++

    It could be argued that having attributes also as the array part of attr is not ++essential (you cannot depend on attribute order in XML) but that's how ++it goes with this standard.

    ++ ++

    lua-expat is another soft dependency of Penlight; generally, the fallback ++parser is good enough for straightforward XML as is commonly found in ++configuration files, etc. doc.basic_parse is not intended to be a proper ++conforming parser (it's only sixty lines) but it handles simple kinds of ++documents that do not have comments or DTD directives. It is intelligent enough ++to ignore the <?xml directive and that is about it.

    ++ ++

    You can get pretty-printing by explicitly calling xml.tostring and passing it ++the initial indent and the per-element indent:

    ++ ++ ++
    ++> = xml.tostring(d,'','  ')
    ++
    ++<nodes>
    ++  <node id='1'>alice</node>
    ++</nodes>
    ++
    ++ ++

    There is a fourth argument which is the attribute indent:

    ++ ++ ++
    ++> a = xml.parse "<frodo name='baggins' age='50' type='hobbit'/>"
    ++> = xml.tostring(a,'','  ','  ')
    ++
    ++<frodo
    ++  type='hobbit'
    ++  name='baggins'
    ++  age='50'
    ++/>
    ++
    ++ ++

    Parsing and Working with Configuration Files

    ++ ++

    It's common to find configurations expressed with XML these days. It's ++straightforward to 'walk' the LOM ++data and extract the data in the form you want:

    ++ ++ ++
    ++require 'pl'
    ++
    ++local config = [[
    ++<config>
    ++    <alpha>1.3</alpha>
    ++    <beta>10</beta>
    ++    <name>bozo</name>
    ++</config>
    ++]]
    ++local d,err = xml.parse(config)
    ++
    ++local t = {}
    ++for item in d:childtags() do
    ++    t[item.tag] = item[1]
    ++end
    ++
    ++pretty.dump(t)
    ++--->
    ++{
    ++  beta = "10",
    ++  alpha = "1.3",
    ++  name = "bozo"
    ++}
    ++
    ++ ++

    The only gotcha is that here we must use the Doc:childtags method, which will ++skip over any text elements.

    ++ ++

    A more involved example is this excerpt from serviceproviders.xml, which is ++usually found at /usr/share/mobile-broadband-provider-info/serviceproviders.xml ++on Debian/Ubuntu Linux systems.

    ++ ++ ++
    ++d = xml.parse [[
    ++<serviceproviders format="2.0">
    ++...
    ++<country code="za">
    ++    <provider>
    ++        <name>Cell-c</name>
    ++        <gsm>
    ++            <network-id mcc="655" mnc="07"/>
    ++            <apn value="internet">
    ++                <username>Cellcis</username>
    ++                <dns>196.7.0.138</dns>
    ++                <dns>196.7.142.132</dns>
    ++            </apn>
    ++        </gsm>
    ++    </provider>
    ++    <provider>
    ++        <name>MTN</name>
    ++        <gsm>
    ++            <network-id mcc="655" mnc="10"/>
    ++            <apn value="internet">
    ++                <dns>196.11.240.241</dns>
    ++                <dns>209.212.97.1</dns>
    ++            </apn>
    ++        </gsm>
    ++    </provider>
    ++    <provider>
    ++        <name>Vodacom</name>
    ++        <gsm>
    ++            <network-id mcc="655" mnc="01"/>
    ++            <apn value="internet">
    ++                <dns>196.207.40.165</dns>
    ++                <dns>196.43.46.190</dns>
    ++            </apn>
    ++            <apn value="unrestricted">
    ++                <name>Unrestricted</name>
    ++                <dns>196.207.32.69</dns>
    ++                <dns>196.43.45.190</dns>
    ++            </apn>
    ++        </gsm>
    ++    </provider>
    ++    <provider>
    ++        <name>Virgin Mobile</name>
    ++        <gsm>
    ++            <apn value="vdata">
    ++                <dns>196.7.0.138</dns>
    ++                <dns>196.7.142.132</dns>
    ++            </apn>
    ++        </gsm>
    ++    </provider>
    ++</country>
    ++....
    ++</serviceproviders>
    ++]]
    ++
    ++ ++

    Getting the names of the providers per-country is straightforward:

    ++ ++ ++
    ++local t = {}
    ++for country in d:childtags() do
    ++    local providers = {}
    ++    t[country.attr.code] = providers
    ++    for provider in country:childtags() do
    ++        table.insert(providers,provider:child_with_name('name'):get_text())
    ++    end
    ++end
    ++
    ++pretty.dump(t)
    ++-->
    ++{
    ++  za = {
    ++    "Cell-c",
    ++    "MTN",
    ++    "Vodacom",
    ++    "Virgin Mobile"
    ++  }
    ++  ....
    ++}
    ++
    ++ ++

    Generating XML with 'xmlification'

    ++ ++

    This feature is inspired by the htmlify function used by ++Orbit to simplify HTML generation, ++except that no function environment magic is used; the tags function returns a ++set of constructors for elements of the given tag names.

    ++ ++ ++
    ++> nodes, node = xml.tags 'nodes, node'
    ++> = node 'alice'
    ++<node>alice</node>
    ++> = nodes { node {id='1','alice'}}
    ++<nodes><node id='1'>alice</node></nodes>
    ++
    ++ ++

    The flexibility of Lua tables is very useful here, since both the attributes and ++the children of an element can be encoded naturally. The argument to these tag ++constructors is either a single value (like a string) or a table where the ++attributes are the named keys and the children are the array values.

    ++ ++

    Generating XML using Templates

    ++ ++

    A template is a little XML document which contains dollar-variables. The subst ++method on a document is fed an array of tables containing values for these ++variables. Note how the parent tag name is specified:

    ++ ++ ++
    ++> templ = xml.parse "<node id='$id'>$name</node>"
    ++> = templ:subst {tag='nodes', {id=1,name='alice'},{id=2,name='john'}}
    ++<nodes><node id='1'>alice</node><node id='2'>john</node></nodes>
    ++
    ++ ++

    Substitution is very related to filtering documents. One of the annoying things ++about XML is that it is a document markup language first, and a data language ++second. Standard parsers will assume you really care about all those extra ++text elements. Consider this fragment, which has been changed by a five-year old:

    ++ ++ ++
    ++T = [[
    ++  <weather>
    ++    boops!
    ++    <current_conditions>
    ++      <condition data='$condition'/>
    ++      <temp_c data='$temp'/>
    ++      <bo>whoops!</bo>
    ++    </current_conditions>
    ++  </weather>
    ++]]
    ++
    ++ ++

    Conformant parsers will give you text elements with the line feed after <current_conditions> ++although it makes handling the data more irritating.

    ++ ++ ++
    ++local function parse (str)
    ++    return xml.parse(str,false,true)
    ++end
    ++
    ++ ++

    Second argument means 'string, not file' and third argument means use the built-in ++Lua parser (instead of LuaExpat if available) which by default is not interested in ++keeping such strings.

    ++ ++

    How to remove the string boops!? clone (also called filter when called as a ++method) copies a LOM document. It can be passed a filter function, which is applied ++to each string found. The powerful thing about this is that this function receives ++structural information - the parent node, and whether this was a tag name, a text ++element or a attribute name:

    ++ ++ ++
    ++d = parse (T)
    ++c = d:filter(function(s,kind,parent)
    ++    print(stringx.strip(s),kind,parent and parent.tag or '?')
    ++    if kind == '*TEXT' and #parent > 1 then return nil end
    ++    return s
    ++end)
    ++--->
    ++weather    *TAG    ?
    ++boops!    *TEXT    weather
    ++current_conditions    *TAG    weather
    ++condition    *TAG    current_conditions
    ++$condition    data    condition
    ++temp_c    *TAG    current_conditions
    ++$temp    data    temp_c
    ++bo    *TAG    current_conditions
    ++whoops!    *TEXT    bo
    ++
    ++ ++

    We can pull out 'boops' and not 'whoops' by discarding text elements which are not ++the single child of an element.

    ++ ++ ++ ++

    Extracting Data using Templates

    ++ ++

    Matching goes in the opposite direction. We have a document, and would like to ++extract values from it using a pattern.

    ++ ++

    A common use of this is parsing the XML result of API queries. The ++(undocumented and subsequently discontinued) Google Weather ++API is a ++good example. Grabbing the result of ++`http://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like ++this, after pretty-printing:

    ++ ++ ++
    ++<xml_api_reply version='1'>
    ++  <weather module_id='0' tab_id='0' mobile_zipped='1' section='0' row='0'
    ++
    ++ ++

    mobile_row='0'>

    ++ ++
    ++<forecast_information>
    ++  <city data='Johannesburg, Gauteng'/>
    ++  <postal_code data='Johannesburg,ZA'/>
    ++  <latitude_e6 data=''/>
    ++  <longitude_e6 data=''/>
    ++  <forecast_date data='2010-10-02'/>
    ++  <current_date_time data='2010-10-02 18:30:00 +0000'/>
    ++  <unit_system data='US'/>
    ++</forecast_information>
    ++<current_conditions>
    ++  <condition data='Clear'/>
    ++  <temp_f data='75'/>
    ++  <temp_c data='24'/>
    ++  <humidity data='Humidity: 19%'/>
    ++  <icon data='/ig/images/weather/sunny.gif'/>
    ++  <wind_condition data='Wind: NW at 7 mph'/>
    ++</current_conditions>
    ++<forecast_conditions>
    ++  <day_of_week data='Sat'/>
    ++  <low data='60'/>
    ++  <high data='89'/>
    ++  <icon data='/ig/images/weather/sunny.gif'/>
    ++  <condition data='Clear'/>
    ++</forecast_conditions>
    ++....
    ++/weather>
    ++l_api_reply>
    ++
    ++ ++

    Assume that the above XML has been read into google. The idea is to write a ++pattern looking like a template, and use it to extract some values of interest:

    ++ ++ ++
    ++t = [[
    ++  <weather>
    ++    <current_conditions>
    ++      <condition data='$condition'/>
    ++      <temp_c data='$temp'/>
    ++    </current_conditions>
    ++  </weather>
    ++]]
    ++
    ++local res, ret = google:match(t)
    ++pretty.dump(res)
    ++
    ++ ++

    And the output is:

    ++ ++ ++
    ++{
    ++  condition = "Clear",
    ++  temp = "24"
    ++}
    ++
    ++ ++

    The match method can be passed a LOM document or some text, which will be ++parsed first.

    ++ ++

    But what if we need to extract values from repeated elements? Match templates may ++contain 'array matches' which are enclosed in '{{..}}':

    ++ ++ ++
    ++<weather>
    ++  {{<forecast_conditions>
    ++    <day_of_week data='$day'/>
    ++    <low data='$low'/>
    ++    <high data='$high'/>
    ++    <condition data='$condition'/>
    ++  </forecast_conditions>}}
    ++</weather>
    ++
    ++ ++

    And the match result is:

    ++ ++ ++
    ++{
    ++  {
    ++    low = "60",
    ++    high = "89",
    ++    day = "Sat",
    ++    condition = "Clear",
    ++  },
    ++  {
    ++    low = "53",
    ++    high = "86",
    ++    day = "Sun",
    ++    condition = "Clear",
    ++  },
    ++  {
    ++    low = "57",
    ++    high = "87",
    ++    day = "Mon",
    ++    condition = "Clear",
    ++  },
    ++  {
    ++    low = "60",
    ++    high = "84",
    ++    day = "Tue",
    ++    condition = "Clear",
    ++  }
    ++}
    ++
    ++ ++

    With this array of tables, you can use tablex or List ++to reshape into the desired form, if you choose. Just as with reading a Unix password ++file with config, you can make the array into a map of days to conditions using:

    ++ ++ ++
    ++tablex.pairmap('|k,v| v,v.day',conditions)
    ++
    ++ ++

    (Here using the alternative string lambda option)

    ++ ++

    However, xml matches can shape the structure of the output. By replacing the day_of_week ++line of the template with <day_of_week data='$_'/> we get the same effect; $_ is ++a special symbol that means that this captured value (or simply capture) becomes the key.

    ++ ++

    Note that $NUMBER means a numerical index, so ++that $1 is the first element of the resulting array, and so forth. You can mix ++numbered and named captures, but it's strongly advised to make the numbered captures ++form a proper array sequence (everything from 1 to n inclusive). $0 has a ++special meaning; if it is the only capture ({[0]='foo'}) then the table is ++collapsed into 'foo'.

    ++ ++ ++
    ++<weather>
    ++  {{<forecast_conditions>
    ++    <day_of_week data='$_'/>
    ++    <low data='$1'/>
    ++    <high data='$2'/>
    ++    <condition data='$3'/>
    ++  </forecast_conditions>}}
    ++</weather>
    ++
    ++ ++

    Now the result is:

    ++ ++ ++
    ++{
    ++  Tue = {
    ++    "60",
    ++    "84",
    ++    "Clear"
    ++  },
    ++  Sun = {
    ++    "53",
    ++    "86",
    ++    "Clear"
    ++  },
    ++  Sat = {
    ++    "60",
    ++    "89",
    ++    "Clear"
    ++  },
    ++  Mon = {
    ++    "57",
    ++    "87",
    ++    "Clear"
    ++  }
    ++}
    ++
    ++ ++

    Applying matches to this config file poses another problem, because the actual ++tags matched are themselves meaningful.

    ++ ++ ++
    ++<config>
    ++    <alpha>1.3</alpha>
    ++    <beta>10</beta>
    ++    <name>bozo</name>
    ++</config>
    ++
    ++ ++

    So there are tag 'wildcards' which are element names ending with a hyphen.

    ++ ++ ++
    ++<config>
    ++    {{<key->$value</key->}}
    ++</config>
    ++
    ++ ++

    You will then get {{alpha='1.3'},...}. The most convenient format would be ++returned by this (note that _- behaves just like $_):

    ++ ++ ++
    ++<config>
    ++    {{<_->$0</_->}}
    ++</config>
    ++
    ++ ++

    which would return {alpha='1.3',beta='10',name='bozo'}.

    ++ ++

    We could play this game endlessly, and encode ways of converting captures, but ++the scheme is complex enough, and it's easy to do the conversion later

    ++ ++ ++
    ++local numbers = {alpha=true,beta=true}
    ++for k,v in pairs(res) do
    ++    if numbers[v] then res[k] = tonumber(v) end
    ++end
    ++
    ++ ++

    HTML Parsing

    ++ ++

    HTML is an unusually degenerate form of XML, and Dennis Schridde has contributed ++a feature which makes parsing it easier. For instance, from the tests:

    ++ ++ ++
    ++doc = xml.parsehtml [[
    ++<BODY>
    ++Hello dolly<br>
    ++HTML is <b>slack</b><br>
    ++</BODY>
    ++]]
    ++
    ++asserteq(xml.tostring(doc),[[
    ++<body>
    ++Hello dolly<br/>
    ++HTML is <b>slack</b><br/></body>]])
    ++
    ++ ++

    That is, all tags are converted to lowercase, and empty HTML elements like br ++are properly closed; attributes do not need to be quoted.

    ++ ++

    Also, DOCTYPE directives and comments are skipped. For truly badly formed HTML, ++this is not the tool for you!

    ++ ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/07-functional.md.html b/extra/penlight/docs/manual/07-functional.md.html +new file mode 100644 +index 0000000..d6d6eab +--- /dev/null ++++ b/extra/penlight/docs/manual/07-functional.md.html +@@ -0,0 +1,834 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Functional Programming

    ++ ++

    ++

    Sequences

    ++ ++ ++

    A Lua iterator (in its simplest form) is a function which can be repeatedly ++called to return a set of one or more values. The for in statement understands ++these iterators, and loops until the function returns nil. There are standard ++sequence adapters for tables in Lua (ipairs and pairs), and io.lines ++returns an iterator over all the lines in a file. In the Penlight libraries, such ++iterators are also called sequences. A sequence of single values (say from ++io.lines) is called single-valued, whereas the sequence defined by pairs is ++double-valued.

    ++ ++

    pl.seq provides a number of useful iterators, and some functions which operate ++on sequences. At first sight this example looks like an attempt to write Python ++in Lua, (with the sequence being inclusive):

    ++ ++ ++
    ++> for i in seq.range(1,4) do print(i) end
    ++1
    ++2
    ++3
    ++4
    ++
    ++ ++

    But range is actually equivalent to Python's xrange, since it generates a ++sequence, not a list. To get a list, use seq.copy(seq.range(1,10)), which ++takes any single-value sequence and makes a table from the result. seq.list is ++like ipairs except that it does not give you the index, just the value.

    ++ ++ ++
    ++> for x in seq.list {1,2,3} do print(x) end
    ++1
    ++2
    ++3
    ++
    ++ ++

    enum takes a sequence and turns it into a double-valued sequence consisting of ++a sequence number and the value, so enum(list(ls)) is actually equivalent to ++ipairs. A more interesting example prints out a file with line numbers:

    ++ ++ ++
    ++for i,v in seq.enum(io.lines(fname)) do print(i..' '..v) end
    ++
    ++ ++

    Sequences can be combined, either by 'zipping' them or by concatenating them.

    ++ ++ ++
    ++> for x,y in seq.zip(l1,l2) do print(x,y) end
    ++10      1
    ++20      2
    ++30      3
    ++> for x in seq.splice(l1,l2) do print(x) end
    ++10
    ++20
    ++30
    ++1
    ++2
    ++3
    ++
    ++ ++

    seq.printall is useful for printing out single-valued sequences, and provides ++some finer control over formatting, such as a delimiter, the number of fields per ++line, and a format string to use (@see string.format)

    ++ ++ ++
    ++> seq.printall(seq.random(10))
    ++0.0012512588885159 0.56358531449324 0.19330423902097 ....
    ++> seq.printall(seq.random(10), ',', 4, '%4.2f')
    ++0.17,0.86,0.71,0.51
    ++0.30,0.01,0.09,0.36
    ++0.15,0.17,
    ++
    ++ ++

    map will apply a function to a sequence.

    ++ ++ ++
    ++> seq.printall(seq.map(string.upper, {'one','two'}))
    ++ONE TWO
    ++> seq.printall(seq.map('+', {10,20,30}, 1))
    ++11 21 31
    ++
    ++ ++

    filter will filter a sequence using a boolean function (often called a ++predicate). For instance, this code only prints lines in a file which are ++composed of digits:

    ++ ++ ++
    ++for l in seq.filter(io.lines(file), stringx.isdigit) do print(l) end
    ++
    ++ ++

    The following returns a table consisting of all the positive values in the ++original table (equivalent to tablex.filter(ls, '>', 0))

    ++ ++ ++
    ++ls = seq.copy(seq.filter(ls, '>', 0))
    ++
    ++ ++

    We're already encountered seq.sum when discussing input.numbers. This can also ++be expressed with seq.reduce:

    ++ ++ ++
    ++> seq.reduce(function(x,y) return x + y end, seq.list{1,2,3,4})
    ++10
    ++
    ++ ++

    seq.reduce applies a binary function in a recursive fashion, so that:

    ++ ++ ++
    ++reduce(op,{1,2,3}) => op(1,reduce(op,{2,3}) => op(1,op(2,3))
    ++
    ++ ++

    it's now possible to easily generate other cumulative operations; the standard ++operations declared in pl.operator are useful here:

    ++ ++ ++
    ++> ops = require 'pl.operator'
    ++> -- can also say '*' instead of ops.mul
    ++> = seq.reduce(ops.mul,input.numbers '1 2 3 4')
    ++24
    ++
    ++ ++

    There are functions to extract statistics from a sequence of numbers:

    ++ ++ ++
    ++> l1 = List {10,20,30}
    ++> l2 = List {1,2,3}
    ++> = seq.minmax(l1)
    ++10      30
    ++> = seq.sum(l1)
    ++60      3
    ++
    ++ ++

    It is common to get sequences where values are repeated, say the words in a file. ++count_map will take such a sequence and count the values, returning a table ++where the keys are the unique values, and the value associated with each key is ++the number of times they occurred:

    ++ ++ ++
    ++> t = seq.count_map {'one','fred','two','one','two','two'}
    ++> = t
    ++{one=2,fred=1,two=3}
    ++
    ++ ++

    This will also work on numerical sequences, but you cannot expect the result to ++be a proper list, i.e. having no 'holes'. Instead, you always need to use pairs ++to iterate over the result - note that there is a hole at index 5:

    ++ ++ ++
    ++> t = seq.count_map {1,2,4,2,2,3,4,2,6}
    ++> for k,v in pairs(t) do print(k,v) end
    ++1       1
    ++2       4
    ++3       1
    ++4       2
    ++6       1
    ++
    ++ ++

    unique uses count_map to return a list of the unique values, that is, just ++the keys of the resulting table.

    ++ ++

    last turns a single-valued sequence into a double-valued sequence with the ++current value and the last value:

    ++ ++ ++
    ++> for current,last in seq.last {10,20,30,40} do print (current,last) end
    ++20      10
    ++30      20
    ++40      30
    ++
    ++ ++

    This makes it easy to do things like identify repeated lines in a file, or ++construct differences between values. filter can handle double-valued sequences ++as well, so one could filter such a sequence to only return cases where the ++current value is less than the last value by using operator.lt or just '<'. ++This code then copies the resulting code into a table.

    ++ ++ ++
    ++> ls = {10,9,10,3}
    ++> = seq.copy(seq.filter(seq.last(s),'<'))
    ++{9,3}
    ++
    ++ ++

    ++

    Sequence Wrappers

    ++ ++

    The functions in pl.seq cover the common patterns when dealing with sequences, ++but chaining these functions together can lead to ugly code. Consider the last ++example of the previous section; seq is repeated three times and the resulting ++expression has to be read right-to-left. The first issue can be helped by local ++aliases, so that the expression becomes copy(filter(last(s),'<')) but the ++second issue refers to the somewhat unnatural order of functional application. ++We tend to prefer reading operations from left to right, which is one reason why ++object-oriented notation has become popular. Sequence adapters allow this ++expression to be written like so:

    ++ ++ ++
    ++seq(s):last():filter('<'):copy()
    ++
    ++ ++

    With this notation, the operation becomes a chain of method calls running from ++left to right.

    ++ ++

    'Sequence' is not a basic Lua type, they are generally functions or callable ++objects. The expression seq(s) wraps a sequence in a sequence wrapper, which ++is an object which understands all the functions in pl.seq as methods. This ++object then explicitly represents sequences.

    ++ ++

    As a special case, the constructor (which is when you call the table seq) will ++make a wrapper for a plain list-like table. Here we apply the length operator to ++a sequence of strings, and print them out.

    ++ ++ ++
    ++> seq{'one','tw','t'} :map '#' :printall()
    ++3 2 1
    ++
    ++ ++

    As a convenience, there is a function seq.lines which behaves just like ++io.lines except it wraps the result as an explicit sequence type. This takes ++the first 10 lines from standard input, makes it uppercase, turns it into a ++sequence with a count and the value, glues these together with the concatenation ++operator, and finally prints out the sequence delimited by a newline.

    ++ ++ ++
    ++seq.lines():take(10):upper():enum():map('..'):printall '\n'
    ++
    ++ ++

    Note the method upper, which is not a seq function. if an unknown method is ++called, sequence wrappers apply that method to all the values in the sequence ++(this is implicit use of mapmethod)

    ++ ++

    It is straightforward to create custom sequences that can be used in this way. On ++Unix, /dev/random gives you an endless sequence of random bytes, so we use ++take to limit the sequence, and then map to scale the result into the desired ++range. The key step is to use seq to wrap the iterator function:

    ++ ++ ++
    ++-- random.lua
    ++local seq = require 'pl.seq'
    ++
    ++function dev_random()
    ++    local f = io.open('/dev/random')
    ++    local byte = string.byte
    ++    return seq(function()
    ++        -- read two bytes into a string and convert into a 16-bit number
    ++        local s = f:read(2)
    ++        return byte(s,1) + 256*byte(s,2)
    ++    end)
    ++end
    ++
    ++-- print 10 random numbers from 0 to 1 !
    ++dev_random():take(10):map('%',100):map('/',100):printall ','
    ++
    ++ ++

    Another Linux one-liner depends on the /proc filesystem and makes a list of all ++the currently running processes:

    ++ ++ ++
    ++pids = seq(lfs.dir '/proc'):filter(stringx.isdigit):map(tonumber):copy()
    ++
    ++ ++

    This version of Penlight has an experimental feature which relies on the fact ++that all Lua types can have metatables, including functions. This makes ++implicit sequence wrapping possible:

    ++ ++ ++
    ++> seq.import()
    ++> seq.random(5):printall(',',5,'%4.1f')
    ++ 0.0, 0.1, 0.4, 0.1, 0.2
    ++
    ++ ++

    This avoids the awkward seq(seq.random(5)) construction. Or the iterator can ++come from somewhere else completely:

    ++ ++ ++
    ++> ('one two three'):gfind('%a+'):printall(',')
    ++one,two,three,
    ++
    ++ ++

    After seq.import, it is no longer necessary to explicitly wrap sequence ++functions.

    ++ ++

    But there is a price to pay for this convenience. Every function is affected, ++so that any function can be used, appropriate or not:

    ++ ++ ++
    ++> math.sin:printall()
    ++..seq.lua:287: bad argument #1 to '(for generator)' (number expected, got nil)
    ++> a = tostring
    ++> = a:find(' ')
    ++function: 0042C920
    ++
    ++ ++

    What function is returned? It's almost certain to be something that makes no ++sense in the current context. So implicit sequences may make certain kinds of ++programming mistakes harder to catch - they are best used for interactive ++exploration and small scripts.

    ++ ++

    ++ ++

    ++

    List Comprehensions

    ++ ++

    List comprehensions are a compact way to create tables by specifying their ++elements. In Python, you can say this:

    ++ ++ ++
    ++ls = [x for x in range(5)]  # == [0,1,2,3,4]
    ++
    ++ ++

    In Lua, using pl.comprehension:

    ++ ++ ++
    ++> C = require('pl.comprehension').new()
    ++> = C ('x for x=1,10') ()
    ++{1,2,3,4,5,6,7,8,9,10}
    ++
    ++ ++

    C is a function which compiles a list comprehension string into a function. ++In this case, the function has no arguments. The parentheses are redundant for a ++function taking a string argument, so this works as well:

    ++ ++ ++
    ++> = C 'x^2 for x=1,4' ()
    ++{1,4,9,16}
    ++> = C '{x,x^2} for x=1,4' ()
    ++{{1,1},{2,4},{3,9},{4,16}}
    ++
    ++ ++

    Note that the expression can be any function of the variable x!

    ++ ++

    The basic syntax so far is <expr> for <set>, where <set> can be anything that ++the Lua for statement understands. <set> can also just be the variable, in ++which case the values will come from the argument of the comprehension. Here ++I'm emphasizing that a comprehension is a function which can take a list argument:

    ++ ++ ++
    ++> = C '2*x for x' {1,2,3}
    ++{2,4,6}
    ++> dbl = C '2*x for x'
    ++> = dbl {10,20,30}
    ++{20,40,60}
    ++
    ++ ++

    Here is a somewhat more explicit way of saying the same thing; _1 is a ++placeholder referring to the first argument passed to the comprehension.

    ++ ++ ++
    ++> = C '2*x for _,x in pairs(_1)' {10,20,30}
    ++{20,40,60}
    ++> = C '_1(x) for x'(tostring,{1,2,3,4})
    ++{'1','2','3','4'}
    ++
    ++ ++

    This extended syntax is useful when you wish to collect the result of some ++iterator, such as io.lines. This comprehension creates a function which creates ++a table of all the lines in a file:

    ++ ++ ++
    ++> f = io.open('array.lua')
    ++> lines = C 'line for line in _1:lines()' (f)
    ++> = #lines
    ++118
    ++
    ++ ++

    There are a number of functions that may be applied to the result of a ++comprehension:

    ++ ++ ++
    ++> = C 'min(x for x)' {1,44,0}
    ++0
    ++> = C 'max(x for x)' {1,44,0}
    ++44
    ++> = C 'sum(x for x)' {1,44,0}
    ++45
    ++
    ++ ++

    (These are equivalent to a reduce operation on a list.)

    ++ ++

    After the for part, there may be a condition, which filters the output. This ++comprehension collects the even numbers from a list:

    ++ ++ ++
    ++> = C 'x for x if x % 2 == 0' {1,2,3,4,5}
    ++{2,4}
    ++
    ++ ++

    There may be a number of for parts:

    ++ ++ ++
    ++> = C '{x,y} for x = 1,2 for y = 1,2' ()
    ++{{1,1},{1,2},{2,1},{2,2}}
    ++> = C '{x,y} for x for y' ({1,2},{10,20})
    ++{{1,10},{1,20},{2,10},{2,20}}
    ++
    ++ ++

    These comprehensions are useful when dealing with functions of more than one ++variable, and are not so easily achieved with the other Penlight functional forms.

    ++ ++

    ++ ++

    ++

    Creating Functions from Functions

    ++ ++ ++

    Lua functions may be treated like any other value, although of course you cannot ++multiply or add them. One operation that makes sense is function composition, ++which chains function calls (so (f * g)(x) is f(g(x)).)

    ++ ++ ++
    ++> func = require 'pl.func'
    ++> printf = func.compose(io.write,string.format)
    ++> printf("hello %s\n",'world')
    ++hello world
    ++true
    ++
    ++ ++

    Many functions require you to pass a function as an argument, say to apply to all ++values of a sequence or as a callback. Often useful functions have the wrong ++number of arguments. So there is a need to construct a function of one argument ++from one of two arguments, binding the extra argument to a given value.

    ++ ++

    partial application takes a function of n arguments and returns a function of n-1 ++arguments where the first argument is bound to some value:

    ++ ++ ++
    ++> p2 = func.bind1(print,'start>')
    ++> p2('hello',2)
    ++start>  hello   2
    ++> ops = require 'pl.operator'
    ++> = tablex.filter({1,-2,10,-1,2},bind1(ops.gt,0))
    ++{-2,-1}
    ++> tablex.filter({1,-2,10,-1,2},bind1(ops.le,0))
    ++{1,10,2}
    ++
    ++ ++

    The last example unfortunately reads backwards, because bind1 always binds the ++first argument! Also unfortunately, in my youth I confused 'currying' with ++'partial application', so the old name for bind1 is curry - this alias still exists.

    ++ ++

    This is a specialized form of function argument binding. Here is another way ++to say the print example:

    ++ ++ ++
    ++> p2 = func.bind(print,'start>',func._1,func._2)
    ++> p2('hello',2)
    ++start>  hello   2
    ++
    ++ ++

    where _1 and _2 are placeholder variables, corresponding to the first and ++second argument respectively.

    ++ ++

    Having func all over the place is distracting, so it's useful to pull all of ++pl.func into the local context. Here is the filter example, this time the right ++way around:

    ++ ++ ++
    ++> utils.import 'pl.func'
    ++> tablex.filter({1,-2,10,-1,2},bind(ops.gt, _1, 0))
    ++{1,10,2}
    ++
    ++ ++

    tablex.merge does a general merge of two tables. This example shows the ++usefulness of binding the last argument of a function.

    ++ ++ ++
    ++> S1 = {john=27, jane=31, mary=24}
    ++> S2 = {jane=31, jones=50}
    ++> intersection = bind(tablex.merge, _1, _2, false)
    ++> union = bind(tablex.merge, _1, _2, true)
    ++> = intersection(S1,S2)
    ++{jane=31}
    ++> = union(S1,S2)
    ++{mary=24,jane=31,john=27,jones=50}
    ++
    ++ ++

    When using bind with print, we got a function of precisely two arguments, ++whereas we really want our function to use varargs like print. This is the role ++of _0:

    ++ ++ ++
    ++> _DEBUG = true
    ++> p = bind(print,'start>', _0)
    ++return function (fn,_v1)
    ++    return function(...) return fn(_v1,...) end
    ++end
    ++
    ++> p(1,2,3,4,5)
    ++start>  1       2       3       4       5
    ++
    ++ ++

    I've turned on the global _DEBUG flag, so that the function generated is ++printed out. It is actually a function which generates the required function; ++the first call binds the value of _v1 to 'start>'.

    ++ ++

    ++

    Placeholder Expressions

    ++ ++

    A common pattern in Penlight is a function which applies another function to all ++elements in a table or a sequence, such as tablex.map or seq.filter. Lua does ++anonymous functions well, although they can be a bit tedious to type:

    ++ ++ ++
    ++> = tablex.map(function(x) return x*x end, {1,2,3,4})
    ++{1,4,9,16}
    ++
    ++ ++

    pl.func allows you to define placeholder expressions, which can cut down on ++the typing required, and also make your intent clearer. First, we bring contents ++of pl.func into our context, and then supply an expression using placeholder ++variables, such as _1,_2,etc. (C++ programmers will recognize this from the ++Boost libraries.)

    ++ ++ ++
    ++> utils.import 'pl.func'
    ++> = tablex.map(_1*_1, {1,2,3,4})
    ++{1,4,9,16}
    ++
    ++ ++

    Functions of up to 5 arguments can be generated.

    ++ ++ ++
    ++> = tablex.map2(_1+_2,{1,2,3}, {10,20,30})
    ++{11,22,33}
    ++
    ++ ++

    These expressions can use arbitrary functions, although they must first be ++registered with the functional library. func.register brings in a single ++function, and func.import brings in a whole table of functions, such as math.

    ++ ++ ++
    ++> sin = register(math.sin)
    ++> = tablex.map(sin(_1), {1,2,3,4})
    ++{0.8414709848079,0.90929742682568,0.14112000805987,-0.75680249530793}
    ++> import 'math'
    ++> = tablex.map(cos(2*_1),{1,2,3,4})
    ++{-0.41614683654714,-0.65364362086361,0.96017028665037,-0.14550003380861}
    ++
    ++ ++

    A common operation is calling a method of a set of objects:

    ++ ++ ++
    ++> = tablex.map(_1:sub(1,1), {'one','four','x'})
    ++{'o','f','x'}
    ++
    ++ ++

    There are some restrictions on what operators can be used in PEs. For instance, ++because the __len metamethod cannot be overridden by plain Lua tables, we need ++to define a special function to express `#_1':

    ++ ++ ++
    ++> = tablex.map(Len(_1), {'one','four','x'})
    ++{3,4,1}
    ++
    ++ ++

    Likewise for comparison operators, which cannot be overloaded for different ++types, and thus also have to be expressed as a special function:

    ++ ++ ++
    ++> = tablex.filter(Gt(_1,0), {1,-1,2,4,-3})
    ++{1,2,4}
    ++
    ++ ++

    It is useful to express the fact that a function returns multiple values. For ++instance, tablex.pairmap expects a function that will be called with the key ++and the value, and returns the new value and the key, in that order.

    ++ ++ ++
    ++> = pairmap(Args(_2,_1:upper()),{fred=1,alice=2})
    ++{ALICE=2,FRED=1}
    ++
    ++ ++

    PEs cannot contain nil values, since PE function arguments are represented as ++an array. Instead, a special value called Nil is provided. So say ++_1:f(Nil,1) instead of _1:f(nil,1).

    ++ ++

    A placeholder expression cannot be automatically used as a Lua function. The ++technical reason is that the call operator must be overloaded to construct ++function calls like _1(1). If you want to force a PE to return a function, use ++func.I.

    ++ ++ ++
    ++> = tablex.map(_1(10),{I(2*_1),I(_1*_1),I(_1+2)})
    ++{20,100,12}
    ++
    ++ ++

    Here we make a table of functions taking a single argument, and then call them ++all with a value of 10.

    ++ ++

    The essential idea with PEs is to 'quote' an expression so that it is not ++immediately evaluated, but instead turned into a function that can be applied ++later to some arguments. The basic mechanism is to wrap values and placeholders ++so that the usual Lua operators have the effect of building up an expression ++tree. (It turns out that you can do symbolic algebra using PEs, see ++symbols.lua in the examples directory, and its test runner testsym.lua, which ++demonstrates symbolic differentiation.)

    ++ ++

    The rule is that if any operator has a PE operand, the result will be quoted. ++Sometimes we need to quote things explicitly. For instance, say we want to pass a ++function to a filter that must return true if the element value is in a set. ++set[_1] is the obvious expression, but it does not give the desired result, ++since it evaluates directly, giving nil. Indexing works differently than a ++binary operation like addition (set+_1 is properly quoted) so there is a need ++for an explicit quoting or wrapping operation. This is the job of the _ ++function; the PE in this case should be _(set)[_1]. This works for functions ++as well, as a convenient alternative to registering functions: _(math.sin)(_1). ++This is equivalent to using the `lines' method:

    ++ ++ ++
    ++for line in I(_(f):read()) do print(line) end
    ++
    ++ ++

    Now this will work for any 'file-like' object which which has a read method ++returning the next line. If you had a LuaSocket client which was being 'pushed' ++by lines sent from a server, then _(s):receive '*l' would create an iterator ++for accepting input. These forms can be convenient for adapting your data flow so ++that it can be passed to the sequence functions in `pl.seq'.

    ++ ++

    Placeholder expressions can be mixed with sequence wrapper expressions. ++lexer.lua will give us a double-valued sequence of tokens, where the first ++value is a type, and the second is a value. We filter out only the values where ++the type is 'iden', extract the actual value using map, get the unique values ++and finally copy to a list.

    ++ ++ ++
    ++> str = 'for i=1,10 do for j = 1,10 do print(i,j) end end'
    ++> = seq(lexer.lua(str)):filter('==','iden'):map(_2):unique():copy()
    ++{i,print,j}
    ++
    ++ ++

    This is a particularly intense line (and I don't always suggest making everything ++a one-liner!); the key is the behaviour of map, which will take both values of ++the sequence, so _2 returns the value part. (Since filter here takes extra ++arguments, it only operates on the type values.)

    ++ ++

    There are some performance considerations to using placeholder expressions. ++Instantiating a PE requires constructing and compiling a function, which is not ++such a fast operation. So to get best performance, factor out PEs from loops like ++this;

    ++ ++ ++
    ++local fn = I(_1:f() + _2:g())
    ++for i = 1,n do
    ++    res[i] = tablex.map2(fn,first[i],second[i])
    ++end
    ++
    ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/08-additional.md.html b/extra/penlight/docs/manual/08-additional.md.html +new file mode 100644 +index 0000000..3f96f05 +--- /dev/null ++++ b/extra/penlight/docs/manual/08-additional.md.html +@@ -0,0 +1,815 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Additional Libraries

    ++ ++

    Libraries in this section are no longer considered to be part of the Penlight ++core, but still provide specialized functionality when needed.

    ++ ++

    ++ ++

    ++

    Simple Input Patterns

    ++ ++

    Lua string pattern matching is very powerful, and usually you will not need a ++traditional regular expression library. Even so, sometimes Lua code ends up ++looking like Perl, which happens because string patterns are not always the ++easiest things to read, especially for the casual reader. Here is a program ++which needs to understand three distinct date formats:

    ++ ++ ++
    ++-- parsing dates using Lua string patterns
    ++months={Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,
    ++Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12}
    ++
    ++function check_and_process(d,m,y)
    ++    d = tonumber(d)
    ++    m = tonumber(m)
    ++    y = tonumber(y)
    ++    ....
    ++end
    ++
    ++for line in f:lines() do
    ++    -- ordinary (English) date format
    ++    local d,m,y = line:match('(%d+)/(%d+)/(%d+)')
    ++    if d then
    ++        check_and_process(d,m,y)
    ++    else -- ISO date??
    ++        y,m,d = line:match('(%d+)%-(%d+)%-(%d+)')
    ++        if y then
    ++            check_and_process(d,m,y)
    ++        else -- <day> <month-name> <year>?
    ++            d,mm,y = line:match('%(d+)%s+(%a+)%s+(%d+)')
    ++            m = months[mm]
    ++            check_and_process(d,m,y)
    ++        end
    ++    end
    ++end
    ++
    ++ ++

    These aren't particularly difficult patterns, but already typical issues are ++appearing, such as having to escape '-'. Also, string.match returns its ++captures, so that we're forced to use a slightly awkward nested if-statement.

    ++ ++

    Verification issues will further cloud the picture, since regular expression ++people try to enforce constraints (like year cannot be more than four digits) ++using regular expressions, on the usual grounds that you shouldn't stop using a ++hammer when you are enjoying yourself.

    ++ ++

    pl.sip provides a simple, intuitive way to detect patterns in strings and ++extract relevant parts.

    ++ ++ ++
    ++> sip = require 'pl.sip'
    ++> dump = require('pl.pretty').dump
    ++> res = {}
    ++> c = sip.compile 'ref=$S{file}:$d{line}'
    ++> = c('ref=hello.c:10',res)
    ++true
    ++> dump(res)
    ++{
    ++  line = 10,
    ++  file = "hello.c"
    ++}
    ++> = c('ref=long name, no line',res)
    ++false
    ++
    ++ ++

    sip.compile creates a pattern matcher function, which takes a string and a ++table as arguments. If the string matches the pattern, then true is returned ++and the table is populated according to the captures within the pattern.

    ++ ++

    Here is another version of the date parser:

    ++ ++ ++
    ++-- using SIP patterns
    ++function check(t)
    ++    check_and_process(t.day,t.month,t.year)
    ++end
    ++
    ++shortdate = sip.compile('$d{day}/$d{month}/$d{year}')
    ++longdate = sip.compile('$d{day} $v{mon} $d{year}')
    ++isodate = sip.compile('$d{year}-$d{month}-$d{day}')
    ++
    ++for line in f:lines() do
    ++    local res = {}
    ++    if shortdate(str,res) then
    ++        check(res)
    ++    elseif isodate(str,res) then
    ++        check(res)
    ++    elseif longdate(str,res) then
    ++        res.month = months[res.mon]
    ++        check(res)
    ++    end
    ++end
    ++
    ++ ++

    SIP captures start with '$', then a one-character type, and then an ++optional variable name in curly braces.

    ++ ++ ++
    ++Type      Meaning
    ++v         identifier
    ++i         possibly signed integer
    ++f         floating-point number
    ++r         rest of line
    ++q         quoted string (quoted using either ' or ")
    ++p         a path name
    ++(         anything inside balanced parentheses
    ++[         anything inside balanced brackets
    ++{         anything inside balanced curly brackets
    ++<         anything inside balanced angle brackets
    ++
    ++ ++

    If a type is not one of the above, then it's assumed to be one of the standard ++Lua character classes, and will match one or more repetitions of that class. ++Any spaces you leave in your pattern will match any number of spaces, including ++zero, unless the spaces are between two identifier characters or patterns ++matching them; in that case, at least one space will be matched.

    ++ ++

    SIP captures (like $v{mon}) do not have to be named. You can use just $v, but ++you have to be consistent; if a pattern contains unnamed captures, then all ++captures must be unnamed. In this case, the result table is a simple list of ++values.

    ++ ++

    sip.match is a useful shortcut if you want to compile and match in one call, ++without saving the compiled pattern. It caches the result, so it is not much ++slower than explicitly using sip.compile.

    ++ ++ ++
    ++> sip.match('($q{first},$q{second})','("john","smith")',res)
    ++true
    ++> res
    ++{second='smith',first='john'}
    ++> res = {}
    ++> sip.match('($q,$q)','("jan","smit")',res)  -- unnamed captures
    ++true
    ++> res
    ++{'jan','smit'}
    ++> sip.match('($q,$q)','("jan", "smit")',res)
    ++false   ---> oops! Can't handle extra space!
    ++> sip.match('( $q , $q )','("jan", "smit")',res)
    ++true
    ++
    ++ ++

    As a general rule, allow for whitespace in your patterns.

    ++ ++

    Finally, putting a '$' at the end of a pattern means 'capture the rest of the ++line, starting at the first non-space'. It is a shortcut for '$r{rest}', ++or just '$r' if no named captures are used.

    ++ ++ ++
    ++> sip.match('( $q , $q ) $','("jan", "smit") and a string',res)
    ++true
    ++> res
    ++{'jan','smit','and a string'}
    ++> res = {}
    ++> sip.match('( $q{first} , $q{last} ) $','("jan", "smit") and a string',res)
    ++true
    ++> res
    ++{first='jan',rest='and a string',last='smit'}
    ++
    ++ ++

    ++ ++

    ++

    Command-line Programs with Lapp

    ++ ++

    pl.lapp is a small and focused Lua module which aims to make standard ++command-line parsing easier and intuitive. It implements the standard GNU style, ++i.e. short flags with one letter start with '-', and there may be an additional ++long flag which starts with '--'. Generally options which take an argument expect ++to find it as the next parameter (e.g. 'gcc test.c -o test') but single short ++options taking a value can dispense with the space (e.g. 'head -n4 ++test.c' or gcc -I/usr/include/lua/5.1 ...)

    ++ ++

    As far as possible, Lapp will convert parameters into their equivalent Lua types, ++i.e. convert numbers and convert filenames into file objects. If any conversion ++fails, or a required parameter is missing, an error will be issued and the usage ++text will be written out. So there are two necessary tasks, supplying the flag ++and option names and associating them with a type.

    ++ ++

    For any non-trivial script, even for personal consumption, it's necessary to ++supply usage text. The novelty of Lapp is that it starts from that point and ++defines a loose format for usage strings which can specify the names and types of ++the parameters.

    ++ ++

    An example will make this clearer:

    ++ ++ ++
    ++-- scale.lua
    ++  lapp = require 'pl.lapp'
    ++  local args = lapp [[
    ++  Does some calculations
    ++    -o,--offset (default 0.0)  Offset to add to scaled number
    ++    -s,--scale  (number)  Scaling factor
    ++    <number> (number)  Number to be scaled
    ++  ]]
    ++
    ++  print(args.offset + args.scale * args.number)
    ++
    ++ ++

    Here is a command-line session using this script:

    ++ ++ ++
    ++$ lua scale.lua
    ++scale.lua:missing required parameter: scale
    ++
    ++Does some calculations
    ++ -o,--offset (default 0.0)  Offset to add to scaled number
    ++ -s,--scale  (number)  Scaling factor
    ++  <number> (number )  Number to be scaled
    ++
    ++$ lua scale.lua -s 2.2 10
    ++22
    ++
    ++$ lua scale.lua -s 2.2 x10
    ++scale.lua:unable to convert to number: x10
    ++
    ++....(usage as before)
    ++
    ++ ++

    There are two kinds of lines in Lapp usage strings which are meaningful; option ++and parameter lines. An option line gives the short option, optionally followed ++by the corresponding long option. A type specifier in parentheses may follow. ++Similarly, a parameter line starts with '', followed by a type ++specifier.

    ++ ++

    Type specifiers usually start with a type name: one of 'boolean', 'string','number','file-in' or ++'file-out'. You may leave this out, but then must say 'default' followed by a value. ++If a flag or parameter has a default, it is not required and is set to the default. The actual ++type is deduced from this value (number, string, file or boolean) if not provided directly. ++'Deduce' is a fancy word for 'guess' and it can be wrong, e.g '(default 1)' ++will always be a number. You can say '(string default 1)' to override the guess. ++There are file values for the predefined console streams: stdin, stdout, stderr.

    ++ ++

    The boolean type is the default for flags. Not providing the type specifier is equivalent to ++'(boolean default false)`. If the flag is meant to be 'turned off' then either the full ++'(boolean default true)` or the shortcut '(default true)' will work.

    ++ ++

    An alternative to default is optional:

    ++ ++ ++
    ++local lapp = require 'pl.lapp'
    ++local args = lapp [[
    ++   --cmd (optional string) Command to run.
    ++]]
    ++
    ++if args.cmd then
    ++  os.execute(args.cmd)
    ++end
    ++
    ++ ++

    Here we're implying that cmd need not be specified (just as with default) but if not ++present, then args.cmd is nil, which will always test false.

    ++ ++

    The rest of the line is ignored and can be used for explanatory text.

    ++ ++

    This script shows the relation between the specified parameter names and the ++fields in the output table.

    ++ ++ ++
    ++-- simple.lua
    ++local args = require ('pl.lapp') [[
    ++Various flags and option types
    ++  -p          A simple optional flag, defaults to false
    ++  -q,--quiet  A simple flag with long name
    ++  -o  (string)  A required option with argument
    ++  -s  (default 'save') Optional string with default 'save' (single quotes ignored)
    ++  -n  (default 1) Optional numerical flag with default 1
    ++  -b  (string default 1)  Optional string flag with default '1' (type explicit)
    ++  <input> (default stdin)  Optional input file parameter, reads from stdin
    ++]]
    ++
    ++for k,v in pairs(args) do
    ++    print(k,v)
    ++end
    ++
    ++ ++

    I've just dumped out all values of the args table; note that args.quiet has ++become true, because it's specified; args.p defaults to false. If there is a long ++name for an option, that will be used in preference as a field name. A type or ++default specifier is not necessary for simple flags, since the default type is ++boolean.

    ++ ++ ++
    ++$ simple -o test -q simple.lua
    ++p       false
    ++input   file (781C1BD8)
    ++quiet   true
    ++o       test
    ++input_name      simple.lua
    ++D:\dev\lua\lapp>simple -o test simple.lua one two three
    ++1       one
    ++2       two
    ++3       three
    ++p       false
    ++quiet   false
    ++input   file (781C1BD8)
    ++o       test
    ++input_name      simple.lua
    ++
    ++ ++

    The parameter input has been set to an open read-only file object - we know it ++must be a read-only file since that is the type of the default value. The field ++input_name is automatically generated, since it's often useful to have access to ++the original filename.

    ++ ++

    Notice that any extra parameters supplied will be put in the result table with ++integer indices, i.e. args[i] where i goes from 1 to #args.

    ++ ++

    Files don't really have to be closed explicitly for short scripts with a quick ++well-defined mission, since the result of garbage-collecting file objects is to ++close them.

    ++ ++

    Enforcing a Range and Enumerations

    ++ ++

    The type specifier can also be of the form '(' MIN '..' MAX ')' or a set of strings ++separated by '|'.

    ++ ++ ++
    ++local lapp = require 'pl.lapp'
    ++local args = lapp [[
    ++    Setting ranges
    ++    <x> (1..10)  A number from 1 to 10
    ++    <y> (-5..1e6) Bigger range
    ++    <z> (slow|medium|fast)
    ++]]
    ++
    ++print(args.x,args.y)
    ++
    ++ ++

    Here the meaning of ranges is that the value is greater or equal to MIN and less or equal ++to MAX. ++An 'enum' is a string that can only have values from a specified set.

    ++ ++

    Custom Types

    ++ ++

    There is no builti-in way to force a parameter to be a whole number, but ++you may define a custom type that does this:

    ++ ++ ++
    ++lapp = require ('pl.lapp')
    ++
    ++lapp.add_type('integer','number',
    ++    function(x)
    ++        lapp.assert(math.ceil(x) == x, 'not an integer!')
    ++    end
    ++)
    ++
    ++local args =  lapp [[
    ++    <ival> (integer) Process PID
    ++]]
    ++
    ++print(args.ival)
    ++
    ++ ++

    lapp.add_type takes three parameters, a type name, a converter and a constraint ++function. The constraint function is expected to throw an assertion if some ++condition is not true; we use lapp.assert because it fails in the standard way ++for a command-line script. The converter argument can either be a type name known ++to Lapp, or a function which takes a string and generates a value.

    ++ ++

    Here's a useful custom type that allows dates to be input as pl.Date values:

    ++ ++ ++
    ++local df = Date.Format()
    ++
    ++lapp.add_type('date',
    ++    function(s)
    ++        local d,e = df:parse(s)
    ++        lapp.assert(d,e)
    ++        return d
    ++    end
    ++)
    ++
    ++ ++

    'varargs' Parameter Arrays

    ++ ++ ++
    ++lapp = require 'pl.lapp'
    ++local args = lapp [[
    ++Summing numbers
    ++    <numbers...> (number) A list of numbers to be summed
    ++]]
    ++
    ++local sum = 0
    ++for i,x in ipairs(args.numbers) do
    ++    sum = sum + x
    ++end
    ++print ('sum is '..sum)
    ++
    ++ ++

    The parameter number has a trailing '...', which indicates that this parameter is ++a 'varargs' parameter. It must be the last parameter, and args.number will be an ++array.

    ++ ++

    Consider this implementation of the head utility from Mac OS X:

    ++ ++ ++
    ++-- implements a BSD-style head
    ++-- (see http://www.manpagez.com/man/1/head/osx-10.3.php)
    ++
    ++lapp = require ('pl.lapp')
    ++
    ++local args = lapp [[
    ++Print the first few lines of specified files
    ++   -n         (default 10)    Number of lines to print
    ++   <files...> (default stdin) Files to print
    ++]]
    ++
    ++-- by default, lapp converts file arguments to an actual Lua file object.
    ++-- But the actual filename is always available as <file>_name.
    ++-- In this case, 'files' is a varargs array, so that 'files_name' is
    ++-- also an array.
    ++local nline = args.n
    ++local nfile = #args.files
    ++for i = 1,nfile do
    ++    local file = args.files[i]
    ++    if nfile > 1 then
    ++        print('==> '..args.files_name[i]..' <==')
    ++    end
    ++    local n = 0
    ++    for line in file:lines() do
    ++        print(line)
    ++        n = n + 1
    ++        if n == nline then break end
    ++    end
    ++end
    ++
    ++ ++

    Note how we have access to all the filenames, because the auto-generated field ++files_name is also an array!

    ++ ++

    (This is probably not a very considerate script, since Lapp will open all the ++files provided, and only close them at the end of the script. See the xhead.lua ++example for another implementation.)

    ++ ++

    Flags and options may also be declared as vararg arrays, and can occur anywhere. ++If there is both a short and long form, then the trailing "..." must happen after the long form, ++for example "-x,--network... (string)...",

    ++ ++

    Bear in mind that short options can be combined (like 'tar -xzf'), so it's ++perfectly legal to have '-vvv'. But normally the value of args.v is just a simple ++true value.

    ++ ++ ++
    ++local args = require ('pl.lapp') [[
    ++   -v...  Verbosity level; can be -v, -vv or -vvv
    ++]]
    ++vlevel = not args.v[1] and 0 or #args.v
    ++print(vlevel)
    ++
    ++ ++

    The vlevel assignment is a bit of Lua voodoo, so consider the cases:

    ++ ++ ++
    ++* No -v flag, v is just { false }
    ++* One -v flags, v is { true }
    ++* Two -v flags, v is { true, true }
    ++* Three -v flags, v is { true, true, true }
    ++
    ++ ++

    Defining a Parameter Callback

    ++ ++

    If a script implements lapp.callback, then Lapp will call it after each ++argument is parsed. The callback is passed the parameter name, the raw unparsed ++value, and the result table. It is called immediately after assignment of the ++value, so the corresponding field is available.

    ++ ++ ++
    ++lapp = require ('pl.lapp')
    ++
    ++function lapp.callback(parm,arg,args)
    ++    print('+',parm,arg)
    ++end
    ++
    ++local args = lapp [[
    ++Testing parameter handling
    ++    -p               Plain flag (defaults to false)
    ++    -q,--quiet       Plain flag with GNU-style optional long name
    ++    -o  (string)     Required string option
    ++    -n  (number)     Required number option
    ++    -s (default 1.0) Option that takes a number, but will default
    ++    <start> (number) Required number argument
    ++    <input> (default stdin)  A parameter which is an input file
    ++    <output> (default stdout) One that is an output file
    ++]]
    ++print 'args'
    ++for k,v in pairs(args) do
    ++    print(k,v)
    ++end
    ++
    ++ ++

    This produces the following output:

    ++ ++ ++
    ++$ args -o name -n 2 10 args.lua
    +++       o       name
    +++       n       2
    +++       start   10
    +++       input   args.lua
    ++args
    ++p       false
    ++s       1
    ++input_name      args.lua
    ++quiet   false
    ++output  file (781C1B98)
    ++start   10
    ++input   file (781C1BD8)
    ++o       name
    ++n       2
    ++
    ++ ++

    Callbacks are needed when you want to take action immediately on parsing an ++argument.

    ++ ++

    Slack Mode

    ++ ++

    If you'd like to use a multi-letter 'short' parameter you need to set ++the lapp.slack variable to true.

    ++ ++

    In the following example we also see how default false and default true flags can be used ++and how to overwrite the default -h help flag (--help still works fine) - this applies ++to non-slack mode as well.

    ++ ++ ++
    ++-- Parsing the command line ----------------------------------------------------
    ++-- test.lua
    ++local lapp = require 'pl.lapp'
    ++local pretty = require 'pl.pretty'
    ++lapp.slack = true
    ++local args = lapp [[
    ++Does some calculations
    ++   -v, --video              (string)             Specify input video
    ++   -w, --width              (default 256)        Width of the video
    ++   -h, --height             (default 144)        Height of the video
    ++   -t, --time               (default 10)         Seconds of video to process
    ++   -sk,--seek               (default 0)          Seek number of seconds
    ++   -f1,--flag1                                   A false flag
    ++   -f2,--flag2                                   A false flag
    ++   -f3,--flag3              (default true)       A true flag
    ++   -f4,--flag4              (default true)       A true flag
    ++]]
    ++
    ++pretty.dump(args)
    ++
    ++ ++

    And here we can see the output of test.lua:

    ++ ++ ++
    ++$> lua test.lua -v abc --time 40 -h 20 -sk 15 --flag1 -f3
    ++---->
    ++{
    ++  width = 256,
    ++  flag1 = true,
    ++  flag3 = false,
    ++  seek = 15,
    ++  flag2 = false,
    ++  video = abc,
    ++  time = 40,
    ++  height = 20,
    ++  flag4 = true
    ++}
    ++
    ++ ++

    ++

    Simple Test Framework

    ++ ++

    pl.test was originally developed for the sole purpose of testing Penlight itself, ++but you may find it useful for your own applications. (There are many other options.)

    ++ ++

    Most of the goodness is in test.asserteq. It uses tablex.deepcompare on its two arguments, ++and by default quits the test application with a non-zero exit code, and an informative ++message printed to stderr:

    ++ ++ ++
    ++local test = require 'pl.test'
    ++
    ++test.asserteq({10,20,30},{10,20,30.1})
    ++
    ++--~ test-test.lua:3: assertion failed
    ++--~ got:    {
    ++--~  [1] = 10,
    ++--~  [2] = 20,
    ++--~  [3] = 30
    ++--~ }
    ++--~ needed:    {
    ++--~  [1] = 10,
    ++--~  [2] = 20,
    ++--~  [3] = 30.1
    ++--~ }
    ++--~ these values were not equal
    ++
    ++ ++

    This covers most cases but it's also useful to compare strings using string.match

    ++ ++ ++
    ++-- must start with bonzo the dog
    ++test.assertmatch ('bonzo the dog is here','^bonzo the dog')
    ++-- must end with an integer
    ++test.assertmatch ('hello 42','%d+$')
    ++
    ++ ++

    Since Lua errors are usually strings, this matching strategy is used to test 'exceptions':

    ++ ++ ++
    ++test.assertraise(function()
    ++    local t = nil
    ++    print(t.bonzo)
    ++end,'nil value')
    ++
    ++ ++

    (Some care is needed to match the essential part of the thrown error if you care ++for portability, since in Lua 5.2 ++the exact error is "attempt to index local 't' (a nil value)" and in Lua 5.3 the error ++is "attempt to index a nil value (local 't')")

    ++ ++

    There is an extra optional argument to these test functions, which is helpful when writing ++test helper functions. There you want to highlight the failed line, not the actual call ++to asserteq or assertmatch - line 33 here is the call to is_iden

    ++ ++ ++
    ++function is_iden(str)
    ++    test.assertmatch(str,'^[%a_][%w_]*$',1)
    ++end
    ++
    ++is_iden 'alpha_dog'
    ++is_iden '$dollars'
    ++
    ++--~ test-test.lua:33: assertion failed
    ++--~ got:    "$dollars"
    ++--~ needed:    "^[%a_][%w_]*$"
    ++--~ these strings did not match
    ++
    ++ ++

    Useful Lua functions often return multiple values, and test.tuple is a convenient way to ++capture these values, whether they contain nils or not.

    ++ ++ ++
    ++T = test.tuple
    ++
    ++--- common error pattern
    ++function failing()
    ++    return nil,'failed'
    ++end
    ++
    ++test.asserteq(T(failing()),T(nil,'failed'))
    ++
    ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/docs/manual/09-discussion.md.html b/extra/penlight/docs/manual/09-discussion.md.html +new file mode 100644 +index 0000000..ad19e96 +--- /dev/null ++++ b/extra/penlight/docs/manual/09-discussion.md.html +@@ -0,0 +1,233 @@ ++ ++ ++ ++ ++ Penlight Documentation ++ ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++

    Technical Choices

    ++ ++

    ++

    Modularity and Granularity

    ++ ++

    In an ideal world, a program should only load the libraries it needs. Penlight is ++intended to work in situations where an extra 100Kb of bytecode could be a ++problem. It is straightforward but tedious to load exactly what you need:

    ++ ++ ++
    ++local data = require 'pl.data'
    ++local List = require 'pl.List'
    ++local array2d = require 'pl.array2d'
    ++local seq = require 'pl.seq'
    ++local utils = require 'pl.utils'
    ++
    ++ ++

    This is the style that I follow in Penlight itself, so that modules don't mess ++with the global environment; also, stringx.import() is not used because it will ++update the global string table.

    ++ ++

    But require 'pl' is more convenient in scripts; the question is how to ensure ++that one doesn't load the whole kitchen sink as the price of convenience. The ++strategy is to only load modules when they are referenced. In 'init.lua' (which ++is loaded by require 'pl') a metatable is attached to the global table with an ++__index metamethod. Any unknown name is looked up in the list of modules, and ++if found, we require it and make that module globally available. So when ++tablex.deepcompare is encountered, looking up tablex causes 'pl.tablex' to be ++required. .

    ++ ++

    Modifying the behaviour of the global table has consequences. For instance, there ++is the famous module strict which comes with Lua itself (perhaps the only ++standard Lua module written in Lua itself) which also does this modification so ++that global variiables must be defined before use. So the implementation in ++'init.lua' allows for a 'not found' hook, which 'pl.strict.lua' uses. Other ++libraries may install their own metatables for _G, but Penlight will now ++forward any unknown name to the __index defined by the original metatable.

    ++ ++

    But the strategy is worth the effort: the old 'kitchen sink' 'init.lua' would ++pull in about 260K of bytecode, whereas now typical programs use about 100K less, ++and short scripts even better - for instance, if they were only needing ++functionality in utils.

    ++ ++

    There are some functions which mark their output table with a special metatable, ++when it seems particularly appropriate. For instance, tablex.makeset creates a ++Set, and seq.copy creates a List. But this does not automatically result in ++the loading of pl.Set and pl.List; only if you try to access any of these ++methods. In 'utils.lua', there is an exported table called stdmt:

    ++ ++ ++
    ++stdmt = { List = {}, Map = {}, Set = {}, MultiMap = {} }
    ++
    ++ ++

    If you go through 'init.lua', then these plain little 'identity' tables get an ++__index metamethod which forces the loading of the full functionality. Here is ++the code from 'list.lua' which starts the ball rolling for lists:

    ++ ++ ++
    ++List = utils.stdmt.List
    ++List.__index = List
    ++List._name = "List"
    ++List._class = List
    ++
    ++ ++

    The 'load-on-demand' strategy helps to modularize the library. Especially for ++more casual use, require 'pl' is a good compromise between convenience and ++modularity.

    ++ ++

    In this current version, I have generally reduced the amount of trickery ++involved. Previously, Map was defined in pl.class; now it is sensibly defined ++in pl.Map; pl.class only contains the basic class mechanism (and returns that ++function.) For consistency, List is returned directly by require 'pl.List' ++(note the uppercase 'L'), Also, the amount of module dependencies in the ++non-core libraries like pl.config have been reduced.

    ++ ++

    ++

    Defining what is Callable

    ++ ++

    'utils.lua' exports function_arg which is used extensively throughout Penlight. ++It defines what is meant by 'callable'. Obviously true functions are immediately ++passed back. But what about strings? The first option is that it represents an ++operator in 'operator.lua', so that '<' is just an alias for operator.lt.

    ++ ++

    We then check whether there is a function factory defined for the metatable of ++the value.

    ++ ++

    (It is true that strings can be made callable, but in practice this turns out to ++be a cute but dubious idea, since all strings share the same metatable. A ++common programming error is to pass the wrong kind of object to a function, and ++it's better to get a nice clean 'attempting to call a string' message rather than ++some obscure trace from the bowels of your library.)

    ++ ++

    The other module that registers a function factory is pl.func. Placeholder ++expressions cannot be directly calleable, and so need to be instantiated and ++cached in as efficient way as possible.

    ++ ++

    (An inconsistency is that utils.is_callable does not do this thorough check.)

    ++ ++ ++ ++ ++
    ++
    ++
    ++generated by LDoc 1.5.0 ++
    ++
    ++ ++ +diff --git a/extra/penlight/doc/manual/01-introduction.md b/extra/penlight/docs_topics/01-introduction.md +similarity index 97% +rename from doc/manual/01-introduction.md +rename to docs_topics/01-introduction.md +index a8bf26a..bbc643d 100644 +--- a/extra/penlight/doc/manual/01-introduction.md ++++ b/extra/penlight/docs_topics/01-introduction.md +@@ -41,10 +41,10 @@ the order, so that the function is passed the value and then the key. Although + perverse, this matches the intended use better. + + The only important external dependence of Penlight is +-[LuaFileSystem](http://keplerproject.github.com/luafilesystem/manual.html) ++[LuaFileSystem](https://lunarmodules.github.io/luafilesystem/manual.html) + (`lfs`), and if you want `dir.copyfile` to work cleanly on Windows, you will need +-either [alien](http://alien.luaforge.net/) or be using +-[LuaJIT](http://luajit.org) as well. (The fallback is to call the equivalent ++either [alien](https://github.com/mascarenhas/alien) or be using ++[LuaJIT](https://luajit.org) as well. (The fallback is to call the equivalent + shell commands.) + + ### To Inject or not to Inject? +@@ -175,7 +175,7 @@ For example, + + return M + +-If you were to accidently type `mymod.Answer()`, then you would get a runtime ++If you were to accidentally type `mymod.Answer()`, then you would get a runtime + error: "variable 'Answer' is not declared in 'mymod'". + + This can be applied to existing modules. You may desire to have the same level +@@ -231,7 +231,7 @@ a function to all elements of a list is a common operation: + res[i] = fun(ls[i]) + end + +-This can be efficiently and succintly expressed as `ls:map(fun)`. Not only is ++This can be efficiently and succinctly expressed as `ls:map(fun)`. Not only is + there less typing but the intention of the code is clearer. If readers of your + code spend too much time trying to guess your intention by analyzing your loops, + then you have failed to express yourself clearly. Similarly, `ls:filter('>',0)` +@@ -311,7 +311,7 @@ upfront, since in general you won't know what values are needed. + + Penlight is fully compatible with Lua 5.1, 5.2 and LuaJIT 2. To ensure this, + `utils` also defines the global Lua 5.2 +-[load](http://www.lua.org/work/doc/manual.html#pdf-load) function as `utils.load` ++[load](https://www.lua.org/work/doc/manual.html#pdf-load) function as `utils.load` + + * the input (either a string or a function) + * the source name used in debug information +@@ -330,7 +330,7 @@ for functions which don't access any globals. + + `app.parse_args` is a simple command-line argument parser. If called without any + arguments, it tries to use the global `arg` array. It returns the _flags_ +-(options begining with '-') as a table of name/value pairs, and the _arguments_ ++(options beginning with '-') as a table of name/value pairs, and the _arguments_ + as an array. It knows about long GNU-style flag names, e.g. `--value`, and + groups of short flags are understood, so that `-ab` is short for `-a -b`. The + flags result would then look like `{value=true,a=true,b=true}`. +@@ -483,7 +483,7 @@ if no `__tostring` method is explicitly defined. + So `Alice = class(); Alice._name = 'Alice'` is exactly the same as `class.Alice()`. + + This useful notation is borrowed from Hugo Etchegoyen's +-[classlib](http://lua-users.org/wiki/MultipleInheritanceClasses) which further ++[classlib](https://lua-users.org/wiki/MultipleInheritanceClasses) which further + extends this concept to allow for multiple inheritance. Notice that the + more convenient form puts the class name in the _current environment_! That is, + you may use it safely within modules using the old-fashioned `module()` +diff --git a/extra/penlight/doc/manual/02-arrays.md b/extra/penlight/docs_topics/02-arrays.md +similarity index 99% +rename from doc/manual/02-arrays.md +rename to docs_topics/02-arrays.md +index 9ee292f..cb9bc71 100644 +--- a/extra/penlight/doc/manual/02-arrays.md ++++ b/extra/penlight/docs_topics/02-arrays.md +@@ -129,7 +129,7 @@ there is already `pop` (remove and return last value) and `append` acts like + `push` (add a value to the end). `push` is provided as an alias for `append`, and + the other stack operation (size) is simply the size operator `#`. Queues can + also be implemented; you use `pop` to take values out of the queue, and `put` to +-insert a value at the begining. ++insert a value at the beginning. + + You may derive classes from `List`, and since the list-returning methods + are covariant, the result of `slice` etc will return lists of the derived type, +@@ -520,7 +520,7 @@ compulsory way to use Penlight table operations. + Two-dimensional tables are of course easy to represent in Lua, for instance + `{{1,2},{3,4}}` where we store rows as subtables and index like so `A[col][row]`. + This is the common representation used by matrix libraries like +-[LuaMatrix](http://lua-users.org/wiki/LuaMatrix). `pl.array2d` does not provide ++[LuaMatrix](https://lua-users.org/wiki/LuaMatrix). `pl.array2d` does not provide + matrix operations, since that is the job for a specialized library, but rather + provides generalizations of the higher-level operations provided by `pl.tablex` + for one-dimensional arrays. +diff --git a/extra/penlight/doc/manual/03-strings.md b/extra/penlight/docs_topics/03-strings.md +similarity index 95% +rename from doc/manual/03-strings.md +rename to docs_topics/03-strings.md +index 3808175..7aa00cf 100644 +--- a/extra/penlight/doc/manual/03-strings.md ++++ b/extra/penlight/docs_topics/03-strings.md +@@ -37,7 +37,7 @@ Most of these can be fairly easily implemented using the Lua string library, + which is more general and powerful. But they are convenient operations to have + easily at hand. Note that can be injected into the `string` table if you use + `stringx.import`, but a simple alias like `local stringx = require 'pl.stringx'` +-is preferrable. This is the recommended practice when writing modules for ++is preferable. This is the recommended practice when writing modules for + consumption by other people, since it is bad manners to change the global state + of the rest of the system. Magic may be used for convenience, but there is always + a price. +@@ -104,7 +104,7 @@ lines that fit into a desired line width. As an extension, there is also `indent + for indenting multiline strings. + + New in Penlight with the 0.9 series is `text.format_operator`. Calling this +-enables Python-style string formating using the modulo operator `%`: ++enables Python-style string formatting using the modulo operator `%`: + + > text.format_operator() + > = '%s[%d]' % {'dog',1} +@@ -122,11 +122,11 @@ metatable. But in your own scripts you can feel free to do this. + ### Another Style of Template + + A new module is `template`, which is a version of Rici Lake's [Lua +-Preprocessor](http://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor). This ++Preprocessor](https://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor). This + allows you to mix Lua code with your templates in a straightforward way. There + are only two rules: + +- - Lines begining with `#` are Lua ++ - Lines beginning with `#` are Lua + - Otherwise, anything inside `$()` is a Lua expression. + + So a template generating an HTML list would look like this: +@@ -223,6 +223,6 @@ takes the same arguments as standard file objects: + string. + + `stringio.create` creates a writeable file-like object. You then use `write` to +-this stream, and finally extract the builded string using `value`. This 'string ++this stream, and finally extract the built string using `value`. This 'string + builder' pattern is useful for efficiently creating large strings. + +diff --git a/extra/penlight/doc/manual/04-paths.md b/extra/penlight/docs_topics/04-paths.md +similarity index 97% +rename from doc/manual/04-paths.md +rename to docs_topics/04-paths.md +index 4367fe6..9717713 100644 +--- a/extra/penlight/doc/manual/04-paths.md ++++ b/extra/penlight/docs_topics/04-paths.md +@@ -90,7 +90,7 @@ For example, this little script converts a file into upper case: + text = assert(file.read(arg[1])) + assert(file.write(arg[2],text:upper())) + +-Copying files is suprisingly tricky. `file.copy` and `file.move` attempt to use ++Copying files is surprisingly tricky. `file.copy` and `file.move` attempt to use + the best implementation possible. On Windows, they link to the API functions + `CopyFile` and `MoveFile`, but only if the `alien` package is installed (this is + true for Lua for Windows.) Otherwise, the system copy command is used. This can +@@ -109,7 +109,7 @@ table, unlike `lfs.dir` which returns an iterator.) + + `dir.makepath` can create a full path, creating subdirectories as necessary; + `rmtree` is the Nuclear Option of file deleting functions, since it will +-recursively clear out and delete all directories found begining at a path (there ++recursively clear out and delete all directories found beginning at a path (there + is a similar function with this name in the Python `shutils` module.) + + > = dir.makepath 't\\temp\\bonzo' +diff --git a/extra/penlight/doc/manual/05-dates.md b/extra/penlight/docs_topics/05-dates.md +similarity index 93% +rename from doc/manual/05-dates.md +rename to docs_topics/05-dates.md +index c2431cf..0df29d8 100644 +--- a/extra/penlight/doc/manual/05-dates.md ++++ b/extra/penlight/docs_topics/05-dates.md +@@ -2,10 +2,12 @@ + + + ++NOTE: the Date module is deprecated ++ + ### Creating and Displaying Dates + + The `Date` class provides a simplified way to work with [date and +-time](http://www.lua.org/pil/22.1.html) in Lua; it leans heavily on the functions ++time](https://www.lua.org/pil/22.1.html) in Lua; it leans heavily on the functions + `os.date` and `os.time`. + + A `Date` object can be constructed from a table, just like with `os.time`. +@@ -41,7 +43,7 @@ you full control of the format for both parsing and displaying dates: + > = amer:tostring(d) + 04/10/2010 + +-With the 0.9.7 relase, the `Date` constructor has become more flexible. You may ++With the 0.9.7 release, the `Date` constructor has become more flexible. You may + omit any of the 'year', 'month' or 'day' fields: + + > = Date { year = 2008 } +diff --git a/extra/penlight/doc/manual/06-data.md b/extra/penlight/docs_topics/06-data.md +similarity index 98% +rename from doc/manual/06-data.md +rename to docs_topics/06-data.md +index 8c759d7..038e297 100644 +--- a/extra/penlight/doc/manual/06-data.md ++++ b/extra/penlight/docs_topics/06-data.md +@@ -353,7 +353,7 @@ this data. In fact, these functions are available as methods; e.g. + v = data.read('dat.txt'):flatten() + + The data is also in exactly the right shape to be treated as matrices by +-[LuaMatrix](http://lua-users.org/wiki/LuaMatrix): ++[LuaMatrix](https://lua-users.org/wiki/LuaMatrix): + + > matrix = require 'matrix' + > m = matrix(data.read 'mat.txt') +@@ -665,7 +665,7 @@ nicely delimited by newlines. For example, here is a snippet of a in-house file + format which it was my task to maintain: + + points +-(818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1) ++ (818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1) + ,(818327.4,-20388,-0.1),(818322,-20387.7,-0.1),(818316.3,-20388.6,-0.1) + ,(818309.7,-20389.4,-0.1),(818303.5,-20390.6,-0.1),(818295.8,-20388.3,-0.1) + ,(818290.5,-20386.9,-0.1),(818285.2,-20386.1,-0.1),(818279.3,-20383.6,-0.1) +@@ -704,7 +704,7 @@ gracefully.) + The scanners all have a second optional argument, which is a table which controls + whether you want to exclude spaces and/or comments. The default for `lexer.lua` + is `{space=true,comments=true}`. There is a third optional argument which +-determines how string and number tokens are to be processsed. ++determines how string and number tokens are to be processed. + + The ultimate highly-structured data is of course, program source. Here is a + snippet from 'text-lexer.lua': +@@ -775,7 +775,7 @@ specialized library. + + #### Parsing and Pretty-Printing + +-The semi-standard XML parser in the Lua universe is [lua-expat](http://matthewwild.co.uk/projects/luaexpat/). ++The semi-standard XML parser in the Lua universe is [lua-expat](https://lunarmodules.github.io/luaexpat/). + In particular, + it has a function called `lxp.lom.parse` which will parse XML into the Lua Object + Model (LOM) format. However, it does not provide a way to convert this data back +@@ -846,7 +846,7 @@ There is a fourth argument which is the _attribute indent_: + #### Parsing and Working with Configuration Files + + It's common to find configurations expressed with XML these days. It's +-straightforward to 'walk' the [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) ++straightforward to 'walk' the [LOM](https://lunarmodules.github.io/luaexpat/lom.html) + data and extract the data in the form you want: + + require 'pl' +@@ -960,7 +960,7 @@ Getting the names of the providers per-country is straightforward: + #### Generating XML with 'xmlification' + + This feature is inspired by the `htmlify` function used by +-[Orbit](http://keplerproject.github.com/orbit/) to simplify HTML generation, ++[Orbit](https://keplerproject.github.io/orbit/) to simplify HTML generation, + except that no function environment magic is used; the `tags` function returns a + set of _constructors_ for elements of the given tag names. + +@@ -1047,9 +1047,9 @@ extract values from it using a pattern. + + A common use of this is parsing the XML result of API queries. The + [(undocumented and subsequently discontinued) Google Weather +-API](http://blog.programmableweb.com/2010/02/08/googles-secret-weather-api/) is a ++API](https://blog.programmableweb.com/2010/02/08/googles-secret-weather-api/) is a + good example. Grabbing the result of +-`http://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like ++`https://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like + this, after pretty-printing: + + +diff --git a/extra/penlight/doc/manual/07-functional.md b/extra/penlight/docs_topics/07-functional.md +similarity index 97% +rename from doc/manual/07-functional.md +rename to docs_topics/07-functional.md +index 5921a3d..30a447f 100644 +--- a/extra/penlight/doc/manual/07-functional.md ++++ b/extra/penlight/docs_topics/07-functional.md +@@ -54,7 +54,7 @@ Sequences can be _combined_, either by 'zipping' them or by concatenating them. + 3 + + `seq.printall` is useful for printing out single-valued sequences, and provides +-some finer control over formating, such as a delimiter, the number of fields per ++some finer control over formatting, such as a delimiter, the number of fields per + line, and a format string to use (@see string.format) + + > seq.printall(seq.random(10)) +@@ -82,7 +82,7 @@ original table (equivalent to `tablex.filter(ls, '>', 0)`) + + ls = seq.copy(seq.filter(ls, '>', 0)) + +-We're already encounted `seq.sum` when discussing `input.numbers`. This can also ++We're already encountered `seq.sum` when discussing `input.numbers`. This can also + be expressed with `seq.reduce`: + + > seq.reduce(function(x,y) return x + y end, seq.list{1,2,3,4}) +@@ -289,7 +289,7 @@ I'm emphasizing that a comprehension is a function which can take a list argumen + {20,40,60} + + Here is a somewhat more explicit way of saying the same thing; `_1` is a +-_placeholder_ refering to the _first_ argument passed to the comprehension. ++_placeholder_ referring to the _first_ argument passed to the comprehension. + + > = C '2*x for _,x in pairs(_1)' {10,20,30} + {20,40,60} +@@ -366,7 +366,7 @@ arguments where the first argument is bound to some value: + > tablex.filter({1,-2,10,-1,2},bind1(ops.le,0)) + {1,10,2} + +-The last example unfortunately reads backwards, because `bind1` alway binds the ++The last example unfortunately reads backwards, because `bind1` always binds the + first argument! Also unfortunately, in my youth I confused 'currying' with + 'partial application', so the old name for `bind1` is `curry` - this alias still exists. + +@@ -441,7 +441,7 @@ Functions of up to 5 arguments can be generated. + > = tablex.map2(_1+_2,{1,2,3}, {10,20,30}) + {11,22,33} + +-These expressions can use arbitrary functions, altho they must first be ++These expressions can use arbitrary functions, although they must first be + registered with the functional library. `func.register` brings in a single + function, and `func.import` brings in a whole table of functions, such as `math`. + +@@ -458,7 +458,7 @@ A common operation is calling a method of a set of objects: + {'o','f','x'} + + There are some restrictions on what operators can be used in PEs. For instance, +-because the `__len` metamethod cannot be overriden by plain Lua tables, we need ++because the `__len` metamethod cannot be overridden by plain Lua tables, we need + to define a special function to express `#_1': + + > = tablex.map(Len(_1), {'one','four','x'}) +diff --git a/extra/penlight/doc/manual/08-additional.md b/extra/penlight/docs_topics/08-additional.md +similarity index 99% +rename from doc/manual/08-additional.md +rename to docs_topics/08-additional.md +index 2c99497..149056b 100644 +--- a/extra/penlight/doc/manual/08-additional.md ++++ b/extra/penlight/docs_topics/08-additional.md +@@ -373,7 +373,7 @@ array. + Consider this implementation of the head utility from Mac OS X: + + -- implements a BSD-style head +- -- (see http://www.manpagez.com/man/1/head/osx-10.3.php) ++ -- (see https://www.manpagez.com/man/1/head/osx-10.3.php) + + lapp = require ('pl.lapp') + +@@ -423,7 +423,7 @@ perfectly legal to have '-vvv'. But normally the value of args.v is just a simpl + vlevel = not args.v[1] and 0 or #args.v + print(vlevel) + +-The vlevel assigment is a bit of Lua voodoo, so consider the cases: ++The vlevel assignment is a bit of Lua voodoo, so consider the cases: + + * No -v flag, v is just { false } + * One -v flags, v is { true } +@@ -528,7 +528,7 @@ And here we can see the output of `test.lua`: + ### Simple Test Framework + + `pl.test` was originally developed for the sole purpose of testing Penlight itself, +-but you may find it useful for your own applications. ([There are many other options](http://lua-users.org/wiki/UnitTesting).) ++but you may find it useful for your own applications. ([There are many other options](https://lua-users.org/wiki/UnitTesting).) + + Most of the goodness is in `test.asserteq`. It uses `tablex.deepcompare` on its two arguments, + and by default quits the test application with a non-zero exit code, and an informative +diff --git a/extra/penlight/doc/manual/09-discussion.md b/extra/penlight/docs_topics/09-discussion.md +similarity index 100% +rename from doc/manual/09-discussion.md +rename to docs_topics/09-discussion.md +diff --git a/extra/penlight/examples/seesubst.lua b/extra/penlight/examples/seesubst.lua +index e13a5fb..a2d0f18 100644 +--- a/extra/penlight/examples/seesubst.lua ++++ b/extra/penlight/examples/seesubst.lua +@@ -4,10 +4,11 @@ + -- or 'pl.seq.map' (a function reference); these cases must be distinguished + -- and a Markdown link generated pointing to the LuaDoc file. + +-require 'pl' ++local sip = require 'pl.sip' ++local stringx = require 'pl.stringx' + + local res = {} +-s = [[ ++local s = [[ + (@see pl.bonzo.dog) + remember about @see pl.bonzo + +@@ -15,7 +16,7 @@ remember about @see pl.bonzo + + local _gsub_patterns = {} + +-function gsub (s,pat,subst,start) ++local function gsub (s,pat,subst,start) + local fpat = _gsub_patterns[pat] + if not fpat then + -- use SIP to generate a proper string pattern. +diff --git a/extra/penlight/examples/sipscan.lua b/extra/penlight/examples/sipscan.lua +index 37f712e..78ac75b 100644 +--- a/extra/penlight/examples/sipscan.lua ++++ b/extra/penlight/examples/sipscan.lua +@@ -3,9 +3,10 @@ + -- pattern generated: + -- SYNC%s*%[([+%-%d]%d*)%]%s*([+%-%d]%d*)%s*([+%-%d]%d*) + +-require 'pl' ++local sip = require 'pl.sip' ++local stringx = require 'pl.stringx' + +-s = [[ ++local s = [[ + SYNC [1] 0 547 (14679 sec) + SYNC [2] 0 555 (14679 sec) + SYNC [3] 0 563 (14679 sec) +@@ -16,7 +17,7 @@ SYNC [6] 0 587 (14679 sec) + + + local first = true +-local start ++local expected + local res = {} + local pat = 'SYNC [$i{seq}] $i{diff} $i{val}' + print(sip.create_pattern(pat)) +diff --git a/extra/penlight/examples/symbols.lua b/extra/penlight/examples/symbols.lua +index 1aed745..e73c4ba 100644 +--- a/extra/penlight/examples/symbols.lua ++++ b/extra/penlight/examples/symbols.lua +@@ -4,7 +4,7 @@ local ops = require 'pl.operator' + local List = require 'pl.List' + local append,concat = table.insert,table.concat + local compare,find_if,compare_no_order,imap,reduce,count_map = tablex.compare,tablex.find_if,tablex.compare_no_order,tablex.imap,tablex.reduce,tablex.count_map +-local unpack = utils.unpack ++local unpack = table.unpack + + function bindval (self,val) + rawset(self,'value',val) +diff --git a/extra/penlight/examples/test-cmp.lua b/extra/penlight/examples/test-cmp.lua +index e3748a3..cbab394 100644 +--- a/extra/penlight/examples/test-cmp.lua ++++ b/extra/penlight/examples/test-cmp.lua +@@ -1,4 +1,3 @@ +-A = require 'pl.tablex' +-ops = require 'pl.operator' ++local A = require 'pl.tablex' + print(A.compare_no_order({1,2,3},{2,1,3})) + print(A.compare_no_order({1,2,3},{2,1,3},'==')) +diff --git a/extra/penlight/examples/test-listcallbacks.lua b/extra/penlight/examples/test-listcallbacks.lua +index 6bcdad3..a9a31c3 100644 +--- a/extra/penlight/examples/test-listcallbacks.lua ++++ b/extra/penlight/examples/test-listcallbacks.lua +@@ -1,7 +1,8 @@ + -- demonstrates how to use a list of callbacks +-require 'pl' +-actions = List() +-L = utils.string_lambda ++local List = require 'pl.List' ++local utils = require 'pl.utils' ++local actions = List() ++local L = utils.string_lambda + + actions:append(function() print 'hello' end) + actions:append(L '|| print "yay"') +diff --git a/extra/penlight/examples/test-pretty.lua b/extra/penlight/examples/test-pretty.lua +index 1423c1c..7b2b553 100644 +--- a/extra/penlight/examples/test-pretty.lua ++++ b/extra/penlight/examples/test-pretty.lua +@@ -1,6 +1,6 @@ + local pretty = require 'pl.pretty' + +-tb = { ++local tb = { + 'one','two','three',{1,2,3}, + alpha=1,beta=2,gamma=3,['&']=true,[0]=false, + _fred = {true,true}, +diff --git a/extra/penlight/examples/testapp.lua b/extra/penlight/examples/testapp.lua +deleted file mode 100644 +index 650ac4e..0000000 +--- a/extra/penlight/examples/testapp.lua ++++ /dev/null +@@ -1,5 +0,0 @@ +--- shows how a script can get a private file path +--- the output on my Windows machine is: +--- C:\Documents and Settings\steve\.testapp\test.txt +-require 'pl' +-print(app.appfile 'test.txt') +diff --git a/extra/penlight/examples/testclone.lua b/extra/penlight/examples/testclone.lua +index c211bc4..b0d948f 100644 +--- a/extra/penlight/examples/testclone.lua ++++ b/extra/penlight/examples/testclone.lua +@@ -1,8 +1,10 @@ + --cloning a directory tree. + local lfs = require 'lfs' +-require 'pl' +-p1 = [[examples]] +-p2 = [[copy/of/examples]] ++local path = require 'pl.path' ++local dir = require 'pl.dir' ++ ++local p1 = [[examples]] ++local p2 = [[copy/of/examples]] + + if not path.isfile 'examples/testclone.lua' then + return print 'please run this in the penlight folder (below examples)' +@@ -14,7 +16,7 @@ dir.clonetree(p1,p2,dir.copyfile) + assert(path.isdir 'copy') + + print '---' +-t = os.time() ++local t = os.time() + print(lfs.touch('examples/testclone.lua',t,t+10)) + + -- this should only update this file +diff --git a/extra/penlight/examples/testconfig.lua b/extra/penlight/examples/testconfig.lua +index 03349d6..4712747 100644 +--- a/extra/penlight/examples/testconfig.lua ++++ b/extra/penlight/examples/testconfig.lua +@@ -1,7 +1,7 @@ + local stringio = require 'pl.stringio' + local config = require 'pl.config' + +-function dump(t,indent) ++local function dump(t,indent) + if type(t) == 'table' then + io.write(indent,'{\n') + local newindent = indent..' ' +@@ -17,7 +17,7 @@ function dump(t,indent) + end + + +-function testconfig(test) ++local function testconfig(test) + local f = stringio.open(test) + local c = config.read(f) + f:close() +diff --git a/extra/penlight/examples/testglobal.lua b/extra/penlight/examples/testglobal.lua +index 053b6bf..0baaaef 100644 +--- a/extra/penlight/examples/testglobal.lua ++++ b/extra/penlight/examples/testglobal.lua +@@ -15,7 +15,7 @@ local path = require 'pl.path' + + utils.on_error 'quit' + +-local txt,err = file.read(arg[1] or path.normpath('examples/testglobal.lua')) ++local txt = file.read(arg[1] or path.normpath('examples/testglobal.lua')) + local globals = List() + for t,v in lexer.lua(txt) do + if t == 'iden' and rawget(_G,v) then +diff --git a/extra/penlight/examples/testinputfields.lua b/extra/penlight/examples/testinputfields.lua +index 37b3a8c..9269488 100644 +--- a/extra/penlight/examples/testinputfields.lua ++++ b/extra/penlight/examples/testinputfields.lua +@@ -1,4 +1,4 @@ +-require 'pl' ++local input = require 'pl.input' + local sum = 0.0 + local count = 0 + local text = [[ +diff --git a/extra/penlight/examples/testinputfields2.lua b/extra/penlight/examples/testinputfields2.lua +index fd7b0cd..fc649eb 100644 +--- a/extra/penlight/examples/testinputfields2.lua ++++ b/extra/penlight/examples/testinputfields2.lua +@@ -1,4 +1,5 @@ +-require 'pl' ++local input = require 'pl.input' ++local seq = require 'pl.seq' + local text = [[ + 981124001 2.0 18988.4 10047.1 4149.7 + 981125001 0.8 19104.0 9970.4 5088.7 +diff --git a/extra/penlight/examples/testxml.lua b/extra/penlight/examples/testxml.lua +index e7c7476..2528020 100644 +--- a/extra/penlight/examples/testxml.lua ++++ b/extra/penlight/examples/testxml.lua +@@ -3,12 +3,13 @@ + -- This is (clearly) not a professional XML parser, so don't use it + -- on your homework! + +-require 'pl' ++local lexer = require 'pl.lexer' ++local pretty = require 'pl.pretty' + + local append = table.insert + local skipws,expecting = lexer.skipws,lexer.expecting + +-function parse_element (tok,tag) ++local function parse_element (tok,tag) + local tbl,t,v,attrib + tbl = {} + tbl.tag = tag -- LOM 'tag' is the element tag +@@ -51,21 +52,22 @@ function parse_element (tok,tag) + end + end + +-function parse_xml (tok) +- local t,v = skipws(tok) ++local function parse_xml (tok) ++ local t = skipws(tok) ++ local v + while t == '<' do + t,v = tok() + if t == '?' or t == '!' then + -- skip meta stuff and commentary + repeat t = tok() until t == '>' +- t,v = expecting(tok,'<') ++ t = expecting(tok,'<') + else + return parse_element(tok,v) + end + end + end + +-s = [[ ++local s = [[ + + + ++ ++ ++ ++ $(ldoc.title) ++ ++# if ldoc.custom_css then -- add custom CSS file if configured. ++ ++# end ++ ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++# local no_spaces = ldoc.no_spaces ++# local use_li = ldoc.use_li ++# local display_name = ldoc.display_name ++# local iter = ldoc.modules.iter ++# local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end ++# local nowrap = ldoc.wrap and '' or 'nowrap' ++ ++ ++ ++ ++ ++
    ++ ++# if ldoc.body then -- verbatim HTML as contents; 'non-code' entries ++ $(ldoc.body) ++# elseif module then -- module documentation ++

    $(ldoc.module_typename(module)) $(module.name)

    ++

    $(M(module.summary,module))

    ++

    $(M(module.description,module))

    ++# if module.tags.include then ++ $(M(ldoc.include_file(module.tags.include))) ++# end ++# if module.see then ++# local li,il = use_li(module.see) ++

    See also:

    ++
      ++# for see in iter(module.see) do ++ $(li)$(see.label)$(il) ++# end -- for ++
    ++# end -- if see ++# if module.usage then ++# local li,il = use_li(module.usage) ++

    Usage:

    ++
      ++# for usage in iter(module.usage) do ++ $(li)
      $(ldoc.escape(usage))
      $(il) ++# end -- for ++
    ++# end -- if usage ++# if module.info then ++

    Info:

    ++
      ++# for tag, value in module.info:iter() do ++
    • $(tag): $(M(value,module))
    • ++# end ++
    ++# end -- if module.info ++ ++ ++# if not ldoc.no_summary then ++# -- bang out the tables of item types for this module (e.g Functions, Tables, etc) ++# for kind,items in module.kinds() do ++

    $(kind)

    ++ ++# for item in items() do ++ ++ ++ ++ ++# end -- for items ++
    $(display_name(item))$(M(item.summary,item))
    ++#end -- for kinds ++ ++
    ++
    ++ ++#end -- if not no_summary ++ ++# --- currently works for both Functions and Tables. The params field either contains ++# --- function parameters or table fields. ++# local show_return = not ldoc.no_return_or_parms ++# local show_parms = show_return ++# for kind, items in module.kinds() do ++# local kitem = module.kinds:get_item(kind) ++# local has_description = kitem and ldoc.descript(kitem) ~= "" ++

    $(kind)

    ++ $(M(module.kinds:get_section_description(kind),nil)) ++# if kitem then ++# if has_description then ++
    ++ $(M(ldoc.descript(kitem),kitem)) ++
    ++# end ++# if kitem.usage then ++

    Usage:

    ++
    $(ldoc.prettify(kitem.usage[1]))
    ++# end ++# end ++
    ++# for item in items() do ++
    ++ ++ $(display_name(item)) ++# if ldoc.prettify_files and ldoc.is_file_prettified[item.module.file.filename] then ++ line $(item.lineno) ++# end ++
    ++
    ++ $(M(ldoc.descript(item),item)) ++ ++# if ldoc.custom_tags then ++# for custom in iter(ldoc.custom_tags) do ++# local tag = item.tags[custom[1]] ++# if tag and not custom.hidden then ++# local li,il = use_li(tag) ++

    $(custom.title or custom[1]):

    ++
      ++# for value in iter(tag) do ++ $(li)$(custom.format and custom.format(value) or M(value))$(il) ++# end -- for ++# end -- if tag ++
    ++# end -- iter tags ++# end ++ ++# if show_parms and item.params and #item.params > 0 then ++# local subnames = module.kinds:type_of(item).subnames ++# if subnames then ++

    $(subnames):

    ++# end ++
      ++# for parm in iter(item.params) do ++# local param,sublist = item:subparam(parm) ++# if sublist then ++
    • $(sublist)$(M(item.params.map[sublist],item)) ++
        ++# end ++# for p in iter(param) do ++# local name,tp,def = item:display_name_of(p), ldoc.typename(item:type_of_param(p)), item:default_of_param(p) ++
      • $(name) ++# if tp ~= '' then ++ $(tp) ++# end ++ $(M(item.params.map[p],item)) ++# if def == true then ++ (optional) ++# elseif def then ++ (default $(def)) ++# end ++# if item:readonly(p) then ++ readonly ++# end ++
      • ++# end ++# if sublist then ++
      ++# end ++# end -- for ++
    ++# end -- if params ++ ++# if show_return and item.retgroups then local groups = item.retgroups ++

    Returns:

    ++# for i,group in ldoc.ipairs(groups) do local li,il = use_li(group) ++
      ++# for r in group:iter() do local type, ctypes = item:return_type(r); local rt = ldoc.typename(type) ++ $(li) ++# if rt ~= '' then ++ $(rt) ++# end ++ $(M(r.text,item))$(il) ++# if ctypes then ++
        ++# for c in ctypes:iter() do ++
      • $(c.name) ++ $(ldoc.typename(c.type)) ++ $(M(c.comment,item))
      • ++# end ++
      ++# end -- if ctypes ++# end -- for r ++
    ++# if i < #groups then ++

    Or

    ++# end ++# end -- for group ++# end -- if returns ++ ++# if show_return and item.raise then ++

    Raises:

    ++ $(M(item.raise,item)) ++# end ++ ++# if item.see then ++# local li,il = use_li(item.see) ++

    See also:

    ++
      ++# for see in iter(item.see) do ++ $(li)$(see.label)$(il) ++# end -- for ++
    ++# end -- if see ++ ++# if item.usage then ++# local li,il = use_li(item.usage) ++

    Usage:

    ++
      ++# for usage in iter(item.usage) do ++ $(li)
      $(ldoc.prettify(usage))
      $(il) ++# end -- for ++
    ++# end -- if usage ++ ++
    ++# end -- for items ++
    ++# end -- for kinds ++ ++# else -- if module; project-level contents ++ ++# if ldoc.description then ++

    $(M(ldoc.description,nil))

    ++# end ++# if ldoc.full_description then ++

    $(M(ldoc.full_description,nil))

    ++# end ++ ++# for kind, mods in ldoc.kinds() do ++

    $(kind)

    ++# kind = kind:lower() ++ ++# for m in mods() do ++ ++ ++ ++ ++# end -- for modules ++
    $(m.name)$(M(ldoc.strip_header(m.summary),m))
    ++# end -- for kinds ++# end -- if module ++ ++
    ++
    ++
    ++generated by LDoc $(ldoc.version) ++
    ++
    ++ ++ +diff --git a/extra/penlight/lua/pl/Date.lua b/extra/penlight/lua/pl/Date.lua +index 7a5d1ed..039dbaf 100644 +--- a/extra/penlight/lua/pl/Date.lua ++++ b/extra/penlight/lua/pl/Date.lua +@@ -1,6 +1,9 @@ + --- Date and Date Format classes. + -- See @{05-dates.md|the Guide}. + -- ++-- NOTE: the date module is deprecated! see ++-- https://github.com/lunarmodules/Penlight/issues/285 ++-- + -- Dependencies: `pl.class`, `pl.stringx`, `pl.utils` + -- @classmod pl.Date + -- @pragma nostrip +@@ -11,6 +14,15 @@ local stringx = require 'pl.stringx' + local utils = require 'pl.utils' + local assert_arg,assert_string = utils.assert_arg,utils.assert_string + ++ ++utils.raise_deprecation { ++ source = "Penlight " .. utils._VERSION, ++ message = "the 'Date' module is deprecated, see https://github.com/lunarmodules/Penlight/issues/285", ++ version_removed = "2.0.0", ++ version_deprecated = "1.9.2", ++} ++ ++ + local Date = class() + Date.Format = class() + +@@ -94,9 +106,9 @@ function Date.tzone (ts) + ts = os_time() + elseif type(ts) == "table" then + if getmetatable(ts) == Date then +- ts = ts.time ++ ts = ts.time + else +- ts = Date(ts).time ++ ts = Date(ts).time + end + end + local utc = os_date('!*t',ts) +@@ -510,7 +522,7 @@ Allowed patterns: + + ]] + +-local function looks_like_a_month(w) ++local function looks_like_a_month(w) + return w:match '^%a+,*$' ~= nil + end + local is_number = stringx.isdigit +@@ -537,7 +549,7 @@ local function parse_iso_end(p,ns,sec) + -- (we're working with the date as lower case, hence 'z') + if p:match 'z$' then -- we're UTC! + return sec, {h=0,m=0} +- end ++ end + p = p:gsub(':','') -- turn 00:30 to 0030 + local _,_,sign,offs = p:find('^([%+%-])(%d+)') + if not sign then return sec, nil end -- not UTC +diff --git a/extra/penlight/lua/pl/List.lua b/extra/penlight/lua/pl/List.lua +index 95d8c0e..a65cf3b 100644 +--- a/extra/penlight/lua/pl/List.lua ++++ b/extra/penlight/lua/pl/List.lua +@@ -68,7 +68,7 @@ end + -- this will return a plain table with an appropriate metatable. + -- we pass anything which isn't a simple table to iterate() to work out + -- an appropriate iterator +--- @see List.iterate ++-- @see List.iterate + -- @param[opt] t An optional list-like table + -- @return a new List + -- @usage ls = List(); ls = List {1,2,3,4} +@@ -85,7 +85,7 @@ function List:clone() + return ls + end + +----Add an item to the end of the list. ++--- Add an item to the end of the list. + -- @param i An item + -- @return the list + function List:append(i) +@@ -116,7 +116,7 @@ function List:insert(i, x) + return self + end + +---- Insert an item at the begining of the list. ++--- Insert an item at the beginning of the list. + -- @param x a data item + -- @return the list + function List:put (x) +@@ -169,7 +169,7 @@ List.get = List.pop + local tfind = tablex.find + List.index = tfind + +---- does this list contain the value?. ++--- Does this list contain the value? + -- @param x A data value + -- @return true or false + function List:contains(x) +@@ -196,7 +196,7 @@ function List:sort(cmp) + return self + end + +---- return a sorted copy of this list. ++--- Return a sorted copy of this list. + -- @func[opt='<'] cmp an optional comparison function + -- @return a new list + function List:sorted(cmp) +@@ -215,7 +215,7 @@ function List:reverse() + return self + end + +---- return the minimum and the maximum value of the list. ++--- Return the minimum and the maximum value of the list. + -- @return minimum value + -- @return maximum value + function List:minmax() +@@ -239,7 +239,7 @@ function List:slice(first,last) + return tsub(self,first,last) + end + +---- empty the list. ++--- Empty the list. + -- @return the list + function List:clear() + for i=1,#self do tremove(self) end +@@ -309,7 +309,7 @@ function List:splice(idx,list) + return self + end + +---- general slice assignment s[i1:i2] = seq. ++--- General slice assignment s[i1:i2] = seq. + -- @int i1 start index + -- @int i2 end index + -- @tparam List seq a list +@@ -323,7 +323,7 @@ function List:slice_assign(i1,i2,seq) + return self + end + +---- concatenation operator. ++--- Concatenation operator. + -- @within metamethods + -- @tparam List L another List + -- @return a new list consisting of the list with the elements of the new list appended +@@ -334,7 +334,7 @@ function List:__concat(L) + return ls + end + +---- equality operator ==. True iff all elements of two lists are equal. ++--- Equality operator ==. True iff all elements of two lists are equal. + -- @within metamethods + -- @tparam List L another List + -- @return true or false +@@ -346,7 +346,7 @@ function List:__eq(L) + return true + end + +---- join the elements of a list using a delimiter. ++--- Join the elements of a list using a delimiter. + -- This method uses tostring on all elements. + -- @string[opt=''] delim a delimiter string, can be empty. + -- @return a string +@@ -356,7 +356,7 @@ function List:join (delim) + return concat(array_tostring(self),delim) + end + +---- join a list of strings.
    ++--- Join a list of strings.
    + -- Uses `table.concat` directly. + -- @function List:concat + -- @string[opt=''] delim a delimiter +@@ -371,14 +371,14 @@ local function tostring_q(val) + return s + end + +---- how our list should be rendered as a string. Uses join(). ++--- How our list should be rendered as a string. Uses join(). + -- @within metamethods + -- @see List:join + function List:__tostring() + return '{'..self:join(',',tostring_q)..'}' + end + +---- call the function on each element of the list. ++--- Call the function on each element of the list. + -- @func fun a function or callable object + -- @param ... optional values to pass to function + function List:foreach (fun,...) +@@ -394,7 +394,7 @@ local function lookup_fun (obj,name) + return f + end + +---- call the named method on each element of the list. ++--- Call the named method on each element of the list. + -- @string name the method name + -- @param ... optional values to pass to function + function List:foreachm (name,...) +@@ -405,7 +405,7 @@ function List:foreachm (name,...) + end + end + +---- create a list of all elements which match a function. ++--- Create a list of all elements which match a function. + -- @func fun a boolean function + -- @param[opt] arg optional argument to be passed as second argument of the predicate + -- @return a new filtered list. +@@ -413,7 +413,7 @@ function List:filter (fun,arg) + return makelist(filter(self,fun,arg),self) + end + +---- split a string using a delimiter. ++--- Split a string using a delimiter. + -- @string s the string + -- @string[opt] delim the delimiter (default spaces) + -- @return a List of strings +@@ -423,7 +423,7 @@ function List.split (s,delim) + return makelist(split(s,delim)) + end + +---- apply a function to all elements. ++--- Apply a function to all elements. + -- Any extra arguments will be passed to the function. + -- @func fun a function of at least one argument + -- @param ... arbitrary extra arguments. +@@ -434,17 +434,17 @@ function List:map (fun,...) + return makelist(imap(fun,self,...),self) + end + +---- apply a function to all elements, in-place. ++--- Apply a function to all elements, in-place. + -- Any extra arguments are passed to the function. + -- @func fun A function that takes at least one argument + -- @param ... arbitrary extra arguments. + -- @return the list. + function List:transform (fun,...) + transform(fun,self,...) +- return self ++ return self + end + +---- apply a function to elements of two lists. ++--- Apply a function to elements of two lists. + -- Any extra arguments will be passed to the function + -- @func fun a function of at least two arguments + -- @tparam List ls another list +@@ -501,7 +501,7 @@ function List:reduce (fun) + return reduce(fun,self) + end + +---- partition a list using a classifier function. ++--- Partition a list using a classifier function. + -- The function may return nil, but this will be converted to the string key ''. + -- @func fun a function of at least one argument + -- @param ... will also be passed to the function +@@ -526,7 +526,7 @@ function List:iter () + return iter(self) + end + +---- Create an iterator over a seqence. ++--- Create an iterator over a sequence. + -- This captures the Python concept of 'sequence'. + -- For tables, iterates over all values with integer indices. + -- @param seq a sequence; a string (over characters), a table, a file object (over lines) or an iterator function +diff --git a/extra/penlight/lua/pl/Map.lua b/extra/penlight/lua/pl/Map.lua +index b4c79ec..06fd096 100644 +--- a/extra/penlight/lua/pl/Map.lua ++++ b/extra/penlight/lua/pl/Map.lua +@@ -37,10 +37,14 @@ local function makelist(t) + return setmetatable(t, require('pl.List')) + end + +---- list of keys. ++--- return a List of all keys. ++-- @class function ++-- @name Map:keys + Map.keys = tablex.keys + +---- list of values. ++--- return a List of all values. ++-- @class function ++-- @name Map:values + Map.values = tablex.values + + --- return an iterator over all key-value pairs. +@@ -48,17 +52,24 @@ function Map:iter () + return pairs(self) + end + +---- return a List of all key-value pairs, sorted by the keys. ++--- return a List of all key-value pairs, sorted by the keys in ascending order. + function Map:items() + local ls = makelist(tablex.pairmap (function (k,v) return makelist {k,v} end, self)) +- ls:sort(function(t1,t2) return t1[1] < t2[1] end) +- return ls ++ ls:sort(function(t1,t2) return t1[1] < t2[1] end) ++ return ls + end + +--- Will return the existing value, or if it doesn't exist it will set +--- a default value and return it. +-function Map:setdefault(key, defaultval) +- return self[key] or self:set(key,defaultval) or defaultval ++--- set a value in the map if it doesn't exist yet. ++-- @param key the key ++-- @param default value to set ++-- @return the value stored in the map (existing value, or the new value) ++function Map:setdefault(key, default) ++ local val = self[key] ++ if val ~= nil then ++ return val ++ end ++ self:set(key,default) ++ return default + end + + --- size of map. +diff --git a/extra/penlight/lua/pl/OrderedMap.lua b/extra/penlight/lua/pl/OrderedMap.lua +index 3e49af5..379c44f 100644 +--- a/extra/penlight/lua/pl/OrderedMap.lua ++++ b/extra/penlight/lua/pl/OrderedMap.lua +@@ -32,7 +32,7 @@ end + local assert_arg,raise = utils.assert_arg,utils.raise + + --- update an OrderedMap using a table. +--- If the table is itself an OrderedMap, then its entries will be appended. ++-- If the table is itself an OrderedMap, then its entries will be appended. + -- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended. + -- + -- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. +@@ -132,7 +132,7 @@ end + function OrderedMap:iter () + local i = 0 + local keys = self._keys +- local n,idx = #keys ++ local idx + return function() + i = i + 1 + if i > #keys then return nil end +diff --git a/extra/penlight/lua/pl/Set.lua b/extra/penlight/lua/pl/Set.lua +index ffc50a7..ce428f0 100644 +--- a/extra/penlight/lua/pl/Set.lua ++++ b/extra/penlight/lua/pl/Set.lua +@@ -20,7 +20,7 @@ + -- [banana,apricot,apple,orange] [banana,apple,orange] + -- + -- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.Map`, (`pl.List` if __tostring is used) +--- @module pl.Set ++-- @classmod pl.Set + + local tablex = require 'pl.tablex' + local utils = require 'pl.utils' +diff --git a/extra/penlight/lua/pl/app.lua b/extra/penlight/lua/pl/app.lua +index 21b6a7b..736e166 100644 +--- a/extra/penlight/lua/pl/app.lua ++++ b/extra/penlight/lua/pl/app.lua +@@ -10,29 +10,55 @@ local path = require 'pl.path' + + local app = {} + +-local function check_script_name () +- if _G.arg == nil then error('no command line args available\nWas this run from a main script?') end +- return _G.arg[0] ++--- return the name of the current script running. ++-- The name will be the name as passed on the command line ++-- @return string filename ++function app.script_name() ++ if _G.arg and _G.arg[0] then ++ return _G.arg[0] ++ end ++ return utils.raise("No script name found") + end + +---- add the current script's path to the Lua module path. ++--- prefixes the current script's path to the Lua module path. + -- Applies to both the source and the binary module paths. It makes it easy for + -- the main file of a multi-file program to access its modules in the same directory. + -- `base` allows these modules to be put in a specified subdirectory, to allow for + -- cleaner deployment and resolve potential conflicts between a script name and its + -- library directory. +--- @string base optional base directory. ++-- ++-- Note: the path is prefixed, so it is searched first when requiring modules. ++-- @string base optional base directory (absolute, or relative path). ++-- @bool nofollow always use the invocation's directory, even if the invoked file is a symlink + -- @treturn string the current script's path with a trailing slash +-function app.require_here (base) +- local p = path.dirname(check_script_name()) ++function app.require_here (base, nofollow) ++ local p = app.script_name() + if not path.isabs(p) then + p = path.join(path.currentdir(),p) + end ++ if not nofollow then ++ local t = path.link_attrib(p) ++ if t and t.mode == 'link' then ++ t = t.target ++ if not path.isabs(t) then ++ t = path.join(path.dirname(p), t) ++ end ++ p = t ++ end ++ end ++ p = path.normpath(path.dirname(p)) + if p:sub(-1,-1) ~= path.sep then + p = p..path.sep + end + if base then +- p = p..base..path.sep ++ if path.is_windows then ++ base = base:gsub('/','\\') ++ end ++ if path.isabs(base) then ++ p = base .. path.sep ++ else ++ p = p..base..path.sep ++ end + end + local so_ext = path.is_windows and 'dll' or 'so' + local lsep = package.path:find '^;' and '' or ';' +@@ -45,16 +71,24 @@ end + --- return a suitable path for files private to this application. + -- These will look like '~/.SNAME/file', with '~' as with expanduser and + -- SNAME is the name of the script without .lua extension. ++-- If the directory does not exist, it will be created. + -- @string file a filename (w/out path) + -- @return a full pathname, or nil +--- @return 'cannot create' error +-function app.appfile (file) +- local sname = path.basename(check_script_name()) +- local name,ext = path.splitext(sname) ++-- @return cannot create directory error ++-- @usage ++-- -- when run from a script called 'testapp' (on Windows): ++-- local app = require 'pl.app' ++-- print(app.appfile 'test.txt') ++-- -- C:\Documents and Settings\steve\.testapp\test.txt ++function app.appfile(file) ++ local sfullname, err = app.script_name() ++ if not sfullname then return utils.raise(err) end ++ local sname = path.basename(sfullname) ++ local name = path.splitext(sname) + local dir = path.join(path.expanduser('~'),'.'..name) + if not path.isdir(dir) then + local ret = path.mkdir(dir) +- if not ret then return utils.raise ('cannot create '..dir) end ++ if not ret then return utils.raise('cannot create '..dir) end + end + return path.join(dir,file) + end +@@ -74,38 +108,141 @@ function app.platform() + end + + --- return the full command-line used to invoke this script. +--- Any extra flags occupy slots, so that `lua -lpl` gives us `{[-2]='lua',[-1]='-lpl'}` ++-- It will not include the scriptname itself, see `app.script_name`. + -- @return command-line + -- @return name of Lua program used +-function app.lua () +- local args = _G.arg or error "not in a main program" +- local imin = 0 +- for i in pairs(args) do +- if i < imin then imin = i end ++-- @usage ++-- -- execute: lua -lluacov -e 'print(_VERSION)' myscript.lua ++-- ++-- -- myscript.lua ++-- print(require("pl.app").lua()) --> "lua -lluacov -e 'print(_VERSION)'", "lua" ++function app.lua() ++ local args = _G.arg ++ if not args then ++ return utils.raise "not in a main program" + end +- local cmd, append = {}, table.insert +- for i = imin,-1 do +- append(cmd, utils.quote_arg(args[i])) ++ ++ local cmd = {} ++ local i = -1 ++ while true do ++ table.insert(cmd, 1, args[i]) ++ if not args[i-1] then ++ return utils.quote_arg(cmd), args[i] ++ end ++ i = i - 1 + end +- return table.concat(cmd,' '),args[imin] + end + + --- parse command-line arguments into flags and parameters. + -- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`). +--- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`); +--- note that a number value can be given without a space. ++-- ++-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`), ++-- a number value can be given without a space. If the flag is marked ++-- as having a value, then a space-separated value is also accepted (`-i hello`), ++-- see the `flags_with_values` argument). ++-- + -- Multiple short args can be combined like so: ( `-abcd`). ++-- ++-- When specifying the `flags_valid` parameter, its contents can also contain ++-- aliases, to convert short/long flags to the same output name. See the ++-- example below. ++-- ++-- Note: if a flag is repeated, the last value wins. + -- @tparam {string} args an array of strings (default is the global `arg`) +--- @tab flags_with_values any flags that take values, e.g. `{out=true}` ++-- @tab flags_with_values any flags that take values, either list or hash ++-- table e.g. `{ out=true }` or `{ "out" }`. ++-- @tab flags_valid (optional) flags that are valid, either list or hashtable. ++-- If not given, everything ++-- will be accepted(everything in `flags_with_values` will automatically be allowed) + -- @return a table of flags (flag=value pairs) + -- @return an array of parameters + -- @raise if args is nil, then the global `args` must be available! +-function app.parse_args (args,flags_with_values) ++-- @usage ++-- -- Simple form: ++-- local flags, params = app.parse_args(nil, ++-- { "hello", "world" }, -- list of flags taking values ++-- { "l", "a", "b"}) -- list of allowed flags (value ones will be added) ++-- ++-- -- More complex example using aliases: ++-- local valid = { ++-- long = "l", -- if 'l' is specified, it is reported as 'long' ++-- new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new' ++-- } ++-- local values = { ++-- "value", -- will automatically be added to the allowed set of flags ++-- "new", -- will mark 'n' and 'old' as requiring a value as well ++-- } ++-- local flags, params = app.parse_args(nil, values, valid) ++-- ++-- -- command: myapp.lua -l --old:hello --value world param1 param2 ++-- -- will yield: ++-- flags = { ++-- long = true, -- input from 'l' ++-- new = "hello", -- input from 'old' ++-- value = "world", -- allowed because it was in 'values', note: space separated! ++-- } ++-- params = { ++-- [1] = "param1" ++-- [2] = "param2" ++-- } ++function app.parse_args (args,flags_with_values, flags_valid) + if not args then + args = _G.arg +- if not args then error "Not in a main program: 'arg' not found" end ++ if not args then utils.raise "Not in a main program: 'arg' not found" end ++ end ++ ++ local with_values = {} ++ for k,v in pairs(flags_with_values or {}) do ++ if type(k) == "number" then ++ k = v ++ end ++ with_values[k] = true + end +- flags_with_values = flags_with_values or {} ++ ++ local valid ++ if not flags_valid then ++ -- if no allowed flags provided, we create a table that always returns ++ -- the keyname, no matter what you look up ++ valid = setmetatable({},{ __index = function(_, key) return key end }) ++ else ++ valid = {} ++ for k,aliases in pairs(flags_valid) do ++ if type(k) == "number" then -- array/list entry ++ k = aliases ++ end ++ if type(aliases) == "string" then -- single alias ++ aliases = { aliases } ++ end ++ if type(aliases) == "table" then -- list of aliases ++ -- it's the alternate name, so add the proper mappings ++ for i, alias in ipairs(aliases) do ++ valid[alias] = k ++ end ++ end ++ valid[k] = k ++ end ++ do ++ local new_with_values = {} -- needed to prevent "invalid key to 'next'" error ++ for k,v in pairs(with_values) do ++ if not valid[k] then ++ valid[k] = k -- add the with_value entry as a valid one ++ new_with_values[k] = true ++ else ++ new_with_values[valid[k]] = true --set, but by its alias ++ end ++ end ++ with_values = new_with_values ++ end ++ end ++ ++ -- now check that all flags with values are reported as such under all ++ -- of their aliases ++ for k, main_alias in pairs(valid) do ++ if with_values[main_alias] then ++ with_values[k] = true ++ end ++ end ++ + local _args = {} + local flags = {} + local i = 1 +@@ -113,20 +250,24 @@ function app.parse_args (args,flags_with_values) + local a = args[i] + local v = a:match('^-(.+)') + local is_long +- if v then -- we have a flag ++ if not v then ++ -- we have a parameter ++ _args[#_args+1] = a ++ else ++ -- it's a flag + if v:find '^-' then + is_long = true + v = v:sub(2) + end +- if flags_with_values[v] then ++ if with_values[v] then + if i == #args or args[i+1]:find '^-' then + return utils.raise ("no value for '"..v.."'") + end +- flags[v] = args[i+1] ++ flags[valid[v]] = args[i+1] + i = i + 1 + else + -- a value can also be indicated with = or : +- local var,val = utils.splitv (v,'[=:]') ++ local var,val = utils.splitv (v,'[=:]', false, 2) + var = var or v + val = val or true + if not is_long then +@@ -136,7 +277,13 @@ function app.parse_args (args,flags_with_values) + var = var:sub(1,1) + else -- multiple short flags + for i = 1,#var do +- flags[var:sub(i,i)] = true ++ local f = var:sub(i,i) ++ if not valid[f] then ++ return utils.raise("unknown flag '"..f.."'") ++ else ++ f = valid[f] ++ end ++ flags[f] = true + end + val = nil -- prevents use of var as a flag below + end +@@ -145,11 +292,14 @@ function app.parse_args (args,flags_with_values) + end + end + if val then ++ if not valid[var] then ++ return utils.raise("unknown flag '"..var.."'") ++ else ++ var = valid[var] ++ end + flags[var] = val + end + end +- else +- _args[#_args+1] = a + end + i = i + 1 + end +diff --git a/extra/penlight/lua/pl/array2d.lua b/extra/penlight/lua/pl/array2d.lua +index b15e39e..0bc910e 100644 +--- a/extra/penlight/lua/pl/array2d.lua ++++ b/extra/penlight/lua/pl/array2d.lua +@@ -1,11 +1,17 @@ + --- Operations on two-dimensional arrays. + -- See @{02-arrays.md.Operations_on_two_dimensional_tables|The Guide} + -- ++-- The size of the arrays is determined by using the length operator `#` hence ++-- the module is not `nil` safe, and the usual precautions apply. ++-- ++-- Note: all functions taking `i1,j1,i2,j2` as arguments will normalize the ++-- arguments using `default_range`. ++-- + -- Dependencies: `pl.utils`, `pl.tablex`, `pl.types` + -- @module pl.array2d + +-local type,tonumber,assert,tostring,io,ipairs,string,table = +- _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table ++local tonumber,tostring,io,ipairs,string,table = ++ _G.tonumber,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table + local setmetatable,getmetatable = setmetatable,getmetatable + + local tablex = require 'pl.tablex' +@@ -16,6 +22,8 @@ local remove = table.remove + local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg + local byte = string.byte + local stdout = io.stdout ++local min = math.min ++ + + local array2d = {} + +@@ -31,29 +39,48 @@ local function makelist (res) + return setmetatable(res, require('pl.List')) + end + +- +-local function index (t,k) +- return t[k] ++--- return the row and column size. ++-- Size is calculated using the Lua length operator #, so usual precautions ++-- regarding `nil` values apply. ++-- @array2d a a 2d array ++-- @treturn int number of rows (`#a`) ++-- @treturn int number of cols (`#a[1]`) ++function array2d.size (a) ++ assert_arg(1,a,'table') ++ return #a,#a[1] + end + +---- return the row and column size. +--- @array2d t a 2d array +--- @treturn int number of rows +--- @treturn int number of cols +-function array2d.size (t) +- assert_arg(1,t,'table') +- return #t,#t[1] ++do ++ local function index (t,k) ++ return t[k] ++ end ++ ++ --- extract a column from the 2D array. ++ -- @array2d a 2d array ++ -- @param j column index ++ -- @return 1d array ++ function array2d.column (a,j) ++ assert_arg(1,a,'table') ++ return makelist(imap(index,a,j)) ++ end + end ++local column = array2d.column + +---- extract a column from the 2D array. ++--- extract a row from the 2D array. ++-- Added in line with `column`, for read-only purposes directly ++-- accessing a[i] is more performant. + -- @array2d a 2d array +--- @param key an index or key +--- @return 1d array +-function array2d.column (a,key) ++-- @param i row index ++-- @return 1d array (copy of the row) ++function array2d.row(a,i) + assert_arg(1,a,'table') +- return makelist(imap(index,a,key)) ++ local row = a[i] ++ local r = {} ++ for n,v in ipairs(row) do ++ r[n] = v ++ end ++ return makelist(r) + end +-local column = array2d.column + + --- map a function over a 2D array + -- @func f a function of at least one argument +@@ -61,7 +88,7 @@ local column = array2d.column + -- @param arg an optional extra argument to be passed to the function. + -- @return 2d array + function array2d.map (f,a,arg) +- assert_arg(1,a,'table') ++ assert_arg(2,a,'table') + f = utils.function_arg(1,f) + return obj(a,imap(function(row) return imap(f,row,arg) end, a)) + end +@@ -96,15 +123,11 @@ function array2d.reduce2 (opc,opr,a) + return reduce(opc,tmp) + end + +-local function dimension (t) +- return type(t[1])=='table' and 2 or 1 +-end +- + --- map a function over two arrays. + -- They can be both or either 2D arrays + -- @func f function of at least two arguments +--- @int ad order of first array (1 or 2) +--- @int bd order of second array (1 or 2) ++-- @int ad order of first array (`1` if `a` is a list/array, `2` if it is a 2d array) ++-- @int bd order of second array (`1` if `b` is a list/array, `2` if it is a 2d array) + -- @tab a 1d or 2d array + -- @tab b 1d or 2d array + -- @param arg optional extra argument to pass to function +@@ -140,9 +163,9 @@ function array2d.product (f,t1,t2) + f = utils.function_arg(1,f) + assert_arg(2,t1,'table') + assert_arg(3,t2,'table') +- local res, map = {}, tablex.map ++ local res = {} + for i,v in ipairs(t2) do +- res[i] = map(f,t1,v) ++ res[i] = tmap(f,t1,v) + end + return res + end +@@ -155,19 +178,21 @@ end + function array2d.flatten (t) + local res = {} + local k = 1 +- for _,a in ipairs(t) do -- for all rows +- for i = 1,#a do +- res[k] = a[i] ++ local rows, cols = array2d.size(t) ++ for r = 1, rows do ++ local row = t[r] ++ for c = 1, cols do ++ res[k] = row[c] + k = k + 1 + end + end + return makelist(res) + end + +---- reshape a 2D array. ++--- reshape a 2D array. Reshape the array by specifying a new nr of rows. + -- @array2d t 2d array + -- @int nrows new number of rows +--- @bool co column-order (Fortran-style) (default false) ++-- @bool co use column-order (Fortran-style) (default false) + -- @return a new 2d array + function array2d.reshape (t,nrows,co) + local nr,nc = array2d.size(t) +@@ -197,30 +222,43 @@ function array2d.reshape (t,nrows,co) + return obj(t,res) + end + ++--- transpose a 2D array. ++-- @array2d t 2d array ++-- @return a new 2d array ++function array2d.transpose(t) ++ assert_arg(1,t,'table') ++ local _, c = array2d.size(t) ++ return array2d.reshape(t,c,true) ++end ++ + --- swap two rows of an array. + -- @array2d t a 2d array + -- @int i1 a row index + -- @int i2 a row index ++-- @return t (same, modified 2d array) + function array2d.swap_rows (t,i1,i2) + assert_arg(1,t,'table') + t[i1],t[i2] = t[i2],t[i1] ++ return t + end + + --- swap two columns of an array. + -- @array2d t a 2d array + -- @int j1 a column index + -- @int j2 a column index ++-- @return t (same, modified 2d array) + function array2d.swap_cols (t,j1,j2) + assert_arg(1,t,'table') +- for i = 1,#t do +- local row = t[i] ++ for _, row in ipairs(t) do + row[j1],row[j2] = row[j2],row[j1] + end ++ return t + end + + --- extract the specified rows. + -- @array2d t 2d array + -- @tparam {int} ridx a table of row indices ++-- @return a new 2d array with the extracted rows + function array2d.extract_rows (t,ridx) + return obj(t,index_by(t,ridx)) + end +@@ -228,6 +266,7 @@ end + --- extract the specified columns. + -- @array2d t 2d array + -- @tparam {int} cidx a table of column indices ++-- @return a new 2d array with the extracted columns + function array2d.extract_cols (t,cidx) + assert_arg(1,t,'table') + local res = {} +@@ -253,74 +292,97 @@ function array2d.remove_col (t,j) + end + end + +-local Ai = byte 'A' ++do ++ local function _parse (s) ++ local r, c = s:match 'R(%d+)C(%d+)' ++ if r then ++ r,c = tonumber(r),tonumber(c) ++ return r,c ++ end ++ c,r = s:match '(%a+)(%d+)' ++ if c then ++ local cv = 0 ++ for i = 1, #c do ++ cv = cv * 26 + byte(c:sub(i,i)) - byte 'A' + 1 ++ end ++ return tonumber(r), cv ++ end ++ error('bad cell specifier: '..s) ++ end + +-local function _parse (s) +- local c,r +- if s:sub(1,1) == 'R' then +- r,c = s:match 'R(%d+)C(%d+)' +- r,c = tonumber(r),tonumber(c) +- else +- c,r = s:match '(.)(.)' +- c = byte(c) - byte 'A' + 1 +- r = tonumber(r) ++ --- parse a spreadsheet range or cell. ++ -- The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for ++ -- single cells as 'A1' or 'R1C1'. ++ -- @string s a range (case insensitive). ++ -- @treturn int start row ++ -- @treturn int start col ++ -- @treturn int end row (or `nil` if the range was a single cell) ++ -- @treturn int end col (or `nil` if the range was a single cell) ++ function array2d.parse_range (s) ++ assert_arg(1,s,'string') ++ s = s:upper() ++ if s:find ':' then ++ local start,finish = splitv(s,':') ++ local i1,j1 = _parse(start) ++ local i2,j2 = _parse(finish) ++ return i1,j1,i2,j2 ++ else -- single value ++ local i,j = _parse(s) ++ return i,j ++ end + end +- assert(c ~= nil and r ~= nil,'bad cell specifier: '..s) +- return r,c + end + +---- parse a spreadsheet range. +--- The range can be specified either as 'A1:B2' or 'R1C1:R2C2'; +--- a special case is a single element (e.g 'A1' or 'R1C1') +--- @string s a range. +--- @treturn int start col +--- @treturn int start row +--- @treturn int end col +--- @treturn int end row +-function array2d.parse_range (s) +- if s:find ':' then +- local start,finish = splitv(s,':') +- local i1,j1 = _parse(start) +- local i2,j2 = _parse(finish) +- return i1,j1,i2,j2 +- else -- single value +- local i,j = _parse(s) +- return i,j +- end ++--- get a slice of a 2D array. ++-- Same as `slice`. ++-- @see slice ++function array2d.range (...) ++ return array2d.slice(...) + end + +---- get a slice of a 2D array using spreadsheet range notation. @see parse_range +--- @array2d t a 2D array +--- @string rstr range expression +--- @return a slice +--- @see array2d.parse_range +--- @see array2d.slice +-function array2d.range (t,rstr) +- assert_arg(1,t,'table') +- local i1,j1,i2,j2 = array2d.parse_range(rstr) +- if i2 then +- return array2d.slice(t,i1,j1,i2,j2) +- else -- single value +- return t[i1][j1] ++local default_range do ++ local function norm_value(v, max) ++ if not v then return v end ++ if v < 0 then ++ v = max + v + 1 ++ end ++ if v < 1 then v = 1 end ++ if v > max then v = max end ++ return v + end +-end + +-local function default_range (t,i1,j1,i2,j2) +- local nr, nc = array2d.size(t) +- i1,j1 = i1 or 1, j1 or 1 +- i2,j2 = i2 or nr, j2 or nc +- if i2 < 0 then i2 = nr + i2 + 1 end +- if j2 < 0 then j2 = nc + j2 + 1 end +- return i1,j1,i2,j2 ++ --- normalizes coordinates to valid positive entries and defaults. ++ -- Negative indices will be counted from the end, too low, or too high ++ -- will be limited by the array sizes. ++ -- @array2d t a 2D array ++ -- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++ -- @tparam[opt=1] int j1 start col ++ -- @tparam[opt=N] int i2 end row ++ -- @tparam[opt=M] int j2 end col ++ -- @see parse_range ++ -- @return i1, j1, i2, j2 ++ function array2d.default_range (t,i1,j1,i2,j2) ++ if (type(i1) == 'string') and not (j1 or i2 or j2) then ++ i1, j1, i2, j2 = array2d.parse_range(i1) ++ end ++ local nr, nc = array2d.size(t) ++ i1 = norm_value(i1 or 1, nr) ++ j1 = norm_value(j1 or 1, nc) ++ i2 = norm_value(i2 or nr, nr) ++ j2 = norm_value(j2 or nc, nc) ++ return i1,j1,i2,j2 ++ end ++ default_range = array2d.default_range + end + + --- get a slice of a 2D array. Note that if the specified range has + -- a 1D result, the rank of the result will be 1. + -- @array2d t a 2D array +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range + -- @return an array, 2D in general but 1D in special cases. + function array2d.slice (t,i1,j1,i2,j2) + assert_arg(1,t,'table') +@@ -345,16 +407,25 @@ end + + --- set a specified range of an array to a value. + -- @array2d t a 2D array +--- @param value the value (may be a function) +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) ++-- @param value the value (may be a function, called as `val(i,j)`) ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range + -- @see tablex.set + function array2d.set (t,value,i1,j1,i2,j2) + i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) +- for i = i1,i2 do ++ local i = i1 ++ if types.is_callable(value) then ++ local old_f = value ++ value = function(j) ++ return old_f(i,j) ++ end ++ end ++ while i <= i2 do + tset(t[i],value,j1,j2) ++ i = i + 1 + end + end + +@@ -362,10 +433,11 @@ end + -- @array2d t a 2D array + -- @param f a file object (default stdout) + -- @string fmt a format string (default is just to use tostring) +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range + function array2d.write (t,f,fmt,i1,j1,i2,j2) + assert_arg(1,t,'table') + f = f or stdout +@@ -383,12 +455,13 @@ end + + --- perform an operation for all values in a 2D array. + -- @array2d t 2D array +--- @func row_op function to call on each value +--- @func end_row_op function to call at end of each row +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) ++-- @func row_op function to call on each value; `row_op(row,j)` ++-- @func end_row_op function to call at end of each row; `end_row_op(i)` ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range + function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2) + assert_arg(1,t,'table') + i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) +@@ -401,17 +474,16 @@ function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2) + end + end + +-local min, max = math.min, math.max +- + ---- move a block from the destination to the source. + -- @array2d dest a 2D array + -- @int di start row in dest + -- @int dj start col in dest + -- @array2d src a 2D array +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range + function array2d.move (dest,di,dj,src,i1,j1,i2,j2) + assert_arg(1,dest,'table') + assert_arg(4,src,'table') +@@ -430,27 +502,27 @@ end + + --- iterate over all elements in a 2D array, with optional indices. + -- @array2d a 2D array +--- @tparam {int} indices with indices (default false) +--- @int i1 start row (default 1) +--- @int j1 start col (default 1) +--- @int i2 end row (default N) +--- @int j2 end col (default M) +--- @return either value or i,j,value depending on indices +-function array2d.iter (a,indices,i1,j1,i2,j2) ++-- @bool indices with indices (default false) ++-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` ++-- @tparam[opt=1] int j1 start col ++-- @tparam[opt=N] int i2 end row ++-- @tparam[opt=M] int j2 end col ++-- @see parse_range ++-- @return either `value` or `i,j,value` depending on the value of `indices` ++function array2d.iter(a,indices,i1,j1,i2,j2) + assert_arg(1,a,'table') +- local norowset = not (i2 and j2) + i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2) +- local n,i,j = i2-i1+1,i1-1,j1-1 +- local row,nr = nil,0 +- local onr = j2 - j1 + 1 ++ local i,j = i1,j1-1 ++ local row = a[i] + return function() + j = j + 1 +- if j > nr then ++ if j > j2 then + j = j1 + i = i + 1 +- if i > i2 then return nil end + row = a[i] +- nr = norowset and #row or onr ++ if i > i2 then ++ return nil ++ end + end + if indices then + return i,j,row[j] +@@ -462,16 +534,32 @@ end + + --- iterate over all columns. + -- @array2d a a 2D array +--- @return each column in turn +-function array2d.columns (a) +- assert_arg(1,a,'table') +- local n = a[1][1] +- local i = 0 +- return function() +- i = i + 1 +- if i > n then return nil end +- return column(a,i) +- end ++-- @return column, column-index ++function array2d.columns(a) ++ assert_arg(1,a,'table') ++ local n = #a[1] ++ local i = 0 ++ return function() ++ i = i + 1 ++ if i > n then return nil end ++ return column(a,i), i ++ end ++end ++ ++--- iterate over all rows. ++-- Returns a copy of the row, for read-only purposes directly iterating ++-- is more performant; `ipairs(a)` ++-- @array2d a a 2D array ++-- @return row, row-index ++function array2d.rows(a) ++ assert_arg(1,a,'table') ++ local n = #a ++ local i = 0 ++ return function() ++ i = i + 1 ++ if i > n then return nil end ++ return array2d.row(a,i), i ++ end + end + + --- new array of specified dimensions +@@ -495,5 +583,3 @@ function array2d.new(rows,cols,val) + end + + return array2d +- +- +diff --git a/extra/penlight/lua/pl/class.lua b/extra/penlight/lua/pl/class.lua +index a57ac71..cd041c8 100644 +--- a/extra/penlight/lua/pl/class.lua ++++ b/extra/penlight/lua/pl/class.lua +@@ -1,4 +1,4 @@ +---- Provides a reuseable and convenient framework for creating classes in Lua. ++--- Provides a reusable and convenient framework for creating classes in Lua. + -- Two possible notations: + -- + -- B = class(A) +@@ -17,23 +17,29 @@ local compat + -- this trickery is necessary to prevent the inheritance of 'super' and + -- the resulting recursive call problems. + local function call_ctor (c,obj,...) +- -- nice alias for the base class ctor +- local base = rawget(c,'_base') +- if base then +- local parent_ctor = rawget(base,'_init') +- while not parent_ctor do +- base = rawget(base,'_base') +- if not base then break end +- parent_ctor = rawget(base,'_init') ++ local init = rawget(c,'_init') ++ local parent_with_init = rawget(c,'_parent_with_init') ++ ++ if parent_with_init then ++ if not init then -- inheriting an init ++ init = rawget(parent_with_init, '_init') ++ parent_with_init = rawget(parent_with_init, '_parent_with_init') + end +- if parent_ctor then ++ if parent_with_init then -- super() points to one above wherever _init came from + rawset(obj,'super',function(obj,...) +- call_ctor(base,obj,...) ++ call_ctor(parent_with_init,obj,...) + end) + end ++ else ++ -- Without this, calling super() where none exists will sometimes loop and stack overflow ++ rawset(obj,'super',nil) ++ end ++ ++ local res = init(obj,...) ++ if parent_with_init then -- If this execution of call_ctor set a super, unset it ++ rawset(obj,'super',nil) + end +- local res = c._init(obj,...) +- rawset(obj,'super',nil) ++ + return res + end + +@@ -60,7 +66,7 @@ end + -- if pussycat:is_a(Cat) then + -- -- it's true, it is a Lion, but also a Cat + -- end +--- ++-- + -- if pussycat:is_a() == Lion then + -- -- It's true + -- end +@@ -131,7 +137,7 @@ local function _class(base,c_arg,c) + else + c = c or {} + end +- ++ + if type(base) == 'table' then + -- our new class is a shallow copy of the base class! + -- but be careful not to wipe out any methods we have been given at this point! +@@ -146,6 +152,7 @@ local function _class(base,c_arg,c) + c.__index = c + setmetatable(c,mt) + if not plain then ++ if base and rawget(base,'_init') then c._parent_with_init = base end -- For super and inherited init + c._init = nil + end + +@@ -160,15 +167,12 @@ local function _class(base,c_arg,c) + if not obj then obj = {} end + setmetatable(obj,c) + +- if rawget(c,'_init') then -- explicit constructor ++ if rawget(c,'_init') or rawget(c,'_parent_with_init') then -- constructor exists + local res = call_ctor(c,obj,...) + if res then -- _if_ a ctor returns a value, it becomes the object... + obj = res + setmetatable(obj,c) + end +- elseif base and rawget(base,'_init') then -- default constructor +- -- make sure that any stuff from the base class is initialized! +- call_ctor(base,obj,...) + end + + if base and rawget(base,'_post_init') then +diff --git a/extra/penlight/lua/pl/compat.lua b/extra/penlight/lua/pl/compat.lua +index 58cf6b2..dfcb91d 100644 +--- a/extra/penlight/lua/pl/compat.lua ++++ b/extra/penlight/lua/pl/compat.lua +@@ -1,35 +1,58 @@ + ---------------- + --- Lua 5.1/5.2/5.3 compatibility. +--- Ensures that `table.pack` and `package.searchpath` are available +--- for Lua 5.1 and LuaJIT. +--- The exported function `load` is Lua 5.2 compatible. +--- `compat.setfenv` and `compat.getfenv` are available for Lua 5.2, although +--- they are not always guaranteed to work. ++-- Injects `table.pack`, `table.unpack`, and `package.searchpath` in the global ++-- environment, to make sure they are available for Lua 5.1 and LuaJIT. ++-- ++-- All other functions are exported as usual in the returned module table. ++-- ++-- NOTE: everything in this module is also available in `pl.utils`. + -- @module pl.compat +- + local compat = {} + ++--- boolean flag this is Lua 5.1 (or LuaJIT). ++-- @field lua51 + compat.lua51 = _VERSION == 'Lua 5.1' + +-local isJit = (tostring(assert):match('builtin') ~= nil) +-if isJit then ++--- boolean flag this is LuaJIT. ++-- @field jit ++compat.jit = (tostring(assert):match('builtin') ~= nil) ++ ++--- boolean flag this is LuaJIT with 5.2 compatibility compiled in. ++-- @field jit52 ++if compat.jit then + -- 'goto' is a keyword when 52 compatibility is enabled in LuaJit + compat.jit52 = not loadstring("local goto = 1") + end + ++--- the directory separator character for the current platform. ++-- @field dir_separator + compat.dir_separator = _G.package.config:sub(1,1) ++ ++--- boolean flag this is a Windows platform. ++-- @field is_windows + compat.is_windows = compat.dir_separator == '\\' + +---- execute a shell command. +--- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 ++--- execute a shell command, in a compatible and platform independent way. ++-- This is a compatibility function that returns the same for Lua 5.1 and ++-- Lua 5.2+. ++-- ++-- NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems ++-- only use exitcodes 0-255, anything else is undefined. ++-- ++-- NOTE2: In Lua 5.2 and 5.3 a Windows exitcode of -1 would not properly be ++-- returned, this function will return it properly for all versions. + -- @param cmd a shell command + -- @return true if successful + -- @return actual return code +-function compat.execute (cmd) +- local res1,_,res3 = os.execute(cmd) ++function compat.execute(cmd) ++ local res1,res2,res3 = os.execute(cmd) ++ if res2 == "No error" and res3 == 0 and compat.is_windows then ++ -- os.execute bug in Lua 5.2/5.3 not reporting -1 properly on Windows ++ -- this was fixed in 5.4 ++ res3 = -1 ++ end + if compat.lua51 and not compat.jit52 then + if compat.is_windows then +- res1 = res1 > 255 and res1 % 256 or res1 + return res1==0,res1 + else + res1 = res1 > 255 and res1 / 256 or res1 +@@ -37,7 +60,6 @@ function compat.execute (cmd) + end + else + if compat.is_windows then +- res3 = res3 > 255 and res3 % 256 or res3 + return res3==0,res3 + else + return not not res1,res3 +@@ -46,7 +68,7 @@ function compat.execute (cmd) + end + + ---------------- +--- Load Lua code as a text or binary chunk. ++-- Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way). + -- @param ld code string or loader + -- @param[opt] source name of chunk for errors + -- @param[opt] mode 'b', 't' or 'bt' +@@ -54,20 +76,21 @@ end + -- @function compat.load + + --------------- +--- Get environment of a function. +--- With Lua 5.2, may return nil for a function with no global references! ++-- Get environment of a function (in a Lua 5.1 compatible way). ++-- Not 100% compatible, so with Lua 5.2 it may return nil for a function with no ++-- global references! + -- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html) + -- @param f a function or a call stack reference + -- @function compat.getfenv + + --------------- +--- Set environment of a function ++-- Set environment of a function (in a Lua 5.1 compatible way). + -- @param f a function or a call stack reference + -- @param env a table that becomes the new environment of `f` + -- @function compat.setfenv + + if compat.lua51 then -- define Lua 5.2 style load() +- if not isJit then -- but LuaJIT's load _is_ compatible ++ if not compat.jit then -- but LuaJIT's load _is_ compatible + local lua51_load = load + function compat.load(str,src,mode,env) + local chunk,err +@@ -122,36 +145,108 @@ else + end + end + +---- Lua 5.2 Functions Available for 5.1 ++ ++--- Global exported functions (for Lua 5.1 & LuaJIT) + -- @section lua52 + + --- pack an argument list into a table. + -- @param ... any arguments + -- @return a table with field n set to the length +--- @return the length + -- @function table.pack + if not table.pack then +- function table.pack (...) ++ function table.pack (...) -- luacheck: ignore + return {n=select('#',...); ...} + end + end + +------- +--- return the full path where a Lua module name would be matched. +--- @param mod module name, possibly dotted +--- @param path a path in the same form as package.path or package.cpath ++--- unpack a table and return the elements. ++-- ++-- NOTE: this version does NOT honor the n field, and hence it is not nil-safe. ++-- See `utils.unpack` for a version that is nil-safe. ++-- @param t table to unpack ++-- @param[opt] i index from which to start unpacking, defaults to 1 ++-- @param[opt] j index of the last element to unpack, defaults to #t ++-- @return multiple return values from the table ++-- @function table.unpack ++-- @see utils.unpack ++if not table.unpack then ++ table.unpack = unpack -- luacheck: ignore ++end ++ ++--- return the full path where a file name would be matched. ++-- This function was introduced in Lua 5.2, so this compatibility version ++-- will be injected in Lua 5.1 engines. ++-- @string name file name, possibly dotted ++-- @string path a path-template in the same form as package.path or package.cpath ++-- @string[opt] sep template separate character to be replaced by path separator. Default: "." ++-- @string[opt] rep the path separator to use, defaults to system separator. Default; "/" on Unixes, "\" on Windows. + -- @see path.package_path + -- @function package.searchpath ++-- @return on success: path of the file ++-- @return on failure: nil, error string listing paths tried + if not package.searchpath then +- local sep = package.config:sub(1,1) +- function package.searchpath (mod,path) +- mod = mod:gsub('%.',sep) ++ function package.searchpath (name,path,sep,rep) -- luacheck: ignore ++ if type(name) ~= "string" then ++ error(("bad argument #1 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) ++ end ++ if type(path) ~= "string" then ++ error(("bad argument #2 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) ++ end ++ if sep ~= nil and type(sep) ~= "string" then ++ error(("bad argument #3 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) ++ end ++ if rep ~= nil and type(rep) ~= "string" then ++ error(("bad argument #4 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) ++ end ++ sep = sep or "." ++ rep = rep or compat.dir_separator ++ do ++ local s, e = name:find(sep, nil, true) ++ while s do ++ name = name:sub(1, s-1) .. rep .. name:sub(e+1, -1) ++ s, e = name:find(sep, s + #rep + 1, true) ++ end ++ end ++ local tried = {} + for m in path:gmatch('[^;]+') do +- local nm = m:gsub('?',mod) ++ local nm = m:gsub('?', name) ++ tried[#tried+1] = nm + local f = io.open(nm,'r') + if f then f:close(); return nm end + end ++ return nil, "\tno file '" .. table.concat(tried, "'\n\tno file '") .. "'" ++ end ++end ++ ++--- Global exported functions (for Lua < 5.4) ++-- @section lua54 ++ ++--- raise a warning message. ++-- This functions mimics the `warn` function added in Lua 5.4. ++-- @function warn ++-- @param ... any arguments ++if not rawget(_G, "warn") then ++ local enabled = false ++ local function warn(arg1, ...) ++ if type(arg1) == "string" and arg1:sub(1, 1) == "@" then ++ -- control message ++ if arg1 == "@on" then ++ enabled = true ++ return ++ end ++ if arg1 == "@off" then ++ enabled = false ++ return ++ end ++ return -- ignore unknown control messages ++ end ++ if enabled then ++ io.stderr:write("Lua warning: ", arg1, ...) ++ io.stderr:write("\n") ++ end + end ++ -- use rawset to bypass OpenResty's protection of global scope ++ rawset(_G, "warn", warn) + end + + return compat +diff --git a/extra/penlight/lua/pl/comprehension.lua b/extra/penlight/lua/pl/comprehension.lua +index 9d8aa59..39be7c5 100644 +--- a/extra/penlight/lua/pl/comprehension.lua ++++ b/extra/penlight/lua/pl/comprehension.lua +@@ -73,7 +73,6 @@ local ops = { + -- @usage "(x+y)^2 for x for y if x > y" -- nested + -- + local function parse_comprehension(expr) +- local t = {} + local pos = 1 + + -- extract opname (if exists) +diff --git a/extra/penlight/lua/pl/config.lua b/extra/penlight/lua/pl/config.lua +index a71c472..ebd531c 100644 +--- a/extra/penlight/lua/pl/config.lua ++++ b/extra/penlight/lua/pl/config.lua +@@ -48,9 +48,9 @@ end + + local config = {} + +---- like io.lines(), but allows for lines to be continued with '\'. ++--- like `io.lines`, but allows for lines to be continued with '`\\`'. + -- @param file a file-like object (anything where read() returns the next line) or a filename. +--- Defaults to stardard input. ++-- Defaults to standard input. + -- @return an iterator over the lines, or nil + -- @return error 'not a file-like object' or 'file is nil' + function config.lines(file) +@@ -93,7 +93,7 @@ end + -- @tab[opt] cnfg a configuration table that may contain these fields: + -- + -- * `smart` try to deduce what kind of config file we have (default false) +--- * `variablilize` make names into valid Lua identifiers (default true) ++-- * `variabilize` make names into valid Lua identifiers (default true) + -- * `convert_numbers` try to convert values into numbers (default true) + -- * `trim_space` ensure that there is no starting or trailing whitespace with values (default true) + -- * `trim_quotes` remove quotes from strings (default false) +@@ -103,7 +103,7 @@ end + -- @return a table containing items, or `nil` + -- @return error message (same as @{config.lines} + function config.read(file,cnfg) +- local f,openf,err,auto ++ local auto + + local iter,err = config.lines(file) + if not iter then return nil,err end +@@ -136,7 +136,7 @@ function config.read(file,cnfg) + local initial_digits = '^[%d%+%-]' + local t = {} + local top_t = t +- local variablilize = check_cnfg ('variabilize',true) ++ local variabilize = check_cnfg ('variabilize',true) + local list_delim = check_cnfg('list_delim',',') + local convert_numbers = check_cnfg('convert_numbers',true) + local convert_boolean = check_cnfg('convert_boolean',false) +@@ -148,7 +148,7 @@ function config.read(file,cnfg) + if list_delim == ' ' then list_delim = '%s+' end + + local function process_name(key) +- if variablilize then ++ if variabilize then + key = key:gsub('[^%w]','_') + end + return key +diff --git a/extra/penlight/lua/pl/data.lua b/extra/penlight/lua/pl/data.lua +index 84f26bd..67b8d71 100644 +--- a/extra/penlight/lua/pl/data.lua ++++ b/extra/penlight/lua/pl/data.lua +@@ -189,10 +189,6 @@ local function open_file (f,mode) + return f,nil,opened + end + +-local function all_n () +- +-end +- + --- read a delimited file in a Lua table. + -- By default, attempts to treat first line as separated list of fieldnames. + -- @param file a filename or a file-like object +@@ -210,7 +206,7 @@ end + -- @return error message. May be a file error, 'not a file-like object' + -- or a conversion error + function data.read(file,cnfg) +- local err,opened,count,line ++ local count,line + local D = {} + if not cnfg then cnfg = {} end + local f,err,opened = open_file(file,'r') +@@ -221,7 +217,7 @@ function data.read(file,cnfg) + + -- note that using dot as the thousands separator (@thousands_dot) + -- requires a special conversion function! For CSV, _empty fields_ are +- -- considered to default to numerial zeroes. ++ -- considered to default to numerical zeroes. + local tonumber = tonumber + local function try_number(x) + if thousands_dot then x = x:gsub('%.(...)','%1') end +@@ -473,7 +469,6 @@ end + + local function process_select (data,parms) + --- preparing fields ---- +- local res,ret + field_error = nil + local fields = parms.fields + local numfields = fields:find '%$' or #data.fieldnames == 0 +@@ -495,7 +490,7 @@ local function process_select (data,parms) + fields = rstrip(fields):gsub('[^,%w]','_') + end + local massage_fields = utils.bind1(massage_fields,data) +- ret = gsub(fields,idpat,massage_fields) ++ local ret = gsub(fields,idpat,massage_fields) + if field_error then return nil,field_error end + parms.fields = fields + parms.proc_fields = ret +@@ -555,7 +550,7 @@ function data.query(data,condn,context,return_row) + else + return nil, "condition must be a string or a table" + end +- local query, k ++ local query + if condn.sort_by then -- use sorted_query + query = sorted_query + else +@@ -565,7 +560,7 @@ function data.query(data,condn,context,return_row) + if return_row then + fields = '{'..fields..'}' + end +- query,k = query:gsub('FIELDLIST',fields) ++ query = query:gsub('FIELDLIST',fields) + if is_string(condn.where) then + query = query:gsub('CONDITION',condn.where) + condn.where = nil +@@ -636,7 +631,6 @@ end + -- @param outfile filename or file-like object + -- @bool dont_fail true if you want to return an error, not just fail + function data.filter (Q,infile,outfile,dont_fail) +- local err + local d = data.read(infile or 'stdin') + local out = open_file(outfile or 'stdout') + local iter,err = d:select(Q) +diff --git a/extra/penlight/lua/pl/dir.lua b/extra/penlight/lua/pl/dir.lua +index 6fc9b12..cde16dc 100644 +--- a/extra/penlight/lua/pl/dir.lua ++++ b/extra/penlight/lua/pl/dir.lua +@@ -15,10 +15,11 @@ local sub = string.sub + local os,pcall,ipairs,pairs,require,setmetatable = os,pcall,ipairs,pairs,require,setmetatable + local remove = os.remove + local append = table.insert +-local wrap = coroutine.wrap +-local yield = coroutine.yield + local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise + ++local exists, isdir = path.exists, path.isdir ++local sep = path.sep ++ + local dir = {} + + local function makelist(l) +@@ -51,7 +52,7 @@ end + + --- Return a list of all file names within an array which match a pattern. + -- @tab filenames An array containing file names. +--- @string pattern A shell pattern. ++-- @string pattern A shell pattern (see `fnmatch`). + -- @treturn List(string) List of matching file names. + -- @raise dir and mask must be strings + function dir.filter(filenames,pattern) +@@ -65,13 +66,13 @@ function dir.filter(filenames,pattern) + return makelist(res) + end + +-local function _listfiles(dir,filemode,match) ++local function _listfiles(dirname,filemode,match) + local res = {} + local check = utils.choose(filemode,path.isfile,path.isdir) +- if not dir then dir = '.' end +- for f in ldir(dir) do ++ if not dirname then dirname = '.' end ++ for f in ldir(dirname) do + if f ~= '.' and f ~= '..' then +- local p = path.join(dir,f) ++ local p = path.join(dirname,f) + if check(p) and (not match or match(f)) then + append(res,p) + end +@@ -80,13 +81,14 @@ local function _listfiles(dir,filemode,match) + return makelist(res) + end + +---- return a list of all files in a directory which match the a shell pattern. +--- @string dir A directory. If not given, all files in current directory are returned. +--- @string mask A shell pattern. If not given, all files are returned. ++--- return a list of all files in a directory which match a shell pattern. ++-- @string[opt='.'] dirname A directory. ++-- @string[opt] mask A shell pattern (see `fnmatch`). If not given, all files are returned. + -- @treturn {string} list of files +--- @raise dir and mask must be strings +-function dir.getfiles(dir,mask) +- assert_dir(1,dir) ++-- @raise dirname and mask must be strings ++function dir.getfiles(dirname,mask) ++ dirname = dirname or '.' ++ assert_dir(1,dirname) + if mask then assert_string(2,mask) end + local match + if mask then +@@ -95,16 +97,17 @@ function dir.getfiles(dir,mask) + return path.normcase(f):find(mask) + end + end +- return _listfiles(dir,true,match) ++ return _listfiles(dirname,true,match) + end + + --- return a list of all subdirectories of the directory. +--- @string dir A directory ++-- @string[opt='.'] dirname A directory. + -- @treturn {string} a list of directories +--- @raise dir must be a a valid directory +-function dir.getdirectories(dir) +- assert_dir(1,dir) +- return _listfiles(dir,false) ++-- @raise dir must be a valid directory ++function dir.getdirectories(dirname) ++ dirname = dirname or '.' ++ assert_dir(1,dirname) ++ return _listfiles(dirname,false) + end + + local alien,ffi,ffi_checked,CopyFile,MoveFile,GetLastError,win32_errors,cmd_tmpfile +@@ -196,9 +199,10 @@ local function file_op (is_copy,src,dest,flag) + -- fallback if there's no Alien, just use DOS commands *shudder* + -- 'rename' involves a copy and then deleting the source. + if not CopyFile then +- src = path.normcase(src) +- dest = path.normcase(dest) +- local cmd = is_copy and 'copy' or 'rename' ++ if path.is_windows then ++ src = src:gsub("/","\\") ++ dest = dest:gsub("/","\\") ++ end + local res, err = execute_command('copy',two_arguments(src,dest)) + if not res then return false,err end + if not is_copy then +@@ -209,7 +213,7 @@ local function file_op (is_copy,src,dest,flag) + if path.isdir(dest) then + dest = path.join(dest,path.basename(src)) + end +- local ret ++ local ret + if is_copy then ret = CopyFile(src,dest,flag) + else ret = MoveFile(src,dest) end + if ret == 0 then +@@ -251,12 +255,12 @@ function dir.movefile (src,dest) + return file_op(false,src,dest,0) + end + +-local function _dirfiles(dir,attrib) ++local function _dirfiles(dirname,attrib) + local dirs = {} + local files = {} +- for f in ldir(dir) do ++ for f in ldir(dirname) do + if f ~= '.' and f ~= '..' then +- local p = path.join(dir,f) ++ local p = path.join(dirname,f) + local mode = attrib(p,'mode') + if mode=='directory' then + append(dirs,f) +@@ -269,15 +273,6 @@ local function _dirfiles(dir,attrib) + end + + +-local function _walker(root,bottom_up,attrib) +- local dirs,files = _dirfiles(root,attrib) +- if not bottom_up then yield(root,dirs,files) end +- for i,d in ipairs(dirs) do +- _walker(root..path.sep..d,bottom_up,attrib) +- end +- if bottom_up then yield(root,dirs,files) end +-end +- + --- return an iterator which walks through a directory tree starting at root. + -- The iterator returns (root,dirs,files) + -- Note that dirs and files are lists of names (i.e. you must say path.join(root,d) +@@ -299,11 +294,33 @@ function dir.walk(root,bottom_up,follow_links) + else + attrib = path.link_attrib + end +- return wrap(function () _walker(root,bottom_up,attrib) end) ++ ++ local to_scan = { root } ++ local to_return = {} ++ local iter = function() ++ while #to_scan > 0 do ++ local current_root = table.remove(to_scan) ++ local dirs,files = _dirfiles(current_root, attrib) ++ for _, d in ipairs(dirs) do ++ table.insert(to_scan, current_root..path.sep..d) ++ end ++ if not bottom_up then ++ return current_root, dirs, files ++ else ++ table.insert(to_return, { current_root, dirs, files }) ++ end ++ end ++ if #to_return > 0 then ++ return utils.unpack(table.remove(to_return)) ++ end ++ end ++ ++ return iter + end + + --- remove a whole directory tree. +--- @string fullpath A directory path ++-- Symlinks in the tree will be deleted without following them. ++-- @string fullpath A directory path (must be an actual directory, not a symlink) + -- @return true or nil + -- @return error if failed + -- @raise fullpath must be a string +@@ -311,50 +328,70 @@ function dir.rmtree(fullpath) + assert_dir(1,fullpath) + if path.islink(fullpath) then return false,'will not follow symlink' end + for root,dirs,files in dir.walk(fullpath,true) do +- for i,f in ipairs(files) do +- local res, err = remove(path.join(root,f)) +- if not res then return nil,err end ++ if path.islink(root) then ++ -- sub dir is a link, remove link, do not follow ++ if is_windows then ++ -- Windows requires using "rmdir". Deleting the link like a file ++ -- will instead delete all files from the target directory!! ++ local res, err = rmdir(root) ++ if not res then return nil,err .. ": " .. root end ++ else ++ local res, err = remove(root) ++ if not res then return nil,err .. ": " .. root end ++ end ++ else ++ for i,f in ipairs(files) do ++ local res, err = remove(path.join(root,f)) ++ if not res then return nil,err .. ": " .. path.join(root,f) end ++ end ++ local res, err = rmdir(root) ++ if not res then return nil,err .. ": " .. root end + end +- local res, err = rmdir(root) +- if not res then return nil,err end + end + return true + end + +-local dirpat +-if path.is_windows then +- dirpat = '(.+)\\[^\\]+$' +-else +- dirpat = '(.+)/[^/]+$' +-end +- +-local _makepath +-function _makepath(p) +- -- windows root drive case +- if p:find '^%a:[\\]*$' then +- return true +- end +- if not path.isdir(p) then +- local subp = p:match(dirpat) +- local ok, err = _makepath(subp) +- if not ok then return nil, err end +- return mkdir(p) +- else +- return true +- end +-end + +---- create a directory path. +--- This will create subdirectories as necessary! +--- @string p A directory path +--- @return true on success, nil + errormsg on failure +--- @raise failure to create +-function dir.makepath (p) +- assert_string(1,p) +- return _makepath(path.normcase(path.abspath(p))) ++do ++ local dirpat ++ if path.is_windows then ++ dirpat = '(.+)\\[^\\]+$' ++ else ++ dirpat = '(.+)/[^/]+$' ++ end ++ ++ local _makepath ++ function _makepath(p) ++ -- windows root drive case ++ if p:find '^%a:[\\]*$' then ++ return true ++ end ++ if not path.isdir(p) then ++ local subp = p:match(dirpat) ++ if subp then ++ local ok, err = _makepath(subp) ++ if not ok then return nil, err end ++ end ++ return mkdir(p) ++ else ++ return true ++ end ++ end ++ ++ --- create a directory path. ++ -- This will create subdirectories as necessary! ++ -- @string p A directory path ++ -- @return true on success, nil + errormsg on failure ++ -- @raise failure to create ++ function dir.makepath (p) ++ assert_string(1,p) ++ if path.is_windows then ++ p = p:gsub("/", "\\") ++ end ++ return _makepath(path.abspath(p)) ++ end + end + +- + --- clone a directory tree. Will always try to create a new directory structure + -- if necessary. + -- @string path1 the base path of the source tree +@@ -379,7 +416,7 @@ function dir.clonetree (path1,path2,file_fun,verbose) + if verbose then verbose('normalized:',path1,path2) end + -- particularly NB that the new path isn't fully contained in the old path + if path1 == path2 then return raise "paths are the same" end +- local i1,i2 = path2:find(path1,1,true) ++ local _,i2 = path2:find(path1,1,true) + if i2 == #path1 and path2:sub(i2+1,i2+1) == path.sep then + return raise 'destination is a subdirectory of the source' + end +@@ -411,46 +448,65 @@ function dir.clonetree (path1,path2,file_fun,verbose) + return true,faildirs,failfiles + end + ++ ++-- each entry of the stack is an array with three items: ++-- 1. the name of the directory ++-- 2. the lfs iterator function ++-- 3. the lfs iterator userdata ++local function treeiter(iterstack) ++ local diriter = iterstack[#iterstack] ++ if not diriter then ++ return -- done ++ end ++ ++ local dirname = diriter[1] ++ local entry = diriter[2](diriter[3]) ++ if not entry then ++ table.remove(iterstack) ++ return treeiter(iterstack) -- tail-call to try next ++ end ++ ++ if entry ~= "." and entry ~= ".." then ++ entry = dirname .. sep .. entry ++ if exists(entry) then -- Just in case a symlink is broken. ++ local is_dir = isdir(entry) ++ if is_dir then ++ table.insert(iterstack, { entry, ldir(entry) }) ++ end ++ return entry, is_dir ++ end ++ end ++ ++ return treeiter(iterstack) -- tail-call to try next ++end ++ ++ + --- return an iterator over all entries in a directory tree + -- @string d a directory + -- @return an iterator giving pathname and mode (true for dir, false otherwise) + -- @raise d must be a non-empty string + function dir.dirtree( d ) + assert( d and d ~= "", "directory parameter is missing or empty" ) +- local exists, isdir = path.exists, path.isdir +- local sep = path.sep + + local last = sub ( d, -1 ) + if last == sep or last == '/' then + d = sub( d, 1, -2 ) + end + +- local function yieldtree( dir ) +- for entry in ldir( dir ) do +- if entry ~= "." and entry ~= ".." then +- entry = dir .. sep .. entry +- if exists(entry) then -- Just in case a symlink is broken. +- local is_dir = isdir(entry) +- yield( entry, is_dir ) +- if is_dir then +- yieldtree( entry ) +- end +- end +- end +- end +- end ++ local iterstack = { {d, ldir(d)} } + +- return wrap( function() yieldtree( d ) end ) ++ return treeiter, iterstack + end + + +---- Recursively returns all the file starting at _path_. It can optionally take a shell pattern and +--- only returns files that match _shell_pattern_. If a pattern is given it will do a case insensitive search. +--- @string start_path A directory. If not given, all files in current directory are returned. +--- @string shell_pattern A shell pattern. If not given, all files are returned. +--- @treturn List(string) containing all the files found recursively starting at _path_ and filtered by _shell_pattern_. ++--- Recursively returns all the file starting at 'path'. It can optionally take a shell pattern and ++-- only returns files that match 'shell_pattern'. If a pattern is given it will do a case insensitive search. ++-- @string[opt='.'] start_path A directory. ++-- @string[opt='*'] shell_pattern A shell pattern (see `fnmatch`). ++-- @treturn List(string) containing all the files found recursively starting at 'path' and filtered by 'shell_pattern'. + -- @raise start_path must be a directory + function dir.getallfiles( start_path, shell_pattern ) ++ start_path = start_path or '.' + assert_dir(1,start_path) + shell_pattern = shell_pattern or "*" + +diff --git a/extra/penlight/lua/pl/file.lua b/extra/penlight/lua/pl/file.lua +index 572d7fc..b8058c4 100644 +--- a/extra/penlight/lua/pl/file.lua ++++ b/extra/penlight/lua/pl/file.lua +@@ -1,5 +1,8 @@ + --- File manipulation functions: reading, writing, moving and copying. + -- ++-- This module wraps a number of functions from other modules into a ++-- file related module for convenience. ++-- + -- Dependencies: `pl.utils`, `pl.dir`, `pl.path` + -- @module pl.file + local os = os +@@ -7,56 +10,46 @@ local utils = require 'pl.utils' + local dir = require 'pl.dir' + local path = require 'pl.path' + +---[[ +-module ('pl.file',utils._module) +-]] + local file = {} + +---- return the contents of a file as a string ++--- return the contents of a file as a string. ++-- This function is a copy of `utils.readfile`. + -- @function file.read +--- @string filename The file path +--- @return file contents + file.read = utils.readfile + +---- write a string to a file ++--- write a string to a file. ++-- This function is a copy of `utils.writefile`. + -- @function file.write +--- @string filename The file path +--- @string str The string + file.write = utils.writefile + + --- copy a file. ++-- This function is a copy of `dir.copyfile`. + -- @function file.copy +--- @string src source file +--- @string dest destination file +--- @bool flag true if you want to force the copy (default) +--- @return true if operation succeeded + file.copy = dir.copyfile + + --- move a file. ++-- This function is a copy of `dir.movefile`. + -- @function file.move +--- @string src source file +--- @string dest destination file +--- @return true if operation succeeded, else false and the reason for the error. + file.move = dir.movefile + + --- Return the time of last access as the number of seconds since the epoch. ++-- This function is a copy of `path.getatime`. + -- @function file.access_time +--- @string path A file path + file.access_time = path.getatime + + ---Return when the file was created. ++-- This function is a copy of `path.getctime`. + -- @function file.creation_time +--- @string path A file path + file.creation_time = path.getctime + +---- Return the time of last modification ++--- Return the time of last modification. ++-- This function is a copy of `path.getmtime`. + -- @function file.modified_time +--- @string path A file path + file.modified_time = path.getmtime + +---- Delete a file ++--- Delete a file. ++-- This function is a copy of `os.remove`. + -- @function file.delete +--- @string path A file path + file.delete = os.remove + + return file +diff --git a/extra/penlight/lua/pl/func.lua b/extra/penlight/lua/pl/func.lua +index 28c01d8..4fdfb46 100644 +--- a/extra/penlight/lua/pl/func.lua ++++ b/extra/penlight/lua/pl/func.lua +@@ -21,7 +21,7 @@ local type,setmetatable,getmetatable,rawset = type,setmetatable,getmetatable,raw + local concat,append = table.concat,table.insert + local tostring = tostring + local utils = require 'pl.utils' +-local pairs,rawget,unpack = pairs,rawget,utils.unpack ++local pairs,rawget,unpack,pack = pairs,rawget,utils.unpack,utils.pack + local tablex = require 'pl.tablex' + local map = tablex.map + local _DEBUG = rawget(_G,'_DEBUG') +@@ -88,7 +88,7 @@ function _PEMT.__tostring (e) + end + + function _PEMT.__unm(arg) +- return P{op='-',arg} ++ return P{op='unm',arg} + end + + function func.Not (arg) +@@ -159,16 +159,22 @@ function func.Args (...) + return P{op='()',_arg,...} + end + +--- binary and unary operators, with their precedences (see 2.5.6) +-local operators = { ++-- binary operators with their precedences (see Lua manual) ++-- precedences might be incremented by one before use depending on ++-- left- or right-associativity, space them out ++local binary_operators = { + ['or'] = 0, +- ['and'] = 1, +- ['=='] = 2, ['~='] = 2, ['<'] = 2, ['>'] = 2, ['<='] = 2, ['>='] = 2, +- ['..'] = 3, +- ['+'] = 4, ['-'] = 4, +- ['*'] = 5, ['/'] = 5, ['%'] = 5, +- ['not'] = 6, ['#'] = 6, ['-'] = 6, +- ['^'] = 7 ++ ['and'] = 2, ++ ['=='] = 4, ['~='] = 4, ['<'] = 4, ['>'] = 4, ['<='] = 4, ['>='] = 4, ++ ['..'] = 6, ++ ['+'] = 8, ['-'] = 8, ++ ['*'] = 10, ['/'] = 10, ['%'] = 10, ++ ['^'] = 14 ++} ++ ++-- unary operators with their precedences ++local unary_operators = { ++ ['not'] = 12, ['#'] = 12, ['unm'] = 12 + } + + -- comparisons (as prefix functions) +@@ -196,19 +202,31 @@ end + function repr (e,lastpred) + local tail = func.tail + if isPE(e) then +- local pred = operators[e.op] +- local ls = map(repr,e,pred) +- if pred then --unary or binary operator +- if #ls ~= 1 then +- local s = concat(ls,' '..e.op..' ') +- if lastpred and lastpred > pred then +- s = '('..s..')' ++ local pred = binary_operators[e.op] or unary_operators[e.op] ++ if pred then ++ -- binary or unary operator ++ local s ++ if binary_operators[e.op] then ++ local left_pred = pred ++ local right_pred = pred ++ if e.op == '..' or e.op == '^' then ++ left_pred = left_pred + 1 ++ else ++ right_pred = right_pred + 1 + end +- return s ++ local left_arg = repr(e[1], left_pred) ++ local right_arg = repr(e[2], right_pred) ++ s = left_arg..' '..e.op..' '..right_arg + else +- return e.op..' '..ls[1] ++ local op = e.op == 'unm' and '-' or e.op ++ s = op..' '..repr(e[1], pred) ++ end ++ if lastpred and lastpred > pred then ++ s = '('..s..')' + end ++ return s + else -- either postfix, or a placeholder ++ local ls = map(repr,e) + if e.op == '[]' then + return ls[1]..'['..ls[2]..']' + elseif e.op == '()' then +@@ -234,7 +252,7 @@ function repr (e,lastpred) + end + func.repr = repr + +--- collect all the non-PE values in this PE into vlist, and replace each occurence ++-- collect all the non-PE values in this PE into vlist, and replace each occurrence + -- with a constant PH (_C1, etc). Return the maximum placeholder index found. + local collect_values + function collect_values (e,vlist) +@@ -318,13 +336,20 @@ utils.add_function_factory(_PEMT,func.I) + func.bind1 = utils.bind1 + func.curry = func.bind1 + +---- create a function which chains two functions. ++--- create a function which chains multiple functions. + -- @func f a function of at least one argument + -- @func g a function of at least one argument ++-- @param ... additional functions to compose + -- @return a function +--- @usage printf = compose(io.write,string.format) +-function func.compose (f,g) +- return function(...) return f(g(...)) end ++-- @usage printf = compose(io.write, string.format) ++-- @usage printf = compose(io.write, string.lower, string.format) ++function func.compose (...) ++ local args = pack(...) ++ return tablex.reduce(function(f, g) ++ return function(...) ++ return f(g(...)) ++ end ++ end, args) + end + + --- bind the arguments of a function to given values. +@@ -335,7 +360,7 @@ end + -- @usage (bind(f,_1,a))(b) == f(a,b) + -- @usage (bind(f,_2,_1))(a,b) == f(b,a) + function func.bind(fn,...) +- local args = table.pack(...) ++ local args = pack(...) + local holders,parms,bvalues,values = {},{},{'fn'},{} + local nv,maxplace,varargs = 1,0,false + for i = 1,args.n do +@@ -365,7 +390,7 @@ return function (%s) + end + ]]):format(bvalues,parms,holders) + if _DEBUG then print(fstr) end +- local res,err = utils.load(fstr) ++ local res = utils.load(fstr) + res = res() + return res(fn,unpack(values)) + end +diff --git a/extra/penlight/lua/pl/import_into.lua b/extra/penlight/lua/pl/import_into.lua +index 7ef54f8..214b0f8 100644 +--- a/extra/penlight/lua/pl/import_into.lua ++++ b/extra/penlight/lua/pl/import_into.lua +@@ -1,6 +1,6 @@ + -------------- + -- PL loader, for loading all PL libraries, only on demand. +--- Whenever a module is implicitly accesssed, the table will have the module automatically injected. ++-- Whenever a module is implicitly accessed, the table will have the module automatically injected. + -- (e.g. `_ENV.tablex`) + -- then that module is dynamically loaded. The submodules are all brought into + -- the table that is provided as the argument, or returned in a new table. +@@ -17,64 +17,66 @@ return function(env) + mod = {} + env = {} + end +- local env = env or {} ++ local env = env or {} + +- local modules = { +- utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true, +- input=true,seq=true,lexer=true,stringx=true, +- config=true,pretty=true,data=true,func=true,text=true, +- operator=true,lapp=true,array2d=true, +- comprehension=true,xml=true,types=true, +- test = true, app = true, file = true, class = true, List = true, +- Map = true, Set = true, OrderedMap = true, MultiMap = true, +- Date = true, +- -- classes -- +- } +- rawset(env,'utils',require 'pl.utils') ++ local modules = { ++ utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true, ++ input=true,seq=true,lexer=true,stringx=true, ++ config=true,pretty=true,data=true,func=true,text=true, ++ operator=true,lapp=true,array2d=true, ++ comprehension=true,xml=true,types=true, ++ test = true, app = true, file = true, class = true, ++ luabalanced = true, permute = true, template = true, ++ url = true, compat = true, ++ -- classes -- ++ List = true, Map = true, Set = true, ++ OrderedMap = true, MultiMap = true, Date = true, ++ } ++ rawset(env,'utils',require 'pl.utils') + +- for name,klass in pairs(env.utils.stdmt) do +- klass.__index = function(t,key) +- return require ('pl.'..name)[key] +- end; +- end ++ for name,klass in pairs(env.utils.stdmt) do ++ klass.__index = function(t,key) ++ return require ('pl.'..name)[key] ++ end; ++ end + +- -- ensure that we play nice with libraries that also attach a metatable +- -- to the global table; always forward to a custom __index if we don't +- -- match ++ -- ensure that we play nice with libraries that also attach a metatable ++ -- to the global table; always forward to a custom __index if we don't ++ -- match + +- local _hook,_prev_index +- local gmt = {} +- local prevenvmt = getmetatable(env) +- if prevenvmt then +- _prev_index = prevenvmt.__index +- if prevenvmt.__newindex then +- gmt.__index = prevenvmt.__newindex +- end +- end ++ local _hook,_prev_index ++ local gmt = {} ++ local prevenvmt = getmetatable(env) ++ if prevenvmt then ++ _prev_index = prevenvmt.__index ++ if prevenvmt.__newindex then ++ gmt.__newindex = prevenvmt.__newindex ++ end ++ end + +- function gmt.hook(handler) +- _hook = handler +- end ++ function gmt.hook(handler) ++ _hook = handler ++ end + +- function gmt.__index(t,name) +- local found = modules[name] +- -- either true, or the name of the module containing this class. +- -- either way, we load the required module and make it globally available. +- if found then +- -- e..g pretty.dump causes pl.pretty to become available as 'pretty' +- rawset(env,name,require('pl.'..name)) +- return env[name] +- else +- local res +- if _hook then +- res = _hook(t,name) +- if res then return res end +- end +- if _prev_index then +- return _prev_index(t,name) +- end +- end +- end ++ function gmt.__index(t,name) ++ local found = modules[name] ++ -- either true, or the name of the module containing this class. ++ -- either way, we load the required module and make it globally available. ++ if found then ++ -- e..g pretty.dump causes pl.pretty to become available as 'pretty' ++ rawset(env,name,require('pl.'..name)) ++ return env[name] ++ else ++ local res ++ if _hook then ++ res = _hook(t,name) ++ if res then return res end ++ end ++ if _prev_index then ++ return _prev_index(t,name) ++ end ++ end ++ end + + if mod then + function gmt.__newindex(t,name,value) +@@ -83,7 +85,7 @@ return function(env) + end + end + +- setmetatable(env,gmt) ++ setmetatable(env,gmt) + +- return env,mod or env ++ return env,mod or env + end +diff --git a/extra/penlight/lua/pl/init.lua b/extra/penlight/lua/pl/init.lua +index c27a890..2c97b70 100644 +--- a/extra/penlight/lua/pl/init.lua ++++ b/extra/penlight/lua/pl/init.lua +@@ -1,6 +1,6 @@ + -------------- + -- Entry point for loading all PL libraries only on demand, into the global space. +--- Requiring 'pl' means that whenever a module is implicitly accesssed ++-- Requiring 'pl' means that whenever a module is implicitly accessed + -- (e.g. `utils.split`) + -- then that module is dynamically loaded. The submodules are all brought into + -- the global space. +diff --git a/extra/penlight/lua/pl/lapp.lua b/extra/penlight/lua/pl/lapp.lua +index 5ef42e9..3bd752d 100644 +--- a/extra/penlight/lua/pl/lapp.lua ++++ b/extra/penlight/lua/pl/lapp.lua +@@ -33,11 +33,10 @@ local function lines(s) return s:gmatch('([^\n]*)\n') end + local function lstrip(str) return str:gsub('^%s+','') end + local function strip(str) return lstrip(str):gsub('%s+$','') end + local function at(s,k) return s:sub(k,k) end +-local function isdigit(s) return s:find('^%d+$') == 1 end + + local lapp = {} + +-local open_files,parms,aliases,parmlist,usage,windows,script ++local open_files,parms,aliases,parmlist,usage,script + + lapp.callback = false -- keep Strict happy + +@@ -182,7 +181,6 @@ end + -- @return a table with parameter-value pairs + function lapp.process_options_string(str,args) + local results = {} +- local opts = {at_start=true} + local varargs + local arg = args or _G.arg + open_files = {} +@@ -219,7 +217,7 @@ function lapp.process_options_string(str,args) + + for line in lines(str) do + local res = {} +- local optspec,optparm,i1,i2,defval,vtype,constraint,rest ++ local optparm,defval,vtype,constraint,rest + line = lstrip(line) + local function check(str) + return match(str,line,res) +@@ -239,7 +237,8 @@ function lapp.process_options_string(str,args) + elseif check '$<{name} $' then -- is it ? + -- so becomes input_file ... + optparm,rest = res.name:match '([^%.]+)(.*)' +- optparm = optparm:gsub('%A','_') ++ -- follow lua legal variable names ++ optparm = optparm:sub(1,1):gsub('%A','_') .. optparm:sub(2):gsub('%W', '_') + varargs = rest == '...' + append(parmlist,optparm) + end +@@ -248,6 +247,7 @@ function lapp.process_options_string(str,args) + line = res.rest + res = {} + local optional ++ local defval_str + -- do we have ([optional] [] [default ])? + if match('$({def} $',line,res) or match('$({def}',line,res) then + local typespec = strip(res.def) +@@ -279,7 +279,7 @@ function lapp.process_options_string(str,args) + local enump = '|' .. enums .. '|' + vtype = 'string' + constraint = function(s) +- lapp.assert(enump:match('|'..s..'|'), ++ lapp.assert(enump:find('|'..s..'|', 1, true), + "value '"..s.."' not in "..enums + ) + end +@@ -290,6 +290,7 @@ function lapp.process_options_string(str,args) + -- optional 'default value' clause. Type is inferred as + -- 'string' or 'number' if there's no explicit type + if default or match('default $r{rest}',typespec,res) then ++ defval_str = res.rest + defval,vtype = process_default(res.rest,vtype) + end + else -- must be a plain flag, no extra parameter required +@@ -299,6 +300,7 @@ function lapp.process_options_string(str,args) + local ps = { + type = vtype, + defval = defval, ++ defval_str = defval_str, + required = defval == nil and not optional, + comment = res.rest or optparm, + constraint = constraint, +@@ -314,7 +316,7 @@ function lapp.process_options_string(str,args) + end + ps.constraint = types[vtype].constraint + elseif not builtin_types[vtype] and vtype then +- lapp.error(vtype.." is unknown type") ++ lapp.error(vtype.." is unknown type") + end + parms[optparm] = ps + end +@@ -428,6 +430,9 @@ function lapp.process_options_string(str,args) + if not ps.used then + if ps.required then lapp.error("missing required parameter: "..parm) end + set_result(ps,parm,ps.defval) ++ if builtin_types[ps.type] == "file" then ++ set_result(ps, parm .. "_name", ps.defval_str) ++ end + end + end + return results +diff --git a/extra/penlight/lua/pl/lexer.lua b/extra/penlight/lua/pl/lexer.lua +index 071a62b..9219716 100644 +--- a/extra/penlight/lua/pl/lexer.lua ++++ b/extra/penlight/lua/pl/lexer.lua +@@ -20,11 +20,11 @@ + -- See the Guide for further @{06-data.md.Lexical_Scanning|discussion} + -- @module pl.lexer + +-local yield,wrap = coroutine.yield,coroutine.wrap + local strfind = string.find + local strsub = string.sub + local append = table.insert + ++ + local function assert_arg(idx,val,tp) + if type(val) ~= tp then + error("argument "..idx.." must be "..tp, 2) +@@ -33,11 +33,15 @@ end + + local lexer = {} + +-local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' +-local NUMBER2 = '^[%+%-]?%d+%.?%d*' +-local NUMBER3 = '^0x[%da-fA-F]+' +-local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' +-local NUMBER5 = '^%d+%.?%d*' ++local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' ++local NUMBER1a = '^[%+%-]?%d*%.%d+[eE][%+%-]?%d+' ++local NUMBER2 = '^[%+%-]?%d+%.?%d*' ++local NUMBER2a = '^[%+%-]?%d*%.%d+' ++local NUMBER3 = '^0x[%da-fA-F]+' ++local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' ++local NUMBER4a = '^%d*%.%d+[eE][%+%-]?%d+' ++local NUMBER5 = '^%d+%.?%d*' ++local NUMBER5a = '^%d*%.%d+' + local IDEN = '^[%a_][%w_]*' + local WSPACE = '^%s+' + local STRING1 = "^(['\"])%1" -- empty string +@@ -51,14 +55,14 @@ local PREPRO = '^#.-[^\\]\n' + local plain_matches,lua_matches,cpp_matches,lua_keyword,cpp_keyword + + local function tdump(tok) +- return yield(tok,tok) ++ return tok,tok + end + + local function ndump(tok,options) + if options and options.number then + tok = tonumber(tok) + end +- return yield("number",tok) ++ return "number",tok + end + + -- regular strings, single or double quotes; usually we want them +@@ -67,7 +71,7 @@ local function sdump(tok,options) + if options and options.string then + tok = tok:sub(2,-2) + end +- return yield("string",tok) ++ return "string",tok + end + + -- long Lua strings need extra work to get rid of the quotes +@@ -82,45 +86,45 @@ local function sdump_l(tok,options,findres) + tok = tok:sub(2) + end + end +- return yield("string",tok) ++ return "string",tok + end + + local function chdump(tok,options) + if options and options.string then + tok = tok:sub(2,-2) + end +- return yield("char",tok) ++ return "char",tok + end + + local function cdump(tok) +- return yield('comment',tok) ++ return "comment",tok + end + + local function wsdump (tok) +- return yield("space",tok) ++ return "space",tok + end + + local function pdump (tok) +- return yield('prepro',tok) ++ return "prepro",tok + end + + local function plain_vdump(tok) +- return yield("iden",tok) ++ return "iden",tok + end + + local function lua_vdump(tok) + if lua_keyword[tok] then +- return yield("keyword",tok) ++ return "keyword",tok + else +- return yield("iden",tok) ++ return "iden",tok + end + end + + local function cpp_vdump(tok) + if cpp_keyword[tok] then +- return yield("keyword",tok) ++ return "keyword",tok + else +- return yield("iden",tok) ++ return "iden",tok + end + end + +@@ -149,7 +153,9 @@ function lexer.scan(s,matches,filter,options) + {NUMBER3,ndump}, + {IDEN,plain_vdump}, + {NUMBER1,ndump}, ++ {NUMBER1a,ndump}, + {NUMBER2,ndump}, ++ {NUMBER2a,ndump}, + {STRING1,sdump}, + {STRING2,sdump}, + {STRING3,sdump}, +@@ -158,45 +164,60 @@ function lexer.scan(s,matches,filter,options) + end + matches = plain_matches + end +- local function lex(first_arg) +- local line_nr = 0 +- local next_line = file and file:read() +- local sz = file and 0 or #s +- local idx = 1 +- +- -- res is the value used to resume the coroutine. +- local function handle_requests(res) +- while res do +- local tp = type(res) +- -- insert a token list +- if tp == 'table' then +- res = yield('','') +- for _,t in ipairs(res) do +- res = yield(t[1],t[2]) +- end +- elseif tp == 'string' then -- or search up to some special pattern +- local i1,i2 = strfind(s,res,idx) +- if i1 then +- local tok = strsub(s,i1,i2) +- idx = i2 + 1 +- res = yield('',tok) +- else +- res = yield('','') +- idx = sz + 1 +- end +- else +- res = yield(line_nr,idx) +- end ++ ++ local line_nr = 0 ++ local next_line = file and file:read() ++ local sz = file and 0 or #s ++ local idx = 1 ++ ++ local tlist_i ++ local tlist ++ ++ local first_hit = true ++ ++ local function iter(res) ++ local tp = type(res) ++ ++ if tlist then -- returning the inserted token list ++ local cur = tlist[tlist_i] ++ if cur then ++ tlist_i = tlist_i + 1 ++ return cur[1], cur[2] ++ else ++ tlist = nil + end + end + +- handle_requests(first_arg) +- if not file then line_nr = 1 end ++ if tp == 'string' then -- search up to some special pattern ++ local i1,i2 = strfind(s,res,idx) ++ if i1 then ++ local tok = strsub(s,i1,i2) ++ idx = i2 + 1 ++ return '', tok ++ else ++ idx = sz + 1 ++ return '', '' ++ end ++ ++ elseif tp == 'table' then -- insert a token list ++ tlist_i = 1 ++ tlist = res ++ return '', '' ++ ++ elseif tp ~= 'nil' then -- return position ++ return line_nr, idx ++ ++ else -- look for next token ++ if first_hit then ++ if not file then line_nr = 1 end ++ first_hit = false ++ end + +- while true do + if idx > sz then + if file then +- if not next_line then return end ++ if not next_line then ++ return -- past the end of file, done ++ end + s = next_line + line_nr = line_nr + 1 + next_line = file:read() +@@ -205,9 +226,7 @@ function lexer.scan(s,matches,filter,options) + end + idx, sz = 1, #s + else +- while true do +- handle_requests(yield()) +- end ++ return -- past the end of input, done + end + end + +@@ -219,23 +238,27 @@ function lexer.scan(s,matches,filter,options) + if i1 then + local tok = strsub(s,i1,i2) + idx = i2 + 1 +- local res ++ local ret1, ret2 + if not (filter and filter[fun]) then + lexer.finished = idx > sz +- res = fun(tok, options, findres) ++ ret1, ret2 = fun(tok, options, findres) + end + if not file and tok:find("\n") then + -- Update line number. + local _, newlines = tok:gsub("\n", {}) + line_nr = line_nr + newlines + end +- handle_requests(res) +- break ++ if ret1 then ++ return ret1, ret2 -- found a match ++ else ++ return iter() -- tail-call to try again ++ end + end + end + end + end +- return wrap(lex) ++ ++ return iter + end + + local function isstring (s) +@@ -267,7 +290,7 @@ end + -- @param tok a token stream + -- @return a string + function lexer.getline (tok) +- local t,v = tok('.-\n') ++ local _,v = tok('.-\n') + return v + end + +@@ -284,7 +307,7 @@ end + -- @param tok a token stream + -- @return a string + function lexer.getrest (tok) +- local t,v = tok('.+') ++ local _,v = tok('.+') + return v + end + +@@ -321,7 +344,9 @@ function lexer.lua(s,filter,options) + {NUMBER3,ndump}, + {IDEN,lua_vdump}, + {NUMBER4,ndump}, ++ {NUMBER4a,ndump}, + {NUMBER5,ndump}, ++ {NUMBER5a,ndump}, + {STRING1,sdump}, + {STRING2,sdump}, + {STRING3,sdump}, +@@ -371,7 +396,9 @@ function lexer.cpp(s,filter,options) + {NUMBER3,ndump}, + {IDEN,cpp_vdump}, + {NUMBER4,ndump}, ++ {NUMBER4a,ndump}, + {NUMBER5,ndump}, ++ {NUMBER5a,ndump}, + {CHAR1,chdump}, + {CHAR2,chdump}, + {CHAR3,chdump}, +diff --git a/extra/penlight/lua/pl/luabalanced.lua b/extra/penlight/lua/pl/luabalanced.lua +index a75f6fd..a1f7dc6 100644 +--- a/extra/penlight/lua/pl/luabalanced.lua ++++ b/extra/penlight/lua/pl/luabalanced.lua +@@ -130,6 +130,7 @@ local wordop = {['and']=true, ['or']=true, ['not']=true} + local is_compare = {['>']=true, ['<']=true, ['~']=true} + local function match_expression(s, pos) + pos = pos or 1 ++ local _ + local posa = pos + local lastident + local poscs, posce +@@ -149,7 +150,7 @@ local function match_expression(s, pos) + posce = pos + end + elseif c == '(' or c == '{' or c == '[' then +- local part; part, pos = match_bracketed(s, pos) ++ _, pos = match_bracketed(s, pos) + elseif c == '=' and s:sub(pos+1,pos+1) == '=' then + pos = pos + 2 -- skip over two-char op containing '=' + elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then +diff --git a/extra/penlight/lua/pl/path.lua b/extra/penlight/lua/pl/path.lua +index 7c2fe31..011b979 100644 +--- a/extra/penlight/lua/pl/path.lua ++++ b/extra/penlight/lua/pl/path.lua +@@ -2,6 +2,12 @@ + -- + -- This is modelled after Python's os.path library (10.1); see @{04-paths.md|the Guide}. + -- ++-- NOTE: the functions assume the paths being dealt with to originate ++-- from the OS the application is running on. Windows drive letters are not ++-- to be used when running on a Unix system for example. The one exception ++-- is Windows paths to allow both forward and backward slashes (since Lua ++-- also accepts those) ++-- + -- Dependencies: `pl.utils`, `lfs` + -- @module pl.path + +@@ -10,66 +16,125 @@ local _G = _G + local sub = string.sub + local getenv = os.getenv + local tmpnam = os.tmpname +-local attributes, currentdir, link_attrib + local package = package + local append, concat, remove = table.insert, table.concat, table.remove + local utils = require 'pl.utils' + local assert_string,raise = utils.assert_string,utils.raise + +-local attrib +-local path = {} +- + local res,lfs = _G.pcall(_G.require,'lfs') +-if res then +- attributes = lfs.attributes +- currentdir = lfs.currentdir +- link_attrib = lfs.symlinkattributes +-else ++if not res then + error("pl.path requires LuaFileSystem") + end + +-attrib = attributes +-path.attrib = attrib +-path.link_attrib = link_attrib ++local attrib = lfs.attributes ++local currentdir = lfs.currentdir ++local link_attrib = lfs.symlinkattributes ++ ++local path = {} ++ ++local function err_func(name, param, err, code) ++ local ret = ("%s failed"):format(tostring(name)) ++ if param ~= nil then ++ ret = ret .. (" for '%s'"):format(tostring(param)) ++ end ++ ret = ret .. (": %s"):format(tostring(err)) ++ if code ~= nil then ++ ret = ret .. (" (code %s)"):format(tostring(code)) ++ end ++ return ret ++end + + --- Lua iterator over the entries of a given directory. +--- Behaves like `lfs.dir` ++-- Implicit link to [`luafilesystem.dir`](https://lunarmodules.github.io/luafilesystem/manual.html#dir) ++-- @function dir + path.dir = lfs.dir + + --- Creates a directory. +-path.mkdir = lfs.mkdir ++-- Implicit link to [`luafilesystem.mkdir`](https://lunarmodules.github.io/luafilesystem/manual.html#mkdir) ++-- @function mkdir ++path.mkdir = function(d) ++ local ok, err, code = lfs.mkdir(d) ++ if not ok then ++ return ok, err_func("mkdir", d, err, code), code ++ end ++ return ok, err, code ++end + + --- Removes a directory. +-path.rmdir = lfs.rmdir ++-- Implicit link to [`luafilesystem.rmdir`](https://lunarmodules.github.io/luafilesystem/manual.html#rmdir) ++-- @function rmdir ++path.rmdir = function(d) ++ local ok, err, code = lfs.rmdir(d) ++ if not ok then ++ return ok, err_func("rmdir", d, err, code), code ++ end ++ return ok, err, code ++end + +----- Get the working directory. +-path.currentdir = currentdir ++--- Gets attributes. ++-- Implicit link to [`luafilesystem.attributes`](https://lunarmodules.github.io/luafilesystem/manual.html#attributes) ++-- @function attrib ++path.attrib = function(d, r) ++ local ok, err, code = attrib(d, r) ++ if not ok then ++ return ok, err_func("attrib", d, err, code), code ++ end ++ return ok, err, code ++end + +---- Changes the working directory. +-path.chdir = lfs.chdir ++--- Get the working directory. ++-- Implicit link to [`luafilesystem.currentdir`](https://lunarmodules.github.io/luafilesystem/manual.html#currentdir) ++-- @function currentdir ++path.currentdir = function() ++ local ok, err, code = currentdir() ++ if not ok then ++ return ok, err_func("currentdir", nil, err, code), code ++ end ++ return ok, err, code ++end + ++--- Gets symlink attributes. ++-- Implicit link to [`luafilesystem.symlinkattributes`](https://lunarmodules.github.io/luafilesystem/manual.html#symlinkattributes) ++-- @function link_attrib ++path.link_attrib = function(d, r) ++ local ok, err, code = link_attrib(d, r) ++ if not ok then ++ return ok, err_func("link_attrib", d, err, code), code ++ end ++ return ok, err, code ++end ++ ++--- Changes the working directory. ++-- On Windows, if a drive is specified, it also changes the current drive. If ++-- only specifying the drive, it will only switch drive, but not modify the path. ++-- Implicit link to [`luafilesystem.chdir`](https://lunarmodules.github.io/luafilesystem/manual.html#chdir) ++-- @function chdir ++path.chdir = function(d) ++ local ok, err, code = lfs.chdir(d) ++ if not ok then ++ return ok, err_func("chdir", d, err, code), code ++ end ++ return ok, err, code ++end + + --- is this a directory? + -- @string P A file path + function path.isdir(P) +- assert_string(1,P) +- if P:match("\\$") then +- P = P:sub(1,-2) +- end ++ assert_string(1,P) + return attrib(P,'mode') == 'directory' + end + +---- is this a file?. ++--- is this a file? + -- @string P A file path + function path.isfile(P) +- assert_string(1,P) ++ assert_string(1,P) + return attrib(P,'mode') == 'file' + end + + -- is this a symbolic link? + -- @string P A file path + function path.islink(P) +- assert_string(1,P) ++ assert_string(1,P) + if link_attrib then + return link_attrib(P,'mode')=='link' + else +@@ -80,35 +145,36 @@ end + --- return size of a file. + -- @string P A file path + function path.getsize(P) +- assert_string(1,P) ++ assert_string(1,P) + return attrib(P,'size') + end + +---- does a path exist?. ++--- does a path exist? + -- @string P A file path +--- @return the file path if it exists, nil otherwise ++-- @return the file path if it exists (either as file, directory, socket, etc), false otherwise + function path.exists(P) +- assert_string(1,P) ++ assert_string(1,P) + return attrib(P,'mode') ~= nil and P + end + + --- Return the time of last access as the number of seconds since the epoch. + -- @string P A file path + function path.getatime(P) +- assert_string(1,P) ++ assert_string(1,P) + return attrib(P,'access') + end + +---- Return the time of last modification ++--- Return the time of last modification as the number of seconds since the epoch. + -- @string P A file path + function path.getmtime(P) ++ assert_string(1,P) + return attrib(P,'modification') + end + +----Return the system's ctime. ++---Return the system's ctime as the number of seconds since the epoch. + -- @string P A file path + function path.getctime(P) +- assert_string(1,P) ++ assert_string(1,P) + return path.attrib(P,'change') + end + +@@ -119,16 +185,19 @@ end + + path.is_windows = utils.is_windows + +-local other_sep +--- !constant sep is the directory separator for this platform. ++local sep, other_sep, seps ++-- constant sep is the directory separator for this platform. ++-- constant dirsep is the separator in the PATH environment variable + if path.is_windows then + path.sep = '\\'; other_sep = '/' + path.dirsep = ';' ++ seps = { ['/'] = true, ['\\'] = true } + else + path.sep = '/' + path.dirsep = ':' ++ seps = { ['/'] = true } + end +-local sep,dirsep = path.sep,path.dirsep ++sep = path.sep + + --- are we running Windows? + -- @class field +@@ -145,6 +214,20 @@ local sep,dirsep = path.sep,path.dirsep + --- given a path, return the directory part and a file part. + -- if there's no directory part, the first value will be empty + -- @string P A file path ++-- @return directory part ++-- @return file part ++-- @usage ++-- local dir, file = path.splitpath("some/dir/myfile.txt") ++-- assert(dir == "some/dir") ++-- assert(file == "myfile.txt") ++-- ++-- local dir, file = path.splitpath("some/dir/") ++-- assert(dir == "some/dir") ++-- assert(file == "") ++-- ++-- local dir, file = path.splitpath("some_dir") ++-- assert(dir == "") ++-- assert(file == "some_dir") + function path.splitpath(P) + assert_string(1,P) + local i = #P +@@ -165,9 +248,9 @@ end + -- @string[opt] pwd optional start path to use (default is current dir) + function path.abspath(P,pwd) + assert_string(1,P) +- if pwd then assert_string(2,pwd) end + local use_pwd = pwd ~= nil +- if not use_pwd and not currentdir then return P end ++ if use_pwd then assert_string(2,pwd) end ++ if not use_pwd and not currentdir() then return P end + P = P:gsub('[\\/]$','') + pwd = pwd or currentdir() + if not path.isabs(P) then +@@ -181,14 +264,22 @@ end + --- given a path, return the root part and the extension part. + -- if there's no extension part, the second value will be empty + -- @string P A file path +--- @treturn string root part +--- @treturn string extension part (maybe empty) ++-- @treturn string root part (everything upto the "."", maybe empty) ++-- @treturn string extension part (including the ".", maybe empty) ++-- @usage ++-- local file_path, ext = path.splitext("/bonzo/dog_stuff/cat.txt") ++-- assert(file_path == "/bonzo/dog_stuff/cat") ++-- assert(ext == ".txt") ++-- ++-- local file_path, ext = path.splitext("") ++-- assert(file_path == "") ++-- assert(ext == "") + function path.splitext(P) + assert_string(1,P) + local i = #P + local ch = at(P,i) + while i > 0 and ch ~= '.' do +- if ch == sep or ch == other_sep then ++ if seps[ch] then + return P,'' + end + i = i - 1 +@@ -203,37 +294,59 @@ end + + --- return the directory part of a path + -- @string P A file path ++-- @treturn string everything before the last dir-separator ++-- @see splitpath ++-- @usage ++-- path.dirname("/some/path/file.txt") -- "/some/path" ++-- path.dirname("file.txt") -- "" (empty string) + function path.dirname(P) + assert_string(1,P) +- local p1,p2 = path.splitpath(P) ++ local p1 = path.splitpath(P) + return p1 + end + + --- return the file part of a path + -- @string P A file path ++-- @treturn string ++-- @see splitpath ++-- @usage ++-- path.basename("/some/path/file.txt") -- "file.txt" ++-- path.basename("/some/path/file/") -- "" (empty string) + function path.basename(P) + assert_string(1,P) +- local p1,p2 = path.splitpath(P) ++ local _,p2 = path.splitpath(P) + return p2 + end + + --- get the extension part of a path. + -- @string P A file path ++-- @treturn string ++-- @see splitext ++-- @usage ++-- path.extension("/some/path/file.txt") -- ".txt" ++-- path.extension("/some/path/file_txt") -- "" (empty string) + function path.extension(P) + assert_string(1,P) +- local p1,p2 = path.splitext(P) ++ local _,p2 = path.splitext(P) + return p2 + end + +---- is this an absolute path?. ++--- is this an absolute path? + -- @string P A file path ++-- @usage ++-- path.isabs("hello/path") -- false ++-- path.isabs("/hello/path") -- true ++-- -- Windows; ++-- path.isabs("hello\path") -- false ++-- path.isabs("\hello\path") -- true ++-- path.isabs("C:\hello\path") -- true ++-- path.isabs("C:hello\path") -- false + function path.isabs(P) + assert_string(1,P) +- if path.is_windows then +- return at(P,1) == '/' or at(P,1)=='\\' or at(P,2)==':' +- else +- return at(P,1) == '/' ++ if path.is_windows and at(P,2) == ":" then ++ return seps[at(P,3)] ~= nil + end ++ return seps[at(P,1)] ~= nil + end + + --- return the path resulting from combining the individual paths. +@@ -242,6 +355,11 @@ end + -- @string p1 A file path + -- @string p2 A file path + -- @string ... more file paths ++-- @treturn string the combined path ++-- @usage ++-- path.join("/first","second","third") -- "/first/second/third" ++-- path.join("first","second/third") -- "first/second/third" ++-- path.join("/first","/second","third") -- "/second/third" + function path.join(p1,p2,...) + assert_string(1,p1) + assert_string(2,p2) +@@ -262,21 +380,28 @@ function path.join(p1,p2,...) + return p1..p2 + end + +---- normalize the case of a pathname. On Unix, this returns the path unchanged; +--- for Windows, it converts the path to lowercase, and it also converts forward slashes +--- to backward slashes. ++--- normalize the case of a pathname. On Unix, this returns the path unchanged, ++-- for Windows it converts; ++-- ++-- * the path to lowercase ++-- * forward slashes to backward slashes + -- @string P A file path ++-- @usage path.normcase("/Some/Path/File.txt") ++-- -- Windows: "\some\path\file.txt" ++-- -- Others : "/Some/Path/File.txt" + function path.normcase(P) + assert_string(1,P) + if path.is_windows then +- return (P:lower():gsub('/','\\')) ++ return P:gsub('/','\\'):lower() + else + return P + end + end + + --- normalize a path name. +--- A//B, A/./B and A/foo/../B all become A/B. ++-- `A//B`, `A/./B`, and `A/foo/../B` all become `A/B`. ++-- ++-- An empty path results in '.'. + -- @string P a file path + function path.normpath(P) + assert_string(1,P) +@@ -286,13 +411,13 @@ function path.normpath(P) + if P:match '^\\\\' then -- UNC + anchor = '\\\\' + P = P:sub(3) +- elseif at(P, 1) == '/' or at(P, 1) == '\\' then ++ elseif seps[at(P, 1)] then + anchor = '\\' + P = P:sub(2) + elseif at(P, 2) == ':' then + anchor = P:sub(1, 2) + P = P:sub(3) +- if at(P, 1) == '/' or at(P, 1) == '\\' then ++ if seps[at(P, 1)] then + anchor = anchor..'\\' + P = P:sub(2) + end +@@ -326,23 +451,23 @@ function path.normpath(P) + return P + end + +-local function ATS (P) +- if at(P,#P) ~= path.sep then +- P = P..path.sep +- end +- return path.normcase(P) +-end +- + --- relative path from current directory or optional start point + -- @string P a path + -- @string[opt] start optional start point (default current directory) + function path.relpath (P,start) + assert_string(1,P) +- if start then assert_string(2,start) end +- local split,normcase,min,append = utils.split, path.normcase, math.min, table.insert +- P = normcase(path.abspath(P,start)) ++ if start then assert_string(2,start) end ++ local split,min,append = utils.split, math.min, table.insert ++ P = path.abspath(P,start) + start = start or currentdir() +- start = normcase(start) ++ local compare ++ if path.is_windows then ++ P = P:gsub("/","\\") ++ start = start:gsub("/","\\") ++ compare = function(v) return v:lower() end ++ else ++ compare = function(v) return v end ++ end + local startl, Pl = split(start,sep), split(P,sep) + local n = min(#startl,#Pl) + if path.is_windows and n > 0 and at(Pl[1],2) == ':' and Pl[1] ~= startl[1] then +@@ -350,7 +475,7 @@ function path.relpath (P,start) + end + local k = n+1 -- default value if this loop doesn't bail out! + for i = 1,n do +- if startl[i] ~= Pl[i] then ++ if compare(startl[i]) ~= compare(Pl[i]) then + k = i + break + end +@@ -368,22 +493,40 @@ end + -- In windows, if HOME isn't set, then USERPROFILE is used in preference to + -- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. + -- @string P A file path ++-- @treturn[1] string The file path with the `~` prefix substituted, or the input path if it had no prefix. ++-- @treturn[2] nil ++-- @treturn[2] string Error message if the environment variables were unavailable. + function path.expanduser(P) + assert_string(1,P) +- if at(P,1) == '~' then +- local home = getenv('HOME') +- if not home then -- has to be Windows +- home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH') +- end +- return home..sub(P,2) +- else ++ if P:sub(1,1) ~= '~' then + return P + end ++ ++ local home = getenv('HOME') ++ if (not home) and (not path.is_windows) then ++ -- no more options to try on Nix ++ return nil, "failed to expand '~' (HOME not set)" ++ end ++ ++ if (not home) then ++ -- try alternatives on Windows ++ home = getenv 'USERPROFILE' ++ if not home then ++ local hd = getenv 'HOMEDRIVE' ++ local hp = getenv 'HOMEPATH' ++ if not (hd and hp) then ++ return nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" ++ end ++ home = hd..hp ++ end ++ end ++ ++ return home..sub(P,2) + end + + + ---Return a suitable full path to a new temporary file name. +--- unlike os.tmpnam(), it always gives you a writeable path (uses TEMP environment variable on Windows) ++-- unlike os.tmpname(), it always gives you a writeable path (uses TEMP environment variable on Windows) + function path.tmpname () + local res = tmpnam() + -- On Windows if Lua is compiled using MSVC14 os.tmpname +@@ -398,15 +541,22 @@ end + --- return the largest common prefix path of two paths. + -- @string path1 a file path + -- @string path2 a file path ++-- @return the common prefix (Windows: separators will be normalized, casing will be original) + function path.common_prefix (path1,path2) + assert_string(1,path1) + assert_string(2,path2) +- path1, path2 = path.normcase(path1), path.normcase(path2) + -- get them in order! + if #path1 > #path2 then path2,path1 = path1,path2 end ++ local compare ++ if path.is_windows then ++ path1 = path1:gsub("/", "\\") ++ path2 = path2:gsub("/", "\\") ++ compare = function(v) return v:lower() end ++ else ++ compare = function(v) return v end ++ end + for i = 1,#path1 do +- local c1 = at(path1,i) +- if c1 ~= at(path2,i) then ++ if compare(at(path1,i)) ~= compare(at(path2,i)) then + local cp = path1:sub(1,i-1) + if at(path1,i-1) ~= sep then + cp = path.dirname(cp) +@@ -424,16 +574,15 @@ end + -- either be a Lua file or a shared library. + -- @string mod name of the module + -- @return on success: path of module, lua or binary +--- @return on error: nil,error string ++-- @return on error: nil, error string listing paths tried + function path.package_path(mod) + assert_string(1,mod) +- local res +- mod = mod:gsub('%.',sep) +- res = package.searchpath(mod,package.path) ++ local res, err1, err2 ++ res, err1 = package.searchpath(mod,package.path) + if res then return res,true end +- res = package.searchpath(mod,package.cpath) ++ res, err2 = package.searchpath(mod,package.cpath) + if res then return res,false end +- return raise 'cannot find module on path' ++ return raise ('cannot find module on path\n' .. err1 .. "\n" .. err2) + end + + +diff --git a/extra/penlight/lua/pl/permute.lua b/extra/penlight/lua/pl/permute.lua +index 56d1a8d..1df9dd2 100644 +--- a/extra/penlight/lua/pl/permute.lua ++++ b/extra/penlight/lua/pl/permute.lua +@@ -6,58 +6,191 @@ local tablex = require 'pl.tablex' + local utils = require 'pl.utils' + local copy = tablex.deepcopy + local append = table.insert +-local coroutine = coroutine +-local resume = coroutine.resume + local assert_arg = utils.assert_arg + + + local permute = {} + +--- PiL, 9.3 + +-local permgen +-permgen = function (a, n, fn) +- if n == 0 then +- fn(a) +- else +- for i=1,n do +- -- put i-th element as the last one +- a[n], a[i] = a[i], a[n] +- +- -- generate all permutations of the other elements +- permgen(a, n - 1, fn) +- +- -- restore i-th element +- a[n], a[i] = a[i], a[n] +- +- end +- end +-end +- +---- an iterator over all permutations of the elements of a list. ++--- an iterator over all order-permutations of the elements of a list. + -- Please note that the same list is returned each time, so do not keep references! + -- @param a list-like table + -- @return an iterator which provides the next permutation as a list +-function permute.iter (a) ++function permute.order_iter(a) + assert_arg(1,a,'table') +- local n = #a +- local co = coroutine.create(function () permgen(a, n, coroutine.yield) end) +- return function () -- iterator +- local code, res = resume(co) +- return res ++ ++ local t = #a ++ local stack = { 1 } ++ local function iter() ++ local h = #stack ++ local n = t - h + 1 ++ ++ local i = stack[h] ++ if i > t then ++ return ++ end ++ ++ if n == 0 then ++ table.remove(stack) ++ h = h - 1 ++ ++ stack[h] = stack[h] + 1 ++ return a ++ ++ elseif i <= n then ++ ++ -- put i-th element as the last one ++ a[n], a[i] = a[i], a[n] ++ ++ -- generate all permutations of the other elements ++ table.insert(stack, 1) ++ ++ else ++ ++ table.remove(stack) ++ h = h - 1 ++ ++ n = n + 1 ++ i = stack[h] ++ ++ -- restore i-th element ++ a[n], a[i] = a[i], a[n] ++ ++ stack[h] = stack[h] + 1 ++ end ++ return iter() -- tail-call + end ++ ++ return iter + end + +---- construct a table containing all the permutations of a list. ++ ++--- construct a table containing all the order-permutations of a list. + -- @param a list-like table + -- @return a table of tables +--- @usage permute.table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}} +-function permute.table (a) ++-- @usage permute.order_table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}} ++function permute.order_table (a) + assert_arg(1,a,'table') + local res = {} +- local n = #a +- permgen(a,n,function(t) append(res,copy(t)) end) ++ for t in permute.iter(a) do ++ append(res,copy(t)) ++ end + return res + end + ++ ++ ++--- an iterator over all permutations of the elements of the given lists. ++-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided (see `utils.pack`) ++-- @return an iterator which provides the next permutation as return values in the same order as the provided lists, preceded by an index ++-- @usage ++-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety ++-- local bools = utils.pack(true, false) ++-- local iter = permute.list_iter(strs, bools) ++-- ++-- print(iter()) --> 1, one, true ++-- print(iter()) --> 2, nil, true ++-- print(iter()) --> 3, three, true ++-- print(iter()) --> 4, one, false ++-- print(iter()) --> 5, nil, false ++-- print(iter()) --> 6, three, false ++function permute.list_iter(...) ++ local elements = {...} ++ local pointers = {} ++ local sizes = {} ++ local size = #elements ++ for i, list in ipairs(elements) do ++ assert_arg(i,list,'table') ++ pointers[i] = 1 ++ sizes[i] = list.n or #list ++ end ++ local count = 0 ++ ++ return function() ++ if pointers[size] > sizes[size] then return end -- we're done ++ count = count + 1 ++ local r = { n = #elements } ++ local cascade_up = true ++ for i = 1, size do ++ r[i] = elements[i][pointers[i]] ++ if cascade_up then ++ pointers[i] = pointers[i] + 1 ++ if pointers[i] <= sizes[i] then ++ -- this list is not done yet, stop cascade ++ cascade_up = false ++ else ++ -- this list is done ++ if i ~= size then ++ -- reset pointer ++ pointers[i] = 1 ++ end ++ end ++ end ++ end ++ return count, utils.unpack(r) ++ end ++end ++ ++ ++ ++--- construct a table containing all the permutations of a set of lists. ++-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided ++-- @return a list of lists, the sub-lists have an 'n' field for nil-safety ++-- @usage ++-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety ++-- local bools = utils.pack(true, false) ++-- local results = permute.list_table(strs, bools) ++-- -- results = { ++-- -- { "one, true, n = 2 } ++-- -- { nil, true, n = 2 }, ++-- -- { "three, true, n = 2 }, ++-- -- { "one, false, n = 2 }, ++-- -- { nil, false, n = 2 }, ++-- -- { "three", false, n = 2 }, ++-- -- } ++function permute.list_table(...) ++ local iter = permute.list_iter(...) ++ local results = {} ++ local i = 1 ++ while true do ++ local values = utils.pack(iter()) ++ if values[1] == nil then return results end ++ for i = 1, values.n do values[i] = values[i+1] end ++ values.n = values.n - 1 ++ results[i] = values ++ i = i + 1 ++ end ++end ++ ++ ++-- backward compat, to be deprecated ++ ++--- deprecated. ++-- @param ... ++-- @see permute.order_iter ++function permute.iter(...) ++ utils.raise_deprecation { ++ source = "Penlight " .. utils._VERSION, ++ message = "function 'iter' was renamed to 'order_iter'", ++ version_removed = "2.0.0", ++ deprecated_after = "1.9.2", ++ } ++ ++ return permute.order_iter(...) ++end ++ ++--- deprecated. ++-- @param ... ++-- @see permute.order_iter ++function permute.table(...) ++ utils.raise_deprecation { ++ source = "Penlight " .. utils._VERSION, ++ message = "function 'table' was renamed to 'order_table'", ++ version_removed = "2.0.0", ++ deprecated_after = "1.9.2", ++ } ++ ++ return permute.order_table(...) ++end ++ + return permute +diff --git a/extra/penlight/lua/pl/pretty.lua b/extra/penlight/lua/pl/pretty.lua +index 9de158d..df9a923 100644 +--- a/extra/penlight/lua/pl/pretty.lua ++++ b/extra/penlight/lua/pl/pretty.lua +@@ -7,7 +7,8 @@ + + local append = table.insert + local concat = table.concat +-local mfloor, mhuge, mtype = math.floor, math.huge, math.type ++local mfloor, mhuge = math.floor, math.huge ++local mtype = math.type + local utils = require 'pl.utils' + local lexer = require 'pl.lexer' + local debug = require 'debug' +@@ -16,6 +17,42 @@ local assert_arg = utils.assert_arg + + local original_tostring = tostring + ++-- Calculate min and max integer supported by lua_Number ++-- Assumptions: ++-- 1. max_int = 2 ^ n - 1 ++-- 2. min_int = -max_int ++-- 3. if n > max_int versions with integer support will have ++-- integer overflow and versions without integers will lose least significant bit ++-- Note: if lua_Integer is smaller than lua_Number mantissa string.format('%d') ++-- can throw runtime error ++local max_int, min_int ++local next_cand = 1 ++while next_cand > 0 and next_cand % 2 == 1 do ++ max_int = next_cand ++ min_int = -next_cand ++ next_cand = next_cand * 2 + 1 ++end ++ ++local function is_integer(value) ++ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then ++ return mtype(value) == "integer" ++ end ++ if value < min_int or value > max_int then ++ return false ++ end ++ return math.floor(value) == value ++end ++ ++local function is_float(value) ++ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then ++ return mtype(value) == "float" ++ end ++ if value < min_int or value > max_int then ++ return true ++ end ++ return mfloor(value) == value ++end ++ + -- Patch tostring to format numbers with better precision + -- and to produce cross-platform results for + -- infinite values and NaN. +@@ -28,11 +65,11 @@ local function tostring(value) + return "Inf" + elseif value == -mhuge then + return "-Inf" +- elseif (_VERSION ~= "Lua 5.3" or mtype(value) == "integer") and mfloor(value) == value then ++ elseif is_integer(value) then + return ("%d"):format(value) + else + local res = ("%.14g"):format(value) +- if _VERSION == "Lua 5.3" and mtype(value) == "float" and not res:find("%.") then ++ if is_float(value) and not res:find("%.") then + -- Number is internally a float but looks like an integer. + -- Insert ".0" after first run of digits. + res = res:gsub("%d+", "%0.0", 1) +@@ -46,12 +83,12 @@ local pretty = {} + local function save_global_env() + local env = {} + env.hook, env.mask, env.count = debug.gethook() +- ++ + -- env.hook is "external hook" if is a C hook function +- if env.hook~="external hook" then +- debug.sethook() +- end +- ++ if env.hook~="external hook" then ++ debug.sethook() ++ end ++ + env.string_mt = getmetatable("") + debug.setmetatable("", nil) + return env +@@ -164,11 +201,17 @@ local function index (numkey,key) + end + + +---- Create a string representation of a Lua table. ++--- Create a string representation of a Lua table. + -- This function never fails, but may complain by returning an + -- extra value. Normally puts out one item per line, using + -- the provided indent; set the second parameter to an empty string + -- if you want output on one line. ++-- ++-- *NOTE:* this is NOT a serialization function, not a full blown ++-- debug function. Checkout out respectively the ++-- [serpent](https://github.com/pkulchenko/serpent) ++-- or [inspect](https://github.com/kikito/inspect.lua) ++-- Lua modules for that if you need them. + -- @tab tbl Table to serialize to a string. + -- @string[opt] space The indent to use. + -- Defaults to two spaces; pass an empty string for no indentation. +@@ -210,7 +253,7 @@ function pretty.write (tbl,space,not_clever) + end + + local function eat_last_comma () +- local n,lastch = #lines ++ local n = #lines + local lastch = lines[n]:sub(-1,-1) + if lastch == ',' then + lines[n] = lines[n]:sub(1,-2) +@@ -218,6 +261,29 @@ function pretty.write (tbl,space,not_clever) + end + + ++ -- safe versions for iterators since 5.3+ honors metamethods that can throw ++ -- errors ++ local ipairs = function(t) ++ local i = 0 ++ local ok, v ++ local getter = function() return t[i] end ++ return function() ++ i = i + 1 ++ ok, v = pcall(getter) ++ if v == nil or not ok then return end ++ return i, t[i] ++ end ++ end ++ local pairs = function(t) ++ local k, v, ok ++ local getter = function() return next(t, k) end ++ return function() ++ ok, k, v = pcall(getter) ++ if not ok then return end ++ return k, v ++ end ++ end ++ + local writeit + writeit = function (t,oldindent,indent) + local tp = type(t) +@@ -247,7 +313,20 @@ function pretty.write (tbl,space,not_clever) + used[i] = true + end + end +- for key,val in pairs(t) do ++ local ordered_keys = {} ++ for k,v in pairs(t) do ++ if type(k) ~= 'number' then ++ ordered_keys[#ordered_keys + 1] = k ++ end ++ end ++ table.sort(ordered_keys, function (a, b) ++ if type(a) == type(b) then ++ return tostring(a) < tostring(b) ++ else ++ return type(a) < type(b) ++ end ++ end) ++ local function write_entry (key, val) + local tkey = type(key) + local numkey = tkey == 'number' + if not_clever then +@@ -267,6 +346,16 @@ function pretty.write (tbl,space,not_clever) + end + end + end ++ for i = 1, #ordered_keys do ++ local key = ordered_keys[i] ++ local val = t[key] ++ write_entry(key, val) ++ end ++ for key,val in pairs(t) do ++ if type(key) == 'number' then ++ write_entry(key, val) ++ end ++ end + tables[t] = nil + eat_last_comma() + putln(oldindent..'},') +@@ -292,6 +381,49 @@ function pretty.dump (t, filename) + end + end + ++--- Dump a series of arguments to stdout for debug purposes. ++-- This function is attached to the module table `__call` method, to make it ++-- extra easy to access. So the full: ++-- ++-- print(require("pl.pretty").write({...})) ++-- ++-- Can be shortened to: ++-- ++-- require"pl.pretty" (...) ++-- ++-- Any `nil` entries will be printed as `""` to make them explicit. ++-- @param ... the parameters to dump to stdout. ++-- @usage ++-- -- example debug output ++-- require"pl.pretty" ("hello", nil, "world", { bye = "world", true} ) ++-- ++-- -- output: ++-- { ++-- ["arg 1"] = "hello", ++-- ["arg 2"] = "", ++-- ["arg 3"] = "world", ++-- ["arg 4"] = { ++-- true, ++-- bye = "world" ++-- } ++-- } ++function pretty.debug(...) ++ local n = select("#", ...) ++ local t = { ... } ++ for i = 1, n do ++ local value = t[i] ++ if value == nil then ++ value = "" ++ end ++ t[i] = nil ++ t["arg " .. i] = value ++ end ++ ++ print(pretty.write(t)) ++ return true ++end ++ ++ + local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'} + + local function comma (val) +@@ -335,4 +467,8 @@ function pretty.number (num,kind,prec) + end + end + +-return pretty ++return setmetatable(pretty, { ++ __call = function(self, ...) ++ return self.debug(...) ++ end ++}) +diff --git a/extra/penlight/lua/pl/seq.lua b/extra/penlight/lua/pl/seq.lua +index 238aa7a..1c08d20 100644 +--- a/extra/penlight/lua/pl/seq.lua ++++ b/extra/penlight/lua/pl/seq.lua +@@ -79,9 +79,9 @@ end + -- @return iterator over keys + function seq.keys(t) + assert_arg(1,t,'table') +- local key,value ++ local key + return function() +- key,value = next(t,key) ++ key = next(t,key) + return key + end + end +@@ -434,15 +434,12 @@ end + -- @param iter a sequence + function seq.last (iter) + iter = default_iter(iter) +- local l = iter() +- if l == nil then return nil end ++ local val, l = iter(), nil ++ if val == nil then return list{} end + return function () +- local val,ll +- val = iter() ++ val,l = iter(),val + if val == nil then return nil end +- ll = l +- l = val +- return val,ll ++ return val,l + end + end + +@@ -514,7 +511,6 @@ setmetatable(seq,{ + -- @param ... for Lua 5.2 only, optional format specifiers, as in `io.read`. + -- @return a sequence wrapper + function seq.lines (f,...) +- local n = select('#',...) + local iter,obj + if f == 'STDIN' then + f = io.stdin +diff --git a/extra/penlight/lua/pl/strict.lua b/extra/penlight/lua/pl/strict.lua +index d4e0fc7..67cbf5b 100644 +--- a/extra/penlight/lua/pl/strict.lua ++++ b/extra/penlight/lua/pl/strict.lua +@@ -1,7 +1,7 @@ + --- Checks uses of undeclared global variables. + -- All global variables must be 'declared' through a regular assignment + -- (even assigning `nil` will do) in a main chunk before being used +--- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index` ++-- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index` + -- metamethods are respected. + -- + -- You can set any table to have strict behaviour using `strict.module`. Creating a new +@@ -23,15 +23,25 @@ local function what () + end + + --- make an existing table strict. +--- @string name name of table (optional) +--- @tab[opt] mod table - if `nil` then we'll return a new table ++-- @string[opt] name name of table ++-- @tab[opt] mod the table to protect - if `nil` then we'll return a new table + -- @tab[opt] predeclared - table of variables that are to be considered predeclared. + -- @return the given table, or a new table ++-- @usage ++-- local M = { hello = "world" } ++-- strict.module ("Awesome_Module", M, { ++-- Lua = true, -- defines allowed keys ++-- }) ++-- ++-- assert(M.hello == "world") ++-- assert(M.Lua == nil) -- access allowed, but has no value yet ++-- M.Lua = "Rocks" ++-- assert(M.Lua == "Rocks") ++-- M.not_allowed = "bad boy" -- throws an error + function strict.module (name,mod,predeclared) +- local mt, old_newindex, old_index, old_index_type, global, closed ++ local mt, old_newindex, old_index, old_index_type, global + if predeclared then + global = predeclared.__global +- closed = predeclared.__closed + end + if type(mod) == 'table' then + mt = getmetatable(mod) +@@ -71,17 +81,17 @@ function strict.module (name,mod,predeclared) + local fallback = old_index[n] + if fallback ~= nil then + return fallback +- end ++ end + else + local res = old_index(t, n) + if res ~= nil then + return res + end +- end ++ end + end + local msg = "variable '"..n.."' is not declared" + if name then +- msg = msg .. " in '"..name.."'" ++ msg = msg .. " in '"..tostring(name).."'" + end + error(msg, 2) + end +@@ -93,7 +103,7 @@ end + --- make all tables in a table strict. + -- So `strict.make_all_strict(_G)` prevents monkey-patching + -- of any global table +--- @tab T ++-- @tab T the table containing the tables to protect. Table `T` itself will NOT be protected. + function strict.make_all_strict (T) + for k,v in pairs(T) do + if type(v) == 'table' and v ~= T then +@@ -103,7 +113,11 @@ function strict.make_all_strict (T) + end + + --- make a new module table which is closed to further changes. ++-- @tab mod module table ++-- @string name module name + function strict.closed_module (mod,name) ++ -- No clue to what this is useful for? see tests ++ -- Deprecate this and remove??? + local M = {} + mod = mod or {} + local mt = getmetatable(mod) +@@ -118,8 +132,7 @@ function strict.closed_module (mod,name) + end + + if not rawget(_G,'PENLIGHT_NO_GLOBAL_STRICT') then +- strict.module(nil,_G,{_PROMPT=true,__global=true}) ++ strict.module(nil,_G,{_PROMPT=true,_PROMPT2=true,__global=true}) + end + + return strict +- +diff --git a/extra/penlight/lua/pl/stringio.lua b/extra/penlight/lua/pl/stringio.lua +index 10fd102..666f415 100644 +--- a/extra/penlight/lua/pl/stringio.lua ++++ b/extra/penlight/lua/pl/stringio.lua +@@ -72,7 +72,7 @@ function SR:_read(fmt) + res = str:sub(i) + self.i = sz + elseif fmt == '*n' then +- local _,i2,i2,idx ++ local _,i2,idx + _,idx = str:find ('%s*%d+',i) + _,i2 = str:find ('^%.%d+',idx+1) + if i2 then idx = i2 end +diff --git a/extra/penlight/lua/pl/stringx.lua b/extra/penlight/lua/pl/stringx.lua +index f5a27d4..3fb8a75 100644 +--- a/extra/penlight/lua/pl/stringx.lua ++++ b/extra/penlight/lua/pl/stringx.lua +@@ -6,9 +6,10 @@ + -- + -- See @{03-strings.md|the Guide} + -- +--- Dependencies: `pl.utils` ++-- Dependencies: `pl.utils`, `pl.types` + -- @module pl.stringx + local utils = require 'pl.utils' ++local is_callable = require 'pl.types'.is_callable + local string = string + local find = string.find + local type,setmetatable,ipairs = type,setmetatable,ipairs +@@ -16,12 +17,16 @@ local error = error + local gsub = string.gsub + local rep = string.rep + local sub = string.sub ++local reverse = string.reverse + local concat = table.concat + local append = table.insert ++local remove = table.remove + local escape = utils.escape + local ceil, max = math.ceil, math.max + local assert_arg,usplit = utils.assert_arg,utils.split + local lstrip ++local unpack = utils.unpack ++local pack = utils.pack + + local function assert_string (n,s) + assert_arg(n,s,'string') +@@ -66,7 +71,8 @@ function stringx.isalnum(s) + return find(s,'^%w+$') == 1 + end + +---- does s only contain spaces? ++--- does s only contain whitespace? ++-- Matches on pattern '%s' so matches space, newline, tabs, etc. + -- @string s a string + function stringx.isspace(s) + assert_string(1,s) +@@ -128,9 +134,10 @@ end + -- @section lists + + --- concatenate the strings using this string as a delimiter. ++-- Note that the arguments are reversed from `string.concat`. + -- @string s the string + -- @param seq a table of strings or numbers +--- @usage (' '):join {1,2,3} == '1 2 3' ++-- @usage stringx.join(' ', {1,2,3}) == '1 2 3' + function stringx.join(s,seq) + assert_string(1,s) + return concat(seq,s) +@@ -143,6 +150,7 @@ end + -- Splitting an empty string results in an empty list. + -- @string s the string. + -- @bool[opt] keep_ends include line ends. ++-- @return List of lines + function stringx.splitlines(s, keep_ends) + assert_string(1, s) + local res = {} +@@ -178,9 +186,10 @@ end + -- @string s the string + -- @string[opt] re a delimiter (defaults to whitespace) + -- @int[opt] n maximum number of results +--- @usage #(('one two'):split()) == 2 +--- @usage ('one,two,three'):split(',') == List{'one','two','three'} +--- @usage ('one,two,three'):split(',',2) == List{'one','two,three'} ++-- @return List ++-- @usage #(stringx.split('one two')) == 2 ++-- @usage stringx.split('one,two,three', ',') == List{'one','two','three'} ++-- @usage stringx.split('one,two,three', ',', 2) == List{'one','two,three'} + function stringx.split(s,re,n) + assert_string(1,s) + local plain = true +@@ -189,29 +198,37 @@ function stringx.split(s,re,n) + plain = false + end + local res = usplit(s,re,plain,n) +- if re and re ~= '' and find(s,re,-#re,true) then ++ if re and re ~= '' and ++ find(s,re,-#re,true) and ++ (n or math.huge) > #res then + res[#res+1] = "" + end +- return makelist(res) ++ return makelist(res) + end + + --- replace all tabs in s with tabsize spaces. If not specified, tabsize defaults to 8. +--- with 0.9.5 this now correctly expands to the next tab stop (if you really +--- want to just replace tabs, use :gsub('\t',' ') etc) ++-- Tab stops will be honored. + -- @string s the string + -- @int tabsize[opt=8] number of spaces to expand each tab ++-- @return expanded string ++-- @usage stringx.expandtabs('\tone,two,three', 4) == ' one,two,three' ++-- @usage stringx.expandtabs(' \tone,two,three', 4) == ' one,two,three' + function stringx.expandtabs(s,tabsize) +- assert_string(1,s) +- tabsize = tabsize or 8 +- return (s:gsub("([^\t\r\n]*)\t", function(before_tab) ++ assert_string(1,s) ++ tabsize = tabsize or 8 ++ return (s:gsub("([^\t\r\n]*)\t", function(before_tab) ++ if tabsize == 0 then ++ return before_tab ++ else + return before_tab .. (" "):rep(tabsize - #before_tab % tabsize) ++ end + end)) + end + + --- Finding and Replacing + -- @section find + +-local function _find_all(s,sub,first,last) ++local function _find_all(s,sub,first,last,allow_overlap) + first = first or 1 + last = last or #s + if sub == '' then return last+1,last-first+1 end +@@ -222,7 +239,11 @@ local function _find_all(s,sub,first,last) + if last and i2 > last then break end + res = i1 + k = k + 1 +- i1,i2 = find(s,sub,i2+1,true) ++ if allow_overlap then ++ i1,i2 = find(s,sub,i1+1,true) ++ else ++ i1,i2 = find(s,sub,i2+1,true) ++ end + end + return res,k + end +@@ -232,6 +253,7 @@ end + -- @string sub substring + -- @int[opt] first first index + -- @int[opt] last last index ++-- @return start index, or nil if not found + function stringx.lfind(s,sub,first,last) + assert_string(1,s) + assert_string(2,sub) +@@ -249,14 +271,15 @@ end + -- @string sub substring + -- @int[opt] first first index + -- @int[opt] last last index ++-- @return start index, or nil if not found + function stringx.rfind(s,sub,first,last) + assert_string(1,s) + assert_string(2,sub) +- return (_find_all(s,sub,first,last)) ++ return (_find_all(s,sub,first,last,true)) + end + + --- replace up to n instances of old by new in the string s. +--- if n is not present, replace all instances. ++-- If n is not present, replace all instances. + -- @string s the string + -- @string old the target substring + -- @string new the substitution +@@ -272,9 +295,13 @@ end + --- count all instances of substring in string. + -- @string s the string + -- @string sub substring +-function stringx.count(s,sub) ++-- @bool[opt] allow_overlap allow matches to overlap ++-- @usage ++-- assert(stringx.count('banana', 'ana') == 1) ++-- assert(stringx.count('banana', 'ana', true) == 2) ++function stringx.count(s,sub,allow_overlap) + assert_string(1,s) +- local i,k = _find_all(s,sub,1) ++ local _,k = _find_all(s,sub,1,false,allow_overlap) + return k + end + +@@ -308,6 +335,7 @@ end + -- @string s the string + -- @int w width of justification + -- @string[opt=' '] ch padding character ++-- @usage stringx.ljust('hello', 10, '*') == '*****hello' + function stringx.ljust(s,w,ch) + assert_string(1,s) + assert_arg(2,w,'number') +@@ -318,6 +346,7 @@ end + -- @string s the string + -- @int w width of justification + -- @string[opt=' '] ch padding character ++-- @usage stringx.rjust('hello', 10, '*') == 'hello*****' + function stringx.rjust(s,w,ch) + assert_string(1,s) + assert_arg(2,w,'number') +@@ -328,6 +357,7 @@ end + -- @string s the string + -- @int w width of justification + -- @string[opt=' '] ch padding character ++-- @usage stringx.center('hello', 10, '*') == '**hello***' + function stringx.center(s,w,ch) + assert_string(1,s) + assert_arg(2,w,'number') +@@ -340,63 +370,75 @@ local function _strip(s,left,right,chrs) + else + chrs = '['..escape(chrs)..']' + end ++ local f = 1 ++ local t + if left then + local i1,i2 = find(s,'^'..chrs..'*') + if i2 >= i1 then +- s = sub(s,i2+1) ++ f = i2+1 + end + end + if right then +- local i1,i2 = find(s,chrs..'*$') +- if i2 >= i1 then +- s = sub(s,1,i1-1) ++ if #s < 200 then ++ local i1,i2 = find(s,chrs..'*$',f) ++ if i2 >= i1 then ++ t = i1-1 ++ end ++ else ++ local rs = reverse(s) ++ local i1,i2 = find(rs, '^'..chrs..'*') ++ if i2 >= i1 then ++ t = -i2-1 ++ end + end + end +- return s ++ return sub(s,f,t) + end + +---- trim any whitespace on the left of s. ++--- trim any characters on the left of s. + -- @string s the string + -- @string[opt='%s'] chrs default any whitespace character, +--- but can be a string of characters to be trimmed ++-- but can be a string of characters to be trimmed + function stringx.lstrip(s,chrs) + assert_string(1,s) + return _strip(s,true,false,chrs) + end + lstrip = stringx.lstrip + +---- trim any whitespace on the right of s. ++--- trim any characters on the right of s. + -- @string s the string + -- @string[opt='%s'] chrs default any whitespace character, +--- but can be a string of characters to be trimmed ++-- but can be a string of characters to be trimmed + function stringx.rstrip(s,chrs) + assert_string(1,s) + return _strip(s,false,true,chrs) + end + +---- trim any whitespace on both left and right of s. ++--- trim any characters on both left and right of s. + -- @string s the string + -- @string[opt='%s'] chrs default any whitespace character, +--- but can be a string of characters to be trimmed ++-- but can be a string of characters to be trimmed ++-- @usage stringx.strip(' --== Hello ==-- ', "- =") --> 'Hello' + function stringx.strip(s,chrs) + assert_string(1,s) + return _strip(s,true,true,chrs) + end + +---- Partioning Strings +--- @section partioning ++--- Partitioning Strings ++-- @section partitioning + + --- split a string using a pattern. Note that at least one value will be returned! + -- @string s the string + -- @string[opt='%s'] re a Lua string pattern (defaults to whitespace) + -- @return the parts of the string + -- @usage a,b = line:splitv('=') ++-- @see utils.splitv + function stringx.splitv(s,re) + assert_string(1,s) + return utils.splitv(s,re) + end + +--- The partition functions split a string using a delimiter into three parts: ++-- The partition functions split a string using a delimiter into three parts: + -- the part before, the delimiter itself, and the part afterwards + local function _partition(p,delim,fn) + local i1,i2 = fn(p,delim) +@@ -408,28 +450,36 @@ local function _partition(p,delim,fn) + end + end + +---- partition the string using first occurance of a delimiter ++--- partition the string using first occurrence of a delimiter + -- @string s the string +--- @string ch delimiter ++-- @string ch delimiter (match as plain string, no patterns) + -- @return part before ch + -- @return ch + -- @return part after ch ++-- @usage {stringx.partition('a,b,c', ','))} == {'a', ',', 'b,c'} ++-- @usage {stringx.partition('abc', 'x'))} == {'abc', '', ''} + function stringx.partition(s,ch) + assert_string(1,s) + assert_nonempty_string(2,ch) + return _partition(s,ch,stringx.lfind) + end + +---- partition the string p using last occurance of a delimiter ++--- partition the string p using last occurrence of a delimiter + -- @string s the string +--- @string ch delimiter ++-- @string ch delimiter (match as plain string, no patterns) + -- @return part before ch + -- @return ch + -- @return part after ch ++-- @usage {stringx.rpartition('a,b,c', ','))} == {'a,b', ',', 'c'} ++-- @usage {stringx.rpartition('abc', 'x'))} == {'', '', 'abc'} + function stringx.rpartition(s,ch) + assert_string(1,s) + assert_nonempty_string(2,ch) +- return _partition(s,ch,stringx.rfind) ++ local a,b,c = _partition(s,ch,stringx.rfind) ++ if a == s then -- no match found ++ return c,b,a ++ end ++ return a,b,c + end + + --- return the 'character' at the index. +@@ -442,22 +492,273 @@ function stringx.at(s,idx) + return sub(s,idx,idx) + end + +---- Miscelaneous ++ ++--- Text handling ++-- @section text ++ ++ ++--- indent a multiline string. ++-- @tparam string s the (multiline) string ++-- @tparam integer n the size of the indent ++-- @tparam[opt=' '] string ch the character to use when indenting ++-- @return indented string ++function stringx.indent (s,n,ch) ++ assert_arg(1,s,'string') ++ assert_arg(2,n,'number') ++ local lines = usplit(s ,'\n') ++ local prefix = string.rep(ch or ' ',n) ++ for i, line in ipairs(lines) do ++ lines[i] = prefix..line ++ end ++ return concat(lines,'\n')..'\n' ++end ++ ++ ++--- dedent a multiline string by removing any initial indent. ++-- useful when working with [[..]] strings. ++-- Empty lines are ignored. ++-- @tparam string s the (multiline) string ++-- @return a string with initial indent zero. ++-- @usage ++-- local s = dedent [[ ++-- One ++-- ++-- Two ++-- ++-- Three ++-- ]] ++-- assert(s == [[ ++-- One ++-- ++-- Two ++-- ++-- Three ++-- ]]) ++function stringx.dedent (s) ++ assert_arg(1,s,'string') ++ local lst = usplit(s,'\n') ++ if #lst>0 then ++ local ind_size = math.huge ++ for i, line in ipairs(lst) do ++ local i1, i2 = lst[i]:find('^%s*[^%s]') ++ if i1 and i2 < ind_size then ++ ind_size = i2 ++ end ++ end ++ for i, line in ipairs(lst) do ++ lst[i] = lst[i]:sub(ind_size, -1) ++ end ++ end ++ return concat(lst,'\n')..'\n' ++end ++ ++ ++ ++do ++ local buildline = function(words, size, breaklong) ++ -- if overflow is set, a word longer than size, will overflow the size ++ -- otherwise it will be chopped in line-length pieces ++ local line = {} ++ if #words[1] > size then ++ -- word longer than line ++ if not breaklong then ++ line[1] = words[1] ++ remove(words, 1) ++ else ++ line[1] = words[1]:sub(1, size) ++ words[1] = words[1]:sub(size + 1, -1) ++ end ++ else ++ local len = 0 ++ while words[1] and (len + #words[1] <= size) or ++ (len == 0 and #words[1] == size) do ++ if words[1] ~= "" then ++ line[#line+1] = words[1] ++ len = len + #words[1] + 1 ++ end ++ remove(words, 1) ++ end ++ end ++ return stringx.strip(concat(line, " ")), words ++ end ++ ++ --- format a paragraph into lines so that they fit into a line width. ++ -- It will not break long words by default, so lines can be over the length ++ -- to that extent. ++ -- @tparam string s the string to format ++ -- @tparam[opt=70] integer width the margin width ++ -- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split. ++ -- @return a list of lines (List object), use `fill` to return a string instead of a `List`. ++ -- @see pl.List ++ -- @see fill ++ stringx.wrap = function(s, width, breaklong) ++ s = s:gsub('\n',' ') -- remove line breaks ++ s = stringx.strip(s) -- remove leading/trailing whitespace ++ if s == "" then ++ return { "" } ++ end ++ width = width or 70 ++ local out = {} ++ local words = usplit(s, "%s") ++ while words[1] do ++ out[#out+1], words = buildline(words, width, breaklong) ++ end ++ return makelist(out) ++ end ++end ++ ++--- format a paragraph so that it fits into a line width. ++-- @tparam string s the string to format ++-- @tparam[opt=70] integer width the margin width ++-- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split. ++-- @return a string, use `wrap` to return a list of lines instead of a string. ++-- @see wrap ++function stringx.fill (s,width,breaklong) ++ return concat(stringx.wrap(s,width,breaklong),'\n') .. '\n' ++end ++ ++--- Template ++-- @section Template ++ ++ ++local function _substitute(s,tbl,safe) ++ local subst ++ if is_callable(tbl) then ++ subst = tbl ++ else ++ function subst(f) ++ local s = tbl[f] ++ if not s then ++ if safe then ++ return f ++ else ++ error("not present in table "..f) ++ end ++ else ++ return s ++ end ++ end ++ end ++ local res = gsub(s,'%${([%w_]+)}',subst) ++ return (gsub(res,'%$([%w_]+)',subst)) ++end ++ ++ ++ ++local Template = {} ++stringx.Template = Template ++Template.__index = Template ++setmetatable(Template, { ++ __call = function(obj,tmpl) ++ return Template.new(tmpl) ++ end ++}) ++ ++--- Creates a new Template class. ++-- This is a shortcut to `Template.new(tmpl)`. ++-- @tparam string tmpl the template string ++-- @function Template ++-- @treturn Template ++function Template.new(tmpl) ++ assert_arg(1,tmpl,'string') ++ local res = {} ++ res.tmpl = tmpl ++ setmetatable(res,Template) ++ return res ++end ++ ++--- substitute values into a template, throwing an error. ++-- This will throw an error if no name is found. ++-- @tparam table tbl a table of name-value pairs. ++-- @return string with place holders substituted ++function Template:substitute(tbl) ++ assert_arg(1,tbl,'table') ++ return _substitute(self.tmpl,tbl,false) ++end ++ ++--- substitute values into a template. ++-- This version just passes unknown names through. ++-- @tparam table tbl a table of name-value pairs. ++-- @return string with place holders substituted ++function Template:safe_substitute(tbl) ++ assert_arg(1,tbl,'table') ++ return _substitute(self.tmpl,tbl,true) ++end ++ ++--- substitute values into a template, preserving indentation.
    ++-- If the value is a multiline string _or_ a template, it will insert ++-- the lines at the correct indentation.
    ++-- Furthermore, if a template, then that template will be substituted ++-- using the same table. ++-- @tparam table tbl a table of name-value pairs. ++-- @return string with place holders substituted ++function Template:indent_substitute(tbl) ++ assert_arg(1,tbl,'table') ++ if not self.strings then ++ self.strings = usplit(self.tmpl,'\n') ++ end ++ ++ -- the idea is to substitute line by line, grabbing any spaces as ++ -- well as the $var. If the value to be substituted contains newlines, ++ -- then we split that into lines and adjust the indent before inserting. ++ local function subst(line) ++ return line:gsub('(%s*)%$([%w_]+)',function(sp,f) ++ local subtmpl ++ local s = tbl[f] ++ if not s then error("not present in table "..f) end ++ if getmetatable(s) == Template then ++ subtmpl = s ++ s = s.tmpl ++ else ++ s = tostring(s) ++ end ++ if s:find '\n' then ++ local lines = usplit(s, '\n') ++ for i, line in ipairs(lines) do ++ lines[i] = sp..line ++ end ++ s = concat(lines, '\n') .. '\n' ++ end ++ if subtmpl then ++ return _substitute(s, tbl) ++ else ++ return s ++ end ++ end) ++ end ++ ++ local lines = {} ++ for i, line in ipairs(self.strings) do ++ lines[i] = subst(line) ++ end ++ return concat(lines,'\n')..'\n' ++end ++ ++ ++ ++--- Miscellaneous + -- @section misc + + --- return an iterator over all lines in a string + -- @string s the string + -- @return an iterator ++-- @usage ++-- local line_no = 1 ++-- for line in stringx.lines(some_text) do ++-- print(line_no, line) ++-- line_no = line_no + 1 ++-- end + function stringx.lines(s) + assert_string(1,s) + if not s:find '\n$' then s = s..'\n' end + return s:gmatch('([^\n]*)\n') + end + +---- iniital word letters uppercase ('title case'). ++--- initial word letters uppercase ('title case'). + -- Here 'words' mean chunks of non-space characters. + -- @string s the string + -- @return a string with each word's first letter uppercase ++-- @usage stringx.title("hello world") == "Hello World") + function stringx.title(s) + assert_string(1,s) + return (s:gsub('(%S)(%S*)',function(f,r) +@@ -467,80 +768,148 @@ end + + stringx.capitalize = stringx.title + +-local ellipsis = '...' +-local n_ellipsis = #ellipsis +- +---- Return a shortened version of a string. +--- Fits string within w characters. Removed characters are marked with ellipsis. +--- @string s the string +--- @int w the maxinum size allowed +--- @bool tail true if we want to show the end of the string (head otherwise) +--- @usage ('1234567890'):shorten(8) == '12345...' +--- @usage ('1234567890'):shorten(8, true) == '...67890' +--- @usage ('1234567890'):shorten(20) == '1234567890' +-function stringx.shorten(s,w,tail) +- assert_string(1,s) +- if #s > w then +- if w < n_ellipsis then return ellipsis:sub(1,w) end +- if tail then +- local i = #s - w + 1 + n_ellipsis +- return ellipsis .. s:sub(i) +- else +- return s:sub(1,w-n_ellipsis) .. ellipsis +- end ++do ++ local ellipsis = '...' ++ local n_ellipsis = #ellipsis ++ ++ --- Return a shortened version of a string. ++ -- Fits string within w characters. Removed characters are marked with ellipsis. ++ -- @string s the string ++ -- @int w the maximum size allowed ++ -- @bool tail true if we want to show the end of the string (head otherwise) ++ -- @usage ('1234567890'):shorten(8) == '12345...' ++ -- @usage ('1234567890'):shorten(8, true) == '...67890' ++ -- @usage ('1234567890'):shorten(20) == '1234567890' ++ function stringx.shorten(s,w,tail) ++ assert_string(1,s) ++ if #s > w then ++ if w < n_ellipsis then return ellipsis:sub(1,w) end ++ if tail then ++ local i = #s - w + 1 + n_ellipsis ++ return ellipsis .. s:sub(i) ++ else ++ return s:sub(1,w-n_ellipsis) .. ellipsis ++ end ++ end ++ return s ++ end ++end ++ ++ ++do ++ -- Utility function that finds any patterns that match a long string's an open or close. ++ -- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. ++ -- Right now, it simply returns the greatest number of them found. ++ -- @param s The string ++ -- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. ++ local function has_lquote(s) ++ local lstring_pat = '([%[%]])(=*)%1' ++ local equals, new_equals, _ ++ local finish = 1 ++ repeat ++ _, finish, _, new_equals = s:find(lstring_pat, finish) ++ if new_equals then ++ equals = max(equals or 0, #new_equals) ++ end ++ until not new_equals ++ ++ return equals ++ end ++ ++ --- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. ++ -- @param s The string to be quoted. ++ -- @return The quoted string. ++ function stringx.quote_string(s) ++ assert_string(1,s) ++ -- Find out if there are any embedded long-quote sequences that may cause issues. ++ -- This is important when strings are embedded within strings, like when serializing. ++ -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. ++ local equal_signs = has_lquote(s .. "]") ++ ++ -- Note that strings containing "\r" can't be quoted using long brackets ++ -- as Lua lexer converts all newlines to "\n" within long strings. ++ if (s:find("\n") or equal_signs) and not s:find("\r") then ++ -- If there is an embedded sequence that matches a long quote, then ++ -- find the one with the maximum number of = signs and add one to that number. ++ equal_signs = ("="):rep((equal_signs or -1) + 1) ++ -- Long strings strip out leading newline. We want to retain that, when quoting. ++ if s:find("^\n") then s = "\n" .. s end ++ local lbracket, rbracket = ++ "[" .. equal_signs .. "[", ++ "]" .. equal_signs .. "]" ++ s = lbracket .. s .. rbracket ++ else ++ -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. ++ s = ("%q"):format(s):gsub("\r", "\\r") ++ end ++ return s ++ end ++end ++ ++ ++--- Python-style formatting operator. ++-- Calling `text.format_operator()` overloads the % operator for strings to give ++-- Python/Ruby style formatted output. ++-- This is extended to also do template-like substitution for map-like data. ++-- ++-- Note this goes further than the original, and will allow these cases: ++-- ++-- 1. a single value ++-- 2. a list of values ++-- 3. a map of var=value pairs ++-- 4. a function, as in gsub ++-- ++-- For the second two cases, it uses $-variable substitution. ++-- ++-- When called, this function will monkey-patch the global `string` metatable by ++-- adding a `__mod` method. ++-- ++-- See the lua-users wiki ++-- ++-- @usage ++-- require 'pl.text'.format_operator() ++-- local out1 = '%s = %5.3f' % {'PI',math.pi} --> 'PI = 3.142' ++-- local out2 = '$name = $value' % {name='dog',value='Pluto'} --> 'dog = Pluto' ++function stringx.format_operator() ++ ++ local format = string.format ++ ++ -- a more forgiving version of string.format, which applies ++ -- tostring() to any value with a %s format. ++ local function formatx (fmt,...) ++ local args = pack(...) ++ local i = 1 ++ for p in fmt:gmatch('%%.') do ++ if p == '%s' and type(args[i]) ~= 'string' then ++ args[i] = tostring(args[i]) ++ end ++ i = i + 1 + end +- return s +-end +- +---- Utility function that finds any patterns that match a long string's an open or close. +--- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. +--- Right now, it simply returns the greatest number of them found. +--- @param s The string +--- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. +-local function has_lquote(s) +- local lstring_pat = '([%[%]])(=*)%1' +- local equals +- local start, finish, bracket, new_equals = nil, 1, nil, nil +- +- repeat +- start, finish, bracket, new_equals = s:find(lstring_pat, finish) +- if new_equals then +- equals = max(equals or 0, #new_equals) +- end +- until not new_equals +- +- return equals +-end +- +---- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. +--- @param s The string to be quoted. +--- @return The quoted string. +-function stringx.quote_string(s) +- assert_string(1,s) +- -- Find out if there are any embedded long-quote sequences that may cause issues. +- -- This is important when strings are embedded within strings, like when serializing. +- -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. +- local equal_signs = has_lquote(s .. "]") +- +- -- Note that strings containing "\r" can't be quoted using long brackets +- -- as Lua lexer converts all newlines to "\n" within long strings. +- if (s:find("\n") or equal_signs) and not s:find("\r") then +- -- If there is an embedded sequence that matches a long quote, then +- -- find the one with the maximum number of = signs and add one to that number. +- equal_signs = ("="):rep((equal_signs or -1) + 1) +- -- Long strings strip out leading newline. We want to retain that, when quoting. +- if s:find("^\n") then s = "\n" .. s end +- local lbracket, rbracket = +- "[" .. equal_signs .. "[", +- "]" .. equal_signs .. "]" +- s = lbracket .. s .. rbracket ++ return format(fmt,unpack(args)) ++ end ++ ++ local function basic_subst(s,t) ++ return (s:gsub('%$([%w_]+)',t)) ++ end ++ ++ getmetatable("").__mod = function(a, b) ++ if b == nil then ++ return a ++ elseif type(b) == "table" and getmetatable(b) == nil then ++ if #b == 0 then -- assume a map-like table ++ return _substitute(a,b,true) ++ else ++ return formatx(a,unpack(b)) ++ end ++ elseif type(b) == 'function' then ++ return basic_subst(a,b) + else +- -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. +- s = ("%q"):format(s):gsub("\r", "\\r") ++ return formatx(a,b) + end +- return s ++ end + end + ++--- import the stringx functions into the global string (meta)table + function stringx.import() + utils.import(stringx,string) + end +diff --git a/extra/penlight/lua/pl/tablex.lua b/extra/penlight/lua/pl/tablex.lua +index 03ecba9..3606912 100644 +--- a/extra/penlight/lua/pl/tablex.lua ++++ b/extra/penlight/lua/pl/tablex.lua +@@ -95,37 +95,36 @@ function tablex.copy (t) + return res + end + +---- make a deep copy of a table, recursively copying all the keys and fields. +--- This will also set the copied table's metatable to that of the original. +--- @within Copying +--- @tab t A table +--- @return new table +-function tablex.deepcopy(t) ++local function cycle_aware_copy(t, cache) + if type(t) ~= 'table' then return t end ++ if cache[t] then return cache[t] end + assert_arg_iterable(1,t) +- local mt = getmetatable(t) + local res = {} ++ cache[t] = res ++ local mt = getmetatable(t) + for k,v in pairs(t) do +- if type(v) == 'table' then +- v = tablex.deepcopy(v) +- end ++ k = cycle_aware_copy(k, cache) ++ v = cycle_aware_copy(v, cache) + res[k] = v + end + setmetatable(res,mt) + return res + end + +-local abs, deepcompare = math.abs ++--- make a deep copy of a table, recursively copying all the keys and fields. ++-- This supports cycles in tables; cycles will be reproduced in the copy. ++-- This will also set the copied table's metatable to that of the original. ++-- @within Copying ++-- @tab t A table ++-- @return new table ++function tablex.deepcopy(t) ++ return cycle_aware_copy(t,{}) ++end + +---- compare two values. +--- if they are tables, then compare their keys and fields recursively. +--- @within Comparing +--- @param t1 A value +--- @param t2 A value +--- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false) +--- @number[opt] eps if defined, then used for any number comparisons +--- @return true or false +-function tablex.deepcompare(t1,t2,ignore_mt,eps) ++local abs = math.abs ++ ++local function cycle_aware_compare(t1,t2,ignore_mt,eps,cache) ++ if cache[t1] and cache[t1][t2] then return true end + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false end +@@ -143,21 +142,39 @@ function tablex.deepcompare(t1,t2,ignore_mt,eps) + for k2 in pairs(t2) do + if t1[k2]==nil then return false end + end ++ cache[t1] = cache[t1] or {} ++ cache[t1][t2] = true + for k1,v1 in pairs(t1) do + local v2 = t2[k1] +- if not deepcompare(v1,v2,ignore_mt,eps) then return false end ++ if not cycle_aware_compare(v1,v2,ignore_mt,eps,cache) then return false end + end +- + return true + end + +-deepcompare = tablex.deepcompare ++--- compare two values. ++-- if they are tables, then compare their keys and fields recursively. ++-- @within Comparing ++-- @param t1 A value ++-- @param t2 A value ++-- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false) ++-- @number[opt] eps if defined, then used for any number comparisons ++-- @return true or false ++function tablex.deepcompare(t1,t2,ignore_mt,eps) ++ return cycle_aware_compare(t1,t2,ignore_mt,eps,{}) ++end + + --- compare two arrays using a predicate. + -- @within Comparing + -- @array t1 an array + -- @array t2 an array +--- @func cmp A comparison function ++-- @func cmp A comparison function; `bool = cmp(t1_value, t2_value)` ++-- @return true or false ++-- @usage ++-- assert(tablex.compare({ 1, 2, 3 }, { 1, 2, 3 }, "==")) ++-- ++-- assert(tablex.compare( ++-- {1,2,3, hello = "world"}, -- fields are not compared! ++-- {1,2,3}, function(v1, v2) return v1 == v2 end) + function tablex.compare (t1,t2,cmp) + assert_arg_indexable(1,t1) + assert_arg_indexable(2,t2) +@@ -183,14 +200,16 @@ function tablex.compare_no_order (t1,t2,cmp) + for i = 1,#t1 do + local val = t1[i] + local gotcha +- for j = 1,#t2 do if not visited[j] then +- local match +- if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end +- if match then +- gotcha = j +- break ++ for j = 1,#t2 do ++ if not visited[j] then ++ local match ++ if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end ++ if match then ++ gotcha = j ++ break ++ end + end +- end end ++ end + if not gotcha then return false end + visited[gotcha] = true + end +@@ -224,7 +243,7 @@ end + -- @within Finding + -- @array t A list-like table + -- @param val A value +--- @param idx index to start; -1 means last element,etc (default 1) ++-- @param idx index to start; -1 means last element,etc (default `#t`) + -- @return index of value or nil if not found + -- @usage rfind({10,10,10},10) == 3 + function tablex.rfind(t,val,idx) +@@ -239,12 +258,38 @@ end + + + --- return the index (or key) of a value in a table using a comparison function. ++-- ++-- *NOTE*: the 2nd return value of this function, the value returned ++-- by the comparison function, has a limitation that it cannot be `false`. ++-- Because if it is, then it indicates the comparison failed, and the ++-- function will continue the search. See examples. + -- @within Finding + -- @tab t A table + -- @func cmp A comparison function + -- @param arg an optional second argument to the function + -- @return index of value, or nil if not found +--- @return value returned by comparison function ++-- @return value returned by comparison function (cannot be `false`!) ++-- @usage ++-- -- using an operator ++-- local lst = { "Rudolph", true, false, 15 } ++-- local idx, cmp_result = tablex.rfind(lst, "==", "Rudolph") ++-- assert(idx == 1) ++-- assert(cmp_result == true) ++-- ++-- local idx, cmp_result = tablex.rfind(lst, "==", false) ++-- assert(idx == 3) ++-- assert(cmp_result == true) -- looking up 'false' works! ++-- ++-- -- using a function returning the value looked up ++-- local cmp = function(v1, v2) return v1 == v2 and v2 end ++-- local idx, cmp_result = tablex.rfind(lst, cmp, "Rudolph") ++-- assert(idx == 1) ++-- assert(cmp_result == "Rudolph") -- the value is returned ++-- ++-- -- NOTE: this fails, since 'false' cannot be returned! ++-- local idx, cmp_result = tablex.rfind(lst, cmp, false) ++-- assert(idx == nil) -- looking up 'false' failed! ++-- assert(cmp_result == nil) + function tablex.find_if(t,cmp,arg) + assert_arg_iterable(1,t) + cmp = function_arg(2,cmp) +@@ -313,6 +358,28 @@ end + -- @string name the method name + -- @array t a list-like table + -- @param ... any extra arguments to the method ++-- @return a `List` with the results of the method (1st result only) ++-- @usage ++-- local Car = {} ++-- Car.__index = Car ++-- function Car.new(car) ++-- return setmetatable(car or {}, Car) ++-- end ++-- Car.speed = 0 ++-- function Car:faster(increase) ++-- self.speed = self.speed + increase ++-- return self.speed ++-- end ++-- ++-- local ferrari = Car.new{ name = "Ferrari" } ++-- local lamborghini = Car.new{ name = "Lamborghini", speed = 50 } ++-- local cars = { ferrari, lamborghini } ++-- ++-- assert(ferrari.speed == 0) ++-- assert(lamborghini.speed == 50) ++-- tablex.map_named_method("faster", cars, 10) ++-- assert(ferrari.speed == 10) ++-- assert(lamborghini.speed == 60) + function tablex.map_named_method (name,t,...) + utils.assert_string(1,name) + assert_arg_indexable(2,t) +@@ -329,7 +396,8 @@ end + -- Any extra arguments are passed to the function. + -- @func fun A function that takes at least one argument + -- @tab t a table +--- @param ... extra arguments ++-- @param ... extra arguments passed to `fun` ++-- @see tablex.foreach + function tablex.transform (fun,t,...) + assert_arg_iterable(1,t) + fun = function_arg(1,fun) +@@ -422,8 +490,9 @@ end + -- Note that the Lua 5.0 function table.foreach passed the _key_ first. + -- @within Iterating + -- @tab t a table +--- @func fun a function with at least one argument +--- @param ... extra arguments ++-- @func fun a function on the elements; `function(value, key, ...)` ++-- @param ... extra arguments passed to `fun` ++-- @see tablex.transform + function tablex.foreach(t,fun,...) + assert_arg_iterable(1,t) + fun = function_arg(2,fun) +@@ -455,7 +524,7 @@ end + -- @func fun a function of n arguments + -- @tab ... n tables + -- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333} +--- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300} ++-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300} + -- @param fun A function that takes as many arguments as there are tables + function tablex.mapn(fun,...) + fun = function_arg(1,fun) +@@ -493,15 +562,15 @@ function tablex.pairmap(fun,t,...) + for k,v in pairs(t) do + local rv,rk = fun(k,v,...) + if rk then +- if res[rk] then +- if type(res[rk]) == 'table' then +- table.insert(res[rk],rv) +- else +- res[rk] = {res[rk], rv} +- end +- else +- res[rk] = rv +- end ++ if res[rk] then ++ if type(res[rk]) == 'table' then ++ table.insert(res[rk],rv) ++ else ++ res[rk] = {res[rk], rv} ++ end ++ else ++ res[rk] = rv ++ end + else + res[#res+1] = rv + end +@@ -513,7 +582,7 @@ local function keys_op(i,v) return i end + + --- return all the keys of a table in arbitrary order. + -- @within Extraction +--- @tab t A table ++-- @tab t A list-like table where the values are the keys of the input table + function tablex.keys(t) + assert_arg_iterable(1,t) + return makelist(tablex.pairmap(keys_op,t)) +@@ -523,7 +592,7 @@ local function values_op(i,v) return v end + + --- return all the values of the table in arbitrary order + -- @within Extraction +--- @tab t A table ++-- @tab t A list-like table where the values are the values of the input table + function tablex.values(t) + assert_arg_iterable(1,t) + return makelist(tablex.pairmap(values_op,t)) +@@ -861,8 +930,8 @@ end + -- @tab t the table + -- @param value the value + -- @array[opt] exclude any tables to avoid searching +--- @usage search(_G,math.sin,{package.path}) == 'math.sin' + -- @return a fieldspec, e.g. 'a.b' or 'math.sin' ++-- @usage search(_G,math.sin,{package.path}) == 'math.sin' + function tablex.search (t,value,exclude) + assert_arg_iterable(1,t) + local tables = {[t]=true} +@@ -910,8 +979,11 @@ end + --- modifies a table to be read only. + -- This only offers weak protection. Tables can still be modified with + -- `table.insert` and `rawset`. ++-- ++-- *NOTE*: for Lua 5.1 length, pairs and ipairs will not work, since the ++-- equivalent metamethods are only available in Lua 5.2 and newer. + -- @tab t the table +--- @return the table read only. ++-- @return the table read only (a proxy). + function tablex.readonly(t) + local mt = { + __index=t, +diff --git a/extra/penlight/lua/pl/template.lua b/extra/penlight/lua/pl/template.lua +index 3de5eda..c10a1f6 100644 +--- a/extra/penlight/lua/pl/template.lua ++++ b/extra/penlight/lua/pl/template.lua +@@ -30,57 +30,72 @@ + + local utils = require 'pl.utils' + +-local append,format,strsub,strfind,strgsub = table.insert,string.format,string.sub,string.find,string.gsub ++local append, concat = table.insert, table.concat ++local format, strsub, strfind, strgsub, strrep = string.format, string.sub, string.find, string.gsub, string.rep + +-local APPENDER = "\n__R_size = __R_size + 1; __R_table[__R_size] = " ++local APPENDER = " __R_size = __R_size + 1; __R_table[__R_size] = " + ++-- When this function returns, `pieces` is guaranteed to hold a complete Lua ++-- statement, meaning that new statements can be appended without creating ++-- invalid Lua code. + local function parseDollarParen(pieces, chunk, exec_pat, newline) + local s = 1 + for term, executed, e in chunk:gmatch(exec_pat) do +- executed = '('..strsub(executed,2,-2)..')' +- append(pieces, APPENDER..format("%q", strsub(chunk,s, term - 1))) +- append(pieces, APPENDER..format("(%s or '')", executed)) ++ executed = '(' .. strsub(executed, 2, -2) .. ')' ++ append(pieces, APPENDER .. format("%q;", strsub(chunk, s, term - 1))) ++ append(pieces, APPENDER .. format("__tostring(%s or '');", executed)) + s = e + end +- local r ++ local remainder, newlines_removed + if newline then +- r = format("%q", strgsub(strsub(chunk,s),"\n","")) ++ remainder, newlines_removed = strgsub(strsub(chunk, s), "\n", "") + else +- r = format("%q", strsub(chunk,s)) ++ remainder, newlines_removed = strsub(chunk, s), 0 + end +- if r ~= '""' then +- append(pieces, APPENDER..r) ++ if remainder ~= "" then ++ append(pieces, APPENDER .. format("%q;", remainder)) ++ end ++ if newlines_removed > 0 then ++ append(pieces, strrep("\n", newlines_removed)) + end + end + +-local function parseHashLines(chunk,inline_escape,brackets,esc,newline) +- local exec_pat = "()"..inline_escape.."(%b"..brackets..")()" ++local function parseHashLines(chunk, inline_escape, brackets, esc, newline) ++ -- Escape special characters to avoid invalid expressions ++ inline_escape = utils.escape(inline_escape) ++ esc = utils.escape(esc) ++ ++ local exec_pat = "()" .. inline_escape .. "(%b" .. brackets .. ")()" + +- local esc_pat = esc.."+([^\n]*\n?)" +- local esc_pat1, esc_pat2 = "^"..esc_pat, "\n"..esc_pat +- local pieces, s = {"return function()\nlocal __R_size, __R_table = 0, {}", n = 1}, 1 ++ local esc_pat = esc .. "+([^\n]*\n?)" ++ local esc_pat1, esc_pat2 = "^" .. esc_pat, "\n" .. esc_pat ++ local pieces, s = {"return function() local __R_size, __R_table, __tostring = 0, {}, __tostring; "}, 1 + while true do +- local ss, e, lua = strfind(chunk,esc_pat1, s) ++ local _, e, lua = strfind(chunk, esc_pat1, s) + if not e then +- ss, e, lua = strfind(chunk,esc_pat2, s) +- parseDollarParen(pieces, strsub(chunk,s, ss), exec_pat, newline) ++ local ss ++ ss, e, lua = strfind(chunk, esc_pat2, s) ++ parseDollarParen(pieces, strsub(chunk, s, ss), exec_pat, newline) + if not e then break end + end +- append(pieces, "\n"..lua) ++ if strsub(lua, -1, -1) ~= "\n" then lua = lua .. "\n" end -- Ensure trailing newline ++ append(pieces, lua) ++ -- since `lua` ends with a newline, there is no danger of subsequent ++ -- statements being gobbled up by comments or being altered + s = e + 1 + end +- append(pieces, "\nreturn __R_table\nend") +- ++ append(pieces, "return __R_table; end") ++ + -- let's check for a special case where there is nothing to template, but it's + -- just a single static string + local short = false +- if (#pieces == 3) and (pieces[2]:find(APPENDER, 1, true) == 1) then +- pieces = { "return " .. pieces[2]:sub(#APPENDER+1,-1) } ++ if (#pieces == 3) and (strfind(pieces[2], APPENDER, 1, true) == 1) then ++ pieces = { "return " .. strsub(pieces[2], #APPENDER + 1, -1) } + short = true + end + -- if short == true, the generated function will not return a table of strings, + -- but a single string +- return table.concat(pieces), short ++ return concat(pieces), short + end + + local template = {} +@@ -88,33 +103,34 @@ local template = {} + --- expand the template using the specified environment. + -- This function will compile and render the template. For more performant + -- recurring usage use the two step approach by using `compile` and `ct:render`. +--- There are six special fields in the environment table `env` +--- +--- * `_parent`: continue looking up in this table (e.g. `_parent=_G`). +--- * `_brackets`: bracket pair that wraps inline Lua expressions, default is '()'. +--- * `_escape`: character marking Lua lines, default is '#' +--- * `_inline_escape`: character marking inline Lua expression, default is '$'. +--- * `_chunk_name`: chunk name for loaded templates, used if there +--- is an error in Lua code. Default is 'TMP'. +--- * `_debug`: if truthy, the generated code will be printed upon a render error +--- + -- @string str the template string +--- @tab[opt] env the environment +--- @return `rendered template + nil + source_code`, or `nil + error + source_code`. The last +--- return value (`source_code`) is only returned if the debug option is used. +-function template.substitute(str,env) ++-- @tparam[opt] table env the environment. This table has the following special fields: ++-- @tparam[opt=nil] table env._parent continue looking up in this table (e.g. `_parent=_G`). ++-- @tparam[opt="()"] string env._brackets bracket pair that wraps inline Lua expressions. ++-- @tparam[opt="#"] string env._escape character marking Lua lines. ++-- @tparam[opt="$"] string env._inline_escape character marking inline Lua expression. ++-- @tparam[opt="TMP"] string env._chunk_name chunk name for loaded templates, used if there ++-- is an error in Lua code. ++-- @tparam[opt=false] boolean env._debug if truthy, the generated code will be printed upon a render error. ++-- @treturn[1] string render result ++-- @treturn[1] nil ++-- @treturn[1] string source_code (only if '`env._debug`' was truthy). ++-- @treturn[2] nil ++-- @treturn[2] string error message ++-- @treturn[2] string source_code (only if '`env._debug`' was truthy). ++function template.substitute(str, env) + env = env or {} + local t, err = template.compile(str, { +- chunk_name = rawget(env,"_chunk_name"), +- escape = rawget(env,"_escape"), +- inline_escape = rawget(env,"_inline_escape"), +- inline_brackets = rawget(env,"_brackets"), +- newline = nil, +- debug = rawget(env,"_debug") ++ chunk_name = rawget(env, "_chunk_name"), ++ escape = rawget(env, "_escape"), ++ inline_escape = rawget(env, "_inline_escape"), ++ inline_brackets = rawget(env, "_brackets"), ++ newline = false, ++ debug = rawget(env, "_debug") + }) + if not t then return t, err end +- +- return t:render(env, rawget(env,"_parent"), rawget(env,"_debug")) ++ ++ return t:render(env, rawget(env, "_parent"), rawget(env, "_debug")) + end + + --- executes the previously compiled template and renders it. +@@ -123,12 +139,16 @@ end + -- @tab[opt] parent continue looking up in this table (e.g. `parent=_G`). + -- @bool[opt] db if thruthy, it will print the code upon a render error + -- (provided the template was compiled with the debug option). +--- @return `rendered template + nil + source_code`, or `nil + error + source_code`. The last return value +--- (`source_code`) is only returned if the template was compiled with the debug option. ++-- @treturn[1] string render result ++-- @treturn[1] nil ++-- @treturn[1] string source_code (only if '`env._debug`' was truthy). ++-- @treturn[2] nil ++-- @treturn[2] string error message ++-- @treturn[2] string source_code (only if '`env._debug`' was truthy). + -- @usage + -- local ct, err = template.compile(my_template) + -- local rendered , err = ct:render(my_env, parent) +-local render = function(self, env, parent, db) ++local function render(self, env, parent, db) + env = env or {} + if parent then -- parent is a bit silly, but for backward compatibility retained + setmetatable(env, {__index = parent}) +@@ -140,25 +160,27 @@ local render = function(self, env, parent, db) + if self.code and db then print(self.code) end + return nil, out, self.code + end +- return table.concat(out), nil, self.code ++ return concat(out), nil, self.code + end + + --- compiles the template. + -- Returns an object that can repeatedly be rendered without parsing/compiling +--- the template again. +--- The options passed in the `opts` table support the following options: +--- +--- * `chunk_name`: chunk name for loaded templates, used if there +--- is an error in Lua code. Default is 'TMP'. +--- * `escape`: character marking Lua lines, default is '#' +--- * `inline_escape`: character marking inline Lua expression, default is '$'. +--- * `inline_brackets`: bracket pair that wraps inline Lua expressions, default is '()'. +--- * `newline`: string to replace newline characters, default is `nil` (not replacing newlines). +--- * `debug`: if truthy, the generated source code will be retained within the compiled template object, default is `nil`. +--- +--- @string str the template string +--- @tab[opt] opts the compilation options to use +--- @return template object, or `nil + error + source_code` ++-- the template again. Preserves the line layout of the template so that line ++-- numbers in error messages should point to the correct lines in the source ++-- string. ++-- @tparam string str the template string ++-- @tparam[opt] table opts the compilation options to use. This table supports the following options: ++-- @tparam[opt="TMP"] string opts.chunk_name chunk name for loaded templates, used if there ++-- is an error in Lua code. ++-- @tparam[opt="#"] string opts.escape character marking Lua lines. ++-- @tparam[opt="$"] string opts.inline_escape character marking inline Lua expression. ++-- @tparam[opt="()"] string opts.inline_brackets bracket pair that wraps inline Lua expressions. ++-- @tparam[opt=false] boolean opts.newline if truthy, newlines will be stripped from text in the template. ++-- @tparam[opt=false] boolean opts.debug if truthy, the generated code will be printed upon a render error. ++-- @treturn[1] ct compiled template object ++-- @treturn[2] nil ++-- @treturn[2] string error message ++-- @treturn[2] string source_code + -- @usage + -- local ct, err = template.compile(my_template) + -- local rendered , err = ct:render(my_env, parent) +@@ -168,10 +190,10 @@ function template.compile(str, opts) + local escape = opts.escape or '#' + local inline_escape = opts.inline_escape or '$' + local inline_brackets = opts.inline_brackets or '()' +- +- local code, short = parseHashLines(str,inline_escape,inline_brackets,escape,opts.newline) +- local env = {} +- local fn, err = utils.load(code, chunk_name,'t',env) ++ ++ local code, short = parseHashLines(str, inline_escape, inline_brackets, escape, opts.newline) ++ local env = { __tostring = tostring } ++ local fn, err = utils.load(code, chunk_name, 't', env) + if not fn then return nil, err, code end + + if short then +diff --git a/extra/penlight/lua/pl/test.lua b/extra/penlight/lua/pl/test.lua +index c77eba7..694bbc5 100644 +--- a/extra/penlight/lua/pl/test.lua ++++ b/extra/penlight/lua/pl/test.lua +@@ -12,7 +12,7 @@ local tablex = require 'pl.tablex' + local utils = require 'pl.utils' + local pretty = require 'pl.pretty' + local path = require 'pl.path' +-local type,unpack = type,utils.pack ++local type,unpack,pack = type,utils.unpack,utils.pack + local clock = os.clock + local debug = require 'debug' + local io = io +@@ -91,7 +91,7 @@ function test.assertraise(fn,e,where) + else + ok, err = pcall(fn) + end +- if not err or err:match(e)==nil then ++ if ok or err:match(e)==nil then + complain (err,e,"these errors did not match",where) + end + end +@@ -112,7 +112,7 @@ end + -- tuple type -- + + local tuple_mt = { +- unpack = table.unpack ++ unpack = unpack + } + tuple_mt.__index = tuple_mt + +@@ -146,7 +146,7 @@ end + -- and there is an `unpack` method. + -- @usage asserteq(tuple( ('ab'):find 'a'), tuple(1,1)) + function test.tuple(...) +- return setmetatable(table.pack(...), tuple_mt) ++ return setmetatable(pack(...), tuple_mt) + end + + --- Time a function. Call the function a given number of times, and report the number of seconds taken, +diff --git a/extra/penlight/lua/pl/text.lua b/extra/penlight/lua/pl/text.lua +index 7e0e933..8b6a53f 100644 +--- a/extra/penlight/lua/pl/text.lua ++++ b/extra/penlight/lua/pl/text.lua +@@ -4,245 +4,23 @@ + -- libraries, see string.Template). It also provides similar functions to those + -- found in the textwrap module. + -- +--- See @{03-strings.md.String_Templates|the Guide}. +--- +--- Calling `text.format_operator()` overloads the % operator for strings to give Python/Ruby style formated output. +--- This is extended to also do template-like substitution for map-like data. ++-- IMPORTANT: this module has been deprecated and will be removed in a future ++-- version (2.0). The contents of this module have moved to the `pl.stringx` ++-- module. + -- +--- > require 'pl.text'.format_operator() +--- > = '%s = %5.3f' % {'PI',math.pi} +--- PI = 3.142 +--- > = '$name = $value' % {name='dog',value='Pluto'} +--- dog = Pluto ++-- See @{03-strings.md.String_Templates|the Guide}. + -- +--- Dependencies: `pl.utils`, `pl.types` ++-- Dependencies: `pl.stringx`, `pl.utils` + -- @module pl.text + +-local gsub = string.gsub +-local concat,append = table.concat,table.insert +-local utils = require 'pl.utils' +-local bind1,usplit,assert_arg = utils.bind1,utils.split,utils.assert_arg +-local is_callable = require 'pl.types'.is_callable +-local unpack = utils.unpack +- +-local function makelist(l) +- return setmetatable(l, require('pl.List')) +-end +- +-local function lstrip(str) return (str:gsub('^%s+','')) end +-local function strip(str) return (lstrip(str):gsub('%s+$','')) end +-local function split(s,delim) return makelist(usplit(s,delim)) end +- +-local function imap(f,t,...) +- local res = {} +- for i = 1,#t do res[i] = f(t[i],...) end +- return res +-end +- +---[[ +-module ('pl.text',utils._module) +-]] +- +-local text = {} +- +-local function _indent (s,sp) +- local sl = split(s,'\n') +- return concat(imap(bind1('..',sp),sl),'\n')..'\n' +-end +- +---- indent a multiline string. +--- @param s the string +--- @param n the size of the indent +--- @param ch the character to use when indenting (default ' ') +--- @return indented string +-function text.indent (s,n,ch) +- assert_arg(1,s,'string') +- assert_arg(2,n,'number') +- return _indent(s,string.rep(ch or ' ',n)) +-end +- +---- dedent a multiline string by removing any initial indent. +--- useful when working with [[..]] strings. +--- @param s the string +--- @return a string with initial indent zero. +-function text.dedent (s) +- assert_arg(1,s,'string') +- local sl = split(s,'\n') +- local i1,i2 = (#sl>0 and sl[1] or ''):find('^%s*') +- sl = imap(string.sub,sl,i2+1) +- return concat(sl,'\n')..'\n' +-end +- +---- format a paragraph into lines so that they fit into a line width. +--- It will not break long words, so lines can be over the length +--- to that extent. +--- @param s the string +--- @param width the margin width, default 70 +--- @return a list of lines +-function text.wrap (s,width) +- assert_arg(1,s,'string') +- width = width or 70 +- s = s:gsub('\n',' ') +- local i,nxt = 1 +- local lines,line = {} +- while i < #s do +- nxt = i+width +- if s:find("[%w']",nxt) then -- inside a word +- nxt = s:find('%W',nxt+1) -- so find word boundary +- end +- line = s:sub(i,nxt) +- i = i + #line +- append(lines,strip(line)) +- end +- return makelist(lines) +-end +- +---- format a paragraph so that it fits into a line width. +--- @param s the string +--- @param width the margin width, default 70 +--- @return a string +--- @see wrap +-function text.fill (s,width) +- return concat(text.wrap(s,width),'\n') .. '\n' +-end +- +-local Template = {} +-text.Template = Template +-Template.__index = Template +-setmetatable(Template, { +- __call = function(obj,tmpl) +- return Template.new(tmpl) +- end}) +- +-function Template.new(tmpl) +- assert_arg(1,tmpl,'string') +- local res = {} +- res.tmpl = tmpl +- setmetatable(res,Template) +- return res +-end +- +-local function _substitute(s,tbl,safe) +- local subst +- if is_callable(tbl) then +- subst = tbl +- else +- function subst(f) +- local s = tbl[f] +- if not s then +- if safe then +- return f +- else +- error("not present in table "..f) +- end +- else +- return s +- end +- end +- end +- local res = gsub(s,'%${([%w_]+)}',subst) +- return (gsub(res,'%$([%w_]+)',subst)) +-end +- +---- substitute values into a template, throwing an error. +--- This will throw an error if no name is found. +--- @param tbl a table of name-value pairs. +-function Template:substitute(tbl) +- assert_arg(1,tbl,'table') +- return _substitute(self.tmpl,tbl,false) +-end +- +---- substitute values into a template. +--- This version just passes unknown names through. +--- @param tbl a table of name-value pairs. +-function Template:safe_substitute(tbl) +- assert_arg(1,tbl,'table') +- return _substitute(self.tmpl,tbl,true) +-end +- +---- substitute values into a template, preserving indentation.
    +--- If the value is a multiline string _or_ a template, it will insert +--- the lines at the correct indentation.
    +--- Furthermore, if a template, then that template will be subsituted +--- using the same table. +--- @param tbl a table of name-value pairs. +-function Template:indent_substitute(tbl) +- assert_arg(1,tbl,'table') +- if not self.strings then +- self.strings = split(self.tmpl,'\n') +- end +- -- the idea is to substitute line by line, grabbing any spaces as +- -- well as the $var. If the value to be substituted contains newlines, +- -- then we split that into lines and adjust the indent before inserting. +- local function subst(line) +- return line:gsub('(%s*)%$([%w_]+)',function(sp,f) +- local subtmpl +- local s = tbl[f] +- if not s then error("not present in table "..f) end +- if getmetatable(s) == Template then +- subtmpl = s +- s = s.tmpl +- else +- s = tostring(s) +- end +- if s:find '\n' then +- s = _indent(s,sp) +- end +- if subtmpl then return _substitute(s,tbl) +- else return s +- end +- end) +- end +- local lines = imap(subst,self.strings) +- return concat(lines,'\n')..'\n' +-end +- +-------- Python-style formatting operator ------ +--- (see the lua-users wiki) -- +- +-function text.format_operator() +- +- local format = string.format +- +- -- a more forgiving version of string.format, which applies +- -- tostring() to any value with a %s format. +- local function formatx (fmt,...) +- local args = {...} +- local i = 1 +- for p in fmt:gmatch('%%.') do +- if p == '%s' and type(args[i]) ~= 'string' then +- args[i] = tostring(args[i]) +- end +- i = i + 1 +- end +- return format(fmt,unpack(args)) +- end +- +- local function basic_subst(s,t) +- return (s:gsub('%$([%w_]+)',t)) +- end ++local utils = require("pl.utils") + +- -- Note this goes further than the original, and will allow these cases: +- -- 1. a single value +- -- 2. a list of values +- -- 3. a map of var=value pairs +- -- 4. a function, as in gsub +- -- For the second two cases, it uses $-variable substituion. +- getmetatable("").__mod = function(a, b) +- if b == nil then +- return a +- elseif type(b) == "table" and getmetatable(b) == nil then +- if #b == 0 then -- assume a map-like table +- return _substitute(a,b,true) +- else +- return formatx(a,unpack(b)) +- end +- elseif type(b) == 'function' then +- return basic_subst(a,b) +- else +- return formatx(a,b) +- end +- end +-end ++utils.raise_deprecation { ++ source = "Penlight " .. utils._VERSION, ++ message = "the contents of module 'pl.text' has moved into 'pl.stringx'", ++ version_removed = "2.0.0", ++ deprecated_after = "1.11.0", ++ no_trace = true, ++} + +-return text ++return require "pl.stringx" +diff --git a/extra/penlight/lua/pl/types.lua b/extra/penlight/lua/pl/types.lua +index f4ab236..ce82efa 100644 +--- a/extra/penlight/lua/pl/types.lua ++++ b/extra/penlight/lua/pl/types.lua +@@ -4,28 +4,47 @@ + -- @module pl.types + + local utils = require 'pl.utils' ++local math_ceil = math.ceil ++local assert_arg = utils.assert_arg + local types = {} + +---- is the object either a function or a callable object?. +--- @param obj Object to check. +-function types.is_callable (obj) +- return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call and true ++do ++ -- we prefer debug.getmetatable, but only if available ++ local gmt = (debug or {}).getmetatable or getmetatable ++ ++ --- is the object either a function or a callable object?. ++ -- @param obj Object to check. ++ function types.is_callable (obj) ++ if type(obj) == 'function' then ++ return true ++ end ++ local mt = gmt(obj) ++ if not mt then ++ return false ++ end ++ return type(rawget(mt, "__call")) == "function" ++ end + end + + --- is the object of the specified type?. +--- If the type is a string, then use type, otherwise compare with metatable ++-- If the type is a string, then use type, otherwise compare with metatable. ++-- ++-- NOTE: this function is imported from `utils.is_type`. + -- @param obj An object to check +--- @param tp String of what type it should be ++-- @param tp The expected type + -- @function is_type ++-- @see utils.is_type + types.is_type = utils.is_type + + local fileMT = getmetatable(io.stdout) + + --- a string representation of a type. +--- For tables with metatables, we assume that the metatable has a `_name` +--- field. Knows about Lua file objects. ++-- For tables and userdata with metatables, we assume that the metatable has a `_name` ++-- field. If the field is not present it will return 'unknown table' or ++-- 'unknown userdata'. ++-- Lua file objects return the type 'file'. + -- @param obj an object +--- @return a string like 'number', 'table' or 'List' ++-- @return a string like 'number', 'table', 'file' or 'List' + function types.type (obj) + local t = type(obj) + if t == 'table' or t == 'userdata' then +@@ -35,6 +54,7 @@ function types.type (obj) + elseif mt == nil then + return t + else ++ -- TODO: the "unknown" is weird, it should just return the type + return mt._name or "unknown "..t + end + else +@@ -45,22 +65,32 @@ end + --- is this number an integer? + -- @param x a number + -- @raise error if x is not a number ++-- @return boolean + function types.is_integer (x) +- return math.ceil(x)==x ++ return math_ceil(x)==x + end + + --- Check if the object is "empty". +--- An object is considered empty if it is nil, a table with out any items (key, +--- value pairs or indexes), or a string with no content (""). ++-- An object is considered empty if it is: ++-- ++-- - `nil` ++-- - a table without any items (key-value pairs or indexes) ++-- - a string with no content ("") ++-- - not a nil/table/string + -- @param o The object to check if it is empty. + -- @param ignore_spaces If the object is a string and this is true the string is +--- considered empty is it only contains spaces. +--- @return true if the object is empty, otherwise false. ++-- considered empty if it only contains spaces. ++-- @return `true` if the object is empty, otherwise a falsy value. + function types.is_empty(o, ignore_spaces) +- if o == nil or (type(o) == "table" and not next(o)) or (type(o) == "string" and (o == "" or (ignore_spaces and o:match("^%s+$")))) then ++ if o == nil then ++ return true ++ elseif type(o) == "table" then ++ return next(o) == nil ++ elseif type(o) == "string" then ++ return o == "" or (not not ignore_spaces and (not not o:find("^%s+$"))) ++ else + return true + end +- return false + end + + local function check_meta (val) +@@ -69,7 +99,14 @@ local function check_meta (val) + end + + --- is an object 'array-like'? ++-- An object is array like if: ++-- ++-- - it is a table, or ++-- - it has a metatable with `__len` and `__index` methods ++-- ++-- NOTE: since `__len` is 5.2+, on 5.1 is usually returns `false` for userdata + -- @param val any value. ++-- @return `true` if the object is array-like, otherwise a falsy value. + function types.is_indexable (val) + local mt = check_meta(val) + if mt == true then return true end +@@ -77,7 +114,14 @@ function types.is_indexable (val) + end + + --- can an object be iterated over with `pairs`? ++-- An object is iterable if: ++-- ++-- - it is a table, or ++-- - it has a metatable with a `__pairs` meta method ++-- ++-- NOTE: since `__pairs` is 5.2+, on 5.1 is usually returns `false` for userdata + -- @param val any value. ++-- @return `true` if the object is iterable, otherwise a falsy value. + function types.is_iterable (val) + local mt = check_meta(val) + if mt == true then return true end +@@ -85,20 +129,27 @@ function types.is_iterable (val) + end + + --- can an object accept new key/pair values? ++-- An object is iterable if: ++-- ++-- - it is a table, or ++-- - it has a metatable with a `__newindex` meta method ++-- + -- @param val any value. ++-- @return `true` if the object is writeable, otherwise a falsy value. + function types.is_writeable (val) + local mt = check_meta(val) + if mt == true then return true end + return mt and mt.__newindex and true + end + +--- Strings that should evaluate to true. ++-- Strings that should evaluate to true. -- TODO: add on/off ??? + local trues = { yes=true, y=true, ["true"]=true, t=true, ["1"]=true } + -- Conditions types should evaluate to true. + local true_types = { + boolean=function(o, true_strs, check_objs) return o end, + string=function(o, true_strs, check_objs) +- if trues[o:lower()] then ++ o = o:lower() ++ if trues[o] then + return true + end + -- Check alternative user provided strings. +@@ -119,18 +170,17 @@ local true_types = { + -- * string: 'yes', 'y', 'true', 't', '1' or additional strings specified by `true_strs`. + -- * number: Any non-zero value. + -- * table: Is not empty and `check_objs` is true. +--- * object: Is not `nil` and `check_objs` is true. ++-- * everything else: Is not `nil` and `check_objs` is true. + -- + -- @param o The object to evaluate. + -- @param[opt] true_strs optional Additional strings that when matched should evaluate to true. Comparison is case insensitive. + -- This should be a List of strings. E.g. "ja" to support German. +--- @param[opt] check_objs True if objects should be evaluated. Default is to evaluate objects as true if not nil +--- or if it is a table and it is not empty. ++-- @param[opt] check_objs True if objects should be evaluated. + -- @return true if the input evaluates to true, otherwise false. + function types.to_bool(o, true_strs, check_objs) + local true_func + if true_strs then +- utils.assert_arg(2, true_strs, "table") ++ assert_arg(2, true_strs, "table") + end + true_func = true_types[type(o)] + if true_func then +diff --git a/extra/penlight/lua/pl/url.lua b/extra/penlight/lua/pl/url.lua +index 9628871..8c7cfeb 100644 +--- a/extra/penlight/lua/pl/url.lua ++++ b/extra/penlight/lua/pl/url.lua +@@ -11,6 +11,7 @@ end + --- Quote the url, replacing special characters using the '%xx' escape. + -- @string s the string + -- @bool quote_plus Also escape slashes and replace spaces by plus signs. ++-- @return The quoted string, or if `s` wasn't a string, just plain unaltered `s`. + function url.quote(s, quote_plus) + if type(s) ~= "string" then + return s +@@ -34,6 +35,7 @@ end + + --- Unquote the url, replacing '%xx' escapes and plus signs. + -- @string s the string ++-- @return The unquoted string, or if `s` wasn't a string, just plain unaltered `s`. + function url.unquote(s) + if type(s) ~= "string" then + return s +diff --git a/extra/penlight/lua/pl/utils.lua b/extra/penlight/lua/pl/utils.lua +index 5fd11a2..ca2a022 100644 +--- a/extra/penlight/lua/pl/utils.lua ++++ b/extra/penlight/lua/pl/utils.lua +@@ -1,120 +1,529 @@ + --- Generally useful routines. + -- See @{01-introduction.md.Generally_useful_functions|the Guide}. + -- +--- Dependencies: `pl.compat` ++-- Dependencies: `pl.compat`, all exported fields and functions from ++-- `pl.compat` are also available in this module. + -- + -- @module pl.utils + local format = string.format + local compat = require 'pl.compat' + local stdout = io.stdout + local append = table.insert +-local unpack = rawget(_G,'unpack') or rawget(table,'unpack') +- +-local utils = { +- _VERSION = "1.5.2", +- lua51 = compat.lua51, +- setfenv = compat.setfenv, +- getfenv = compat.getfenv, +- load = compat.load, +- execute = compat.execute, +- dir_separator = compat.dir_separator, +- is_windows = compat.is_windows, +- unpack = unpack ++local concat = table.concat ++local _unpack = table.unpack -- always injected by 'compat' ++local find = string.find ++local sub = string.sub ++local next = next ++local floor = math.floor ++ ++local is_windows = compat.is_windows ++local err_mode = 'default' ++local raise ++local operators ++local _function_factories = {} ++ ++ ++local utils = { _VERSION = "1.14.0" } ++ ++for k, v in pairs(compat) do utils[k] = v end ++ ++--- Some standard patterns ++-- @table patterns ++utils.patterns = { ++ FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', -- floating point number ++ INTEGER = '[+%-%d]%d*', -- integer number ++ IDEN = '[%a_][%w_]*', -- identifier ++ FILE = '[%a%.\\][:%][%w%._%-\\]*', -- file + } + +---- end this program gracefully. +--- @param code The exit code or a message to be printed +--- @param ... extra arguments for message's format' +--- @see utils.fprintf +-function utils.quit(code,...) +- if type(code) == 'string' then +- utils.fprintf(io.stderr,code,...) +- code = -1 +- else +- utils.fprintf(io.stderr,...) +- end +- io.stderr:write('\n') +- os.exit(code) ++ ++--- Standard meta-tables as used by other Penlight modules ++-- @table stdmt ++-- @field List the List metatable ++-- @field Map the Map metatable ++-- @field Set the Set metatable ++-- @field MultiMap the MultiMap metatable ++utils.stdmt = { ++ List = {_name='List'}, ++ Map = {_name='Map'}, ++ Set = {_name='Set'}, ++ MultiMap = {_name='MultiMap'}, ++} ++ ++ ++--- pack an argument list into a table. ++-- @param ... any arguments ++-- @return a table with field `n` set to the length ++-- @function utils.pack ++-- @see compat.pack ++-- @see utils.npairs ++-- @see utils.unpack ++utils.pack = table.pack -- added here to be symmetrical with unpack ++ ++--- unpack a table and return its contents. ++-- ++-- NOTE: this implementation differs from the Lua implementation in the way ++-- that this one DOES honor the `n` field in the table `t`, such that it is 'nil-safe'. ++-- @param t table to unpack ++-- @param[opt] i index from which to start unpacking, defaults to 1 ++-- @param[opt] j index of the last element to unpack, defaults to `t.n` or else `#t` ++-- @return multiple return values from the table ++-- @function utils.unpack ++-- @see compat.unpack ++-- @see utils.pack ++-- @see utils.npairs ++-- @usage ++-- local t = table.pack(nil, nil, nil, 4) ++-- local a, b, c, d = table.unpack(t) -- this `unpack` is NOT nil-safe, so d == nil ++-- ++-- local a, b, c, d = utils.unpack(t) -- this is nil-safe, so d == 4 ++function utils.unpack(t, i, j) ++ return _unpack(t, i or 1, j or t.n or #t) + end + + --- print an arbitrary number of arguments using a format. +--- @param fmt The format (see string.format) ++-- Output will be sent to `stdout`. ++-- @param fmt The format (see `string.format`) + -- @param ... Extra arguments for format +-function utils.printf(fmt,...) +- utils.assert_string(1,fmt) +- utils.fprintf(stdout,fmt,...) ++function utils.printf(fmt, ...) ++ utils.assert_string(1, fmt) ++ utils.fprintf(stdout, fmt, ...) + end + + --- write an arbitrary number of arguments to a file using a format. + -- @param f File handle to write to. +--- @param fmt The format (see string.format). ++-- @param fmt The format (see `string.format`). + -- @param ... Extra arguments for format + function utils.fprintf(f,fmt,...) + utils.assert_string(2,fmt) + f:write(format(fmt,...)) + end + +-local function import_symbol(T,k,v,libname) +- local key = rawget(T,k) +- -- warn about collisions! +- if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then +- utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k) +- return ++do ++ local function import_symbol(T,k,v,libname) ++ local key = rawget(T,k) ++ -- warn about collisions! ++ if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then ++ utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k) ++ return ++ end ++ rawset(T,k,v) ++ end ++ ++ local function lookup_lib(T,t) ++ for k,v in pairs(T) do ++ if v == t then return k end ++ end ++ return '?' ++ end ++ ++ local already_imported = {} ++ ++ --- take a table and 'inject' it into the local namespace. ++ -- @param t The table (table), or module name (string), defaults to this `utils` module table ++ -- @param T An optional destination table (defaults to callers environment) ++ function utils.import(t,T) ++ T = T or _G ++ t = t or utils ++ if type(t) == 'string' then ++ t = require (t) ++ end ++ local libname = lookup_lib(T,t) ++ if already_imported[t] then return end ++ already_imported[t] = libname ++ for k,v in pairs(t) do ++ import_symbol(T,k,v,libname) ++ end + end +- rawset(T,k,v) + end + +-local function lookup_lib(T,t) +- for k,v in pairs(T) do +- if v == t then return k end ++--- return either of two values, depending on a condition. ++-- @param cond A condition ++-- @param value1 Value returned if cond is truthy ++-- @param value2 Value returned if cond is falsy ++function utils.choose(cond, value1, value2) ++ if cond then ++ return value1 ++ else ++ return value2 ++ end ++end ++ ++--- convert an array of values to strings. ++-- @param t a list-like table ++-- @param[opt] temp (table) buffer to use, otherwise allocate ++-- @param[opt] tostr custom tostring function, called with (value,index). Defaults to `tostring`. ++-- @return the converted buffer ++function utils.array_tostring (t,temp,tostr) ++ temp, tostr = temp or {}, tostr or tostring ++ for i = 1,#t do ++ temp[i] = tostr(t[i],i) + end +- return '?' ++ return temp ++end ++ ++ ++ ++--- is the object of the specified type? ++-- If the type is a string, then use type, otherwise compare with metatable ++-- @param obj An object to check ++-- @param tp String of what type it should be ++-- @return boolean ++-- @usage utils.is_type("hello world", "string") --> true ++-- -- or check metatable ++-- local my_mt = {} ++-- local my_obj = setmetatable(my_obj, my_mt) ++-- utils.is_type(my_obj, my_mt) --> true ++function utils.is_type (obj,tp) ++ if type(tp) == 'string' then return type(obj) == tp end ++ local mt = getmetatable(obj) ++ return tp == mt + end + +-local already_imported = {} + +---- take a table and 'inject' it into the local namespace. +--- @param t The Table +--- @param T An optional destination table (defaults to callers environment) +-function utils.import(t,T) +- T = T or _G +- t = t or utils +- if type(t) == 'string' then +- t = require (t) ++ ++--- an iterator with indices, similar to `ipairs`, but with a range. ++-- This is a nil-safe index based iterator that will return `nil` when there ++-- is a hole in a list. To be safe ensure that table `t.n` contains the length. ++-- @tparam table t the table to iterate over ++-- @tparam[opt=1] integer i_start start index ++-- @tparam[opt=t.n or #t] integer i_end end index ++-- @tparam[opt=1] integer step step size ++-- @treturn integer index ++-- @treturn any value at index (which can be `nil`!) ++-- @see utils.pack ++-- @see utils.unpack ++-- @usage ++-- local t = utils.pack(nil, 123, nil) -- adds an `n` field when packing ++-- ++-- for i, v in utils.npairs(t, 2) do -- start at index 2 ++-- t[i] = tostring(t[i]) ++-- end ++-- ++-- -- t = { n = 3, [2] = "123", [3] = "nil" } ++function utils.npairs(t, i_start, i_end, step) ++ step = step or 1 ++ if step == 0 then ++ error("iterator step-size cannot be 0", 2) ++ end ++ local i = (i_start or 1) - step ++ i_end = i_end or t.n or #t ++ if step < 0 then ++ return function() ++ i = i + step ++ if i < i_end then ++ return nil ++ end ++ return i, t[i] + end +- local libname = lookup_lib(T,t) +- if already_imported[t] then return end +- already_imported[t] = libname +- for k,v in pairs(t) do +- import_symbol(T,k,v,libname) ++ ++ else ++ return function() ++ i = i + step ++ if i > i_end then ++ return nil ++ end ++ return i, t[i] + end ++ end + end + +-utils.patterns = { +- FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', +- INTEGER = '[+%-%d]%d*', +- IDEN = '[%a_][%w_]*', +- FILE = '[%a%.\\][:%][%w%._%-\\]*' +-} + +---- escape any 'magic' characters in a string +--- @param s The input string +-function utils.escape(s) +- utils.assert_string(1,s) +- return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) ++ ++--- an iterator over all non-integer keys (inverse of `ipairs`). ++-- It will skip any key that is an integer number, so negative indices or an ++-- array with holes will not return those either (so it returns slightly less than ++-- 'the inverse of `ipairs`'). ++-- ++-- This uses `pairs` under the hood, so any value that is iterable using `pairs` ++-- will work with this function. ++-- @tparam table t the table to iterate over ++-- @treturn key ++-- @treturn value ++-- @usage ++-- local t = { ++-- "hello", ++-- "world", ++-- hello = "hallo", ++-- world = "Welt", ++-- } ++-- ++-- for k, v in utils.kpairs(t) do ++-- print("German: ", v) ++-- end ++-- ++-- -- output; ++-- -- German: hallo ++-- -- German: Welt ++function utils.kpairs(t) ++ local index ++ return function() ++ local value ++ while true do ++ index, value = next(t, index) ++ if type(index) ~= "number" or floor(index) ~= index then ++ break ++ end ++ end ++ return index, value ++ end + end + +---- return either of two values, depending on a condition. +--- @param cond A condition +--- @param value1 Value returned if cond is true +--- @param value2 Value returned if cond is false (can be optional) +-function utils.choose(cond,value1,value2) +- if cond then return value1 +- else return value2 ++ ++ ++--- Error handling ++-- @section Error-handling ++ ++--- assert that the given argument is in fact of the correct type. ++-- @param n argument index ++-- @param val the value ++-- @param tp the type ++-- @param verify an optional verification function ++-- @param msg an optional custom message ++-- @param lev optional stack position for trace, default 2 ++-- @return the validated value ++-- @raise if `val` is not the correct type ++-- @usage ++-- local param1 = assert_arg(1,"hello",'table') --> error: argument 1 expected a 'table', got a 'string' ++-- local param4 = assert_arg(4,'!@#$%^&*','string',path.isdir,'not a directory') ++-- --> error: argument 4: '!@#$%^&*' not a directory ++function utils.assert_arg (n,val,tp,verify,msg,lev) ++ if type(val) ~= tp then ++ error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2) + end ++ if verify and not verify(val) then ++ error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) ++ end ++ return val + end + +-local raise ++--- creates an Enum or constants lookup table for improved error handling. ++-- This helps prevent magic strings in code by throwing errors for accessing ++-- non-existing values, and/or converting strings/identifiers to other values. ++-- ++-- Calling on the object does the same, but returns a soft error; `nil + err`, if ++-- the call is successful (the key exists), it will return the value. ++-- ++-- When calling with varargs or an array the values will be equal to the keys. ++-- The enum object is read-only. ++-- @tparam table|vararg ... the input for the Enum. If varargs or an array then the ++-- values in the Enum will be equal to the names (must be strings), if a hash-table ++-- then values remain (any type), and the keys must be strings. ++-- @return Enum object (read-only table/object) ++-- @usage -- Enum access at runtime ++-- local obj = {} ++-- obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT") ++-- ++-- if current_movement == obj.MOVEMENT.FORWARD then ++-- -- do something ++-- ++-- elseif current_movement == obj.MOVEMENT.REVERES then ++-- -- throws error due to typo 'REVERES', so a silent mistake becomes a hard error ++-- -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')" ++-- ++-- end ++-- @usage -- standardized error codes ++-- local obj = { ++-- ERR = utils.enum { ++-- NOT_FOUND = "the item was not found", ++-- OUT_OF_BOUNDS = "the index is outside the allowed range" ++-- }, ++-- ++-- some_method = function(self) ++-- return nil, self.ERR.OUT_OF_BOUNDS ++-- end, ++-- } ++-- ++-- local result, err = obj:some_method() ++-- if not result then ++-- if err == obj.ERR.NOT_FOUND then ++-- -- check on error code, not magic strings ++-- ++-- else ++-- -- return the error description, contained in the constant ++-- return nil, "error: "..err -- "error: the index is outside the allowed range" ++-- end ++-- end ++-- @usage -- validating/converting user-input ++-- local color = "purple" ++-- local ansi_colors = utils.enum { ++-- black = 30, ++-- red = 31, ++-- green = 32, ++-- } ++-- local color_code, err = ansi_colors(color) -- calling on the object, returns the value from the enum ++-- if not color_code then ++-- print("bad 'color', " .. err) ++-- -- "bad 'color', 'purple' is not a valid value (expected one of: 'black', 'red', 'green')" ++-- os.exit(1) ++-- end ++function utils.enum(...) ++ local first = select(1, ...) ++ local enum = {} ++ local lst ++ ++ if type(first) ~= "table" then ++ -- vararg with strings ++ lst = utils.pack(...) ++ for i, value in utils.npairs(lst) do ++ utils.assert_arg(i, value, "string") ++ enum[value] = value ++ end ++ ++ else ++ -- table/array with values ++ utils.assert_arg(1, first, "table") ++ lst = {} ++ -- first add array part ++ for i, value in ipairs(first) do ++ if type(value) ~= "string" then ++ error(("expected 'string' but got '%s' at index %d"):format(type(value), i), 2) ++ end ++ lst[i] = value ++ enum[value] = value ++ end ++ -- add key-ed part ++ for key, value in utils.kpairs(first) do ++ if type(key) ~= "string" then ++ error(("expected key to be 'string' but got '%s'"):format(type(key)), 2) ++ end ++ if enum[key] then ++ error(("duplicate entry in array and hash part: '%s'"):format(key), 2) ++ end ++ enum[key] = value ++ lst[#lst+1] = key ++ end ++ end ++ ++ if not lst[1] then ++ error("expected at least 1 entry", 2) ++ end ++ ++ local valid = "(expected one of: '" .. concat(lst, "', '") .. "')" ++ setmetatable(enum, { ++ __index = function(self, key) ++ error(("'%s' is not a valid value %s"):format(tostring(key), valid), 2) ++ end, ++ __newindex = function(self, key, value) ++ error("the Enum object is read-only", 2) ++ end, ++ __call = function(self, key) ++ if type(key) == "string" then ++ local v = rawget(self, key) ++ if v ~= nil then ++ return v ++ end ++ end ++ return nil, ("'%s' is not a valid value %s"):format(tostring(key), valid) ++ end ++ }) ++ ++ return enum ++end ++ ++ ++--- process a function argument. ++-- This is used throughout Penlight and defines what is meant by a function: ++-- Something that is callable, or an operator string as defined by pl.operator, ++-- such as '>' or '#'. If a function factory has been registered for the type, it will ++-- be called to get the function. ++-- @param idx argument index ++-- @param f a function, operator string, or callable object ++-- @param msg optional error message ++-- @return a callable ++-- @raise if idx is not a number or if f is not callable ++function utils.function_arg (idx,f,msg) ++ utils.assert_arg(1,idx,'number') ++ local tp = type(f) ++ if tp == 'function' then return f end -- no worries! ++ -- ok, a string can correspond to an operator (like '==') ++ if tp == 'string' then ++ if not operators then operators = require 'pl.operator'.optable end ++ local fn = operators[f] ++ if fn then return fn end ++ local fn, err = utils.string_lambda(f) ++ if not fn then error(err..': '..f) end ++ return fn ++ elseif tp == 'table' or tp == 'userdata' then ++ local mt = getmetatable(f) ++ if not mt then error('not a callable object',2) end ++ local ff = _function_factories[mt] ++ if not ff then ++ if not mt.__call then error('not a callable object',2) end ++ return f ++ else ++ return ff(f) -- we have a function factory for this type! ++ end ++ end ++ if not msg then msg = " must be callable" end ++ if idx > 0 then ++ error("argument "..idx..": "..msg,2) ++ else ++ error(msg,2) ++ end ++end ++ ++ ++--- assert the common case that the argument is a string. ++-- @param n argument index ++-- @param val a value that must be a string ++-- @return the validated value ++-- @raise val must be a string ++-- @usage ++-- local val = 42 ++-- local param2 = utils.assert_string(2, val) --> error: argument 2 expected a 'string', got a 'number' ++function utils.assert_string (n, val) ++ return utils.assert_arg(n,val,'string',nil,nil,3) ++end ++ ++--- control the error strategy used by Penlight. ++-- This is a global setting that controls how `utils.raise` behaves: ++-- ++-- - 'default': return `nil + error` (this is the default) ++-- - 'error': throw a Lua error ++-- - 'quit': exit the program ++-- ++-- @param mode either 'default', 'quit' or 'error' ++-- @see utils.raise ++function utils.on_error (mode) ++ mode = tostring(mode) ++ if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then ++ err_mode = mode ++ else ++ -- fail loudly ++ local err = "Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'" ++ if err_mode == 'default' then ++ error(err, 2) -- even in 'default' mode fail loud in this case ++ end ++ raise(err) ++ end ++end ++ ++--- used by Penlight functions to return errors. Its global behaviour is controlled ++-- by `utils.on_error`. ++-- To use this function you MUST use it in conjunction with `return`, since it might ++-- return `nil + error`. ++-- @param err the error string. ++-- @see utils.on_error ++-- @usage ++-- if some_condition then ++-- return utils.raise("some condition was not met") -- MUST use 'return'! ++-- end ++function utils.raise (err) ++ if err_mode == 'default' then ++ return nil, err ++ elseif err_mode == 'quit' then ++ return utils.quit(err) ++ else ++ error(err, 2) ++ end ++end ++raise = utils.raise ++ ++ ++ ++--- File handling ++-- @section files + + --- return the contents of a file as a string + -- @param filename The file path +@@ -124,7 +533,7 @@ function utils.readfile(filename,is_bin) + local mode = is_bin and 'b' or '' + utils.assert_string(1,filename) + local f,open_err = io.open(filename,'r'..mode) +- if not f then return utils.raise (open_err) end ++ if not f then return raise (open_err) end + local res,read_err = f:read('*a') + f:close() + if not res then +@@ -148,15 +557,20 @@ function utils.writefile(filename,str,is_bin) + utils.assert_string(2,str) + local f,err = io.open(filename,'w'..mode) + if not f then return raise(err) end +- f:write(str) ++ local ok, write_err = f:write(str) + f:close() ++ if not ok then ++ -- Errors in io.open have "filename: " prefix, ++ -- error in file:write don't, add it. ++ return raise (filename..": "..write_err) ++ end + return true + end + + --- return the contents of a file as a list of lines + -- @param filename The file path + -- @return file contents as a table +--- @raise errror if filename is not a string ++-- @raise error if filename is not a string + function utils.readlines(filename) + utils.assert_string(1,filename) + local f,err = io.open(filename,'r') +@@ -169,71 +583,64 @@ function utils.readlines(filename) + return res + end + +---- split a string into a list of strings separated by a delimiter. +--- @param s The input string +--- @param re A Lua string pattern; defaults to '%s+' +--- @param plain don't use Lua patterns +--- @param n optional maximum number of splits +--- @return a list-like table +--- @raise error if s is not a string +-function utils.split(s,re,plain,n) +- utils.assert_string(1,s) +- local find,sub,append = string.find, string.sub, table.insert +- local i1,ls = 1,{} +- if not re then re = '%s+' end +- if re == '' then return {s} end +- while true do +- local i2,i3 = find(s,re,i1,plain) +- if not i2 then +- local last = sub(s,i1) +- if last ~= '' then append(ls,last) end +- if #ls == 1 and ls[1] == '' then +- return {} +- else +- return ls +- end +- end +- append(ls,sub(s,i1,i2-1)) +- if n and #ls == n then +- ls[#ls] = sub(s,i1) +- return ls +- end +- i1 = i3+1 +- end +-end ++--- OS functions ++-- @section OS-functions + +---- split a string into a number of values. +--- @param s the string +--- @param re the delimiter, default space +--- @return n values +--- @usage first,next = splitv('jane:doe',':') +--- @see split +-function utils.splitv (s,re) +- return unpack(utils.split(s,re)) +-end ++--- Execute a shell command. ++-- This function is a copy of `compat.execute`. ++-- @class function ++-- @name utils.execute + +---- convert an array of values to strings. +--- @param t a list-like table +--- @param temp buffer to use, otherwise allocate +--- @param tostr custom tostring function, called with (value,index). +--- Otherwise use `tostring` +--- @return the converted buffer +-function utils.array_tostring (t,temp,tostr) +- temp, tostr = temp or {}, tostr or tostring +- for i = 1,#t do +- temp[i] = tostr(t[i],i) ++--- execute a shell command and return the output. ++-- This function redirects the output to tempfiles and returns the content of those files. ++-- @param cmd a shell command ++-- @param bin boolean, if true, read output as binary file ++-- @return true if successful ++-- @return actual return code ++-- @return stdout output (string) ++-- @return errout output (string) ++function utils.executeex(cmd, bin) ++ local outfile = os.tmpname() ++ local errfile = os.tmpname() ++ ++ if is_windows and not outfile:find(':') then ++ outfile = os.getenv('TEMP')..outfile ++ errfile = os.getenv('TEMP')..errfile + end +- return temp +-end ++ cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile) + +-local is_windows = utils.is_windows ++ local success, retcode = utils.execute(cmd) ++ local outcontent = utils.readfile(outfile, bin) ++ local errcontent = utils.readfile(errfile, bin) ++ os.remove(outfile) ++ os.remove(errfile) ++ return success, retcode, (outcontent or ""), (errcontent or "") ++end + +---- Quote an argument of a command. +--- Quotes a single argument of a command to be passed ++--- Quote and escape an argument of a command. ++-- Quotes a single (or list of) argument(s) of a command to be passed + -- to `os.execute`, `pl.utils.execute` or `pl.utils.executeex`. +--- @string argument the argument. +--- @return quoted argument. ++-- @param argument (string or table/list) the argument to quote. If a list then ++-- all arguments in the list will be returned as a single string quoted. ++-- @return quoted and escaped argument. ++-- @usage ++-- local options = utils.quote_arg { ++-- "-lluacov", ++-- "-e", ++-- "utils = print(require('pl.utils')._VERSION", ++-- } ++-- -- returns: -lluacov -e 'utils = print(require('\''pl.utils'\'')._VERSION' + function utils.quote_arg(argument) ++ if type(argument) == "table" then ++ -- encode an entire table ++ local r = {} ++ for i, arg in ipairs(argument) do ++ r[i] = utils.quote_arg(arg) ++ end ++ ++ return concat(r, " ") ++ end ++ -- only a single argument + if is_windows then + if argument == "" or argument:find('[ \f\t\v]') then + -- Need to quote the argument. +@@ -259,39 +666,97 @@ function utils.quote_arg(argument) + end + end + +---- execute a shell command and return the output. +--- This function redirects the output to tempfiles and returns the content of those files. +--- @param cmd a shell command +--- @param bin boolean, if true, read output as binary file +--- @return true if successful +--- @return actual return code +--- @return stdout output (string) +--- @return errout output (string) +-function utils.executeex(cmd, bin) +- local mode +- local outfile = os.tmpname() +- local errfile = os.tmpname() ++--- error out of this program gracefully. ++-- @param[opt] code The exit code, defaults to -`1` if omitted ++-- @param msg The exit message will be sent to `stderr` (will be formatted with the extra parameters) ++-- @param ... extra arguments for message's format' ++-- @see utils.fprintf ++-- @usage utils.quit(-1, "Error '%s' happened", "42") ++-- -- is equivalent to ++-- utils.quit("Error '%s' happened", "42") --> Error '42' happened ++function utils.quit(code, msg, ...) ++ if type(code) == 'string' then ++ utils.fprintf(io.stderr, code, msg, ...) ++ io.stderr:write('\n') ++ code = -1 -- TODO: this is odd, see the test. Which returns 255 as exit code ++ elseif msg then ++ utils.fprintf(io.stderr, msg, ...) ++ io.stderr:write('\n') ++ end ++ os.exit(code, true) ++end + +- if is_windows and not outfile:find(':') then +- outfile = os.getenv('TEMP')..outfile +- errfile = os.getenv('TEMP')..errfile ++ ++--- String functions ++-- @section string-functions ++ ++--- escape any Lua 'magic' characters in a string ++-- @param s The input string ++function utils.escape(s) ++ utils.assert_string(1,s) ++ return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) ++end ++ ++--- split a string into a list of strings separated by a delimiter. ++-- @param s The input string ++-- @param re optional A Lua string pattern; defaults to '%s+' ++-- @param plain optional If truthy don't use Lua patterns ++-- @param n optional maximum number of elements (if there are more, the last will remain un-split) ++-- @return a list-like table ++-- @raise error if s is not a string ++-- @see splitv ++function utils.split(s,re,plain,n) ++ utils.assert_string(1,s) ++ local i1,ls = 1,{} ++ if not re then re = '%s+' end ++ if re == '' then return {s} end ++ while true do ++ local i2,i3 = find(s,re,i1,plain) ++ if not i2 then ++ local last = sub(s,i1) ++ if last ~= '' then append(ls,last) end ++ if #ls == 1 and ls[1] == '' then ++ return {} ++ else ++ return ls ++ end ++ end ++ append(ls,sub(s,i1,i2-1)) ++ if n and #ls == n then ++ ls[#ls] = sub(s,i1) ++ return ls ++ end ++ i1 = i3+1 + end +- cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile) ++end + +- local success, retcode = utils.execute(cmd) +- local outcontent = utils.readfile(outfile, bin) +- local errcontent = utils.readfile(errfile, bin) +- os.remove(outfile) +- os.remove(errfile) +- return success, retcode, (outcontent or ""), (errcontent or "") ++--- split a string into a number of return values. ++-- Identical to `split` but returns multiple sub-strings instead of ++-- a single list of sub-strings. ++-- @param s the string ++-- @param re A Lua string pattern; defaults to '%s+' ++-- @param plain don't use Lua patterns ++-- @param n optional maximum number of splits ++-- @return n values ++-- @usage first,next = splitv('user=jane=doe','=', false, 2) ++-- assert(first == "user") ++-- assert(next == "jane=doe") ++-- @see split ++function utils.splitv (s,re, plain, n) ++ return _unpack(utils.split(s,re, plain, n)) + end + ++ ++--- Functional ++-- @section functional ++ ++ + --- 'memoize' a function (cache returned value for next call). + -- This is useful if you have a function which is relatively expensive, + -- but you don't know in advance what values will be required, so + -- building a table upfront is wasteful/impossible. +--- @param func a function of at least one argument +--- @return a function with at least one argument, which is used as the key. ++-- @param func a function that takes exactly one argument (which later serves as the cache key) and returns a single value ++-- @return a function taking one argument and returning a single value either from the cache or by running the original input function + function utils.memoize(func) + local cache = {} + return function(k) +@@ -305,13 +770,6 @@ function utils.memoize(func) + end + + +-utils.stdmt = { +- List = {_name='List'}, Map = {_name='Map'}, +- Set = {_name='Set'}, MultiMap = {_name='MultiMap'} +-} +- +-local _function_factories = {} +- + --- associate a function factory with a type. + -- A function factory takes an object of the given type and + -- returns a function for evaluating it +@@ -322,7 +780,6 @@ function utils.add_function_factory (mt,fun) + end + + local function _string_lambda(f) +- local raise = utils.raise + if f:find '^|' or f:find '_' then + local args,body = f:match '|([^|]*)|(.+)' + if f:find '_' then +@@ -336,61 +793,22 @@ local function _string_lambda(f) + if not fn then return raise(err) end + fn = fn() + return fn +- else return raise 'not a string lambda' ++ else ++ return raise 'not a string lambda' + end + end + ++ + --- an anonymous function as a string. This string is either of the form + -- '|args| expression' or is a function of one argument, '_' + -- @param lf function as a string + -- @return a function +--- @usage string_lambda '|x|x+1' (2) == 3 +--- @usage string_lambda '_+1' (2) == 3 + -- @function utils.string_lambda ++-- @usage ++-- string_lambda '|x|x+1' (2) == 3 ++-- string_lambda '_+1' (2) == 3 + utils.string_lambda = utils.memoize(_string_lambda) + +-local ops +- +---- process a function argument. +--- This is used throughout Penlight and defines what is meant by a function: +--- Something that is callable, or an operator string as defined by pl.operator, +--- such as '>' or '#'. If a function factory has been registered for the type, it will +--- be called to get the function. +--- @param idx argument index +--- @param f a function, operator string, or callable object +--- @param msg optional error message +--- @return a callable +--- @raise if idx is not a number or if f is not callable +-function utils.function_arg (idx,f,msg) +- utils.assert_arg(1,idx,'number') +- local tp = type(f) +- if tp == 'function' then return f end -- no worries! +- -- ok, a string can correspond to an operator (like '==') +- if tp == 'string' then +- if not ops then ops = require 'pl.operator'.optable end +- local fn = ops[f] +- if fn then return fn end +- local fn, err = utils.string_lambda(f) +- if not fn then error(err..': '..f) end +- return fn +- elseif tp == 'table' or tp == 'userdata' then +- local mt = getmetatable(f) +- if not mt then error('not a callable object',2) end +- local ff = _function_factories[mt] +- if not ff then +- if not mt.__call then error('not a callable object',2) end +- return f +- else +- return ff(f) -- we have a function factory for this type! +- end +- end +- if not msg then msg = " must be callable" end +- if idx > 0 then +- error("argument "..idx..": "..msg,2) +- else +- error(msg,2) +- end +-end + + --- bind the first argument of the function to a value. + -- @param fn a function of at least two values (may be an operator string) +@@ -398,119 +816,141 @@ end + -- @return a function such that f(x) is fn(p,x) + -- @raise same as @{function_arg} + -- @see func.bind1 ++-- @usage local function f(msg, name) ++-- print(msg .. " " .. name) ++-- end ++-- ++-- local hello = utils.bind1(f, "Hello") ++-- ++-- print(hello("world")) --> "Hello world" ++-- print(hello("sunshine")) --> "Hello sunshine" + function utils.bind1 (fn,p) + fn = utils.function_arg(1,fn) + return function(...) return fn(p,...) end + end + ++ + --- bind the second argument of the function to a value. + -- @param fn a function of at least two values (may be an operator string) + -- @param p a value + -- @return a function such that f(x) is fn(x,p) + -- @raise same as @{function_arg} ++-- @usage local function f(a, b, c) ++-- print(a .. " " .. b .. " " .. c) ++-- end ++-- ++-- local hello = utils.bind1(f, "world") ++-- ++-- print(hello("Hello", "!")) --> "Hello world !" ++-- print(hello("Bye", "?")) --> "Bye world ?" + function utils.bind2 (fn,p) + fn = utils.function_arg(1,fn) + return function(x,...) return fn(x,p,...) end + end + + +---- assert that the given argument is in fact of the correct type. +--- @param n argument index +--- @param val the value +--- @param tp the type +--- @param verify an optional verification function +--- @param msg an optional custom message +--- @param lev optional stack position for trace, default 2 +--- @raise if the argument n is not the correct type +--- @usage assert_arg(1,t,'table') +--- @usage assert_arg(n,val,'string',path.isdir,'not a directory') +-function utils.assert_arg (n,val,tp,verify,msg,lev) +- if type(val) ~= tp then +- error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2) +- end +- if verify and not verify(val) then +- error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) +- end +-end + +---- assert the common case that the argument is a string. +--- @param n argument index +--- @param val a value that must be a string +--- @raise val must be a string +-function utils.assert_string (n,val) +- utils.assert_arg(n,val,'string',nil,nil,3) +-end + +-local err_mode = 'default' ++--- Deprecation ++-- @section deprecation + +---- control the error strategy used by Penlight. +--- Controls how utils.raise works; the default is for it +--- to return nil and the error string, but if the mode is 'error' then +--- it will throw an error. If mode is 'quit' it will immediately terminate +--- the program. +--- @param mode - either 'default', 'quit' or 'error' +--- @see utils.raise +-function utils.on_error (mode) +- if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then +- err_mode = mode ++do ++ -- the default implementation ++ local deprecation_func = function(msg, trace) ++ if trace then ++ warn(msg, "\n", trace) -- luacheck: ignore + else +- -- fail loudly +- if err_mode == 'default' then err_mode = 'error' end +- utils.raise("Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'") ++ warn(msg) -- luacheck: ignore ++ end ++ end ++ ++ --- Sets a deprecation warning function. ++ -- An application can override this function to support proper output of ++ -- deprecation warnings. The warnings can be generated from libraries or ++ -- functions by calling `utils.raise_deprecation`. The default function ++ -- will write to the 'warn' system (introduced in Lua 5.4, or the compatibility ++ -- function from the `compat` module for earlier versions). ++ -- ++ -- Note: only applications should set/change this function, libraries should not. ++ -- @param func a callback with signature: `function(msg, trace)` both arguments are strings, the latter being optional. ++ -- @see utils.raise_deprecation ++ -- @usage ++ -- -- write to the Nginx logs with OpenResty ++ -- utils.set_deprecation_func(function(msg, trace) ++ -- ngx.log(ngx.WARN, msg, (trace and (" " .. trace) or nil)) ++ -- end) ++ -- ++ -- -- disable deprecation warnings ++ -- utils.set_deprecation_func() ++ function utils.set_deprecation_func(func) ++ if func == nil then ++ deprecation_func = function() end ++ else ++ utils.assert_arg(1, func, "function") ++ deprecation_func = func ++ end ++ end ++ ++ --- raises a deprecation warning. ++ -- For options see the usage example below. ++ -- ++ -- Note: the `opts.deprecated_after` field is the last version in which ++ -- a feature or option was NOT YET deprecated! Because when writing the code it ++ -- is quite often not known in what version the code will land. But the last ++ -- released version is usually known. ++ -- @param opts options table ++ -- @see utils.set_deprecation_func ++ -- @usage ++ -- warn("@on") -- enable Lua warnings, they are usually off by default ++ -- ++ -- function stringx.islower(str) ++ -- raise_deprecation { ++ -- source = "Penlight " .. utils._VERSION, -- optional ++ -- message = "function 'islower' was renamed to 'is_lower'", -- required ++ -- version_removed = "2.0.0", -- optional ++ -- deprecated_after = "1.2.3", -- optional ++ -- no_trace = true, -- optional ++ -- } ++ -- return stringx.is_lower(str) ++ -- end ++ -- -- output: "[Penlight 1.9.2] function 'islower' was renamed to 'is_lower' (deprecated after 1.2.3, scheduled for removal in 2.0.0)" ++ function utils.raise_deprecation(opts) ++ utils.assert_arg(1, opts, "table") ++ if type(opts.message) ~= "string" then ++ error("field 'message' of the options table must be a string", 2) ++ end ++ local trace ++ if not opts.no_trace then ++ trace = debug.traceback("", 2):match("[\n%s]*(.-)$") ++ end ++ local msg ++ if opts.deprecated_after and opts.version_removed then ++ msg = (" (deprecated after %s, scheduled for removal in %s)"):format( ++ tostring(opts.deprecated_after), tostring(opts.version_removed)) ++ elseif opts.deprecated_after then ++ msg = (" (deprecated after %s)"):format(tostring(opts.deprecated_after)) ++ elseif opts.version_removed then ++ msg = (" (scheduled for removal in %s)"):format(tostring(opts.version_removed)) ++ else ++ msg = "" + end +-end + +---- used by Penlight functions to return errors. Its global behaviour is controlled +--- by utils.on_error +--- @param err the error string. +--- @see utils.on_error +-function utils.raise (err) +- if err_mode == 'default' then return nil,err +- elseif err_mode == 'quit' then utils.quit(err) +- else error(err,2) ++ msg = opts.message .. msg ++ ++ if opts.source then ++ msg = "[" .. opts.source .."] " .. msg ++ else ++ if msg:sub(1,1) == "@" then ++ -- in Lua 5.4 "@" prefixed messages are control messages to the warn system ++ error("message cannot start with '@'", 2) ++ end + end +-end + +---- is the object of the specified type?. +--- If the type is a string, then use type, otherwise compare with metatable +--- @param obj An object to check +--- @param tp String of what type it should be +-function utils.is_type (obj,tp) +- if type(tp) == 'string' then return type(obj) == tp end +- local mt = getmetatable(obj) +- return tp == mt +-end ++ deprecation_func(msg, trace) ++ end + +-raise = utils.raise ++end + +---- load a code string or bytecode chunk. +--- @param code Lua code as a string or bytecode +--- @param name for source errors +--- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default) +--- @param env the environment for the new chunk (default nil) +--- @return compiled chunk +--- @return error message (chunk is nil) +--- @function utils.load +- +---------------- +--- Get environment of a function. +--- With Lua 5.2, may return nil for a function with no global references! +--- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html) +--- @param f a function or a call stack reference +--- @function utils.getfenv +- +---------------- +--- Set environment of a function +--- @param f a function or a call stack reference +--- @param env a table that becomes the new environment of `f` +--- @function utils.setfenv +- +---- execute a shell command. +--- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 +--- @param cmd a shell command +--- @return true if successful +--- @return actual return code +--- @function utils.execute + + return utils + +diff --git a/extra/penlight/lua/pl/xml.lua b/extra/penlight/lua/pl/xml.lua +index c4382e6..3f49c49 100644 +--- a/extra/penlight/lua/pl/xml.lua ++++ b/extra/penlight/lua/pl/xml.lua +@@ -2,7 +2,7 @@ + -- + -- This implements some useful things on [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) documents, such as returned by `lxp.lom.parse`. + -- In particular, it can convert LOM back into XML text, with optional pretty-printing control. +--- It is s based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua) ++-- It is based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua) + -- + -- > d = xml.parse "alice" + -- > = d +@@ -30,518 +30,902 @@ + -- @module pl.xml + + local utils = require 'pl.utils' +-local split = utils.split; +-local t_insert = table.insert; +-local t_concat = table.concat; +-local t_remove = table.remove; +-local s_match = string.match; +-local tostring = tostring; +-local setmetatable = setmetatable; +-local getmetatable = getmetatable; +-local pairs = pairs; +-local ipairs = ipairs; +-local type = type; +-local next = next; +-local print = print; +-local unpack = utils.unpack; +-local s_gsub = string.gsub; +-local s_find = string.find; +-local pcall,require,io = pcall,require,io ++local split = utils.split ++local t_insert = table.insert ++local t_concat = table.concat ++local t_remove = table.remove ++local s_match = string.match ++local tostring = tostring ++local setmetatable = setmetatable ++local getmetatable = getmetatable ++local pairs = pairs ++local ipairs = ipairs ++local type = type ++local next = next ++local print = print ++local unpack = utils.unpack ++local s_gsub = string.gsub ++local s_sub = string.sub ++local s_find = string.find ++local pcall = pcall ++local require = require ++ ++ ++utils.raise_deprecation { ++ source = "Penlight " .. utils._VERSION, ++ message = "the contents of module 'pl.xml' has been deprecated, please use a more specialized library instead", ++ version_removed = "2.0.0", ++ deprecated_after = "1.11.0", ++ no_trace = true, ++} ++ ++ + + local _M = {} + local Doc = { __type = "doc" }; + Doc.__index = Doc; + ++ ++local function is_text(s) return type(s) == 'string' end ++local function is_tag(d) return type(d) == 'table' and is_text(d.tag) end ++ ++ ++ + --- create a new document node. +--- @param tag the tag name +--- @param attr optional attributes (table of name-value pairs) ++-- @tparam string tag the tag name ++-- @tparam[opt={}] table attr attributes (table of name-value pairs) ++-- @return the Node object ++-- @see xml.elem ++-- @usage ++-- local doc = xml.new("main", { hello = "world", answer = "42" }) ++-- print(doc) -->
    + function _M.new(tag, attr) +- local doc = { tag = tag, attr = attr or {}, last_add = {}}; +- return setmetatable(doc, Doc); ++ if type(tag) ~= "string" then ++ error("expected 'tag' to be a string value, got: " .. type(tag), 2) ++ end ++ attr = attr or {} ++ if type(attr) ~= "table" then ++ error("expected 'attr' to be a table value, got: " .. type(attr), 2) ++ end ++ ++ local doc = { tag = tag, attr = attr, last_add = {}}; ++ return setmetatable(doc, Doc); + end + +---- parse an XML document. By default, this uses lxp.lom.parse, but +--- falls back to basic_parse, or if use_basic is true +--- @param text_or_file file or string representation ++ ++--- parse an XML document. By default, this uses lxp.lom.parse, but ++-- falls back to basic_parse, or if `use_basic` is truthy ++-- @param text_or_filename file or string representation + -- @param is_file whether text_or_file is a file name or not + -- @param use_basic do a basic parse + -- @return a parsed LOM document with the document metatatables set + -- @return nil, error the error can either be a file error or a parse error +-function _M.parse(text_or_file, is_file, use_basic) +- local parser,status,lom +- if use_basic then parser = _M.basic_parse ++function _M.parse(text_or_filename, is_file, use_basic) ++ local parser,status,lom ++ if use_basic then ++ parser = _M.basic_parse ++ else ++ status,lom = pcall(require,'lxp.lom') ++ if not status then ++ parser = _M.basic_parse + else +- status,lom = pcall(require,'lxp.lom') +- if not status then parser = _M.basic_parse else parser = lom.parse end +- end +- if is_file then +- local f,err = io.open(text_or_file) +- if not f then return nil,err end +- text_or_file = f:read '*a' +- f:close() +- end +- local doc,err = parser(text_or_file) +- if not doc then return nil,err end +- if lom then +- _M.walk(doc,false,function(_,d) +- setmetatable(d,Doc) +- end) ++ parser = lom.parse ++ end ++ end ++ ++ if is_file then ++ local text, err = utils.readfile(text_or_filename) ++ if not text then ++ return nil, err ++ end ++ text_or_filename = text ++ end ++ ++ local doc, err = parser(text_or_filename) ++ if not doc then ++ return nil, err ++ end ++ ++ if lom then ++ _M.walk(doc, false, function(_, d) ++ setmetatable(d, Doc) ++ end) ++ end ++ return doc ++end ++ ++ ++--- Create a Node with a set of children (text or Nodes) and attributes. ++-- @tparam string tag a tag name ++-- @tparam table|string items either a single child (text or Node), or a table where the hash ++-- part is the attributes and the list part is the children (text or Nodes). ++-- @return the new Node ++-- @see xml.new ++-- @see xml.tags ++-- @usage ++-- local doc = xml.elem("top", "hello world") -- hello world ++-- local doc = xml.elem("main", xml.new("child")) --
    ++-- local doc = xml.elem("main", { "this ", "is ", "nice" }) --
    this is nice
    ++-- local doc = xml.elem("main", { xml.new "this", ++-- xml.new "is", ++-- xml.new "nice" }) --
    ++-- local doc = xml.elem("main", { hello = "world" }) --
    ++-- local doc = xml.elem("main", { ++-- "prefix", ++-- xml.elem("child", { "this ", "is ", "nice"}), ++-- "postfix", ++-- attrib = "value" ++-- }) --
    prefixthis is nicepostfix
    " ++function _M.elem(tag, items) ++ local s = _M.new(tag) ++ if is_text(items) then items = {items} end ++ if is_tag(items) then ++ t_insert(s,items) ++ elseif type(items) == 'table' then ++ for k,v in pairs(items) do ++ if is_text(k) then ++ s.attr[k] = v ++ t_insert(s.attr,k) ++ else ++ s[k] = v ++ end ++ end ++ end ++ return s ++end ++ ++ ++--- given a list of names, return a number of element constructors. ++-- If passing a comma-separated string, then whitespace surrounding the values ++-- will be stripped. ++-- ++-- The returned constructor functions are a shortcut to `xml.elem` where you ++-- no longer provide the tag-name, but only the `items` table. ++-- @tparam string|table list a list of names, or a comma-separated string. ++-- @return (multiple) constructor functions; `function(items)`. For the `items` ++-- parameter see `xml.elem`. ++-- @see xml.elem ++-- @usage ++-- local new_parent, new_child = xml.tags 'mom, kid' ++-- doc = new_parent {new_child 'Bob', new_child 'Annie'} ++-- -- BobAnnie ++function _M.tags(list) ++ local ctors = {} ++ if is_text(list) then ++ list = split(list:match("^%s*(.-)%s*$"),'%s*,%s*') ++ end ++ for i,tag in ipairs(list) do ++ local function ctor(items) ++ return _M.elem(tag,items) + end +- return doc ++ ctors[i] = ctor ++ end ++ return unpack(ctors) + end + +----- convenient function to add a document node, This updates the last inserted position. +--- @param tag a tag name +--- @param attrs optional set of attributes (name-string pairs) ++ ++--- Adds a document Node, at current position. ++-- This updates the last inserted position to the new Node. ++-- @tparam string tag the tag name ++-- @tparam[opt={}] table attrs attributes (table of name-value pairs) ++-- @return the current node (`self`) ++-- @usage ++-- local doc = xml.new("main") ++-- doc:addtag("penlight", { hello = "world"}) ++-- doc:addtag("expat") -- added to 'penlight' since position moved ++-- print(doc) -->
    + function Doc:addtag(tag, attrs) +- local s = _M.new(tag, attrs); +- (self.last_add[#self.last_add] or self):add_direct_child(s); +- t_insert(self.last_add, s); +- return self; ++ local s = _M.new(tag, attrs) ++ self:add_child(s) ++ t_insert(self.last_add, s) ++ return self + end + +---- convenient function to add a text node. This updates the last inserted position. +--- @param text a string ++ ++--- Adds a text node, at current position. ++-- @tparam string text a string ++-- @return the current node (`self`) ++-- @usage ++-- local doc = xml.new("main") ++-- doc:text("penlight") ++-- doc:text("expat") ++-- print(doc) -->
    + function Doc:text(text) +- (self.last_add[#self.last_add] or self):add_direct_child(text); +- return self; ++ self:add_child(text) ++ return self + end + +----- go up one level in a document ++ ++--- Moves current position up one level. ++-- @return the current node (`self`) + function Doc:up() +- t_remove(self.last_add); +- return self; ++ t_remove(self.last_add) ++ return self + end + ++ ++--- Resets current position to top level. ++-- Resets to the `self` node. ++-- @return the current node (`self`) + function Doc:reset() +- local last_add = self.last_add; +- for i = 1,#last_add do +- last_add[i] = nil; +- end +- return self; ++ local last_add = self.last_add ++ for i = 1,#last_add do ++ last_add[i] = nil ++ end ++ return self + end + +---- append a child to a document directly. ++ ++--- Append a child to the current Node (ignoring current position). + -- @param child a child node (either text or a document) ++-- @return the current node (`self`) ++-- @usage ++-- local doc = xml.new("main") ++-- doc:add_direct_child("dog") ++-- doc:add_direct_child(xml.new("child")) ++-- doc:add_direct_child("cat") ++-- print(doc) -->
    dogcat
    + function Doc:add_direct_child(child) +- t_insert(self, child); ++ t_insert(self, child) ++ return self + end + +---- append a child to a document at the last element added ++ ++--- Append a child at the current position (without changing position). + -- @param child a child node (either text or a document) ++-- @return the current node (`self`) ++-- @usage ++-- local doc = xml.new("main") ++-- doc:addtag("one") ++-- doc:add_child(xml.new("item1")) ++-- doc:add_child(xml.new("item2")) ++-- doc:add_child(xml.new("item3")) ++-- print(doc) -->
    + function Doc:add_child(child) +- (self.last_add[#self.last_add] or self):add_direct_child(child); +- return self; ++ (self.last_add[#self.last_add] or self):add_direct_child(child) ++ return self + end + ++ + --accessing attributes: useful not to have to expose implementation (attr) + --but also can allow attr to be nil in any future optimizations + +---- set attributes of a document node. +--- @param t a table containing attribute/value pairs +-function Doc:set_attribs (t) +- for k,v in pairs(t) do +- self.attr[k] = v +- end ++ ++--- Set attributes of a document node. ++-- Will add/overwrite values, but will not remove existing ones. ++-- Operates on the Node itself, will not take position into account. ++-- @tparam table t a table containing attribute/value pairs ++-- @return the current node (`self`) ++function Doc:set_attribs(t) ++ -- TODO: keep array part in sync ++ for k,v in pairs(t) do ++ self.attr[k] = v ++ end ++ return self + end + +---- set a single attribute of a document node. ++ ++--- Set a single attribute of a document node. ++-- Operates on the Node itself, will not take position into account. + -- @param a attribute +--- @param v its value ++-- @param v its value, pass in `nil` to delete the attribute ++-- @return the current node (`self`) + function Doc:set_attrib(a,v) +- self.attr[a] = v ++ -- TODO: keep array part in sync ++ self.attr[a] = v ++ return self + end + +---- access the attributes of a document node. ++ ++--- Gets the attributes of a document node. ++-- Operates on the Node itself, will not take position into account. ++-- @return table with attributes (attribute/value pairs) + function Doc:get_attribs() +- return self.attr ++ return self.attr + end + +-local function is_text(s) return type(s) == 'string' end + +---- function to create an element with a given tag name and a set of children. +--- @param tag a tag name +--- @param items either text or a table where the hash part is the attributes and the list part is the children. +-function _M.elem(tag,items) +- local s = _M.new(tag) +- if is_text(items) then items = {items} end +- if _M.is_tag(items) then +- t_insert(s,items) +- elseif type(items) == 'table' then +- for k,v in pairs(items) do +- if is_text(k) then +- s.attr[k] = v +- t_insert(s.attr,k) +- else +- s[k] = v +- end +- end +- end +- return s +-end + +---- given a list of names, return a number of element constructors. +--- @param list a list of names, or a comma-separated string. +--- @usage local parent,children = doc.tags 'parent,children'
    +--- doc = parent {child 'one', child 'two'} +-function _M.tags(list) +- local ctors = {} +- local elem = _M.elem +- if is_text(list) then list = split(list,'%s*,%s*') end +- for _,tag in ipairs(list) do +- local ctor = function(items) return _M.elem(tag,items) end +- t_insert(ctors,ctor) +- end +- return unpack(ctors) +-end +- +-local templ_cache = {} ++local template_cache do ++ local templ_cache = {} + +-local function template_cache (templ) ++ -- @param templ a template, a string being valid xml to be parsed, or a Node object ++ function template_cache(templ) + if is_text(templ) then +- if templ_cache[templ] then +- templ = templ_cache[templ] +- else +- local str,err = templ +- templ,err = _M.parse(str,false,true) +- if not templ then return nil,err end +- templ_cache[str] = templ ++ if templ_cache[templ] then ++ -- cache hit ++ return templ_cache[templ] ++ ++ else ++ -- parse and cache ++ local ptempl, err = _M.parse(templ,false,true) ++ if not ptempl then ++ return nil, err + end +- elseif not _M.is_tag(templ) then +- return nil, "template is not a document" ++ templ_cache[templ] = ptempl ++ return ptempl ++ end ++ end ++ ++ if is_tag(templ) then ++ return templ + end +- return templ ++ ++ return nil, "template is not a document" ++ end + end + +-local function is_data(data) ++ ++do ++ local function is_data(data) + return #data == 0 or type(data[1]) ~= 'table' +-end ++ end ++ + +-local function prepare_data(data) ++ local function prepare_data(data) + -- a hack for ensuring that $1 maps to first element of data, etc. + -- Either this or could change the gsub call just below. + for i,v in ipairs(data) do +- data[tostring(i)] = v ++ data[tostring(i)] = v ++ end ++ end ++ ++ --- create a substituted copy of a document, ++ -- @param template may be a document or a string representation which will be parsed and cached ++ -- @param data a table of name-value pairs or a list of such tables ++ -- @return an XML document ++ function Doc.subst(template, data) ++ if type(data) ~= 'table' or not next(data) then ++ return nil, "data must be a non-empty table" + end +-end + +---- create a substituted copy of a document, +--- @param templ may be a document or a string representation which will be parsed and cached +--- @param data a table of name-value pairs or a list of such tables +--- @return an XML document +-function Doc.subst(templ, data) +- local err +- if type(data) ~= 'table' or not next(data) then return nil, "data must be a non-empty table" end + if is_data(data) then +- prepare_data(data) ++ prepare_data(data) ++ end ++ ++ local templ, err = template_cache(template) ++ if err then ++ return nil, err + end +- templ,err = template_cache(templ) +- if err then return nil, err end ++ + local function _subst(item) +- return _M.clone(templ,function(s) +- return s:gsub('%$(%w+)',item) +- end) ++ return _M.clone(templ, function(s) ++ return s:gsub('%$(%w+)', item) ++ end) + end +- if is_data(data) then return _subst(data) end ++ ++ if is_data(data) then ++ return _subst(data) ++ end ++ + local list = {} +- for _,item in ipairs(data) do +- prepare_data(item) +- t_insert(list,_subst(item)) ++ for _, item in ipairs(data) do ++ prepare_data(item) ++ t_insert(list, _subst(item)) + end ++ + if data.tag then +- list = _M.elem(data.tag,list) ++ list = _M.elem(data.tag,list) + end + return list ++ end + end + + +---- get the first child with a given tag name. ++--- Return the first child with a given tag name (non-recursive). + -- @param tag the tag name ++-- @return the child Node found or `nil` if not found + function Doc:child_with_name(tag) +- for _, child in ipairs(self) do +- if child.tag == tag then return child; end ++ for _, child in ipairs(self) do ++ if child.tag == tag then ++ return child + end ++ end + end + +-local _children_with_name +-function _children_with_name(self,tag,list,recurse) +- for _, child in ipairs(self) do if type(child) == 'table' then +- if child.tag == tag then t_insert(list,child) end +- if recurse then _children_with_name(child,tag,list,recurse) end +- end end +-end + +---- get all elements in a document that have a given tag. +--- @param tag a tag name +--- @param dont_recurse optionally only return the immediate children with this tag name +--- @return a list of elements +-function Doc:get_elements_with_name(tag,dont_recurse) ++do ++ -- @param self document node to traverse ++ -- @param tag tag-name to look for ++ -- @param list array table to add the matching ones to ++ -- @param recurse if truthy, recursively search the node ++ local function _children_with_name(self, tag, list, recurse) ++ -- TODO: protect against recursion ++ for _, child in ipairs(self) do ++ if type(child) == 'table' then ++ if child.tag == tag then ++ t_insert(list, child) ++ end ++ if recurse then ++ _children_with_name(child, tag, list, recurse) ++ end ++ end ++ end ++ end ++ ++ --- Returns all elements in a document that have a given tag. ++ -- @tparam string tag a tag name ++ -- @tparam[opt=false] boolean dont_recurse optionally only return the immediate children with this tag name ++ -- @return a list of elements found, list will be empty if none was found. ++ function Doc:get_elements_with_name(tag, dont_recurse) + local res = {} +- _children_with_name(self,tag,res,not dont_recurse) ++ _children_with_name(self, tag, res, not dont_recurse) + return res ++ end + end + +--- iterate over all children of a document node, including text nodes. ++ ++ ++--- Iterator over all children of a document node, including text nodes. ++-- This function is not recursive, so returns only direct child nodes. ++-- @return iterator that returns a single Node per iteration. + function Doc:children() +- local i = 0; +- return function (a) +- i = i + 1 +- return a[i]; +- end, self, i; ++ local i = 0; ++ return function (a) ++ i = i + 1 ++ return a[i]; ++ end, self, i; + end + +--- return the first child element of a node, if it exists. ++ ++--- Return the first child element of a node, if it exists. ++-- This will skip text nodes. ++-- @return first child Node or `nil` if there is none. + function Doc:first_childtag() +- if #self == 0 then return end +- for _,t in ipairs(self) do +- if type(t) == 'table' then return t end ++ if #self == 0 then ++ return ++ end ++ for _, t in ipairs(self) do ++ if is_tag(t) then ++ return t + end ++ end + end + ++ ++--- Iterator that matches tag names, and a namespace (non-recursive). ++-- @tparam[opt=nil] string tag tag names to return. Returns all tags if not provided. ++-- @tparam[opt=nil] string xmlns the namespace value ('xmlns' attribute) to return. If not ++-- provided will match all namespaces. ++-- @return iterator that returns a single Node per iteration. + function Doc:matching_tags(tag, xmlns) +- xmlns = xmlns or self.attr.xmlns; +- local tags = self; +- local start_i, max_i, v = 1, #tags; +- return function () +- for i=start_i,max_i do +- v = tags[i]; +- if (not tag or v.tag == tag) +- and (not xmlns or xmlns == v.attr.xmlns) then +- start_i = i+1; +- return v; +- end +- end +- end, tags, start_i; ++ -- TODO: this doesn't make sense??? namespaces are not "xmnls", as matched below ++ -- but "xmlns:name"... so should be a string-prefix match if anything... ++ xmlns = xmlns or self.attr.xmlns; ++ local tags = self ++ local next_i = 1 ++ local max_i = #tags ++ local node ++ return function () ++ for i = next_i, max_i do ++ node = tags[i]; ++ if (not tag or node.tag == tag) and ++ (not xmlns or xmlns == node.attr.xmlns) then ++ next_i = i + 1 ++ return node ++ end ++ end ++ end, tags, next_i + end + +---- iterate over all child elements of a document node. ++ ++--- Iterator over all child tags of a document node. This will skip over ++-- text nodes. ++-- @return iterator that returns a single Node per iteration. + function Doc:childtags() +- local i = 0; +- return function (a) +- local v +- repeat +- i = i + 1 +- v = self[i] +- if v and type(v) == 'table' then return v; end +- until not v +- end, self[1], i; +-end +- +---- visit child element of a node and call a function, possibility modifying the document. +--- @param callback a function passed the node (text or element). If it returns nil, that node will be removed. +--- If it returns a value, that will replace the current node. +-function Doc:maptags(callback) +- local is_tag = _M.is_tag +- local i = 1; +- while i <= #self do +- if is_tag(self[i]) then +- local ret = callback(self[i]); +- if ret == nil then +- t_remove(self, i); +- else +- self[i] = ret; +- i = i + 1; +- end ++ local i = 0; ++ return function (a) ++ local v ++ repeat ++ i = i + 1 ++ v = self[i] ++ if v and type(v) == 'table' then ++ return v + end ++ until not v ++ end, self[1], i; ++end ++ ++ ++--- Visit child Nodes of a node and call a function, possibly modifying the document. ++-- Text elements will be skipped. ++-- This is not recursive, so only direct children will be passed. ++-- @tparam function callback a function with signature `function(node)`, passed the node. ++-- The element will be updated with the returned value, or deleted if it returns `nil`. ++function Doc:maptags(callback) ++ local i = 1; ++ ++ while i <= #self do ++ if is_tag(self[i]) then ++ local ret = callback(self[i]); ++ if ret == nil then ++ -- remove it ++ t_remove(self, i); ++ ++ else ++ -- update it ++ self[i] = ret; ++ i = i + 1; ++ end ++ else ++ i = i + 1 + end +- return self; ++ end ++ ++ return self; ++end ++ ++ ++do ++ local escape_table = { ++ ["'"] = "'", ++ ['"'] = """, ++ ["<"] = "<", ++ [">"] = ">", ++ ["&"] = "&", ++ } ++ ++ --- Escapes a string for safe use in xml. ++ -- Handles quotes(single+double), less-than, greater-than, and ampersand. ++ -- @tparam string str string value to escape ++ -- @return escaped string ++ -- @usage ++ -- local esc = xml.xml_escape([["'<>&]]) --> ""'<>&" ++ function _M.xml_escape(str) ++ return (s_gsub(str, "['&<>\"]", escape_table)) ++ end + end ++local xml_escape = _M.xml_escape + +-local xml_escape + do +- local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; +- function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end +- _M.xml_escape = xml_escape; ++ local escape_table = { ++ quot = '"', ++ apos = "'", ++ lt = "<", ++ gt = ">", ++ amp = "&", ++ } ++ ++ --- Unescapes a string from xml. ++ -- Handles quotes(single+double), less-than, greater-than, and ampersand. ++ -- @tparam string str string value to unescape ++ -- @return unescaped string ++ -- @usage ++ -- local unesc = xml.xml_escape(""'<>&") --> [["'<>&]] ++ function _M.xml_unescape(str) ++ return (str:gsub( "&(%a+);", escape_table)) ++ end + end ++local xml_unescape = _M.xml_unescape + + -- pretty printing + -- if indent, then put each new tag on its own line + -- if attr_indent, put each new attribute on its own line +-local function _dostring(t, buf, self, xml_escape, parentns, idn, indent, attr_indent) +- local nsid = 0; +- local tag = t.tag +- local lf,alf = ""," " +- if indent then lf = '\n'..idn end +- if attr_indent then alf = '\n'..idn..attr_indent end +- t_insert(buf, lf.."<"..tag); +- local function write_attr(k,v) +- if s_find(k, "\1", 1, true) then +- local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); +- nsid = nsid + 1; +- t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'"); +- elseif not(k == "xmlns" and v == parentns) then +- t_insert(buf, alf..k.."='"..xml_escape(v).."'"); +- end ++local function _dostring(t, buf, parentns, block_indent, tag_indent, attr_indent) ++ local nsid = 0 ++ local tag = t.tag ++ ++ local lf = "" ++ if tag_indent then ++ lf = '\n'..block_indent ++ end ++ ++ local alf = " " ++ if attr_indent then ++ alf = '\n'..block_indent..attr_indent ++ end ++ ++ t_insert(buf, lf.."<"..tag) ++ ++ local function write_attr(k,v) ++ if s_find(k, "\1", 1, true) then ++ nsid = nsid + 1 ++ local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$") ++ t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'") ++ ++ elseif not (k == "xmlns" and v == parentns) then ++ t_insert(buf, alf..k.."='"..xml_escape(v).."'"); + end +- -- it's useful for testing to have predictable attribute ordering, if available +- if #t.attr > 0 then +- for _,k in ipairs(t.attr) do +- write_attr(k,t.attr[k]) +- end +- else +- for k, v in pairs(t.attr) do +- write_attr(k,v) +- end ++ end ++ ++ -- it's useful for testing to have predictable attribute ordering, if available ++ if #t.attr > 0 then ++ -- TODO: the key-value list is leading, what if they are not in-sync ++ for _,k in ipairs(t.attr) do ++ write_attr(k,t.attr[k]) + end +- local len,has_children = #t; +- if len == 0 then +- local out = "/>" +- if attr_indent then out = '\n'..idn..out end +- t_insert(buf, out); ++ else ++ for k, v in pairs(t.attr) do ++ write_attr(k,v) ++ end ++ end ++ ++ local len = #t ++ local has_children ++ ++ if len == 0 then ++ t_insert(buf, attr_indent and '\n'..block_indent.."/>" or "/>") ++ ++ else ++ t_insert(buf, ">"); ++ ++ for n = 1, len do ++ local child = t[n] ++ ++ if child.tag then ++ has_children = true ++ _dostring(child, buf, t.attr.xmlns, block_indent and block_indent..tag_indent, tag_indent, attr_indent) ++ ++ else ++ -- text element ++ t_insert(buf, xml_escape(child)) ++ end ++ end ++ ++ t_insert(buf, (has_children and lf or '')..""); ++ end ++end ++ ++--- Function to pretty-print an XML document. ++-- @param doc an XML document ++-- @tparam[opt] string|int b_ind an initial block-indent (required when `t_ind` is set) ++-- @tparam[opt] string|int t_ind an tag-indent for each level (required when `a_ind` is set) ++-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line ++-- @tparam[opt] string|bool xml_preface force prefacing with default or custom , if truthy then `<?xml version='1.0'?>` will be used as default. ++-- @return a string representation ++-- @see Doc:tostring ++function _M.tostring(doc, b_ind, t_ind, a_ind, xml_preface) ++ local buf = {} ++ ++ if type(b_ind) == "number" then b_ind = (" "):rep(b_ind) end ++ if type(t_ind) == "number" then t_ind = (" "):rep(t_ind) end ++ if type(a_ind) == "number" then a_ind = (" "):rep(a_ind) end ++ ++ if xml_preface then ++ if type(xml_preface) == "string" then ++ buf[1] = xml_preface + else +- t_insert(buf, ">"); +- for n=1,len do +- local child = t[n]; +- if child.tag then +- self(child, buf, self, xml_escape, t.attr.xmlns,idn and idn..indent, indent, attr_indent ); +- has_children = true +- else -- text element +- t_insert(buf, xml_escape(child)); +- end +- end +- t_insert(buf, (has_children and lf or '')..""); +- end +-end +- +----- pretty-print an XML document +---- @param t an XML document +---- @param idn an initial indent (indents are all strings) +---- @param indent an indent for each level +---- @param attr_indent if given, indent each attribute pair and put on a separate line +---- @param xml force prefacing with default or custom +---- @return a string representation +-function _M.tostring(t,idn,indent, attr_indent, xml) +- local buf = {}; +- if xml then +- if type(xml) == "string" then +- buf[1] = xml +- else +- buf[1] = "" +- end ++ buf[1] = "" + end +- _dostring(t, buf, _dostring, xml_escape, nil,idn,indent, attr_indent); +- return t_concat(buf); ++ end ++ ++ _dostring(doc, buf, nil, b_ind, t_ind, a_ind, xml_preface) ++ ++ return t_concat(buf) + end + ++ + Doc.__tostring = _M.tostring + +---- get the full text value of an element ++ ++--- Method to pretty-print an XML document. ++-- Invokes `xml.tostring`. ++-- @tparam[opt] string|int b_ind an initial indent (required when `t_ind` is set) ++-- @tparam[opt] string|int t_ind an indent for each level (required when `a_ind` is set) ++-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line ++-- @tparam[opt="<?xml version='1.0'?>"] string xml_preface force prefacing with default or custom ++-- @return a string representation ++-- @see xml.tostring ++function Doc:tostring(b_ind, t_ind, a_ind, xml_preface) ++ return _M.tostring(self, b_ind, t_ind, a_ind, xml_preface) ++end ++ ++ ++--- get the full text value of an element. ++-- @return a single string with all text elements concatenated ++-- @usage ++-- local doc = xml.new("main") ++-- doc:text("one") ++-- doc:add_child(xml.elem "two") ++-- doc:text("three") ++-- ++-- local t = doc:get_text() --> "onethree" + function Doc:get_text() +- local res = {} +- for i,el in ipairs(self) do +- if is_text(el) then t_insert(res,el) end +- end +- return t_concat(res); ++ local res = {} ++ for i,el in ipairs(self) do ++ if is_text(el) then t_insert(res,el) end ++ end ++ return t_concat(res); + end + +---- make a copy of a document +--- @param doc the original document +--- @param strsubst an optional function for handling string copying which could do substitution, etc. +-function _M.clone(doc, strsubst) +- local lookup_table = {}; +- local function _copy(object,kind,parent) +- if type(object) ~= "table" then +- if strsubst and is_text(object) then return strsubst(object,kind,parent) +- else return object +- end +- elseif lookup_table[object] then +- return lookup_table[object] +- end +- local new_table = {}; +- lookup_table[object] = new_table +- local tag = object.tag +- new_table.tag = _copy(tag,'*TAG',parent) +- if object.attr then +- local res = {} +- for attr,value in pairs(object.attr) do +- res[attr] = _copy(value,attr,object) +- end +- new_table.attr = res +- end +- for index = 1,#object do +- local v = _copy(object[index],'*TEXT',object) +- t_insert(new_table,v) ++ ++do ++ local function _copy(object, kind, parent, strsubst, lookup_table) ++ if type(object) ~= "table" then ++ if strsubst and is_text(object) then ++ return strsubst(object, kind, parent) ++ else ++ return object ++ end ++ end ++ ++ if lookup_table[object] then ++ error("recursion detected") ++ end ++ lookup_table[object] = true ++ ++ local new_table = {} ++ lookup_table[object] = new_table ++ ++ local tag = object.tag ++ new_table.tag = _copy(tag, '*TAG', parent, strsubst, lookup_table) ++ ++ if object.attr then ++ local res = {} ++ for attr, value in pairs(object.attr) do ++ if type(attr) == "string" then ++ res[attr] = _copy(value, attr, object, strsubst, lookup_table) + end +- return setmetatable(new_table, getmetatable(object)) ++ end ++ new_table.attr = res + end + +- return _copy(doc) ++ for index = 1, #object do ++ local v = _copy(object[index], '*TEXT', object, strsubst, lookup_table) ++ t_insert(new_table,v) ++ end ++ ++ return setmetatable(new_table, getmetatable(object)) ++ end ++ ++ --- Returns a copy of a document. ++ -- The `strsubst` parameter is a callback with signature `function(object, kind, parent)`. ++ -- ++ -- Param `kind` has the following values, and parameters: ++ -- ++ -- - `"*TAG"`: `object` is the tag-name, `parent` is the Node object. Returns the new tag name. ++ -- ++ -- - `"*TEXT"`: `object` is the text-element, `parent` is the Node object. Returns the new text value. ++ -- ++ -- - other strings not prefixed with `*`: `kind` is the attribute name, `object` is the ++ -- attribute value, `parent` is the Node object. Returns the new attribute value. ++ -- ++ -- @tparam Node|string doc a Node object or string (text node) ++ -- @tparam[opt] function strsubst an optional function for handling string copying ++ -- which could do substitution, etc. ++ -- @return copy of the document ++ -- @see Doc:filter ++ function _M.clone(doc, strsubst) ++ return _copy(doc, nil, nil, strsubst, {}) ++ end + end + ++ ++--- Returns a copy of a document. ++-- This is the method version of `xml.clone`. ++-- @see xml.clone ++-- @name Doc:filter ++-- @tparam[opt] function strsubst an optional function for handling string copying + Doc.filter = _M.clone -- also available as method + +---- compare two documents. +--- @param t1 any value +--- @param t2 any value +-function _M.compare(t1,t2) ++do ++ local function _compare(t1, t2, recurse_check) ++ + local ty1 = type(t1) + local ty2 = type(t2) +- if ty1 ~= ty2 then return false, 'type mismatch' end ++ ++ if ty1 ~= ty2 then ++ return false, 'type mismatch' ++ end ++ + if ty1 == 'string' then +- return t1 == t2 and true or 'text '..t1..' ~= text '..t2 ++ if t1 == t2 then ++ return true ++ else ++ return false, 'text '..t1..' ~= text '..t2 ++ end ++ end ++ ++ if ty1 ~= 'table' or ty2 ~= 'table' then ++ return false, 'not a document' ++ end ++ ++ if recurse_check[t1] then ++ return false, "recursive document" ++ end ++ recurse_check[t1] = true ++ ++ if t1.tag ~= t2.tag then ++ return false, 'tag '..t1.tag..' ~= tag '..t2.tag ++ end ++ ++ if #t1 ~= #t2 then ++ return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag + end +- if ty1 ~= 'table' or ty2 ~= 'table' then return false, 'not a document' end +- if t1.tag ~= t2.tag then return false, 'tag '..t1.tag..' ~= tag '..t2.tag end +- if #t1 ~= #t2 then return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag end ++ + -- compare attributes + for k,v in pairs(t1.attr) do +- if t2.attr[k] ~= v then return false, 'mismatch attrib' end ++ local t2_value = t2.attr[k] ++ if type(k) == "string" then ++ if t2_value ~= v then return false, 'mismatch attrib' end ++ else ++ if t2_value ~= nil and t2_value ~= v then return false, "mismatch attrib order" end ++ end + end + for k,v in pairs(t2.attr) do +- if t1.attr[k] ~= v then return false, 'mismatch attrib' end ++ local t1_value = t1.attr[k] ++ if type(k) == "string" then ++ if t1_value ~= v then return false, 'mismatch attrib' end ++ else ++ if t1_value ~= nil and t1_value ~= v then return false, "mismatch attrib order" end ++ end + end ++ + -- compare children +- for i = 1,#t1 do +- local yes,err = _M.compare(t1[i],t2[i]) +- if not yes then return err end ++ for i = 1, #t1 do ++ local ok, err = _compare(t1[i], t2[i], recurse_check) ++ if not ok then ++ return ok, err ++ end + end + return true ++ end ++ ++ --- Compare two documents or elements. ++ -- Equality is based on tag, child nodes (text and tags), attributes and order ++ -- of those (order only fails if both are given, and not equal). ++ -- @tparam Node|string t1 a Node object or string (text node) ++ -- @tparam Node|string t2 a Node object or string (text node) ++ -- @treturn boolean `true` when the Nodes are equal. ++ function _M.compare(t1,t2) ++ return _compare(t1, t2, {}) ++ end + end + ++ + --- is this value a document element? + -- @param d any value +-function _M.is_tag(d) +- return type(d) == 'table' and is_text(d.tag) +-end ++-- @treturn boolean `true` if it is a `table` with property `tag` being a string value. ++-- @name is_tag ++_M.is_tag = is_tag + +---- call the desired function recursively over the document. +--- @param doc the document +--- @param depth_first visit child notes first, then the current node +--- @param operation a function which will receive the current tag name and current node. +-function _M.walk (doc, depth_first, operation) +- if not depth_first then operation(doc.tag,doc) end ++ ++do ++ local function _walk(doc, depth_first, operation, recurse_check) ++ if not depth_first then operation(doc.tag, doc) end + for _,d in ipairs(doc) do +- if _M.is_tag(d) then +- _M.walk(d,depth_first,operation) +- end ++ if is_tag(d) then ++ assert(not recurse_check[d], "recursion detected") ++ recurse_check[d] = true ++ _walk(d, depth_first, operation, recurse_check) ++ end + end +- if depth_first then operation(doc.tag,doc) end ++ if depth_first then operation(doc.tag, doc) end ++ end ++ ++ --- Calls a function recursively over Nodes in the document. ++ -- Will only call on tags, it will skip text nodes. ++ -- The function signature for `operation` is `function(tag_name, Node)`. ++ -- @tparam Node|string doc a Node object or string (text node) ++ -- @tparam boolean depth_first visit child nodes first, then the current node ++ -- @tparam function operation a function which will receive the current tag name and current node. ++ function _M.walk(doc, depth_first, operation) ++ return _walk(doc, depth_first, operation, {}) ++ end + end + ++ + local html_empty_elements = { --lists all HTML empty (void) elements +- br = true, +- img = true, +- meta = true, +- frame = true, +- area = true, +- hr = true, +- base = true, +- col = true, +- link = true, +- input = true, +- option = true, +- param = true, ++ br = true, ++ img = true, ++ meta = true, ++ frame = true, ++ area = true, ++ hr = true, ++ base = true, ++ col = true, ++ link = true, ++ input = true, ++ option = true, ++ param = true, + isindex = true, + embed = true, + } + +-local escapes = { quot = "\"", apos = "'", lt = "<", gt = ">", amp = "&" } +-local function unescape(str) return (str:gsub( "&(%a+);", escapes)); end +- + --- Parse a well-formed HTML file as a string. +--- Tags are case-insenstive, DOCTYPE is ignored, and empty elements can be .. empty. ++-- Tags are case-insensitive, DOCTYPE is ignored, and empty elements can be .. empty. + -- @param s the HTML +-function _M.parsehtml (s) ++function _M.parsehtml(s) + return _M.basic_parse(s,false,true) + end + +@@ -549,9 +933,7 @@ end + -- @param s the XML document to be parsed. + -- @param all_text if true, preserves all whitespace. Otherwise only text containing non-whitespace is included. + -- @param html if true, uses relaxed HTML rules for parsing +-function _M.basic_parse(s,all_text,html) +- local t_insert,t_remove = table.insert,table.remove +- local s_find,s_sub = string.find,string.sub ++function _M.basic_parse(s, all_text, html) + local stack = {} + local top = {} + +@@ -559,12 +941,12 @@ function _M.basic_parse(s,all_text,html) + local arg = {} + s:gsub("([%w:%-_]+)%s*=%s*([\"'])(.-)%2", function (w, _, a) + if html then w = w:lower() end +- arg[w] = unescape(a) ++ arg[w] = xml_unescape(a) + end) + if html then + s:gsub("([%w:%-_]+)%s*=%s*([^\"']+)%s*", function (w, a) + w = w:lower() +- arg[w] = unescape(a) ++ arg[w] = xml_unescape(a) + end) + end + return arg +@@ -572,9 +954,10 @@ function _M.basic_parse(s,all_text,html) + + t_insert(stack, top) + local ni,c,label,xarg, empty, _, istart +- local i, j = 1, 1 ++ local i = 1 ++ local j + -- we're not interested in +- _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*') ++ _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*') + if not istart then -- or + _,istart = s_find(s,'^%s*%s*') + end +@@ -596,11 +979,9 @@ function _M.basic_parse(s,all_text,html) + if html then + label = label:lower() + if html_empty_elements[label] then empty = "/" end +- if label == 'script' then +- end + end + if all_text or not s_find(text, "^%s*$") then +- t_insert(top, unescape(text)) ++ t_insert(top, xml_unescape(text)) + end + if empty == "/" then -- empty element tag + t_insert(top, setmetatable({tag=label, attr=parseargs(xarg), empty=1},Doc)) +@@ -619,11 +1000,11 @@ function _M.basic_parse(s,all_text,html) + t_insert(top, toclose) + end + end +- i = j+1 ++ i = j+1 + end + local text = s_sub(s, i) + if all_text or not s_find(text, "^%s*$") then +- t_insert(stack[#stack], unescape(text)) ++ t_insert(stack[#stack], xml_unescape(text)) + end + if #stack > 1 then + error("unclosed "..stack[#stack].tag) +@@ -632,145 +1013,151 @@ function _M.basic_parse(s,all_text,html) + return is_text(res[1]) and res[2] or res[1] + end + +-local function empty(attr) return not attr or not next(attr) end +-local function is_element(d) return type(d) == 'table' and d.tag ~= nil end ++do ++ local match do + +--- returns the key,value pair from a table if it has exactly one entry +-local function has_one_element(t) +- local key,value = next(t) +- if next(t,key) ~= nil then return false end +- return key,value +-end ++ local function empty(attr) return not attr or not next(attr) end + +-local function append_capture(res,tbl) +- if not empty(tbl) then -- no point in capturing empty tables... +- local key +- if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table +- key = tbl._ +- tbl._ = nil +- if empty(tbl) then return end +- end +- -- a table with only one pair {[0]=value} shall be reduced to that value +- local numkey,val = has_one_element(tbl) +- if numkey == 0 then tbl = val end +- if key then +- res[key] = tbl +- else -- otherwise, we append the captured table +- t_insert(res,tbl) +- end ++ local append_capture do ++ -- returns the key,value pair from a table if it has exactly one entry ++ local function has_one_element(t) ++ local key,value = next(t) ++ if next(t,key) ~= nil then return false end ++ return key,value ++ end ++ ++ function append_capture(res,tbl) ++ if not empty(tbl) then -- no point in capturing empty tables... ++ local key ++ if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table ++ key = tbl._ ++ tbl._ = nil ++ if empty(tbl) then return end ++ end ++ -- a table with only one pair {[0]=value} shall be reduced to that value ++ local numkey,val = has_one_element(tbl) ++ if numkey == 0 then tbl = val end ++ if key then ++ res[key] = tbl ++ else -- otherwise, we append the captured table ++ t_insert(res,tbl) ++ end ++ end ++ end + end +-end + +-local function make_number(pat) +- if pat:find '^%d+$' then -- $1 etc means use this as an array location +- pat = tonumber(pat) ++ local function make_number(pat) ++ if pat:find '^%d+$' then -- $1 etc means use this as an array location ++ pat = tonumber(pat) ++ end ++ return pat + end +- return pat +-end + +-local function capture_attrib(res,pat,value) +- pat = make_number(pat:sub(2)) +- res[pat] = value +- return true +-end ++ local function capture_attrib(res,pat,value) ++ pat = make_number(pat:sub(2)) ++ res[pat] = value ++ return true ++ end + +-local match +-function match(d,pat,res,keep_going) +- local ret = true +- if d == nil then d = '' end --return false end +- -- attribute string matching is straight equality, except if the pattern is a $ capture, +- -- which always succeeds. +- if is_text(d) then +- if not is_text(pat) then return false end +- if _M.debug then print(d,pat) end +- if pat:find '^%$' then +- return capture_attrib(res,pat,d) ++ function match(d,pat,res,keep_going) ++ local ret = true ++ if d == nil then d = '' end --return false end ++ -- attribute string matching is straight equality, except if the pattern is a $ capture, ++ -- which always succeeds. ++ if is_text(d) then ++ if not is_text(pat) then return false end ++ if _M.debug then print(d,pat) end ++ if pat:find '^%$' then ++ return capture_attrib(res,pat,d) ++ else ++ return d == pat ++ end + else +- return d == pat +- end +- else +- if _M.debug then print(d.tag,pat.tag) end +- -- this is an element node. For a match to succeed, the attributes must +- -- match as well. +- -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute +- local tagpat = pat.tag:match '^(.-)%-$' +- if tagpat then +- tagpat = make_number(tagpat) +- res[tagpat] = d.tag +- end +- if d.tag == pat.tag or tagpat then +- +- if not empty(pat.attr) then +- if empty(d.attr) then ret = false +- else +- for prop,pval in pairs(pat.attr) do +- local dval = d.attr[prop] +- if not match(dval,pval,res) then ret = false; break end +- end +- end ++ if _M.debug then print(d.tag,pat.tag) end ++ -- this is an element node. For a match to succeed, the attributes must ++ -- match as well. ++ -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute ++ local tagpat = pat.tag:match '^(.-)%-$' ++ if tagpat then ++ tagpat = make_number(tagpat) ++ res[tagpat] = d.tag + end +- -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..} +- if ret and #pat > 0 then +- local i,j = 1,1 +- local function next_elem() +- j = j + 1 -- next child element of data +- if is_text(d[j]) then j = j + 1 end +- return j <= #d +- end +- repeat +- local p = pat[i] +- -- repeated {{<...>}} patterns shall match one or more elements +- -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X} +- if is_element(p) and p.repeated then +- local found +- repeat +- local tbl = {} +- ret = match(d[j],p,tbl,false) +- if ret then +- found = false --true +- append_capture(res,tbl) +- end +- until not next_elem() or (found and not ret) +- i = i + 1 ++ if d.tag == pat.tag or tagpat then ++ ++ if not empty(pat.attr) then ++ if empty(d.attr) then ret = false + else +- ret = match(d[j],p,res,false) +- if ret then i = i + 1 end ++ for prop,pval in pairs(pat.attr) do ++ local dval = d.attr[prop] ++ if not match(dval,pval,res) then ret = false; break end ++ end ++ end ++ end ++ -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..} ++ if ret and #pat > 0 then ++ local i,j = 1,1 ++ local function next_elem() ++ j = j + 1 -- next child element of data ++ if is_text(d[j]) then j = j + 1 end ++ return j <= #d + end +- until not next_elem() or i > #pat -- run out of elements or patterns to match +- -- if every element in our pattern matched ok, then it's been a successful match +- if i > #pat then return true end ++ repeat ++ local p = pat[i] ++ -- repeated {{<...>}} patterns shall match one or more elements ++ -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X} ++ if is_tag(p) and p.repeated then ++ local found ++ repeat ++ local tbl = {} ++ ret = match(d[j],p,tbl,false) ++ if ret then ++ found = false --true ++ append_capture(res,tbl) ++ end ++ until not next_elem() or (found and not ret) ++ i = i + 1 ++ else ++ ret = match(d[j],p,res,false) ++ if ret then i = i + 1 end ++ end ++ until not next_elem() or i > #pat -- run out of elements or patterns to match ++ -- if every element in our pattern matched ok, then it's been a successful match ++ if i > #pat then return true end ++ end ++ if ret then return true end ++ else ++ ret = false + end +- if ret then return true end +- else +- ret = false +- end +- -- keep going anyway - look at the children! +- if keep_going then +- for child in d:childtags() do +- ret = match(child,pat,res,keep_going) +- if ret then break end ++ -- keep going anyway - look at the children! ++ if keep_going then ++ for child in d:childtags() do ++ ret = match(child,pat,res,keep_going) ++ if ret then break end ++ end + end + end ++ return ret + end +- return ret +-end ++ end + +-function Doc:match(pat) +- local err +- pat,err = template_cache(pat) +- if not pat then return nil, err end +- _M.walk(pat,false,function(_,d) +- if is_text(d[1]) and is_element(d[2]) and is_text(d[3]) and +- d[1]:find '%s*{{' and d[3]:find '}}%s*' then +- t_remove(d,1) +- t_remove(d,2) +- d[1].repeated = true +- end +- end) ++ --- does something... ++ function Doc:match(pat) ++ local err ++ pat,err = template_cache(pat) ++ if not pat then return nil, err end ++ _M.walk(pat,false,function(_,d) ++ if is_text(d[1]) and is_tag(d[2]) and is_text(d[3]) and ++ d[1]:find '%s*{{' and d[3]:find '}}%s*' then ++ t_remove(d,1) ++ t_remove(d,2) ++ d[1].repeated = true ++ end ++ end) + +- local res = {} +- local ret = match(self,pat,res,true) +- return res,ret ++ local res = {} ++ local ret = match(self,pat,res,true) ++ return res,ret ++ end + end + + +diff --git a/extra/penlight/penlight-dev-1.rockspec b/extra/penlight/penlight-dev-1.rockspec +new file mode 100644 +index 0000000..c632001 +--- /dev/null ++++ b/extra/penlight/penlight-dev-1.rockspec +@@ -0,0 +1,89 @@ ++local package_name = "penlight" ++local package_version = "dev" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++rockspec_format = "3.0" ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ detailed = [[ ++ Penlight is a set of pure Lua libraries focusing on input data handling ++ (such as reading configuration files), functional programming ++ (such as map, reduce, placeholder expressions,etc), and OS path management. ++ Much of the functionality is inspired by the Python standard libraries. ++ ]], ++ license = "MIT/X11", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ issues_url = "https://github.com/"..github_account_name.."/"..github_repo_name.."/issues", ++ maintainer = "thijs@thijsschreijer.nl", ++} ++ ++dependencies = { ++ "lua >= 5.1", ++ "luafilesystem" ++} ++ ++test_dependencies = { ++ "busted", ++} ++ ++test = { ++ type = "busted", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl"] = "lua/pl/init.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.compat"] = "lua/pl/compat.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.template"] = "lua/pl/template.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} +diff --git a/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec +new file mode 100644 +index 0000000..6bcc6ab +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.10.0" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec +new file mode 100644 +index 0000000..622a219 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.10.0" ++local rockspec_revision = "2" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec +new file mode 100644 +index 0000000..253f8e6 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.11.0" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec +new file mode 100644 +index 0000000..23a6a11 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.11.0" ++local rockspec_revision = "2" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec +new file mode 100644 +index 0000000..9ebefa2 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.12.0" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec +new file mode 100644 +index 0000000..39ddf10 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.12.0" ++local rockspec_revision = "2" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec +new file mode 100644 +index 0000000..8aeb7d4 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.13.0" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec +new file mode 100644 +index 0000000..4451a66 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.13.1" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec +new file mode 100644 +index 0000000..b00255f +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec +@@ -0,0 +1,89 @@ ++local package_name = "penlight" ++local package_version = "1.14.0" ++local rockspec_revision = "1" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++rockspec_format = "3.0" ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ detailed = [[ ++ Penlight is a set of pure Lua libraries focusing on input data handling ++ (such as reading configuration files), functional programming ++ (such as map, reduce, placeholder expressions,etc), and OS path management. ++ Much of the functionality is inspired by the Python standard libraries. ++ ]], ++ license = "MIT/X11", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ issues_url = "https://github.com/"..github_account_name.."/"..github_repo_name.."/issues", ++ maintainer = "thijs@thijsschreijer.nl", ++} ++ ++dependencies = { ++ "lua >= 5.1", ++ "luafilesystem" ++} ++ ++test_dependencies = { ++ "busted", ++} ++ ++test = { ++ type = "busted", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl"] = "lua/pl/init.lua", ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.compat"] = "lua/pl/compat.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua" ++ }, ++ copy_directories = {"docs", "tests"} ++} +diff --git a/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec +new file mode 100644 +index 0000000..0ff4f87 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.14.0" ++local rockspec_revision = "2" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec +new file mode 100644 +index 0000000..e2ca7d0 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec +@@ -0,0 +1,78 @@ ++local package_name = "penlight" ++local package_version = "1.14.0" ++local rockspec_revision = "3" ++local github_account_name = "lunarmodules" ++local github_repo_name = package_name ++local git_checkout = package_version == "dev" and "master" or package_version ++ ++ ++package = package_name ++version = package_version .. "-" .. rockspec_revision ++ ++source = { ++ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", ++ branch = git_checkout ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.compat"] = "lua/pl/compat.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/penlight-scm-1.rockspec b/extra/penlight/rockspecs/penlight-1.6.0-1.rockspec +similarity index 83% +rename from penlight-scm-1.rockspec +rename to rockspecs/penlight-1.6.0-1.rockspec +index 35a834b..760cbf5 100644 +--- a/extra/penlight/penlight-scm-1.rockspec ++++ b/extra/penlight/rockspecs/penlight-1.6.0-1.rockspec +@@ -1,15 +1,16 @@ + package = "penlight" +-version = "scm-1" ++version = "1.6.0-1" + + source = { +- url = "git://github.com/stevedonovan/Penlight.git", ++ url = "git://github.com/Tieske/Penlight.git", ++ branch = "1.6.0" + } + + description = { + summary = "Lua utility libraries loosely based on the Python standard libraries", +- homepage = "http://stevedonovan.github.com/Penlight", ++ homepage = "http://tieske.github.io/Penlight", + license = "MIT/X11", +- maintainer = "steve.j.donovan@gmail.com", ++ maintainer = "thijs@thijsschreijer.nl", + detailed = [[ + Penlight is a set of pure Lua libraries for making it easier to work with common tasks like + iterating over directories, reading configuration files and the like. Provides functional operations +@@ -18,18 +19,18 @@ on tables and sequences. + } + + dependencies = { +- "luafilesystem" ++ "luafilesystem", + } + + build = { + type = "builtin", + modules = { +- ["pl"] = "lua/pl/init.lua", + ["pl.strict"] = "lua/pl/strict.lua", + ["pl.dir"] = "lua/pl/dir.lua", + ["pl.operator"] = "lua/pl/operator.lua", + ["pl.input"] = "lua/pl/input.lua", + ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", + ["pl.seq"] = "lua/pl/seq.lua", + ["pl.stringio"] = "lua/pl/stringio.lua", + ["pl.text"] = "lua/pl/text.lua", +@@ -39,7 +40,6 @@ build = { + ["pl.stringx"] = "lua/pl/stringx.lua", + ["pl.lexer"] = "lua/pl/lexer.lua", + ["pl.utils"] = "lua/pl/utils.lua", +- ["pl.compat"] = "lua/pl/compat.lua", + ["pl.sip"] = "lua/pl/sip.lua", + ["pl.permute"] = "lua/pl/permute.lua", + ["pl.pretty"] = "lua/pl/pretty.lua", +@@ -47,6 +47,7 @@ build = { + ["pl.List"] = "lua/pl/List.lua", + ["pl.data"] = "lua/pl/data.lua", + ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", + ["pl.luabalanced"] = "lua/pl/luabalanced.lua", + ["pl.comprehension"] = "lua/pl/comprehension.lua", + ["pl.path"] = "lua/pl/path.lua", +@@ -61,8 +62,9 @@ build = { + ["pl.Set"] = "lua/pl/Set.lua", + ["pl.xml"] = "lua/pl/xml.lua", + ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", + ["pl.types"] = "lua/pl/types.lua", +- ["pl.import_into"] = "lua/pl/import_into.lua" + }, +- copy_directories = {"doc", "tests"} ++ copy_directories = {"docs", "tests"} + } ++ +diff --git a/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec +new file mode 100644 +index 0000000..b8fbfa7 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.6.0-2" ++ ++source = { ++ url = "git+https://github.com/Tieske/Penlight.git", ++ branch = "1.6.0" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "http://tieske.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec +new file mode 100644 +index 0000000..9b19f39 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.7.0-1" ++ ++source = { ++ url = "git://github.com/Tieske/Penlight.git", ++ branch = "1.7.0" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "http://tieske.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec +new file mode 100644 +index 0000000..5013503 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.7.0-2" ++ ++source = { ++ url = "git+https://github.com/Tieske/Penlight.git", ++ branch = "1.7.0" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "http://tieske.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec +new file mode 100644 +index 0000000..7ee49d3 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.8.0-1" ++ ++source = { ++ url = "git://github.com/Tieske/Penlight.git", ++ branch = "1.8.0" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "http://tieske.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec +new file mode 100644 +index 0000000..d48571e +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.8.0-2" ++ ++source = { ++ url = "git+https://github.com/Tieske/Penlight.git", ++ branch = "1.8.0" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "http://tieske.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec +new file mode 100644 +index 0000000..45919d1 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.8.1-1" ++ ++source = { ++ url = "git://github.com/lunarmodules/Penlight.git", ++ tag = "1.8.1" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec b/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec +new file mode 100644 +index 0000000..7f05877 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.8.1-2" ++ ++source = { ++ url = "git+https://github.com/lunarmodules/Penlight.git", ++ tag = "1.8.1" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec +new file mode 100644 +index 0000000..e546a16 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.9.1-1" ++ ++source = { ++ url = "git://github.com/lunarmodules/Penlight.git", ++ tag = "1.9.1" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec b/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec +new file mode 100644 +index 0000000..f0704ff +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.9.1-2" ++ ++source = { ++ url = "git+https://github.com/lunarmodules/Penlight.git", ++ tag = "1.9.1" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec b/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec +new file mode 100644 +index 0000000..5145b38 +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.9.2-1" ++ ++source = { ++ url = "git://github.com/lunarmodules/Penlight.git", ++ tag = "1.9.2" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec b/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec +new file mode 100644 +index 0000000..0caf00e +--- /dev/null ++++ b/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec +@@ -0,0 +1,70 @@ ++package = "penlight" ++version = "1.9.2-2" ++ ++source = { ++ url = "git+https://github.com/lunarmodules/Penlight.git", ++ tag = "1.9.2" ++} ++ ++description = { ++ summary = "Lua utility libraries loosely based on the Python standard libraries", ++ homepage = "https://lunarmodules.github.io/Penlight", ++ license = "MIT/X11", ++ maintainer = "thijs@thijsschreijer.nl", ++ detailed = [[ ++Penlight is a set of pure Lua libraries for making it easier to work with common tasks like ++iterating over directories, reading configuration files and the like. Provides functional operations ++on tables and sequences. ++]] ++} ++ ++dependencies = { ++ "luafilesystem", ++} ++ ++build = { ++ type = "builtin", ++ modules = { ++ ["pl.strict"] = "lua/pl/strict.lua", ++ ["pl.dir"] = "lua/pl/dir.lua", ++ ["pl.operator"] = "lua/pl/operator.lua", ++ ["pl.input"] = "lua/pl/input.lua", ++ ["pl.config"] = "lua/pl/config.lua", ++ ["pl.compat"] = "lua/pl/config.lua", ++ ["pl.seq"] = "lua/pl/seq.lua", ++ ["pl.stringio"] = "lua/pl/stringio.lua", ++ ["pl.text"] = "lua/pl/text.lua", ++ ["pl.test"] = "lua/pl/test.lua", ++ ["pl.tablex"] = "lua/pl/tablex.lua", ++ ["pl.app"] = "lua/pl/app.lua", ++ ["pl.stringx"] = "lua/pl/stringx.lua", ++ ["pl.lexer"] = "lua/pl/lexer.lua", ++ ["pl.utils"] = "lua/pl/utils.lua", ++ ["pl.sip"] = "lua/pl/sip.lua", ++ ["pl.permute"] = "lua/pl/permute.lua", ++ ["pl.pretty"] = "lua/pl/pretty.lua", ++ ["pl.class"] = "lua/pl/class.lua", ++ ["pl.List"] = "lua/pl/List.lua", ++ ["pl.data"] = "lua/pl/data.lua", ++ ["pl.Date"] = "lua/pl/Date.lua", ++ ["pl.init"] = "lua/pl/init.lua", ++ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", ++ ["pl.comprehension"] = "lua/pl/comprehension.lua", ++ ["pl.path"] = "lua/pl/path.lua", ++ ["pl.array2d"] = "lua/pl/array2d.lua", ++ ["pl.func"] = "lua/pl/func.lua", ++ ["pl.lapp"] = "lua/pl/lapp.lua", ++ ["pl.file"] = "lua/pl/file.lua", ++ ['pl.template'] = "lua/pl/template.lua", ++ ["pl.Map"] = "lua/pl/Map.lua", ++ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", ++ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", ++ ["pl.Set"] = "lua/pl/Set.lua", ++ ["pl.xml"] = "lua/pl/xml.lua", ++ ["pl.url"] = "lua/pl/url.lua", ++ ["pl.import_into"] = "lua/pl/import_into.lua", ++ ["pl.types"] = "lua/pl/types.lua", ++ }, ++ copy_directories = {"docs", "tests"} ++} ++ +diff --git a/extra/penlight/run.lua b/extra/penlight/run.lua +old mode 100644 +new mode 100755 +index 98c3587..4d0764b +--- a/extra/penlight/run.lua ++++ b/extra/penlight/run.lua +@@ -1,3 +1,5 @@ ++#!/usr/bin/env lua ++ + -- Running tests and/or examples. + local lfs = require "lfs" + +@@ -32,7 +34,7 @@ end + + local dir_sep = package.config:sub(1, 1) + local quote = dir_sep == "/" and "'" or '"' +-local pl_src = "lua" .. dir_sep .. "?.lua" ++local pl_src = "lua/?.lua;lua/?/init.lua" + lua = lua .. " -e " .. quote .. "package.path=[[" .. pl_src .. ";]]..package.path" .. quote + + local function run_directory(dir) +diff --git a/extra/penlight/spec/app_spec.lua b/extra/penlight/spec/app_spec.lua +new file mode 100644 +index 0000000..3b1f290 +--- /dev/null ++++ b/extra/penlight/spec/app_spec.lua +@@ -0,0 +1,27 @@ ++local app = require("pl.app") ++ ++describe("pl.app.lua", function () ++ ++ local invocation = app.lua() ++ ++ it("should pick up the arguments used to run this test", function () ++ assert.is.truthy(invocation:match("lua.+package.+busted")) ++ end) ++ ++ it("should be reusable to invoke Lua", function () ++ assert.is.truthy(os.execute(app.lua()..' -e "n=1;os.exit(n-1)"')) ++ end) ++ ++end) ++ ++describe("pl.app.platform", function () ++ ++ -- TODO: Find a reliable alternate way to determine platform to check that ++ -- this is returning the right answer, not just any old answer. ++ it("should at least return a valid platform", function () ++ local platforms = { Linux = true, OSX = true, Windows = true } ++ local detected = app.platform() ++ assert.is.truthy(platforms[detected]) ++ end) ++ ++end) +diff --git a/extra/penlight/spec/array2d_spec.lua b/extra/penlight/spec/array2d_spec.lua +new file mode 100644 +index 0000000..95122de +--- /dev/null ++++ b/extra/penlight/spec/array2d_spec.lua +@@ -0,0 +1,557 @@ ++local array2d = require("pl.array2d") ++ ++describe("pl.array2d", function() ++ ++ describe("new()", function() ++ it("creates an empty 2d array", function() ++ assert.same({{},{},{}}, array2d.new(3,3,nil)) ++ end) ++ ++ it("creates a value-filled 2d array", function() ++ assert.same({{99,99,99}, ++ {99,99,99}, ++ {99,99,99}}, array2d.new(3,3,99)) ++ end) ++ ++ it("creates a function-filled 2d array", function() ++ assert.same({{2,3,4}, ++ {3,4,5}, ++ {4,5,6}}, array2d.new(3,3,function(i,j) return i+j end)) ++ end) ++ end) ++ ++ describe("size()", function() ++ it("returns array size", function() ++ local a = array2d.new(3,5,99) ++ assert.same({3,5}, {array2d.size(a)}) ++ end) ++ ++ it("returns 0 columns for nil arrays", function() ++ local a = array2d.new(3,5,nil) ++ assert.same({3,0}, {array2d.size(a)}) ++ end) ++ end) ++ ++ describe("column()", function() ++ it("returns a column copy", function() ++ local a = {{1,2}, ++ {3,4}, ++ {5,6}} ++ assert.same({1,3,5}, array2d.column(a,1)) ++ assert.same({2,4,6}, array2d.column(a,2)) ++ end) ++ end) ++ ++ describe("row()", function() ++ it("returns a row copy", function() ++ local a = {{1,2}, ++ {3,4}, ++ {5,6}} ++ assert.same({1,2}, array2d.row(a,1)) ++ -- next test: need to remove the metatable to prevent comparison by ++ -- metamethods in Lua 5.3 and 5.4 ++ assert.not_equal(a[1], setmetatable(array2d.row(a,1),nil)) ++ assert.same({3,4}, array2d.row(a,2)) ++ assert.same({5,6}, array2d.row(a,3)) ++ end) ++ end) ++ ++ describe("map()", function() ++ it("maps a function on an array", function() ++ local a1 = array2d.new(2,3,function(i,j) return i+j end) ++ local a2 = array2d.map(function(a,b) return a .. b end, a1, "x") ++ assert.same({{"2x","3x","4x"}, ++ {"3x","4x","5x"}}, a2) ++ end) ++ end) ++ ++ describe("reduce_rows()", function() ++ it("reduces rows", function() ++ local a = {{ 1, 2, 3, 4}, ++ { 10, 20, 30, 40}, ++ { 100, 200, 300, 400}, ++ {1000,2000,3000,4000}} ++ assert.same({10,100,1000,10000},array2d.reduce_rows('+',a)) ++ end) ++ end) ++ ++ describe("reduce_cols()", function() ++ it("reduces columns", function() ++ local a = {{ 1, 2, 3, 4}, ++ { 10, 20, 30, 40}, ++ { 100, 200, 300, 400}, ++ {1000,2000,3000,4000}} ++ assert.same({1111,2222,3333,4444},array2d.reduce_cols('+',a)) ++ end) ++ end) ++ ++ describe("reduce2()", function() ++ it("recuces array to scalar", function() ++ local a = {{1,10}, ++ {2,10}, ++ {3,10}} ++ assert.same(60, array2d.reduce2('+','*',a)) ++ end) ++ end) ++ ++ describe("map2()", function() ++ it("maps over 2 arrays", function() ++ local b = {{10,20}, ++ {30,40}} ++ local a = {{1,2}, ++ {3,4}} ++ -- 2 2d arrays ++ assert.same({{11,22},{33,44}}, array2d.map2('+',2,2,a,b)) ++ -- 1d, 2d ++ assert.same({{11,102},{13,104}}, array2d.map2('+',1,2,{10,100},a)) ++ -- 2d, 1d ++ assert.same({{1,-2},{3,-4}},array2d.map2('*',2,1,a,{1,-1})) ++ end) ++ end) ++ ++ describe("product()", function() ++ it("creates a product array", function() ++ local a = array2d.product('..',{1,2,3},{'a','b','c'}) ++ assert.same({{'1a','2a','3a'},{'1b','2b','3b'},{'1c','2c','3c'}}, a) ++ ++ local a = array2d.product('{}',{1,2},{'a','b','c'}) ++ assert.same({{{1,'a'},{2,'a'}},{{1,'b'},{2,'b'}},{{1,'c'},{2,'c'}}}, a) ++ end) ++ end) ++ ++ describe("flatten()", function() ++ it("flattens a 2darray", function() ++ local a = {{1,2}, ++ {3,4}, ++ {5,6}} ++ assert.same( {1,2,3,4,5,6}, array2d.flatten(a)) ++ end) ++ ++ it("keeps a nil-array 'square'", function() ++ local a = {{ 1,2}, ++ {nil,4}, ++ {nil,6}} ++ assert.same( {1,2,nil,4,nil,6}, array2d.flatten(a)) ++ end) ++ end) ++ ++ describe("reshape()", function() ++ it("reshapes array in new nr of rows", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}, ++ {10,11,12}} ++ local b = array2d.reshape(a, 2, false) ++ assert.same({{ 1, 2, 3, 4, 5, 6}, ++ { 7, 8, 9,10,11,12}}, b) ++ local c = array2d.reshape(b, 4, false) ++ assert.same(a, c) ++ end) ++ it("reshapes array in new nr of rows, column order", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}} ++ local b = array2d.reshape(a, 3, true) ++ assert.same({{ 1, 4, 7}, ++ { 2, 5, 8}, ++ { 3, 6, 9}}, b) ++ end) ++ end) ++ ++ describe("transpose()", function() ++ it("transposes a 2d array", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}} ++ local b = array2d.transpose(a) ++ assert.same({{ 1, 4, 7}, ++ { 2, 5, 8}, ++ { 3, 6, 9}}, b) ++ ++ local a = {{ 1, 2, 3, 4, 5}, ++ { 6, 7, 8, 9, 10}} ++ local b = array2d.transpose(a) ++ assert.same({{ 1, 6}, ++ { 2, 7}, ++ { 3, 8}, ++ { 4, 9}, ++ { 5,10}}, b) ++ end) ++ end) ++ ++ describe("swap_rows()", function() ++ it("swaps 2 rows, in-place", function() ++ local a = {{1,2}, ++ {3,4}, ++ {5,6}} ++ local b = array2d.swap_rows(a, 1, 3) ++ assert.same({{5,6}, ++ {3,4}, ++ {1,2}}, b) ++ assert.equal(a, b) ++ end) ++ end) ++ ++ describe("swap_cols()", function() ++ it("swaps 2 columns, in-place", function() ++ local a = {{1,2,3}, ++ {4,5,6}, ++ {7,8,9}} ++ local b = array2d.swap_cols(a, 1, 3) ++ assert.same({{3,2,1}, ++ {6,5,4}, ++ {9,8,7}}, b) ++ assert.equal(a, b) ++ end) ++ end) ++ ++ describe("extract_rows()", function() ++ it("extracts rows", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}, ++ {10,11,12}} ++ local b = array2d.extract_rows(a, {1, 3}) ++ assert.same({{1,2,3}, ++ {7,8,9}}, b) ++ end) ++ end) ++ ++ describe("extract_cols()", function() ++ it("extracts columns", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}, ++ {10,11,12}} ++ local b = array2d.extract_cols(a, {1, 2}) ++ assert.same({{ 1, 2}, ++ { 4, 5}, ++ { 7, 8}, ++ {10,11}}, b) ++ end) ++ end) ++ ++ describe("remove_row()", function() ++ it("removes a row", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}, ++ {10,11,12}} ++ array2d.remove_row(a, 2) ++ assert.same({{ 1, 2, 3}, ++ { 7, 8, 9}, ++ {10,11,12}}, a) ++ end) ++ end) ++ ++ describe("remove_col()", function() ++ it("removes a colum", function() ++ local a = {{ 1, 2, 3}, ++ { 4, 5, 6}, ++ { 7, 8, 9}, ++ {10,11,12}} ++ array2d.remove_col(a, 2) ++ assert.same({{ 1, 3}, ++ { 4, 6}, ++ { 7, 9}, ++ {10,12}}, a) ++ end) ++ end) ++ ++ describe("parse_range()", function() ++ it("parses A1:B2 format", function() ++ assert.same({4,11,7,12},{array2d.parse_range("K4:L7")}) ++ assert.same({4,28,7,54},{array2d.parse_range("AB4:BB7")}) ++ -- test Col R since it might be mixed up with RxCx format ++ assert.same({4,18,7,18},{array2d.parse_range("R4:R7")}) ++ end) ++ ++ it("parses A1 format", function() ++ assert.same({4,11},{array2d.parse_range("K4")}) ++ -- test Col R since it might be mixed up with RxCx format ++ assert.same({4,18},{array2d.parse_range("R4")}) ++ end) ++ ++ it("parses R1C1:R2C2 format", function() ++ assert.same({4,11,7,12},{array2d.parse_range("R4C11:R7C12")}) ++ end) ++ ++ it("parses R1C1 format", function() ++ assert.same({4,11},{array2d.parse_range("R4C11")}) ++ end) ++ end) ++ ++ describe("range()", function() ++ it("returns a range", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local b = array2d.range(a, "B3:C4") ++ assert.same({{ 8, 9}, ++ {11,12}}, b) ++ end) ++ end) ++ ++ describe("default_range()", function() ++ it("returns the default range", function() ++ local a = array2d.new(4,6,1) ++ assert.same({1,1,4,6}, {array2d.default_range(a, nil, nil, nil, nil)}) ++ end) ++ ++ it("accepts negative indices", function() ++ local a = array2d.new(4,6,1) ++ assert.same({2,2,3,5}, {array2d.default_range(a, -3, -5, -2, -2)}) ++ end) ++ ++ it("corrects out of bounds indices", function() ++ local a = array2d.new(4,6,1) ++ assert.same({1,1,4,6}, {array2d.default_range(a, -100, -100, 100, 100)}) ++ end) ++ end) ++ ++ describe("slice()", function() ++ it("returns a slice", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local b = array2d.slice(a,3,2,4,3) ++ assert.same({{ 8, 9}, ++ {11,12}}, b) ++ end) ++ ++ it("returns a single row if rows are equal", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local b = array2d.slice(a,4,1,4,3) ++ assert.same({10,11,12}, b) ++ end) ++ ++ it("returns a single column if columns are equal", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local b = array2d.slice(a,1,3,4,3) ++ assert.same({3,6,9,12}, b) ++ end) ++ ++ it("returns a single value if rows and columns are equal", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local b = array2d.slice(a,2,2,2,2) ++ assert.same(5, b) ++ end) ++ end) ++ ++ describe("set()", function() ++ it("sets a range to a value", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ array2d.set(a,0,2,2,3,3) ++ assert.same({{1 ,2 ,3}, ++ {4 ,0 ,0}, ++ {7 ,0 ,0}, ++ {10,11,12}}, a) ++ end) ++ ++ it("sets a range to a function value", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local x = 10 ++ local args = {} ++ local f = function(r,c) ++ args[#args+1] = {r,c} ++ x = x + 1 ++ return x ++ end ++ array2d.set(a,f,3,1,4,3) ++ assert.same({{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {11,12,13}, ++ {14,15,16}}, a) ++ -- validate args used to call the function ++ assert.same({{3,1},{3,2},{3,3},{4,1},{4,2},{4,3}}, args) ++ end) ++ end) ++ ++ describe("write()", function() ++ it("writes array to a file", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local f = setmetatable({}, { ++ __index = { ++ write = function(self,str) ++ self[#self+1] = str ++ end ++ } ++ }) ++ array2d.write(a,f,"(%s)") ++ f = table.concat(f) ++ assert.equal([[(1)(2)(3) ++(4)(5)(6) ++(7)(8)(9) ++(10)(11)(12) ++]],f) ++ end) ++ ++ it("writes partial array to a file", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local f = setmetatable({}, { ++ __index = { ++ write = function(self,str) ++ self[#self+1] = str ++ end ++ } ++ }) ++ array2d.write(a,f,"(%s)", 1,1,2,2) ++ f = table.concat(f) ++ assert.equal([[(1)(2) ++(4)(5) ++]],f) ++ end) ++ end) ++ ++ describe("forall()", function() ++ it("runs all value and row functions", function() ++ local r = {} ++ local t = 0 ++ local fval = function(row, j) t = t + row[j] end ++ local frow = function(i) r[#r+1] = t; t = 0 end ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ array2d.forall(a, fval, frow) ++ assert.same({6, 15, 24, 33}, r) ++ r = {} ++ array2d.forall(a, fval, frow, 2,2,4,3) ++ assert.same({11, 17, 23}, r) ++ end) ++ ++ end) ++ ++ describe("move()", function() ++ it("moves block to destination array", function() ++ local a = array2d.new(4,4,0) ++ local b = array2d.new(3,3,1) ++ array2d.move(a,2,2,b) ++ assert.same({{0,0,0,0}, ++ {0,1,1,1}, ++ {0,1,1,1}, ++ {0,1,1,1}}, a) ++ end) ++ end) ++ ++ describe("iter()", function() ++ it("iterates all values", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ for v, i, j in array2d.iter(a) do ++ r[#r+1] = v ++ assert.is_nil(i) ++ assert.is_nil(j) ++ end ++ assert.same({1,2,3,4,5,6,7,8,9,10,11,12}, r) ++ end) ++ ++ it("iterates all values and indices", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ local ri = {} ++ local rj = {} ++ for i, j, v in array2d.iter(a,true) do ++ r[#r+1] = v ++ ri[#ri+1] = i ++ rj[#rj+1] = j ++ end ++ assert.same({1,2,3,4,5,6,7,8,9,10,11,12}, r) ++ assert.same({1,1,1,2,2,2,3,3,3,4,4,4}, ri) ++ assert.same({1,2,3,1,2,3,1,2,3,1,2,3}, rj) ++ end) ++ ++ it("iterates all values of a 2d array part", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ for v, i, j in array2d.iter(a,false,2,2,4,3) do ++ r[#r+1] = v ++ assert.is_nil(i) ++ assert.is_nil(j) ++ end ++ assert.same({5,6,8,9,11,12}, r) ++ end) ++ ++ it("iterates all values and indices of a 2d array part", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ local ri = {} ++ local rj = {} ++ for i, j, v in array2d.iter(a,true,2,2,4,3) do ++ r[#r+1] = v ++ ri[#ri+1] = i ++ rj[#rj+1] = j ++ end ++ assert.same({5,6,8,9,11,12}, r) ++ assert.same({2,2,3,3,4,4}, ri) ++ assert.same({2,3,2,3,2,3}, rj) ++ end) ++ end) ++ ++ describe("columns()", function() ++ it("iterates all columns", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ for col, idx in array2d.columns(a) do ++ r[#r+1] = col ++ col.idx = idx ++ end ++ assert.same({{1,4,7,10, idx=1},{2,5,8,11, idx=2},{3,6,9,12, idx=3}}, r) ++ end) ++ end) ++ ++ describe("rows()", function() ++ it("iterates all columns", function() ++ local a = {{1 ,2 ,3}, ++ {4 ,5 ,6}, ++ {7 ,8 ,9}, ++ {10,11,12}} ++ local r = {} ++ for row, idx in array2d.rows(a) do ++ r[#r+1] = row ++ row.idx = idx ++ end ++ assert.same({{1,2,3, idx=1},{4,5,6, idx=2}, ++ {7,8,9, idx=3},{10,11,12, idx=4}}, r) ++ end) ++ end) ++ ++end) +diff --git a/extra/penlight/spec/date_spec.lua b/extra/penlight/spec/date_spec.lua +new file mode 100644 +index 0000000..1032de2 +--- /dev/null ++++ b/extra/penlight/spec/date_spec.lua +@@ -0,0 +1,48 @@ ++local Date = require("pl.Date") ++ ++describe("pl.Date", function () ++ ++ describe("function", function () ++ ++ describe("Format()", function () ++ ++ it("should output parsable inputs", function () ++ local function assert_date_format(expected, format) ++ local df = Date.Format(format) ++ local d = df:parse(expected) ++ assert.is.equal(expected, df:tostring(d)) ++ end ++ assert_date_format('02/04/10', 'dd/mm/yy') ++ assert_date_format('04/02/2010', 'mm/dd/yyyy') ++ assert_date_format('2011-02-20', 'yyyy-mm-dd') ++ assert_date_format('20070320', 'yyyymmdd') ++ assert_date_format('23:10', 'HH:MM') ++ end) ++ ++ it("should parse 'slack' fields", function () ++ local df = Date.Format("m/d/yy") ++ -- TODO: Re-enable when issue #359 fixed ++ -- assert.is.equal('01/05/99', df:tostring(df:parse('1/5/99'))) ++ assert.is.equal('01/05/01', df:tostring(df:parse('1/5/01'))) ++ assert.is.equal('01/05/32', df:tostring(df:parse('1/5/32'))) ++ end) ++ ++ end) ++ ++ end) ++ ++ describe("meta method", function () ++ ++ describe("__tostring()", function () ++ ++ it("should be suitable for serialization", function () ++ local df = Date.Format() ++ local du = df:parse("2008-07-05") ++ assert.is.equal(du, du:toUTC()) ++ end) ++ ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/func_spec.lua b/extra/penlight/spec/func_spec.lua +new file mode 100644 +index 0000000..2b3a438 +--- /dev/null ++++ b/extra/penlight/spec/func_spec.lua +@@ -0,0 +1,54 @@ ++local func = require("pl.func") ++ ++describe("pl.func", function () ++ ++ describe("compose", function () ++ ++ it("compose(f)(x) == f(x)", function () ++ local f = function(x) return x + 1 end ++ assert.equals(func.compose(f)(1), f(1)) ++ end) ++ ++ it("compose(f, g)(x) == f(g(x))", function () ++ local f = function(x) return x + 1 end ++ local g = function(x) return x + 2 end ++ assert.equals(func.compose(f, g)(1), f(g(1))) ++ end) ++ ++ it("compose(f, g, h)(x) == f(g(h(x)))", function () ++ local f = function(x) return x + 1 end ++ local g = function(x) return x + 2 end ++ local h = function(x) return x + 3 end ++ assert.equals(func.compose(f, g, h)(1), f(g(h(1)))) ++ end) ++ ++ it("compose(f)(x, y) == f(x, y)", function () ++ local f = function(x, y) return x + 1, y + 1 end ++ local ax, ay = func.compose(f)(1, 2) ++ local bx, by = f(1, 2) ++ assert.equals(ax, bx) ++ assert.equals(ay, by) ++ end) ++ ++ it("compose(f, g)(x, y) == f(g(x, y))", function () ++ local f = function(x, y) return x + 1, y + 1 end ++ local g = function(x, y) return x + 2, y + 2 end ++ local ax, ay = func.compose(f, g)(1, 2) ++ local bx, by = f(g(1, 2)) ++ assert.equals(ax, bx) ++ assert.equals(ay, by) ++ end) ++ ++ it("compose(f, g, h)(x, y) == f(g(h(x, y)))", function () ++ local f = function(x, y) return x + 1, y + 1 end ++ local g = function(x, y) return x + 2, y + 2 end ++ local h = function(x, y) return x + 3, y + 3 end ++ local ax, ay = func.compose(f, g, h)(1, 2) ++ local bx, by = f(g(h(1, 2))) ++ assert.equals(ax, bx) ++ assert.equals(ay, by) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/multimap_spec.lua b/extra/penlight/spec/multimap_spec.lua +new file mode 100644 +index 0000000..a83d150 +--- /dev/null ++++ b/extra/penlight/spec/multimap_spec.lua +@@ -0,0 +1,14 @@ ++local MultiMap = require("pl.MultiMap") ++ ++describe("pl.MultiMap", function () ++ ++ it("should hold multiple values per key", function () ++ local map = MultiMap() ++ map:set('foo', 1) ++ map:set('bar', 3) ++ map:set('foo', 2) ++ local expected = { foo = { 1, 2 }, bar = { 3 } } ++ assert.is.same(expected, map) ++ end) ++ ++end) +diff --git a/extra/penlight/spec/path_spec.lua b/extra/penlight/spec/path_spec.lua +new file mode 100644 +index 0000000..7f18f29 +--- /dev/null ++++ b/extra/penlight/spec/path_spec.lua +@@ -0,0 +1,106 @@ ++ ++-- conditional it/pending blocks per platform ++local function nix_it(desc, ...) ++ if package.config:sub(1,1) == "\\" then ++ pending("Skip test on Windows: " .. desc, ...) ++ else ++ it(desc, ...) ++ end ++end ++local function win_it(desc, ...) ++ if package.config:sub(1,1) == "\\" then ++ it(desc, ...) ++ else ++ pending("Skip test on Unix: " .. desc, ...) ++ end ++end ++ ++ ++ ++describe("pl.path", function() ++ ++ local path ++ local mock_envs ++ local old_get_env ++ ++ before_each(function() ++ mock_envs = {} ++ old_get_env = os.getenv ++ os.getenv = function(name) -- luacheck: ignore ++ return mock_envs[name] ++ end ++ package.loaded["pl.path"] = nil ++ path = require "pl.path" ++ end) ++ ++ after_each(function() ++ package.loaded["pl.path"] = nil ++ os.getenv = old_get_env -- luacheck: ignore ++ end) ++ ++ ++ ++ describe("expanduser()", function() ++ ++ it("should expand ~ to the user's home directory", function() ++ mock_envs = { ++ HOME = "/home/user", ++ } ++ assert.equal("/home/user/file", path.expanduser("~/file")) ++ end) ++ ++ ++ nix_it("returns an error if expansion fails: HOME not set", function() ++ mock_envs = {} ++ assert.same( ++ { nil, "failed to expand '~' (HOME not set)" }, ++ { path.expanduser("~/file")} ++ ) ++ end) ++ ++ ++ win_it("returns an error if expansion fails: all Windows vars", function() ++ mock_envs = {} ++ assert.same( ++ { nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" }, ++ { path.expanduser("~/file")} ++ ) ++ end) ++ ++ ++ win_it("HOME is first in line", function() ++ mock_envs = { ++ HOME = "\\home\\user1", ++ USERPROFILE = "\\home\\user2", ++ HOMEDRIVE = "C:", ++ HOMEPATH = "\\home\\user3", ++ } ++ assert.equal("\\home\\user1\\file", path.expanduser("~\\file")) ++ end) ++ ++ ++ win_it("USERPROFILE is second in line", function() ++ mock_envs = { ++ --HOME = "\\home\\user1", ++ USERPROFILE = "\\home\\user2", ++ HOMEDRIVE = "C:", ++ HOMEPATH = "\\home\\user3", ++ } ++ assert.equal("\\home\\user2\\file", path.expanduser("~\\file")) ++ end) ++ ++ ++ win_it("HOMEDRIVE/PATH is third in line", function() ++ mock_envs = { ++ -- HOME = "\\home\\user1", ++ -- USERPROFILE = "\\home\\user2", ++ HOMEDRIVE = "C:", ++ HOMEPATH = "\\home\\user3", ++ } ++ assert.equal("C:\\home\\user3\\file", path.expanduser("~\\file")) ++ end) ++ ++ end) ++ ++end) ++ +diff --git a/extra/penlight/spec/permute_spec.lua b/extra/penlight/spec/permute_spec.lua +new file mode 100644 +index 0000000..49143c0 +--- /dev/null ++++ b/extra/penlight/spec/permute_spec.lua +@@ -0,0 +1,213 @@ ++local permute = require("pl.permute") ++local tcopy = require("pl.tablex").copy ++local utils = require("pl.utils") ++ ++describe("pl.permute", function() ++ ++ describe("order_iter", function() ++ ++ it("returns all order combinations", function() ++ local result = {} ++ for list in permute.order_iter({"one", "two", "three"}) do ++ result[#result+1] = tcopy(list) ++ end ++ assert.same({ ++ [1] = { ++ [1] = 'two', ++ [2] = 'three', ++ [3] = 'one' }, ++ [2] = { ++ [1] = 'three', ++ [2] = 'two', ++ [3] = 'one' }, ++ [3] = { ++ [1] = 'three', ++ [2] = 'one', ++ [3] = 'two' }, ++ [4] = { ++ [1] = 'one', ++ [2] = 'three', ++ [3] = 'two' }, ++ [5] = { ++ [1] = 'two', ++ [2] = 'one', ++ [3] = 'three' }, ++ [6] = { ++ [1] = 'one', ++ [2] = 'two', ++ [3] = 'three' } }, result) ++ end) ++ ++ ++ it("returns nil on empty list", function() ++ local result = {} ++ for list in permute.order_iter({}) do ++ result[#result+1] = tcopy(list) ++ end ++ assert.equal(0, #result) ++ end) ++ ++ end) ++ ++ ++ ++ describe("order_table", function() ++ ++ it("returns all order combinations", function() ++ local result = permute.order_table({"one", "two", "three"}) ++ assert.same({ ++ [1] = { ++ [1] = 'two', ++ [2] = 'three', ++ [3] = 'one' }, ++ [2] = { ++ [1] = 'three', ++ [2] = 'two', ++ [3] = 'one' }, ++ [3] = { ++ [1] = 'three', ++ [2] = 'one', ++ [3] = 'two' }, ++ [4] = { ++ [1] = 'one', ++ [2] = 'three', ++ [3] = 'two' }, ++ [5] = { ++ [1] = 'two', ++ [2] = 'one', ++ [3] = 'three' }, ++ [6] = { ++ [1] = 'one', ++ [2] = 'two', ++ [3] = 'three' } }, result) ++ end) ++ ++ ++ it("returns empty table on empty input list", function() ++ local result = permute.order_table({}) ++ assert.same({}, result) ++ end) ++ ++ end) ++ ++ ++ ++ describe("list_iter", function() ++ ++ it("returns all combinations from sub-lists", function() ++ local result = {} ++ local strs = {"one", "two", "three"} ++ local ints = { 1,2,3 } ++ local bools = { true, false } ++ for count, str, int, bool in permute.list_iter(strs, ints, bools) do ++ result[#result+1] = {count, str, int, bool} ++ end ++ assert.same({ ++ [1] = {1, 'one', 1, true }, ++ [2] = {2, 'two', 1, true }, ++ [3] = {3, 'three', 1, true }, ++ [4] = {4, 'one', 2, true }, ++ [5] = {5, 'two', 2, true }, ++ [6] = {6, 'three', 2, true }, ++ [7] = {7, 'one', 3, true }, ++ [8] = {8, 'two', 3, true }, ++ [9] = {9, 'three', 3, true }, ++ [10] = {10, 'one', 1, false }, ++ [11] = {11, 'two', 1, false }, ++ [12] = {12, 'three', 1, false }, ++ [13] = {13, 'one', 2, false }, ++ [14] = {14, 'two', 2, false }, ++ [15] = {15, 'three', 2, false }, ++ [16] = {16, 'one', 3, false }, ++ [17] = {17, 'two', 3, false }, ++ [18] = {18, 'three', 3, false }, ++ }, result) ++ end) ++ ++ ++ it("is nil-safe, given 'n' is set", function() ++ local result = {} ++ local bools = utils.pack(nil, true, false) ++ local strs = utils.pack("one", "two", nil) ++ for count, bool, str in permute.list_iter(bools, strs) do ++ result[#result+1] = {count, bool, str} ++ end ++ assert.same({ ++ [1] = {1, nil, 'one' }, ++ [2] = {2, true, 'one' }, ++ [3] = {3, false, 'one' }, ++ [4] = {4, nil, 'two' }, ++ [5] = {5, true, 'two' }, ++ [6] = {6, false, 'two' }, ++ [7] = {7, nil, nil }, ++ [8] = {8, true, nil }, ++ [9] = {9, false, nil }, ++ }, result) ++ end) ++ ++ ++ it("returns nil on empty list", function() ++ local count = 0 ++ for list in permute.list_iter({}) do ++ count = count + 1 ++ end ++ assert.equal(0, count) ++ end) ++ ++ end) ++ ++ ++ ++ describe("list_table", function() ++ ++ it("returns all combinations from sub-lists", function() ++ local strs = {"one", "two", "three"} ++ local ints = { 1,2,3 } ++ local bools = { true, false } ++ assert.same({ ++ [1] = {'one', 1, true, n = 3 }, ++ [2] = {'two', 1, true, n = 3 }, ++ [3] = {'three', 1, true, n = 3 }, ++ [4] = {'one', 2, true, n = 3 }, ++ [5] = {'two', 2, true, n = 3 }, ++ [6] = {'three', 2, true, n = 3 }, ++ [7] = {'one', 3, true, n = 3 }, ++ [8] = {'two', 3, true, n = 3 }, ++ [9] = {'three', 3, true, n = 3 }, ++ [10] = {'one', 1, false, n = 3 }, ++ [11] = {'two', 1, false, n = 3 }, ++ [12] = {'three', 1, false, n = 3 }, ++ [13] = {'one', 2, false, n = 3 }, ++ [14] = {'two', 2, false, n = 3 }, ++ [15] = {'three', 2, false, n = 3 }, ++ [16] = {'one', 3, false, n = 3 }, ++ [17] = {'two', 3, false, n = 3 }, ++ [18] = {'three', 3, false, n = 3 }, ++ }, permute.list_table(strs, ints, bools)) ++ end) ++ ++ ++ it("is nil-safe, given 'n' is set", function() ++ local bools = utils.pack(nil, true, false) ++ local strs = utils.pack("one", "two", nil) ++ assert.same({ ++ [1] = {nil, 'one', n = 2 }, ++ [2] = {true, 'one', n = 2 }, ++ [3] = {false, 'one', n = 2 }, ++ [4] = {nil, 'two', n = 2 }, ++ [5] = {true, 'two', n = 2 }, ++ [6] = {false, 'two', n = 2 }, ++ [7] = {nil, nil, n = 2 }, ++ [8] = {true, nil, n = 2 }, ++ [9] = {false, nil, n = 2 }, ++ }, permute.list_table(bools, strs)) ++ end) ++ ++ ++ it("returns nil on empty list", function() ++ assert.same({}, permute.list_table({})) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/pretty_spec.lua b/extra/penlight/spec/pretty_spec.lua +new file mode 100644 +index 0000000..85e3770 +--- /dev/null ++++ b/extra/penlight/spec/pretty_spec.lua +@@ -0,0 +1,40 @@ ++local pretty = require("pl.pretty") ++ ++describe("pl.pretty.number", function () ++ ++ it("should format memory", function () ++ local function assert_memory (expected, input) ++ assert.is.equal(expected, pretty.number(input, "M")) ++ end ++ assert_memory("123B", 123) ++ assert_memory("1.2KiB", 1234) ++ assert_memory("10.0KiB", 10*1024) ++ assert_memory("1.0MiB", 1024*1024) ++ assert_memory("1.0GiB", 1024*1024*1024) ++ end) ++ ++ it("should format postfixes", function () ++ local function assert_postfix(expected, input) ++ assert.is.equal(expected, pretty.number(input, "N", 2)) ++ end ++ assert_postfix("123", 123) ++ assert_postfix("1.23K", 1234) ++ assert_postfix("10.24K", 10*1024) ++ assert_postfix("1.05M", 1024*1024) ++ assert_postfix("1.07B", 1024*1024*1024) ++ end) ++ ++ it("should format postfixes", function () ++ local function assert_separator(expected, input) ++ assert.is.equal(expected, pretty.number(input, "T")) ++ end ++ assert_separator('123', 123) ++ assert_separator('1,234', 1234) ++ assert_separator('12,345', 12345) ++ assert_separator('123,456', 123456) ++ assert_separator('1,234,567', 1234567) ++ assert_separator('12,345,678', 12345678) ++ end) ++ ++ ++end) +diff --git a/extra/penlight/spec/set_spec.lua b/extra/penlight/spec/set_spec.lua +new file mode 100644 +index 0000000..02febca +--- /dev/null ++++ b/extra/penlight/spec/set_spec.lua +@@ -0,0 +1,84 @@ ++local Set = require("pl.Set") ++ ++describe("pl.Set", function () ++ ++ local s = Set() ++ local s1_2 = Set({ 1, 2 }) ++ local s1_2_3 = Set({ 1, 2, 3 }) ++ local s1_3 = Set({ 1, 3 }) ++ local s2 = Set({ 2 }) ++ local s2_1 = Set({ 2, 1 }) ++ local s2_3 = Set({ 2, 3 }) ++ local s3 = Set({ 3 }) ++ local sm = Set({ "foo", "bar" }) ++ ++ it("should produce a set object", function () ++ assert.is.same({ true, true }, s1_2) ++ end) ++ ++ it("should produce identical sets for any ordered input", function () ++ assert.is.same(s1_2, s2_1) ++ end) ++ ++ describe("should have an operator for", function () ++ ++ it("union", function () ++ assert.is.same(s1_2_3, s1_2 + s3) ++ assert.is.same(s1_2_3, s1_2 + 3) ++ end) ++ ++ it("intersection", function () ++ assert.is.same(s2, s1_2 * s2_3) ++ end) ++ ++ it("difference", function () ++ assert.is.same(s2_1, s1_2_3 - s3) ++ assert.is.same(s2_3, s1_2_3 - 1) ++ end) ++ ++ it("symmetric difference", function () ++ assert.is.same(s1_3, s1_2 ^ s2_3) ++ end) ++ ++ it("tostring", function () ++ -- Cannot test multi-entry sets because of non-deterministic key order ++ assert.is.same('[2]', tostring(s2)) ++ end) ++ ++ end) ++ ++ describe("should provide functions", function () ++ ++ it("isempty", function () ++ assert.is.truthy(Set.isempty(s)) ++ assert.is.falsy(Set.isempty(s3)) ++ end) ++ ++ it("set", function () ++ local m = Set() ++ Set.set(m, 'foo', true) ++ m.bar = true ++ assert.is.same(m, sm) ++ assert.is_not.same(m, s1_2) ++ end) ++ ++ end) ++ ++ describe("should have a comparison operator for", function () ++ ++ it("supersets/subsets than", function () ++ assert.is.truthy(s1_2 > s2) ++ assert.is.falsy(s1_3 > s2) ++ assert.is.falsy(s1_2 > s2_3) ++ assert.is.truthy(s1_2 < s1_2_3) ++ assert.is.falsy(s1_2_3 < s1_2) ++ end) ++ ++ it("equality", function () ++ assert.is.truthy(s1_2 == s2_1) ++ assert.is.falsy(s1_2 == s2_3) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/stringx_spec.lua b/extra/penlight/spec/stringx_spec.lua +new file mode 100644 +index 0000000..1738bde +--- /dev/null ++++ b/extra/penlight/spec/stringx_spec.lua +@@ -0,0 +1,740 @@ ++describe("stringx", function() ++ ++ local stringx = require "pl.stringx" ++ ++ ++ it("isalpha()", function() ++ assert.equal(false, stringx.isalpha '') ++ assert.equal(false, stringx.isalpha' ') ++ assert.equal(false, stringx.isalpha'0') ++ assert.equal(false, stringx.isalpha'\0') ++ assert.equal(true, stringx.isalpha'azAZ') ++ assert.equal(false, stringx.isalpha'az9AZ') ++ end) ++ ++ ++ it("isdigit()", function() ++ assert.equal(false, stringx.isdigit'') ++ assert.equal(false, stringx.isdigit' ') ++ assert.equal(false, stringx.isdigit'a') ++ assert.equal(true, stringx.isdigit'0123456789') ++ end) ++ ++ ++ it("isalnum()", function() ++ assert.equal(false, stringx.isalnum'') ++ assert.equal(false, stringx.isalnum' ') ++ assert.equal(true, stringx.isalnum'azAZ01234567890') ++ end) ++ ++ ++ it("isspace()", function() ++ assert.equal(false, stringx.isspace'') ++ assert.equal(true, stringx.isspace' ') ++ assert.equal(true, stringx.isspace' \r\n\f\t') ++ assert.equal(false, stringx.isspace' \r\n-\f\t') ++ end) ++ ++ ++ it("islower()", function() ++ assert.equal(false, stringx.islower'') ++ assert.equal(true, stringx.islower'az') ++ assert.equal(false, stringx.islower'aMz') ++ assert.equal(true, stringx.islower'a z') ++ end) ++ ++ ++ it("isupper()", function() ++ assert.equal(false, stringx.isupper'') ++ assert.equal(true, stringx.isupper'AZ') ++ assert.equal(false, stringx.isupper'AmZ') ++ assert.equal(true, stringx.isupper'A Z') ++ end) ++ ++ ++ it("startswith()", function() ++ local startswith = stringx.startswith ++ assert.equal(true, startswith('', '')) ++ assert.equal(false, startswith('', 'a')) ++ assert.equal(true, startswith('a', '')) ++ assert.equal(true, startswith('a', 'a')) ++ assert.equal(false, startswith('a', 'b')) ++ assert.equal(false, startswith('a', 'ab')) ++ assert.equal(true, startswith('abc', 'ab')) ++ assert.equal(false, startswith('abc', 'bc')) -- off by one ++ assert.equal(false, startswith('abc', '.')) -- Lua pattern char ++ assert.equal(true, startswith('a\0bc', 'a\0b')) -- '\0' ++ ++ assert.equal(true, startswith('abcfoo',{'abc','def'})) ++ assert.equal(true, startswith('deffoo',{'abc','def'})) ++ assert.equal(false, startswith('cdefoo',{'abc','def'})) ++ end) ++ ++ ++ it("endswith()", function() ++ local endswith = stringx.endswith ++ assert.equal(true, endswith("", "")) ++ assert.equal(false, endswith("", "a")) ++ assert.equal(true, endswith("a", "")) ++ assert.equal(true, endswith("a", "a")) ++ assert.equal(false, endswith("a", "A")) -- case sensitive ++ assert.equal(false, endswith("a", "aa")) ++ assert.equal(true, endswith("abc", "")) ++ assert.equal(false, endswith("abc", "ab")) -- off by one ++ assert.equal(true, endswith("abc", "c")) ++ assert.equal(true, endswith("abc", "bc")) ++ assert.equal(true, endswith("abc", "abc")) ++ assert.equal(false, endswith("abc", " abc")) ++ assert.equal(false, endswith("abc", "a")) ++ assert.equal(false, endswith("abc", ".")) -- Lua pattern char ++ assert.equal(true, endswith("ab\0c", "b\0c")) -- \0 ++ assert.equal(false, endswith("ab\0c", "b\0d")) -- \0 ++ ++ assert.equal(true, endswith('dollar.dot',{'.dot','.txt'})) ++ assert.equal(true, endswith('dollar.txt',{'.dot','.txt'})) ++ assert.equal(false, endswith('dollar.rtxt',{'.dot','.txt'})) ++ end) ++ ++ ++ it("join()", function() ++ assert.equal('1 2 3', stringx.join(' ', {1,2,3})) ++ end) ++ ++ ++ it("splitlines", function() ++ assert.same({}, stringx.splitlines('')) ++ assert.same({'a'}, stringx.splitlines('a')) ++ assert.same({''}, stringx.splitlines('\n')) ++ assert.same({'', ''}, stringx.splitlines('\n\n')) ++ assert.same({'', ''}, stringx.splitlines('\r\r')) ++ assert.same({''}, stringx.splitlines('\r\n')) ++ assert.same({'ab', 'cd'}, stringx.splitlines('ab\ncd\n')) ++ assert.same({'ab\n', 'cd\n'}, stringx.splitlines('ab\ncd\n', true)) ++ assert.same({'\n', 'ab\r', '\r\n', 'cd\n'}, stringx.splitlines('\nab\r\r\ncd\n', true)) ++ end) ++ ++ ++ it("split()", function() ++ local split = stringx.split ++ assert.same({''}, split('', '')) ++ assert.same({}, split('', 'z')) --FIX:intended and specified behavior? ++ assert.same({'a'}, split('a', '')) --FIX:intended and specified behavior? ++ assert.same({''}, split('a', 'a')) ++ -- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. ++ -- If you need to split on a pattern, use utils.split() ++ -- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars ++ -- note that leading space is ignored by the default ++ assert.same({'1','2','3'}, split(' 1 2 3 ')) ++ assert.same({'a','bb','c','ddd'}, split('a*bb*c*ddd','*')) ++ assert.same({'dog','fred','bonzo:alice'}, split('dog:fred:bonzo:alice',':',3)) ++ assert.same({'dog','fred','bonzo:alice:'}, split('dog:fred:bonzo:alice:',':',3)) ++ assert.same({'','','',''}, split('///','/')) ++ end) ++ ++ ++ it("expandtabs()", function() ++ assert.equal('', stringx.expandtabs('',0)) ++ assert.equal('', stringx.expandtabs('',1)) ++ assert.equal(' ', stringx.expandtabs(' ',1)) ++ assert.equal((' '):rep(1+8), stringx.expandtabs(' \t ')) ++ assert.equal((' '):rep(3), stringx.expandtabs(' \t ',2)) ++ assert.equal((' '):rep(2), stringx.expandtabs(' \t ',0)) ++ assert.equal(' hi there folks!', stringx.expandtabs('\thi\tthere\tfolks!')) ++ end) ++ ++ ++ it("lfind()", function() ++ assert.equal(1, stringx.lfind('', '')) ++ assert.equal(1, stringx.lfind('a', '')) ++ assert.equal(2, stringx.lfind('ab', 'b')) ++ assert.is_nil(stringx.lfind('abc', 'cd')) ++ assert.equal(2, stringx.lfind('abcbc', 'bc')) ++ assert.equal(3, stringx.lfind('ab..cd', '.')) -- pattern char ++ assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3)) ++ assert.is_nil(stringx.lfind('abcbcbbc', 'bc', 3, 4)) ++ assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3, 5)) ++ assert.equal(2, stringx.lfind('abcbcbbc', 'bc', nil, 5)) ++ end) ++ ++ ++ it("rfind()", function() ++ assert.equal(1, stringx.rfind('', '')) ++ assert.equal(3, stringx.rfind('ab', '')) ++ assert.is_nil(stringx.rfind('abc', 'cd')) ++ assert.equal(4, stringx.rfind('abcbc', 'bc')) ++ assert.equal(4, stringx.rfind('abcbcb', 'bc')) ++ assert.equal(4, stringx.rfind('ab..cd', '.')) -- pattern char ++ assert.equal(7, stringx.rfind('abcbcbbc', 'bc', 3)) ++ assert.is_nil(stringx.rfind('abcbcbbc', 'bc', 3, 4)) ++ assert.equal(4, stringx.rfind('abcbcbbc', 'bc', 3, 5)) ++ assert.equal(4, stringx.rfind('abcbcbbc', 'bc', nil, 5)) ++ assert.equal(4, stringx.rfind('banana', 'ana')) ++ end) ++ ++ ++ it("replace()", function() ++ assert.equal('', stringx.replace('', '', '')) ++ assert.equal(' ', stringx.replace(' ', '', '')) ++ assert.equal(' ', stringx.replace(' ', '', ' ')) ++ assert.equal('', stringx.replace(' ', ' ', '')) ++ assert.equal('aBCaBCaBC', stringx.replace('abcabcabc', 'bc', 'BC')) ++ assert.equal('aBCabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 1)) ++ assert.equal('abcabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 0)) ++ assert.equal('abc', stringx.replace('abc', 'd', 'e')) ++ assert.equal('a%db', stringx.replace('a.b', '.', '%d')) ++ end) ++ ++ ++ it("count()", function() ++ assert.equal(0, stringx.count('', '')) --infinite loop]] ++ assert.equal(2, stringx.count(' ', '')) --infinite loop]] ++ assert.equal(2, stringx.count('a..c', '.')) -- pattern chars ++ assert.equal(0, stringx.count('a1c', '%d')) -- pattern chars ++ assert.equal(3, stringx.count('Anna Anna Anna', 'Anna')) -- no overlap ++ assert.equal(1, stringx.count('banana', 'ana', false)) -- no overlap ++ assert.equal(2, stringx.count('banana', 'ana', true)) -- overlap ++ end) ++ ++ ++ it("ljust()", function() ++ assert.equal('', stringx.ljust('', 0)) ++ assert.equal(' ', stringx.ljust('', 2)) ++ assert.equal('ab ', stringx.ljust('ab', 3)) ++ assert.equal('ab%', stringx.ljust('ab', 3, '%')) ++ assert.equal('abcd', stringx.ljust('abcd', 3)) -- agrees with Python ++ end) ++ ++ ++ it("rjust()", function() ++ assert.equal('', stringx.rjust('', 0)) ++ assert.equal(' ', stringx.rjust('', 2)) ++ assert.equal(' ab', stringx.rjust('ab', 3)) ++ assert.equal('%ab', stringx.rjust('ab', 3, '%')) ++ assert.equal('abcd', stringx.rjust('abcd', 3)) -- agrees with Python ++ end) ++ ++ ++ it("center()", function() ++ assert.equal('', stringx.center('', 0)) ++ assert.equal(' ', stringx.center('', 1)) ++ assert.equal(' ', stringx.center('', 2)) ++ assert.equal('a', stringx.center('a', 1)) ++ assert.equal('a ', stringx.center('a', 2)) ++ assert.equal(' a ', stringx.center('a', 3)) ++ end) ++ ++ ++ it("lstrip()", function() ++ local trim = stringx.lstrip ++ assert.equal('', trim'') ++ assert.equal('', trim' ') ++ assert.equal('', trim' ') ++ assert.equal('a', trim'a') ++ assert.equal('a', trim' a') ++ assert.equal('a ', trim'a ') ++ assert.equal('a ', trim' a ') ++ assert.equal('a ', trim' a ') ++ assert.equal('ab cd ', trim' ab cd ') ++ assert.equal('a\000b \r\t\n\f\v', trim' \t\r\n\f\va\000b \r\t\n\f\v') ++ assert.equal('hello] -- - ', trim(' - -- [hello] -- - ','-[] ')) ++ end) ++ ++ ++ it("rstrip()", function() ++ local trim = stringx.rstrip ++ assert.equal('', trim'') ++ assert.equal('', trim' ') ++ assert.equal('', trim' ') ++ assert.equal('a', trim'a') ++ assert.equal(' a', trim' a') ++ assert.equal('a', trim'a ') ++ assert.equal(' a', trim' a ') ++ assert.equal(' a', trim' a ') ++ assert.equal(' ab cd', trim' ab cd ') ++ assert.equal(' \t\r\n\f\va\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') ++ assert.equal(' - -- [hello', trim(' - -- [hello] -- - ','-[] ')) ++ end) ++ ++ ++ it("strip()", function() ++ local trim = stringx.strip ++ assert.equal('', trim'') ++ assert.equal('', trim' ') ++ assert.equal('', trim' ') ++ assert.equal('a', trim'a') ++ assert.equal('a', trim' a') ++ assert.equal('a', trim'a ') ++ assert.equal('a', trim' a ') ++ assert.equal('a', trim' a ') ++ assert.equal('ab cd', trim' ab cd ') ++ assert.equal('a\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') ++ assert.equal('hello', trim(' - -- [hello] -- - ','-[] ')) ++ local long = 'a' .. string.rep(' ', 200000) .. 'a' ++ assert.equal(long, trim(long)) ++ end) ++ ++ it("splitv()", function() ++ -- is actually 'utils.splitv' ++ assert.same({"hello", "dolly"}, {stringx.splitv("hello dolly")}) ++ end) ++ ++ ++ it("partition()", function() ++ assert.has.error(function() ++ stringx.partition('a', '') ++ end) ++ assert.same({'', 'a', ''}, {stringx.partition('a', 'a')}) ++ assert.same({'a', 'b', 'c'}, {stringx.partition('abc', 'b')}) ++ assert.same({'abc','',''}, {stringx.partition('abc', '.+')}) ++ assert.same({'ab','.','c'}, {stringx.partition('ab.c', '.')}) ++ assert.same({'a',',','b,c'}, {stringx.partition('a,b,c', ',')}) ++ assert.same({'abc', '', ''}, {stringx.partition('abc', '/')}) ++ end) ++ ++ ++ it("rpartition()", function() ++ assert.has.error(function() ++ stringx.rpartition('a', '') ++ end) ++ assert.same({'a/b', '/', 'c'}, {stringx.rpartition('a/b/c', '/')}) ++ assert.same({'a', 'b', 'c'}, {stringx.rpartition('abc', 'b')}) ++ assert.same({'', 'a', ''}, {stringx.rpartition('a', 'a')}) ++ assert.same({'', '', 'abc'}, {stringx.rpartition('abc', '/')}) ++ end) ++ ++ ++ it("at()", function() ++ -- at (works like s:sub(idx,idx), so negative indices allowed ++ assert.equal('a', stringx.at('a', 1)) ++ assert.equal('b', stringx.at('ab', 2)) ++ assert.equal('d', stringx.at('abcd', -1)) ++ assert.equal('', stringx.at('abcd', 10)) -- not found ++ end) ++ ++ ++ ++ describe("indent()", function() ++ ++ it("adds an indent", function() ++ local t = "a whole lot\nof love" ++ ++ assert.equal([[ ++ a whole lot ++ of love ++]], stringx.indent(t, 4)) ++ ++ assert.equal([[ ++**easy ++** ++**enough! ++]], stringx.indent("easy\n\nenough!", 2 ,'*')) ++ end) ++ ++ it("appends a newline if not present", function() ++ assert.equal(" hello\n world\n", stringx.indent("hello\nworld", 2)) ++ assert.equal(" hello\n world\n", stringx.indent("hello\nworld\n", 2)) ++ end) ++ ++ end) ++ ++ ++ ++ describe("dedent()", function() ++ ++ it("removes prefixed whitespace", function() ++ assert.equal([[ ++one ++two ++three ++]], stringx.dedent [[ ++ one ++ two ++ three ++]]) ++ end) ++ ++ it("removes prefixed whitespace, retains structure", function() ++ assert.equal([[ ++ one ++ ++ two ++ ++three ++]], stringx.dedent [[ ++ one ++ ++ two ++ ++ three ++]]) ++ end) ++ ++ it("appends a newline if not present", function() ++ assert.equal("hello\nworld\n", stringx.dedent(" hello\n world")) ++ assert.equal("hello\nworld\n", stringx.dedent(" hello\n world\n")) ++ end) ++ ++ end) ++ ++ ++ ++ ++ describe("fill()/wrap()", function() ++ ++ it("wraps width over limit", function() ++ assert.same({ ++ "abc", ++ "def" ++ }, stringx.wrap("abc def", 2)) ++ end) ++ ++ it("wraps width at limit", function() ++ assert.same({ ++ "abc", ++ "def" ++ }, stringx.wrap("abc def", 3)) ++ assert.same({ ++ "a c", ++ "d f" ++ }, stringx.wrap("a c d f", 3)) ++ end) ++ ++ it("wraps single letters", function() ++ assert.same({"a"}, stringx.wrap("a")) ++ end) ++ ++ it("wraps empty strings", function() ++ assert.same({""}, stringx.wrap("")) ++ assert.same({""}, stringx.wrap(" ")) ++ end) ++ ++ it("handles leading/trailing whitespace", function() ++ assert.same({"hello"}, stringx.wrap(" hello ", 10)) ++ assert.same({"hello"}, stringx.wrap(" hello ", 2)) ++ assert.same({"he", "ll", "o"}, stringx.wrap(" hello ", 2, true)) ++ end) ++ ++ it("handles line-breaks", function() ++ assert.same({"Hello", "Dolly"}, stringx.wrap("Hello\nDolly", 10)) ++ assert.same({"Hello Dolly"}, stringx.wrap("Hello\nDolly", 20)) ++ end) ++ ++ it("doesn't split on accented characters", function() ++ assert.same({"àbcdéfghîj"}, stringx.wrap("àbcdéfghîj")) ++ end) ++ ++ it("word-wraps a text", function() ++ -- local binstring = require("luassert.formatters.binarystring") ++ -- assert:add_formatter(binstring) ++ assert.equal([[ ++It is often said of ++Lua that it does not ++include batteries. ++That is because the ++goal of Lua is to ++produce a lean ++expressive language ++that will be used on ++all sorts of ++machines, (some of ++which don't even ++have hierarchical ++filesystems). The ++Lua language is the ++equivalent of an ++operating system ++kernel; the creators ++of Lua do not see it ++as their ++responsibility to ++create a full ++software ecosystem ++around the language. ++That is the role of ++the community. ++]], stringx.fill("It is often said of Lua that it does not include batteries. That is because the goal of Lua is to produce a lean expressive language that will be used on all sorts of machines, (some of which don't even have hierarchical filesystems). The Lua language is the equivalent of an operating system kernel; the creators of Lua do not see it as their responsibility to create a full software ecosystem around the language. That is the role of the community.", 20)) ++ end) ++ ++ ++ it("generic wrap test", function() ++ local t = [[ ++hello "world" 'this' -is- a bb ccc dddd test... but wouldn't it pass??? final. word-that-can-be-broken ++]] ++ ++ assert.same({ ++ "hello", ++ '"world"', ++ "'this'", ++ "-is-", ++ "a", ++ "bb", ++ "ccc", ++ "dddd", ++ "test...", ++ "but", ++ "wouldn't", ++ "it", ++ "pass???", ++ "final.", ++ "word-that-can-be-broken", ++ }, stringx.wrap(t, 3)) ++ end) ++ ++ it("generic wrap test, with overflow breaking", function() ++ local t = [[ ++hello "world" 'this' -is- a bb ccc dddd test... but wouldn't it pass??? final. word-that-can-be-broken ++]] ++ ++ assert.same({ ++ "hel", ++ "lo", ++ '"wo', ++ 'rld', ++ '"', ++ "'th", ++ "is'", ++ "-is", ++ "- a", ++ "bb", ++ "ccc", ++ "ddd", ++ "d", ++ "tes", ++ "t..", ++ ".", ++ "but", ++ "wou", ++ "ldn", ++ "'t", ++ "it", ++ "pas", ++ "s??", ++ "?", ++ "fin", ++ "al.", ++ "wor", ++ "d-t", ++ "hat", ++ "-ca", ++ "n-b", ++ "e-b", ++ "rok", ++ "en", ++ }, stringx.wrap(t, 3, true)) ++ end) ++ ++ end) ++ ++ ++ ++ describe("Template", function() ++ ++ local Template = stringx.Template ++ ++ ++ it("substitute() replaces placeholders", function() ++ local t1 = Template [[ ++while true do ++ $contents ++end ++]] ++ ++ assert.equal([[ ++while true do ++ print "hello" ++end ++]], t1:substitute {contents = 'print "hello"'}) ++ end) ++ ++ ++ it("substitute() replaces multiple placeholders", function () ++ local template = Template("${here} is the $answer") ++ local out = template:substitute({ here = 'one', answer = 'two' }) ++ assert.is.equal('one is the two', out) ++ end) ++ ++ ++ it("indent_substitute() indents replaced multi-lines", function() ++ local t1 = Template [[ ++while true do ++ $contents ++end ++]] ++ ++ assert.equal( ++"while true do\n".. ++" for i = 1,10 do\n".. ++" gotcha(i)\n".. ++" end\n".. ++"\n".. ++"end\n" ++, t1:indent_substitute {contents = [[ ++for i = 1,10 do ++ gotcha(i) ++end ++]]}) ++ end) ++ ++ end) ++ ++ ++ ++ it("lines()", function() ++ local function merge(it, ...) ++ assert(select('#', ...) == 0) ++ local ts = {} ++ for val in it do ts[#ts+1] = val end ++ return ts ++ end ++ assert.same({''}, merge(stringx.lines(''))) ++ assert.same({'ab'}, merge(stringx.lines('ab'))) ++ assert.same({'ab', 'cd'}, merge(stringx.lines('ab\ncd'))) ++ end) ++ ++ ++ it("title()", function() ++ assert.equal('', stringx.title('')) ++ assert.equal('Abc Def1', stringx.title('abC deF1')) -- Python behaviour ++ assert.equal('Hello World', stringx.capitalize('hello world')) ++ end) ++ ++ ++ it("capitalize()", function() ++ -- old name for 'title' ++ assert.equal(stringx.title, stringx.capitalize) ++ end) ++ ++ ++ it("shorten()", function() ++ assert.equal('', stringx.shorten('', 0)) ++ assert.equal('a', stringx.shorten('a', 1)) ++ assert.equal('.', stringx.shorten('ab', 1)) --FIX:ok? ++ assert.equal('abc', stringx.shorten('abc', 3)) ++ assert.equal('...', stringx.shorten('abcd', 3)) ++ assert.equal('abcde', stringx.shorten('abcde', 5)) ++ assert.equal('a...', stringx.shorten('abcde', 4)) ++ assert.equal('...', stringx.shorten('abcde', 3)) ++ assert.equal('..', stringx.shorten('abcde', 2)) ++ assert.equal('', stringx.shorten('abcde', 0)) ++ assert.equal('', stringx.shorten('', 0, true)) ++ assert.equal('a', stringx.shorten('a', 1, true)) ++ assert.equal('.', stringx.shorten('ab', 1, true)) ++ assert.equal('abcde', stringx.shorten('abcde', 5, true)) ++ assert.equal('...e', stringx.shorten('abcde', 4, true)) ++ assert.equal('...', stringx.shorten('abcde', 3, true)) ++ assert.equal('..', stringx.shorten('abcde', 2, true)) ++ assert.equal('', stringx.shorten('abcde', 0, true)) ++ end) ++ ++ ++ it("quote_string()", function() ++ local assert_str_round_trip = function(s) ++ ++ local qs = stringx.quote_string(s) ++ local compiled, err = require("pl.utils").load("return "..qs) ++ ++ if not compiled then ++ print( ++ ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): ++ format(s, qs, err) ++ ) ++ error() ++ else ++ compiled = compiled() ++ end ++ ++ if compiled ~= s then ++ print("stringx.quote_string assert Failed: String compiled but did not round trip.") ++ print("input string:\t\t",s, #s) ++ print("compiled string:\t", compiled, #compiled) ++ print("output string:\t\t",qs, #qs) ++ error() ++ -- else ++ -- print("input string:\t\t",s) ++ -- print("compiled string:\t", compiled) ++ -- print("output string:\t\t",qs) ++ end ++ end ++ ++ assert_str_round_trip( "normal string with nothing weird.") ++ assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") ++ ++ assert_str_round_trip( "Unescapped quote \" in the middle") ++ assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") ++ assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) ++ assert_str_round_trip( "[[Completely normal\n long quote. ]]") ++ assert_str_round_trip( "String with a newline\nending with a closing bracket]") ++ assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") ++ assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") ++ assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') ++ assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") ++ assert_str_round_trip( "This\tincludes\ttabs.") ++ assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") ++ assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") ++ ++ assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") ++ assert_str_round_trip('"A quoted string looks like what?"') ++ assert_str_round_trip( "'I think that it should be quoted, anyway.'") ++ assert_str_round_trip( "[[Even if they're long quoted.]]") ++ assert_str_round_trip( "]=]==]") ++ ++ assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") ++ assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") ++ assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") ++ assert_str_round_trip( "") ++ assert_str_round_trip( " ") ++ assert_str_round_trip( "\n") --tricky. ++ assert_str_round_trip( "\r") ++ assert_str_round_trip( "\r\n") ++ assert_str_round_trip( "\r1\n") ++ assert_str_round_trip( "[[") ++ assert_str_round_trip( "''") ++ assert_str_round_trip( '""') ++ end) ++ ++ ++ ++ describe("format_operator()", function() ++ ++ setup(function() ++ stringx.format_operator() ++ end) ++ ++ ++ it("handles plain substitutions", function() ++ assert.equal('[home]', '[%s]' % 'home') ++ assert.equal('fred = 42', '%s = %d' % {'fred',42}) ++ end) ++ ++ ++ it("invokes tostring on %s formats", function() ++ -- mostly works like string.format, except that %s forces use of tostring() ++ -- rather than throwing an error ++ local List = require 'pl.List' ++ assert.equal('TBL:{1,2,3}', 'TBL:%s' % List{1,2,3}) ++ end) ++ ++ ++ it("replaces '$field' references", function() ++ -- table with keys and format with $ ++ assert.equal('<1>', '<$one>' % {one=1}) ++ end) ++ ++ ++ it("accepts replacement functions", function() ++ local function subst(k) ++ if k == 'A' then ++ return 'ay' ++ elseif k == 'B' then ++ return 'bee' ++ else ++ return '?' ++ end ++ end ++ assert.equal('ay & bee', '$A & $B' % subst) ++ end) ++ ++ end) ++ ++end) ++ +diff --git a/extra/penlight/spec/text_spec.lua b/extra/penlight/spec/text_spec.lua +new file mode 100644 +index 0000000..2254aa0 +--- /dev/null ++++ b/extra/penlight/spec/text_spec.lua +@@ -0,0 +1,10 @@ ++describe("pl.text", function() ++ ++ it("forwarded to stringx", function() ++ assert.equal( ++ require "pl.stringx", ++ require "pl.text" ++ ) ++ end) ++ ++end) +diff --git a/extra/penlight/spec/utils-choose_spec.lua b/extra/penlight/spec/utils-choose_spec.lua +new file mode 100644 +index 0000000..ee4b674 +--- /dev/null ++++ b/extra/penlight/spec/utils-choose_spec.lua +@@ -0,0 +1,21 @@ ++local utils = require("pl.utils") ++ ++describe("pl.utils", function() ++ ++ describe("choose", function () ++ ++ it("handles normal values", function() ++ assert.equal(utils.choose(true, 1, 2), 1) ++ assert.equal(utils.choose(false, 1, 2), 2) ++ end) ++ ++ it("handles nils", function() ++ assert.equal(utils.choose(true, nil, 2), nil) ++ assert.equal(utils.choose(false, nil, 2), 2) ++ assert.equal(utils.choose(true, 1, nil), 1) ++ assert.equal(utils.choose(false, 1, nil), nil) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/utils-deprecate_spec.lua b/extra/penlight/spec/utils-deprecate_spec.lua +new file mode 100644 +index 0000000..1a6e352 +--- /dev/null ++++ b/extra/penlight/spec/utils-deprecate_spec.lua +@@ -0,0 +1,128 @@ ++local utils = require("pl.utils") ++ ++describe("pl.utils", function () ++ ++ local old_fn, last_msg, last_trace ++ ++ before_each(function() ++ old_fn = function() end ++ last_msg = nil ++ last_trace = nil ++ utils.set_deprecation_func(function(msg, trace) ++ last_msg = msg ++ last_trace = trace ++ end) ++ end) ++ ++ ++ after_each(function() ++ utils.deprecation_warning = old_fn ++ end) ++ ++ ++ ++ describe("set_deprecation_func", function () ++ ++ it("accepts nil as callback", function() ++ assert.has.no.error(function() ++ utils.set_deprecation_func() ++ end) ++ end) ++ ++ ++ it("accepts function as callback", function() ++ assert.has.no.error(function() ++ utils.set_deprecation_func(function() end) ++ end) ++ end) ++ ++ ++ it("fails on non-functions", function() ++ assert.has.error(function() ++ utils.set_deprecation_func("not a function") ++ end, "argument 1 expected a 'function', got a 'string'") ++ end) ++ ++ end) ++ ++ ++ ++ describe("raise_deprecation", function () ++ ++ it("requires the opts table", function() ++ assert.has.error(function() utils.raise_deprecation(nil) end, ++ "argument 1 expected a 'table', got a 'nil'") ++ end) ++ ++ ++ it("requires the opts.message field", function() ++ assert.has.error(function() utils.raise_deprecation({}) end, ++ "field 'message' of the options table must be a string") ++ end) ++ ++ ++ it("should output the message", function () ++ utils.raise_deprecation { ++ message = "hello world" ++ } ++ assert.equal("hello world", last_msg) ++ end) ++ ++ ++ it("should output the deprecated version", function () ++ utils.raise_deprecation { ++ message = "hello world", ++ deprecated_after = "2.0.0", ++ } ++ assert.equal("hello world (deprecated after 2.0.0)", last_msg) ++ end) ++ ++ ++ it("should output the removal version", function () ++ utils.raise_deprecation { ++ message = "hello world", ++ version_removed = "3.0.0", ++ } ++ assert.equal("hello world (scheduled for removal in 3.0.0)", last_msg) ++ end) ++ ++ ++ it("should output the deprecated and removal versions", function () ++ utils.raise_deprecation { ++ message = "hello world", ++ deprecated_after = "2.0.0", ++ version_removed = "3.0.0", ++ } ++ assert.equal("hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) ++ end) ++ ++ ++ it("should output the application/module name", function () ++ utils.raise_deprecation { ++ source = "MyApp 1.2.3", ++ message = "hello world", ++ deprecated_after = "2.0.0", ++ version_removed = "3.0.0", ++ } ++ assert.equal("[MyApp 1.2.3] hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) ++ end) ++ ++ ++ it("should add a stracktrace", function () ++ local function my_function_name() ++ utils.raise_deprecation { ++ source = "MyApp 1.2.3", ++ message = "hello world", ++ deprecated_after = "2.0.0", ++ version_removed = "3.0.0", ++ } ++ end ++ my_function_name() ++ ++ assert.Not.match("raise_deprecation", last_trace) ++ assert.match("my_function_name", last_trace) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/utils-enum_spec.lua b/extra/penlight/spec/utils-enum_spec.lua +new file mode 100644 +index 0000000..dbe6954 +--- /dev/null ++++ b/extra/penlight/spec/utils-enum_spec.lua +@@ -0,0 +1,174 @@ ++describe("pl.utils", function () ++ ++ describe("enum()", function () ++ local enum, t ++ ++ before_each(function() ++ enum = require("pl.utils").enum ++ end) ++ ++ ++ describe("creating", function() ++ ++ it("accepts a vararg", function() ++ t = enum("ONE", "two", "THREE") ++ assert.same({ ++ ONE = "ONE", ++ two = "two", ++ THREE = "THREE", ++ }, t) ++ end) ++ ++ ++ it("vararg entries must be strings", function() ++ assert.has.error(function() ++ t = enum("hello", true, "world") ++ end, "argument 2 expected a 'string', got a 'boolean'") ++ -- no holes ++ assert.has.error(function() ++ t = enum("hello", nil, "world") ++ end, "argument 2 expected a 'string', got a 'nil'") ++ end) ++ ++ ++ it("vararg requires at least 1 entry", function() ++ assert.has.error(function() ++ t = enum() ++ end, "expected at least 1 entry") ++ end) ++ ++ ++ it("accepts an array", function() ++ t = enum { "ONE", "two", "THREE" } ++ assert.same({ ++ ONE = "ONE", ++ two = "two", ++ THREE = "THREE", ++ }, t) ++ end) ++ ++ ++ it("array entries must be strings", function() ++ assert.has.error(function() ++ t = enum { "ONE", 999, "THREE" } ++ end, "expected 'string' but got 'number' at index 2") ++ end) ++ ++ ++ it("array requires at least 1 entry", function() ++ assert.has.error(function() ++ t = enum {} ++ end, "expected at least 1 entry") ++ end) ++ ++ ++ it("accepts a hash-table", function() ++ t = enum { ++ FILE_NOT_FOUND = "The file was not found in the filesystem", ++ FILE_READ_ONLY = "The file is read-only", ++ } ++ assert.same({ ++ FILE_NOT_FOUND = "The file was not found in the filesystem", ++ FILE_READ_ONLY = "The file is read-only", ++ }, t) ++ end) ++ ++ ++ it("hash-table keys must be strings", function() ++ assert.has.error(function() ++ t = enum { [{}] = "ONE" } ++ end, "expected key to be 'string' but got 'table'") ++ end) ++ ++ ++ it("hash-table requires at least 1 entry", function() ++ assert.has.error(function() ++ t = enum {} ++ end, "expected at least 1 entry") ++ end) ++ ++ ++ it("accepts a combined array/hash-table", function() ++ t = enum { ++ "BAD_FD", ++ FILE_NOT_FOUND = "The file was not found in the filesystem", ++ FILE_READ_ONLY = "The file is read-only", ++ } ++ assert.same({ ++ BAD_FD = "BAD_FD", ++ FILE_NOT_FOUND = "The file was not found in the filesystem", ++ FILE_READ_ONLY = "The file is read-only", ++ }, t) ++ end) ++ ++ ++ it("keys must be unique with combined array/has-table", function() ++ assert.has.error(function() ++ t = enum { ++ "FILE_NOT_FOUND", ++ FILE_NOT_FOUND = "The file was not found in the filesystem", ++ } ++ end, "duplicate entry in array and hash part: 'FILE_NOT_FOUND'") ++ end) ++ ++ end) ++ ++ ++ ++ describe("accessing", function() ++ ++ before_each(function() ++ t = enum("ONE", "two", "THREE") ++ end) ++ ++ ++ it("errors on unknown values", function() ++ assert.has.error(function() ++ print(t.four) ++ end, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')") ++ end) ++ ++ ++ it("errors on setting new keys", function() ++ assert.has.error(function() ++ t.four = "four" ++ end, "the Enum object is read-only") ++ end) ++ ++ ++ it("keys can have 'format' placeholders", function() ++ t = enum("hello", "contains: %s") ++ assert.has.error(function() ++ print(t["%s"]) -- should still format error properly ++ end, "'%s' is not a valid value (expected one of: 'hello', 'contains: %s')") ++ end) ++ ++ end) ++ ++ ++ ++ describe("calling", function() ++ ++ before_each(function() ++ t = enum("ONE", "two", "THREE") ++ end) ++ ++ ++ it("returns error on unknown values", function() ++ local ok, err = t("four") ++ assert.equal(err, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')") ++ assert.equal(nil, ok) ++ end) ++ ++ ++ it("returns value on success", function() ++ local ok, err = t("THREE") ++ assert.equal(nil, err) ++ assert.equal("THREE", ok) ++ end) ++ ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/utils-kpairs_spec.lua b/extra/penlight/spec/utils-kpairs_spec.lua +new file mode 100644 +index 0000000..7fac079 +--- /dev/null ++++ b/extra/penlight/spec/utils-kpairs_spec.lua +@@ -0,0 +1,38 @@ ++local utils = require("pl.utils") ++ ++describe("pl.utils", function () ++ ++ describe("kpairs", function () ++ local kpairs ++ ++ before_each(function() ++ kpairs = utils.kpairs ++ end) ++ ++ ++ it("iterates over non-integers", function() ++ local func = function() end ++ local bool = true ++ local string = "a string" ++ local float = 123.45 ++ local r = {} ++ for k, v in kpairs { ++ [func] = 1, ++ [bool] = 2, ++ [string] = 3, ++ [float] = 4, ++ 5, 6, 7, ++ } do ++ r[k] = v ++ end ++ ++ assert.same({ ++ [func] = 1, ++ [bool] = 2, ++ [string] = 3, ++ [float] = 4 }, r) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/utils-npairs_spec.lua b/extra/penlight/spec/utils-npairs_spec.lua +new file mode 100644 +index 0000000..7e3c5d4 +--- /dev/null ++++ b/extra/penlight/spec/utils-npairs_spec.lua +@@ -0,0 +1,105 @@ ++local utils = require("pl.utils") ++ ++describe("pl.utils", function () ++ ++ describe("npairs", function () ++ local npairs = utils.npairs ++ ++ it("start index defaults to 1", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, nil, 2) do t2[i] = v end ++ assert.are.same({ 1, 2 }, t2) ++ end) ++ ++ ++ it("end index defaults to `t.n`", function() ++ local t1 = { n = 2, 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1) do t2[i] = v end ++ assert.are.same({1, 2}, t2) ++ end) ++ ++ ++ it("step size defaults to 1", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1) do t2[i] = v end ++ assert.are.same({1, 2, 3}, t2) ++ end) ++ ++ ++ it("step size cannot be 0", function() ++ local t1 = { 1, 2, 3 } ++ assert.has.error(function() ++ npairs(t1, nil, nil, 0) ++ end, "iterator step-size cannot be 0") ++ end) ++ ++ ++ it("end index defaults to `#t` if there is no `t.n`", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1) do t2[i] = v end ++ assert.are.same({1, 2, 3}, t2) ++ end) ++ ++ ++ it("returns nothing if start index is beyond end index", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, 5, 3) do t2[i] = v end ++ assert.are.same({}, t2) ++ end) ++ ++ ++ it("returns nothing if start index is beyond end index, with negative step size", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, 3, 1, -1) do t2[#t2+1] = v end ++ assert.are.same({ 3, 2, 1}, t2) ++ end) ++ ++ ++ it("returns 1 key/value if end == start index", function() ++ local t1 = { 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, 2, 2) do t2[i] = v end ++ assert.are.same({ [2] = 2 }, t2) ++ end) ++ ++ ++ it("returns negative to positive ranges", function() ++ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, -4, 1) do t2[i] = v end ++ assert.are.same({ [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1 }, t2) ++ end) ++ ++ ++ it("returns nil values with the range", function() ++ local t1 = { n = 3 } ++ local t2 = {} ++ for i, v in npairs(t1) do t2[i] = tostring(v) end ++ assert.are.same({ "nil", "nil", "nil" }, t2) ++ end) ++ ++ ++ it("honours positive step size", function() ++ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, -4, 1, 2) do t2[#t2+1] = v end ++ assert.are.same({ -4, -2, 0}, t2) ++ end) ++ ++ ++ it("honours negative step size", function() ++ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } ++ local t2 = {} ++ for i, v in npairs(t1, 0, -5, -2) do t2[#t2+1] = v end ++ assert.are.same({ 0, -2, -4 }, t2) ++ end) ++ ++ end) ++ ++end) +diff --git a/extra/penlight/spec/xml_spec.lua b/extra/penlight/spec/xml_spec.lua +new file mode 100644 +index 0000000..67c9a3d +--- /dev/null ++++ b/extra/penlight/spec/xml_spec.lua +@@ -0,0 +1,960 @@ ++local xml = require "pl.xml" ++ ++describe("xml", function() ++ ++ describe("new()", function() ++ ++ it("creates a new xml-document", function() ++ local doc = xml.new("main") ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("fails without a tag", function() ++ assert.has.error(function() ++ xml.new() ++ end, "expected 'tag' to be a string value, got: nil") ++ end) ++ ++ ++ it("fails with bad attributes", function() ++ assert.has.error(function() ++ xml.new("tag", "not a table...") ++ end, "expected 'attr' to be a table value, got: string") ++ end) ++ ++ ++ it("adds attributes if given", function() ++ local doc = xml.new("main", { hello = "world" }) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("parse()", function() ++ ++ pending("todo", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++ ++ ++ describe("elem()", function() ++ ++ it("creates a node", function() ++ local doc = xml.elem("main") ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with single text element", function() ++ local doc = xml.elem("main", "oh my") ++ assert.equal("
    oh my
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with single child tag/Node", function() ++ local doc = xml.elem("main", xml.new("child")) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with multiple text elements", function() ++ local doc = xml.elem("main", { "this ", "is ", "nice" }) ++ assert.equal("
    this is nice
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with multiple child tags/Nodes", function() ++ local doc = xml.elem("main", { xml.new "this", xml.new "is", xml.new "nice" }) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with attributes", function() ++ local doc = xml.elem("main", { hello = "world" }) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with text/Node children and attributes", function() ++ local doc = xml.elem("main", { ++ "prefix", ++ xml.elem("child", { "this ", "is ", "nice"}), ++ "postfix", ++ attrib = "value" ++ }) ++ assert.equal("
    prefixthis is nicepostfix
    ", doc:tostring()) ++ end) ++ ++ ++ it("creates a node, with text/Node nested children and attributes", function() ++ local doc = xml.elem("main", { ++ "prefix", ++ xml.elem("child", { ++ "this", ++ xml.elem "is", ++ "nice", ++ }), ++ "postfix", ++ attrib = "value" ++ }) ++ assert.equal("
    prefixthisnicepostfix
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("tags()", function() ++ ++ it("creates constructors", function() ++ local parent, child = xml.tags({ "mom" , "kid" }) ++ local doc = parent {child 'Bob', child 'Annie'} ++ assert.equal("BobAnnie", doc:tostring()) ++ end) ++ ++ ++ it("creates constructors from CSV values", function() ++ local parent, child = xml.tags("mom,kid" ) ++ local doc = parent {child 'Bob', child 'Annie'} ++ assert.equal("BobAnnie", doc:tostring()) ++ end) ++ ++ ++ it("creates constructors from CSV values, ignores surrounding whitespace", function() ++ local parent, child = xml.tags(" mom , kid " ) ++ local doc = parent {child 'Bob', child 'Annie'} ++ assert.equal("BobAnnie", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("addtag()", function() ++ ++ it("adds a Node", function() ++ local doc = xml.new("main") ++ doc:addtag("penlight", { hello = "world" }) ++ assert.equal("
    ", doc:tostring()) ++ ++ -- moves position ++ doc:addtag("expat") ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("text()", function() ++ ++ it("adds text", function() ++ local doc = xml.new("main") ++ doc:text("penlight") ++ assert.equal("
    penlight
    ", doc:tostring()) ++ ++ -- moves position ++ doc:text("expat") ++ assert.equal("
    penlightexpat
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("up()", function() ++ ++ it("moves position up 1 level", function() ++ local doc = xml.new("main") ++ doc:addtag("one") ++ doc:addtag("two-a") ++ doc:up() ++ doc:addtag("two-b") ++ assert.equal("
    ", doc:tostring()) ++ ++ -- doesn't move beyond top level ++ for i = 1, 10 do ++ doc:up() ++ end ++ doc:addtag("solong") ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("reset()", function() ++ ++ it("resets position to top Node", function() ++ local doc = xml.new("main") ++ doc:addtag("one") ++ doc:addtag("two") ++ doc:addtag("three") ++ doc:reset() ++ doc:addtag("solong") ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("add_direct_child", function() ++ ++ it("adds a child node", function() ++ local doc = xml.new("main") ++ doc:add_direct_child(xml.new("child")) ++ assert.equal("
    ", doc:tostring()) ++ ++ doc:add_direct_child(xml.new("child")) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ ++ it("adds a text node", function() ++ local doc = xml.new("main") ++ doc:add_direct_child("child") ++ assert.equal("
    child
    ", doc:tostring()) ++ ++ doc:add_direct_child("child") ++ assert.equal("
    childchild
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("add_child()", function() ++ ++ it("adds a child at the current position", function() ++ local doc = xml.new("main") ++ doc:addtag("one") ++ doc:add_child(xml.new("item1")) ++ doc:add_child(xml.new("item2")) ++ doc:add_child(xml.new("item3")) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("set_attribs()", function() ++ ++ it("sets attributes on the Node", function() ++ local doc = xml.new("main") ++ doc:addtag("one") -- moves position ++ ++ ++ doc:set_attribs( { one = "a" }) ++ assert.equal("
    ", doc:tostring()) ++ ++ -- overwrites and adds ++ doc:set_attribs( { one = "1", two = "2" }) ++ assert.matches("one='1'", doc:tostring()) ++ assert.matches("two='2'", doc:tostring()) ++ ++ -- 'two' doesn't get removed ++ doc:set_attribs( { one = "a" }) ++ assert.matches("one='a'", doc:tostring()) ++ assert.matches("two='2'", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("set_attrib()", function() ++ ++ it("sets/deletes a single attribute on the Node", function() ++ local doc = xml.new("main") ++ doc:addtag("one") -- moves position ++ ++ ++ doc:set_attrib("one", "a") ++ assert.equal("
    ", doc:tostring()) ++ ++ -- deletes ++ doc:set_attrib("one", nil) ++ assert.equal("
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("get_attribs()", function() ++ ++ it("gets attributes on the Node", function() ++ local doc = xml.new("main") ++ doc:addtag("one") -- moves position ++ ++ doc:set_attribs( { one = "1", two = "2" }) ++ assert.same({ one = "1", two = "2" }, doc:get_attribs()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("subst()", function() ++ ++ pending("todo", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++ ++ ++ describe("child_with_name()", function() ++ ++ it("returns the first child", function() ++ local doc = xml.new("main") ++ doc:add_child(xml.elem "one") ++ doc:text("hello") ++ doc:add_child(xml.elem "two") ++ doc:text("goodbye") ++ doc:add_child(xml.elem "three") ++ ++ local child = doc:child_with_name("two") ++ assert.not_nil(child) ++ assert.equal(doc[3], child) ++ end) ++ ++ end) ++ ++ ++ ++ describe("tostring()", function() ++ ++ pending("todo still...", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++ ++ ++ describe("get_elements_with_name()", function() ++ ++ it("returns matching nodes", function() ++ local doc = assert(xml.parse[[ ++ ++ John ++ ++ ++ Bob ++ ++ ++ Bob junior ++ ++ ++ ++ ++ Annie ++ ++ ++ Melissa ++ ++ ++ Noel ++ ++ ++ ++ ++ ++ ]]) ++ ++ local list = doc:get_elements_with_name("name") ++ for i, entry in ipairs(list) do ++ list[i] = entry:get_text() ++ end ++ assert.same({"John", "Bob", "Bob junior", "Annie", "Melissa", "Noel"}, list) ++ ++ -- if tag not found, returns empty table ++ local list = doc:get_elements_with_name("unknown") ++ assert.same({}, list) ++ end) ++ ++ end) ++ ++ ++ ++ describe("children()", function() ++ ++ it("iterates over all children", function() ++ local doc = xml.elem("main", { ++ "prefix", ++ xml.elem("child"), ++ "postfix", ++ attrib = "value" ++ }) ++ ++ local lst = {} ++ for node in doc:children() do ++ lst[#lst+1] = tostring(node) ++ end ++ assert.same({ "prefix", "", "postfix"}, lst) ++ end) ++ ++ ++ it("doesn't fail on empty node", function() ++ local doc = xml.elem("main") ++ local lst = {} ++ for node in doc:children() do ++ lst[#lst+1] = tostring(node) ++ end ++ assert.same({}, lst) ++ end) ++ ++ end) ++ ++ ++ ++ describe("first_childtag()", function() ++ ++ it("returns first non-text tag", function() ++ local doc = xml.elem("main", { ++ "prefix", ++ xml.elem("child"), ++ "postfix", ++ attrib = "value" ++ }) ++ ++ local node = doc:first_childtag() ++ assert.same("", tostring(node)) ++ end) ++ ++ ++ it("returns nil if there is none", function() ++ local doc = xml.elem("main", { ++ "prefix", ++ "postfix", ++ attrib = "value" ++ }) ++ ++ local node = doc:first_childtag() ++ assert.is_nil(node) ++ end) ++ ++ end) ++ ++ ++ ++ describe("matching_tags()", function() ++ ++ local _ = [[ ++ ++ ++ ++ ++ Apples ++ Bananas ++ ++ ++ ++ ++ African Coffee Table ++ 80 ++ 120 ++ ++ ++ ++ ]] ++ ++ pending("xmlns is weird...", function() ++ -- the xmlns stuff doesn't make sense ++ end) ++ ++ end) ++ ++ ++ ++ describe("childtags()", function() ++ ++ it("returns the first child", function() ++ local doc = xml.new("main") ++ doc:add_child(xml.elem "one") ++ doc:text("hello") ++ doc:add_child(xml.elem "two") ++ doc:text("goodbye") ++ doc:add_child(xml.elem "three") ++ ++ local lst = {} ++ for node in doc:childtags() do ++ lst[#lst+1] = tostring(node) ++ end ++ assert.same({"", "", ""},lst) ++ end) ++ ++ end) ++ ++ ++ ++ describe("maptags()", function() ++ ++ it("updates nodes", function() ++ local doc = xml.new("main") ++ doc:add_child(xml.elem "one") ++ doc:text("hello") ++ doc:add_child(xml.elem "two") ++ doc:text("goodbye") ++ doc:add_child(xml.elem "three") ++ ++ doc:maptags(function(node) ++ if node.tag then ++ -- return a new object so we know it got replaced ++ return xml.new(node.tag:upper()) ++ end ++ return node ++ end) ++ assert.same("
    hellogoodbye
    ", doc:tostring()) ++ end) ++ ++ ++ it("removes nodes", function() ++ local doc = xml.new("main") ++ doc:add_child(xml.elem "one") ++ doc:text("hello") ++ doc:add_child(xml.elem "two") ++ doc:text("goodbye") ++ doc:add_child(xml.elem "three") ++ ++ doc:maptags(function(node) ++ if node.tag then ++ return nil -- remove it ++ end ++ return node ++ end) ++ assert.same("
    hellogoodbye
    ", doc:tostring()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("xml_escape()", function() ++ ++ it("escapes reserved characters", function() ++ local esc = xml.xml_escape([["'<>&]]) ++ assert.same(""'<>&", esc) ++ end) ++ ++ end) ++ ++ ++ ++ describe("xml_unescape()", function() ++ ++ it("escapes reserved characters", function() ++ local unesc = xml.xml_unescape(""'<>&") ++ assert.same([["'<>&]], unesc) ++ end) ++ ++ end) ++ ++ ++ ++ describe("get_text()", function() ++ ++ it("returns all text concatenated", function() ++ local doc = xml.new("main") ++ doc:text("one") ++ doc:add_child(xml.elem "two") ++ doc:text("three") ++ ++ assert.same("onethree", doc:get_text()) ++ end) ++ ++ it("returns empty string if no text", function() ++ local doc = xml.new("main") ++ doc:add_child(xml.elem "two") ++ ++ assert.same("", doc:get_text()) ++ end) ++ ++ end) ++ ++ ++ ++ describe("clone()", function() ++ ++ it("clones a document", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local doc2 = xml.clone(doc1) ++ assert.are.same(doc1:tostring(), doc2:tostring()) ++ assert.not_equal(doc1, doc2) ++ for i, elem1 in ipairs(doc1) do ++ assert.are.same(tostring(elem1), tostring(doc2[i])) ++ if type(elem1) == "table" then ++ assert.not_equal(elem1, doc2[i]) ++ end ++ end ++ end) ++ ++ ++ it("calls substitution callback and updates", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local repl = { ++ ["*TAG"] = { ++ main = "top", ++ this = "that", ++ is = "was", ++ a = "a", ++ b = "b", ++ nice = "bad", ++ }, ++ ["*TEXT"] = { ++ ["this content"] = "that content", ++ }, ++ hello = { ++ world = "universe", ++ }, ++ } ++ local subst = function(object, kind, parent) ++ if repl[kind] then ++ if repl[kind][object] then ++ return repl[kind][object] ++ else ++ error(("object '%s' of kind '%s' not found"):format(object,kind)) ++ end ++ else ++ error(("kind '%s' not found"):format(kind)) ++ end ++ end ++ ++ local doc2 = xml.clone(doc1, subst) ++ assert.equal("that content", doc2:tostring()) ++ end) ++ ++ ++ it("clones text nodes", function() ++ assert.equal("hello", xml.clone("hello")) ++ end) ++ ++ ++ it("errors on recursion", function() ++ local doc = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ "some", ++ xml.elem "is", ++ "text", ++ xml.elem "nice", ++ }) ++ ++ doc[#doc+1] = doc -- add recursion ++ ++ assert.has.error(function() ++ xml.clone(doc) ++ end, "recursion detected") ++ end) ++ ++ end) ++ ++ ++ ++ describe("compare()", function() ++ ++ it("returns true on equal docs", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local doc2 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local ok, err = xml.compare(doc1, doc2) ++ assert.is_nil(err) ++ assert.is_true(ok) ++ end) ++ ++ ++ it("compares types", function() ++ local ok, err = xml.compare(nil, true) ++ assert.equal("type mismatch", err) ++ assert.is_false(ok) ++ ++ local ok, err = xml.compare("true", true) ++ assert.equal("type mismatch", err) ++ assert.is_false(ok) ++ ++ local ok, err = xml.compare(true, true) ++ assert.equal("not a document", err) ++ assert.is_false(ok) ++ ++ local ok, err = xml.compare("text", "text") ++ assert.is_nil(err) ++ assert.is_true(ok) ++ ++ local ok, err = xml.compare("text1", "text2") ++ assert.equal("text text1 ~= text text2", err) ++ assert.is_false(ok) ++ end) ++ ++ ++ it("compares element size (array part)", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local doc2 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ "plain text", ++ }) ++ ++ local ok, err = xml.compare(doc1, doc2) ++ assert.equal("size 3 ~= size 4 for tag main", err) ++ assert.is_false(ok) ++ end) ++ ++ ++ it("compares children", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local doc2 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "c" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local ok, err = xml.compare(doc1, doc2) ++ assert.equal("tag b ~= tag c", err) ++ assert.is_false(ok) ++ end) ++ ++ ++ it("compares attributes", function() ++ local doc1 = xml.new("main", { ++ hello = "world", ++ goodbye = "universe" ++ }) ++ ++ local ok, err = xml.compare(doc1, xml.new("main", { ++ hello = "world", ++ goodbye = "universe" ++ })) ++ assert.equal(nil, err) ++ assert.is_true(ok) ++ ++ local ok, err = xml.compare(doc1, xml.new("main", { ++ -- hello = "world", -- one less attribute ++ goodbye = "universe" ++ })) ++ assert.equal("mismatch attrib", err) ++ assert.is_false(ok) ++ ++ local ok, err = xml.compare(doc1, xml.new("main", { ++ hello = "world", ++ goodbye = "universe", ++ one = "more", -- one more attribute ++ })) ++ assert.equal("mismatch attrib", err) ++ assert.is_false(ok) ++ end) ++ ++ ++ it("compares attributes order", function() ++ local doc1 = xml.new("main", { ++ [1] = "hello", ++ [2] = "goodbye", ++ hello = "world", ++ goodbye = "universe" ++ }) ++ ++ local ok, err = xml.compare(doc1, xml.new("main", { ++ -- no order, this compares ok ++ hello = "world", ++ goodbye = "universe" ++ })) ++ assert.equal(nil, err) ++ assert.is_true(ok) ++ ++ local ok, err = xml.compare(doc1, xml.new("main", { ++ [2] = "hello", -- order reversed, this should fail ++ [1] = "goodbye", ++ hello = "world", ++ goodbye = "universe" ++ })) ++ assert.equal("mismatch attrib order", err) ++ assert.is_false(ok) ++ end) ++ ++ ++ it("handles recursion", function() ++ local doc1 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ local doc2 = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ xml.elem("is", { ++ xml.elem "a", ++ xml.elem "b" ++ }), ++ xml.elem "nice", ++ }) ++ ++ doc1[#doc1 + 1] = doc1 -- add recursion ++ doc2[#doc2 + 1] = xml.elem "main" -- add tag by same name ++ ++ local ok, err = xml.compare(doc1, doc2) ++ assert.equal("recursive document", err) ++ assert.is_false(ok) ++ end) ++ ++ end) ++ ++ ++ ++ describe("walk()", function() ++ ++ it("calls on all tags", function() ++ local doc = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ "some", ++ xml.elem "is", ++ "text", ++ xml.elem "nice", ++ }) ++ ++ assert.equal("
    this contentsometext
    ", doc:tostring()) ++ ++ local list = {} ++ xml.walk(doc, nil, function(tag_name, node) ++ list[#list+1] = assert(tag_name) ++ end) ++ assert.same({"main", "this", "is", "nice"}, list) ++ ++ -- now depth_first ++ local list = {} ++ xml.walk(doc, true, function(tag_name, node) ++ list[#list+1] = assert(tag_name) ++ end) ++ assert.same({"this", "is", "nice", "main"}, list) ++ end) ++ ++ ++ it("errors on recursion", function() ++ local doc = xml.elem("main", { ++ hello = "world", ++ xml.elem("this", "this content"), ++ "some", ++ xml.elem "is", ++ "text", ++ xml.elem "nice", ++ }) ++ ++ doc[#doc+1] = doc -- add recursion ++ ++ assert.has.error(function() ++ xml.walk(doc, nil, function() end) ++ end, "recursion detected") ++ end) ++ ++ end) ++ ++ ++ ++ describe("parsehtml()", function() ++ ++ pending("to be deprecated...", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++ ++ ++ describe("basic_parse()", function() ++ ++ pending("to be deprecated...", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++ ++ ++ describe("match()", function() ++ ++ pending("figure out what it does...", function() ++ -- TODO: implement ++ end) ++ ++ end) ++ ++end) ++ +diff --git a/extra/penlight/tests/lua/mod52.lua b/extra/penlight/tests/lua/mod52.lua +index 7cc8c7e..4b3ec7c 100644 +--- a/extra/penlight/tests/lua/mod52.lua ++++ b/extra/penlight/tests/lua/mod52.lua +@@ -10,9 +10,17 @@ local _ENV,M = require 'pl.import_into' (rawget(_G,'STRICT')) + function answer () + -- of course, you don't have the usual global environment available + -- so define it as a local up above, or use utils.import(_G). ++ ++ local versioned_errors = { ++ ["1"] = "attempt to call global 'print'", ++ ["2"] = "attempt to call global 'print'", ++ ["3"] = "attempt to call a nil value", ++ ["4"] = "a nil value", ++ } ++ local expected = versioned_errors[LUA_VERSION:match("Lua 5.(%d)")] + test.assertraise(function() + print 'hello' +- end,(LUA_VERSION~="Lua 5.3") and "attempt to call global 'print'" or "attempt to call a nil value") ++ end, expected) + + -- but all the Penlight modules are available + return pretty.write(utils.split '10 20 30', '') +diff --git a/extra/penlight/tests/test-vector.lua b/extra/penlight/tests/test-__vector.lua +similarity index 100% +rename from tests/test-vector.lua +rename to tests/test-__vector.lua +diff --git a/extra/penlight/tests/test-app.lua b/extra/penlight/tests/test-app.lua +new file mode 100644 +index 0000000..fe60537 +--- /dev/null ++++ b/extra/penlight/tests/test-app.lua +@@ -0,0 +1,307 @@ ++local app = require "pl.app" ++local utils = require "pl.utils" ++local path = require "pl.path" ++local asserteq = require 'pl.test'.asserteq ++local lfs = require("lfs") ++ ++local quote = utils.quote_arg ++ ++local _, cmd = app.lua() ++cmd = cmd .. " " .. quote({"-e", "package.path=[[./lua/?.lua;./lua/?/init.lua;]]..package.path"}) ++ ++local function run_script(s, fname) ++ local tmpname = path.tmpname() ++ if fname then ++ tmpname = path.join(path.dirname(tmpname), fname) ++ end ++ assert(utils.writefile(tmpname, s)) ++ local success, code, stdout, stderr = utils.executeex(cmd.." "..tmpname) ++ os.remove(tmpname) ++ return success, code, stdout, stderr ++end ++ ++do -- app.script_name ++ ++ local success, code, stdout, stderr = run_script([[ ++ print(require("pl.app").script_name()) ++ ]], ++ "justsomescriptname.lua") ++ asserteq(stderr, "") ++ asserteq(stdout:match("(justsome.+)$"), "justsomescriptname.lua\n") ++ ++ ++ -- commandline, no scriptname ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = nil -- simulate no scriptname ++ local name, err = require("pl.app").script_name() ++ io.stdout:write(tostring(name)) ++ io.stderr:write(err) ++ ]]) ++ assert(stderr:find("No script name found")) ++ asserteq(stdout, "nil") ++ ++ ++ -- commandline, no args table ++ local success, code, stdout, stderr = run_script([[ ++ arg = nil -- simulate no arg table ++ local name, err = require("pl.app").script_name() ++ io.stdout:write(tostring(name)) ++ io.stderr:write(err) ++ ]]) ++ assert(stderr:find("No script name found")) ++ asserteq(stdout, "nil") ++end ++ ++do -- app.require_here ++ local cd = path.currentdir() --path.dirname(path.tmpname()) ++ ++ -- plain script name ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "justsomescriptname.lua" ++ local p = package.path ++ require("pl.app").require_here() ++ print(package.path:sub(1, -#p-1)) ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ assert(stdout:find(path.normcase(cd.."/?.lua;"), 1, true)) ++ assert(stdout:find(path.normcase(cd.."/?/init.lua;"), 1, true)) ++ ++ ++ -- plain script name, with a relative base name ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "justsomescriptname.lua" ++ local p = package.path ++ require("pl.app").require_here("basepath/to/somewhere") ++ print(package.path:sub(1, -#p-1)) ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?.lua;"), 1, true)) ++ assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?/init.lua;"), 1, true)) ++ ++ ++ -- plain script name, with an absolute base name ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "justsomescriptname.lua" ++ local p = package.path ++ require("pl.app").require_here("/basepath/to/somewhere") ++ print(package.path:sub(1, -#p-1)) ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ asserteq(stdout, path.normcase("/basepath/to/somewhere/?.lua;/basepath/to/somewhere/?/init.lua;\n")) ++ ++ ++ -- scriptname with a relative path ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "relative/prefix/justsomescriptname.lua" ++ local p = package.path ++ require("pl.app").require_here() ++ print(package.path:sub(1, -#p-1)) ++ os.exit() ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ assert(stdout:find(path.normcase(cd.."/relative/prefix/?.lua;"), 1, true)) ++ assert(stdout:find(path.normcase(cd.."/relative/prefix/?/init.lua;"), 1, true)) ++ ++ ++ -- script with an absolute path ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "/fixed/justsomescriptname.lua" ++ local p = package.path ++ require("pl.app").require_here() ++ print(package.path:sub(1, -#p-1)) ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ asserteq(stdout, path.normcase("/fixed/?.lua;/fixed/?/init.lua;\n")) ++ ++ -- symlinked script, check that we look beside the target of the link ++ -- -- step 1: find ourselves ++ local self = app.script_name() ++ if not path.isabs(self) then self = path.join(cd,self) end ++ local tadir = path.normcase(path.join(path.dirname(self),"test-app")) ++ -- -- step 2: create a link to our helper script ++ local scrl = path.tmpname() ++ local linkdir = path.normcase(path.dirname(scrl)) ++ os.remove(scrl) ++ assert(lfs.link(path.join(tadir,"require_here-link-target.lua"), scrl, true)) ++ -- -- step 3: check that we look next to ourselves ++ local success, code, stdout, stderr = utils.executeex(cmd.." "..scrl) ++ stdout = path.normcase(stdout) ++ assert(stdout:find(path.normcase(path.join(tadir, "?.lua;")), 1, true)) ++ assert(stdout:find(path.normcase(path.join(tadir, "?/init.lua;")), 1, true)) ++ assert(not stdout:find(path.normcase(path.join(linkdir, "?.lua;")), 1, true)) ++ assert(not stdout:find(path.normcase(path.join(linkdir, "?/init.lua;")), 1, true)) ++ -- -- step 4: ... but not if we turn on nofollow ++ local success, code, stdout, stderr = utils.executeex(cmd.." "..scrl.." x") ++ stdout = path.normcase(stdout) ++ assert(not stdout:find(path.normcase(path.join(tadir, "?.lua;")), 1, true)) ++ assert(not stdout:find(path.normcase(path.join(tadir, "?/init.lua;")), 1, true)) ++ assert(stdout:find(path.normcase(path.join(linkdir, "?.lua;")), 1, true)) ++ assert(stdout:find(path.normcase(path.join(linkdir, "?/init.lua;")), 1, true)) ++ os.remove(scrl) ++ ++end ++ ++ ++do -- app.appfile ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "some/path/justsomescriptname_for_penlight_testing.lua" ++ print(require("pl.app").appfile("filename.data")) ++ ]]) ++ asserteq(stderr, "") ++ stdout = path.normcase(stdout) ++ local fname = path.normcase(path.expanduser("~/.justsomescriptname_for_penlight_testing/filename.data")) ++ asserteq(stdout, fname .."\n") ++ assert(path.isdir(path.dirname(fname))) ++ path.rmdir(path.dirname(fname)) ++ ++end ++ ++ ++do -- app.lua ++ local success, code, stdout, stderr = run_script([[ ++ arg[0] = "justsomescriptname.lua" ++ local a,b = require("pl.app").lua() ++ print(a) ++ ]]) ++ asserteq(stderr, "") ++ asserteq(stdout, cmd .."\n") ++ ++end ++ ++ ++do -- app.parse_args ++ ++ -- no value specified ++ local args = utils.split("-a -b") ++ local t,s = app.parse_args(args, { a = true}) ++ asserteq(t, nil) ++ asserteq(s, "no value for 'a'") ++ ++ ++ -- flag that take a value, space separated ++ local args = utils.split("-a -b value -c") ++ local t,s = app.parse_args(args, { b = true}) ++ asserteq(t, { ++ a = true, ++ b = "value", ++ c = true, ++ }) ++ asserteq(s, {}) ++ ++ ++ -- flag_with_values specified as a list ++ local args = utils.split("-a -b value -c") ++ local t,s = app.parse_args(args, { "b" }) ++ asserteq(t, { ++ a = true, ++ b = "value", ++ c = true, ++ }) ++ asserteq(s, {}) ++ ++ ++ -- flag_with_values missing value at end ++ local args = utils.split("-a -b") ++ local t,s = app.parse_args(args, { "b" }) ++ asserteq(t, nil) ++ asserteq(s, "no value for 'b'") ++ ++ ++ -- error on an unknown flag ++ local args = utils.split("-a -b value -c") ++ local t,s = app.parse_args(args, { b = true }, { "b", "c" }) ++ asserteq(t, nil) ++ asserteq(s, "unknown flag 'a'") ++ ++ ++ -- flag that doesn't take a value ++ local args = utils.split("-a -b:value") ++ local t,s = app.parse_args(args, {}) ++ asserteq(t, { ++ ["a"] = true, ++ ["b"] = "value" ++ }) ++ asserteq(s, {}) ++ ++ ++ -- correctly parsed values, spaces, :, =, and multiple : or = ++ local args = utils.split("-a value -b value:one=two -c=value2:2") ++ local t,s = app.parse_args(args, { "a", "b", "c" }) ++ asserteq(t, { ++ ["a"] = "value", ++ ["b"] = "value:one=two", ++ ["c"] = "value2:2", ++ }) ++ asserteq(s, {}) ++ ++ ++ -- many values, duplicates, and parameters mixed ++ local args = utils.split( ++ "-a -b -cde --long1 --ff:ffvalue --gg=ggvalue -h:hvalue -i=ivalue " .. ++ "-i=2ndvalue param -i:3rdvalue -j1 -k2 -1:hello remaining values") ++ local t,s = app.parse_args(args) ++ asserteq({ ++ i = "3rdvalue", ++ ["1"] = "hello", ++ ff = "ffvalue", ++ long1 = true, ++ c = true, ++ b = true, ++ gg = "ggvalue", ++ j = "1", ++ k = "2", ++ d = true, ++ h = "hvalue", ++ a = true, ++ e = true ++ }, t) ++ asserteq({ ++ "param", ++ "remaining", ++ "values" ++ }, s) ++ ++ ++ -- specify valid flags and aliases ++ local args = utils.split("-a -b value -e -f3") ++ local t,s = app.parse_args(args, ++ { ++ "b", ++ f = true, ++ }, { ++ bully = "b", -- b with value will be reported as 'bully', alias as string ++ a = true, -- hash-type value ++ c = { "d", "e" }, -- e will be reported as c, aliases as list/table ++ }) ++ asserteq(t, { ++ a = true, ++ bully = "value", ++ c = true, ++ f = "3", ++ }) ++ asserteq(s, {}) ++ ++ ++ -- error on an unknown flag, in a chain of short ones ++ local args = utils.split("-b value -cd") ++ local t,s = app.parse_args(args, { b = true }, { "b", "c" }) ++ asserteq(t, nil) ++ asserteq(s, "unknown flag 'd'") ++ ++ ++ -- flag, in a chain of short ones, gets converted to alias ++ local args = utils.split("-dbc") ++ local t,s = app.parse_args(args, nil, { "d", full_name = "b", "c" }) ++ asserteq(t, { ++ full_name = true, -- specified as b in a chain of short ones ++ c = true, ++ d = true, ++ }) ++ asserteq(s, {}) ++ ++end +diff --git a/extra/penlight/tests/test-app/require_here-link-target.lua b/extra/penlight/tests/test-app/require_here-link-target.lua +new file mode 100644 +index 0000000..db0f1eb +--- /dev/null ++++ b/extra/penlight/tests/test-app/require_here-link-target.lua +@@ -0,0 +1,6 @@ ++-- This file is used as a symbolic link target in test-app.lua, to verify the ++-- behaviors of pl.app.require_here() ++ ++local p = package.path ++require("pl.app").require_here(nil, #arg > 0) ++print(package.path:sub(1, -#p-1)) +diff --git a/extra/penlight/tests/test-args.lua b/extra/penlight/tests/test-args.lua +deleted file mode 100644 +index 9d91874..0000000 +--- a/extra/penlight/tests/test-args.lua ++++ /dev/null +@@ -1,48 +0,0 @@ +--- testing app.parse_args +-local asserteq = require 'pl.test'.asserteq +-local app = require 'pl.app' +-local path = require 'pl.path' +-local parse_args = app.parse_args +- +--- shows the use of plain flags, long and short: +-local flags,args = parse_args({'-abc','--flag','-v','one'}) +- +-asserteq(flags,{a=true,b=true,c=true,flag=true,v=true}) +-asserteq(args,{'one'}) +- +--- flags may be given values if the value follows or is separated by equals +-flags,args = parse_args({'-n10','--out=20'}) +- +-asserteq(flags,{n='10',out='20'}) +-asserteq(args,{}) +- +--- a flag can be explicitly specified as taking a value: +-flags,args = parse_args({'-k','-b=23','-o','hello','--out'},{o=true}) +- +-asserteq(flags,{out=true,o="hello",k=true,b="23"}) +-asserteq(args,{}) +- +-local ok,err = parse_args({'-n'},{n=true}) +-asserteq(ok,nil) +-asserteq(err, "no value for 'n'") +- +-ok,err = parse_args({'-n','-n'},{n=true}) +-asserteq(ok,nil) +-asserteq(err, "no value for 'n'") +- +--- modify this script's module path so it looks in the 'lua' subdirectory +--- for its modules +-app.require_here 'lua' +- +-asserteq(require 'foo.args'.answer(),42) +-asserteq(require 'bar'.name(),'bar') +- +- +-asserteq( +- app.appfile 'config', +- path.expanduser('~/.test-args/config'):gsub('/',path.sep) +-) +- +- +- +- +diff --git a/extra/penlight/tests/test-array.lua b/extra/penlight/tests/test-array.lua +deleted file mode 100644 +index 9ed5bde..0000000 +--- a/extra/penlight/tests/test-array.lua ++++ /dev/null +@@ -1,77 +0,0 @@ +-local array = require 'pl.array2d' +-local asserteq = require('pl.test').asserteq +-local L = require 'pl.utils'. string_lambda +- +-local A = { +- {1,2,3,4}, +- {10,20,30,40}, +- {100,200,300,400}, +- {1000,2000,3000,4000}, +-} +- +-asserteq(array.column(A,2),{2,20,200,2000}) +-asserteq(array.reduce_rows('+',A),{10,100,1000,10000}) +-asserteq(array.reduce_cols('+',A),{1111,2222,3333,4444}) +- +---array.write(A) +- +-local dump = require 'pl.pretty'.dump +- +-asserteq(array.range(A,'A1:B1'),{1,2}) +- +-asserteq(array.range(A,'A1:B2'),{{1,2},{10,20}}) +- +-asserteq( +- array.product('..',{1,2,3},{'a','b','c'}), +- {{'1a','2a','3a'},{'1b','2b','3b'},{'1c','2c','3c'}} +-) +- +-asserteq( +- array.product('{}',{1,2},{'a','b','c'}), +- {{{1,'a'},{2,'a'}},{{1,'b'},{2,'b'}},{{1,'c'},{2,'c'}}} +-) +- +-asserteq( +- array.flatten {{1,2},{3,4},{5,6}}, +- {1,2,3,4,5,6} +-) +- +- +-A = {{1,2,3},{4,5,6}} +- +--- flatten in column order! +-asserteq( +- array.reshape(A,1,true), +- {{1,4,2,5,3,6}} +-) +- +--- regular row-order reshape +-asserteq( +- array.reshape(A,3), +- {{1,2},{3,4},{5,6}} +-) +- +-asserteq( +- array.new(3,3,0), +- {{0,0,0},{0,0,0},{0,0,0}} +-) +- +-asserteq( +- array.new(3,3,L'|i,j| i==j and 1 or 0'), +- {{1,0,0},{0,1,0},{0,0,1}} +-) +- +-asserteq( +- array.reduce2('+','*',{{1,10},{2,10},{3,10}}), +- 60 -- i.e. 1*10 + 2*10 + 3*10 +-) +- +-A = array.new(4,4,0) +-B = array.new(3,3,1) +-array.move(A,2,2,B) +-asserteq(A,{{0,0,0,0},{0,1,1,1},{0,1,1,1},{0,1,1,1}}) +- +- +- +- +- +diff --git a/extra/penlight/tests/test-class.lua b/extra/penlight/tests/test-class.lua +index 43dc4dc..8621005 100644 +--- a/extra/penlight/tests/test-class.lua ++++ b/extra/penlight/tests/test-class.lua +@@ -27,6 +27,10 @@ function B:foo () + self.eee = 1 + end + ++function B:foo2 () ++ self.g = 8 ++end ++ + asserteq(B(),{a=1,b=2}) + + -- can continue this chain +@@ -47,6 +51,77 @@ c = C() + c:foo() + + asserteq(c,{a=1,b=2,c=3,eee=1}) ++ ++-- test indirect inherit ++ ++D = class(C) ++ ++E = class(D) ++ ++function E:_init () ++ self:super() ++ self.e = 4 ++end ++ ++function E:foo () ++ -- recommended way to call inherited version of method... ++ self.eeee = 5 ++ C.foo(self) ++end ++ ++F = class(E) ++ ++function F:_init () ++ self:super() ++ self.f = 6 ++end ++ ++f = F() ++f:foo() ++f:foo2() -- Test : invocation inherits this function from all the way up in B ++ ++asserteq(f,{a=1,b=2,c=3,eee=1,e=4,eeee=5,f=6,g=8}) ++ ++-- Test that inappropriate calls to super() fail gracefully ++ ++G = class() -- Class with no init ++ ++H = class(G) -- Class with an init that wrongly calls super() ++ ++function H:_init() ++ self:super() -- Notice: G has no _init ++end ++ ++I = class(H) -- Inherits the init with a bad super ++J = class(I) -- Grandparent-inits the init with a bad super ++ ++K = class(J) -- Has an init, which calls the init with a bad super ++ ++function K:_init() ++ self:super() ++end ++ ++local function createG() ++ return G() ++end ++ ++local function createH() -- Wrapper function for pcall ++ return H() ++end ++ ++local function createJ() ++ return J() ++end ++ ++local function createK() ++ return K() ++end ++ ++assert(pcall(createG)) -- Should succeed ++assert(not pcall(createH)) -- These three should fail ++assert(not pcall(createJ)) ++assert(not pcall(createK)) ++ + --- class methods! + assert(c:is_a(C)) + assert(c:is_a(B)) +diff --git a/extra/penlight/tests/test-animal.lua b/extra/penlight/tests/test-class2.lua +similarity index 100% +rename from tests/test-animal.lua +rename to tests/test-class2.lua +diff --git a/extra/penlight/tests/test-klass.lua b/extra/penlight/tests/test-class3.lua +similarity index 100% +rename from tests/test-klass.lua +rename to tests/test-class3.lua +diff --git a/extra/penlight/tests/test-super.lua b/extra/penlight/tests/test-class4.lua +similarity index 100% +rename from tests/test-super.lua +rename to tests/test-class4.lua +diff --git a/extra/penlight/tests/test-compare-no-order.lua b/extra/penlight/tests/test-compare-no-order.lua +deleted file mode 100644 +index fc0e5b7..0000000 +--- a/extra/penlight/tests/test-compare-no-order.lua ++++ /dev/null +@@ -1,13 +0,0 @@ +--- test-compare-no-order.lua +- +-local T = require 'pl.tablex' +-local P = require 'pl.permute' +- +-local t = {10,20,5,5,10,'one',555} +- +-local permutations = P.table(t) +-print('permutations',#permutations) +-for _,p in ipairs(permutations) do +- if not T.compare_no_order(t,p) then return print 'different!' end +-end +-print 'DONE' +diff --git a/extra/penlight/tests/test-compat.lua b/extra/penlight/tests/test-compat.lua +index a52c6d4..c4a2724 100644 +--- a/extra/penlight/tests/test-compat.lua ++++ b/extra/penlight/tests/test-compat.lua +@@ -5,13 +5,25 @@ local compat = require "pl.compat" + local coroutine = require "coroutine" + + local code_generator = coroutine.wrap(function() +- local result = {"ret", "urn \"Hello World!\""} +- for _,v in ipairs(result) do +- coroutine.yield(v) +- end +- coroutine.yield(nil) ++ local result = {"ret", "urn \"Hello World!\""} ++ for _,v in ipairs(result) do ++ coroutine.yield(v) ++ end ++ coroutine.yield(nil) + end) + + local f, err = compat.load(code_generator) + asserteq(err, nil) + asserteq(f(), "Hello World!") ++ ++ ++-- package.searchpath ++if compat.lua51 and not compat.jit then ++ assert(package.searchpath("pl.compat", package.path):match("lua[/\\]pl[/\\]compat")) ++ ++ local path = "some/?/nice.path;another/?.path" ++ local ok, err = package.searchpath("my.file.name", path, ".", "/") ++ asserteq(err, "\tno file 'some/my/file/name/nice.path'\n\tno file 'another/my/file/name.path'") ++ local ok, err = package.searchpath("my/file/name", path, "/", ".") ++ asserteq(err, "\tno file 'some/my.file.name/nice.path'\n\tno file 'another/my.file.name.path'") ++end +\ No newline at end of file +diff --git a/extra/penlight/tests/test-config.lua b/extra/penlight/tests/test-config.lua +index 13fc1ac..a7da380 100644 +--- a/extra/penlight/tests/test-config.lua ++++ b/extra/penlight/tests/test-config.lua +@@ -195,7 +195,7 @@ MemFree: 220292 kB + } + ) + +--- altho this works, rather use pl.data.read for this kind of purpose. ++-- although this works, rather use pl.data.read for this kind of purpose. + testconfig ([[ + # this is just a set of comma-separated values + 1000,444,222 +diff --git a/extra/penlight/tests/test-job-query.lua b/extra/penlight/tests/test-data2.lua +similarity index 100% +rename from tests/test-job-query.lua +rename to tests/test-data2.lua +diff --git a/extra/penlight/tests/test-date.lua b/extra/penlight/tests/test-date.lua +index f8487bc..6a91a09 100644 +--- a/extra/penlight/tests/test-date.lua ++++ b/extra/penlight/tests/test-date.lua +@@ -1,44 +1,10 @@ + local test = require 'pl.test' +-local app = require 'pl.app' +-local utils = require 'pl.utils' + local asserteq, assertmatch = test.asserteq, test.assertmatch + local dump = require 'pl.pretty'.dump + local T = require 'pl.test'.tuple + + local Date = require 'pl.Date' + +---[[ +-d = Date() +-print(d) +-print(d:year()) +-d:day(20) +-print(d) +-d:add {day = 2} +-print(d:day()) +-d = Date() -- 'now' +-print(d:last_day():day()) +-print(d:month(7):last_day()) +---]] +- +-function check_df(fmt,str,no_check) +- local df = Date.Format(fmt) +- local d = df:parse(str) +- --print(str,d) +- if not no_check then +- asserteq(df:tostring(d),str) +- end +-end +- +-check_df('dd/mm/yy','02/04/10') +-check_df('mm/dd/yyyy','04/02/2010') +-check_df('yyyy-mm-dd','2011-02-20') +-check_df('yyyymmdd','20070320') +- +--- use single fields for 'slack' parsing +-check_df('m/d/yyyy','1/5/2001',true) +- +-check_df('HH:MM','23:10') +- + iso = Date.Format 'yyyy-mm-dd' -- ISO date + d = iso:parse '2010-04-10' + asserteq(T(d:day(),d:month(),d:year()),T(10,4,2010)) +@@ -70,7 +36,7 @@ end + -- specified as UTC plus/minus offset + + function parse_utc (s) +- local d = parse_date(s) ++ local d = parse_date(s) + return d:toUTC() + end + +@@ -119,10 +85,3 @@ asserteq(tostring(nxt - d), '1 month ') + --- Can explicitly get UTC date; these of course refer to same time + local now,utc = Date(), Date 'utc' + asserteq(tostring(now - utc),'zero') +- +-if app.platform() ~= 'Windows' then +- print(app.lua()) +- if not utils.execute ("TZ='Europe/London' "..app.lua().." tests/test-tzone.lua") then +- error "buggered!" +- end +-end +diff --git a/extra/penlight/tests/test-dir.lua b/extra/penlight/tests/test-dir.lua +index 6c208e8..b293abc 100644 +--- a/extra/penlight/tests/test-dir.lua ++++ b/extra/penlight/tests/test-dir.lua +@@ -4,6 +4,7 @@ local dir = require( "pl.dir" ) + local file = require( "pl.file" ) + local path = require( "pl.path" ) + local asserteq = require( "pl.test" ).asserteq ++local lfs = require("lfs") + + asserteq(dir.fnmatch("foobar", "foo*bar"), true) + asserteq(dir.fnmatch("afoobar", "foo*bar"), false) +@@ -20,11 +21,11 @@ asserteq(filtered, {"foobar", "foonbar"}) + + local normpath = path.normpath + +-local doc_files = dir.getfiles(normpath "doc/", "*.ld") +-asserteq(doc_files, {normpath "doc/config.ld"}) ++local doc_files = dir.getfiles(normpath "docs/", "*.css") ++asserteq(doc_files, {normpath "docs/ldoc_fixed.css"}) + +-local all_doc_files = dir.getallfiles(normpath "doc/", "*.ld") +-asserteq(all_doc_files, {normpath "doc/config.ld"}) ++local all_doc_files = dir.getallfiles(normpath "docs/", "*.css") ++asserteq(all_doc_files, {normpath "docs/ldoc_fixed.css"}) + + local test_samples = dir.getallfiles(normpath "tests/lua") + table.sort(test_samples) +@@ -39,10 +40,10 @@ asserteq(test_samples, { + -- Test move files ----------------------------------------- + + -- Create a dummy file +-local fileName = path.tmpname() ++local fileName = path.tmpname() .. "Xx" + file.write( fileName, string.rep( "poot ", 1000 ) ) + +-local newFileName = path.tmpname() ++local newFileName = path.tmpname() .. "Xx" + local err, msg = dir.movefile( fileName, newFileName ) + + -- Make sure the move is successful +@@ -54,6 +55,18 @@ asserteq( path.exists( fileName ), false ) + -- Check to make sure the new file is there + asserteq( path.exists( newFileName ) , newFileName ) + ++-- Test existence again, but explicitly check for correct casing ++local files = dir.getfiles(path.dirname(newFileName)) ++local found = false ++for i, filename in ipairs(files) do ++ if filename == newFileName then ++ found = true ++ break ++ end ++end ++assert(found, "file was not found in directory, check casing: " .. newFileName) ++ ++ + -- Try to move the original file again (which should fail) + local newFileName2 = path.tmpname() + local err, msg = dir.movefile( fileName, newFileName2 ) +@@ -69,7 +82,7 @@ file.delete( newFileName ) + local fileName = path.tmpname() + file.write( fileName, string.rep( "poot ", 1000 ) ) + +-local newFileName = path.tmpname() ++local newFileName = path.tmpname() .. "xX" + local err, msg = dir.copyfile( fileName, newFileName ) + + -- Make sure the move is successful +@@ -78,6 +91,59 @@ assert( err, msg ) + -- Check to make sure the new file is there + asserteq( path.exists( newFileName ) , newFileName ) + ++-- Test existence again, but explicitly check for correct casing ++local files = dir.getfiles(path.dirname(newFileName)) ++local found = false ++for i, filename in ipairs(files) do ++ if filename == newFileName then ++ found = true ++ break ++ end ++end ++assert(found, "file was not found in directory, check casing: " .. newFileName) ++ ++ ++-- Try to move a non-existant file (which should fail) ++local fileName2 = 'blub' ++local newFileName2 = 'snortsh' ++local err, msg = dir.copyfile( fileName2, newFileName2 ) ++asserteq( err, false ) ++ ++-- Clean up the files ++file.delete( fileName ) ++file.delete( newFileName ) ++ ++ ++ ++-- Test make directory ----------------------------------------- ++ ++-- Create a dummy file ++local dirName = path.tmpname() .. "xX" ++local fullPath = dirName .. "/and/one/more" ++if path.is_windows then ++ fullPath = fullPath:gsub("/", "\\") ++end ++local err, msg = dir.makepath(fullPath) ++ ++-- Make sure the move is successful ++assert( err, msg ) ++ ++-- Check to make sure the new file is there ++assert(path.isdir(dirName)) ++assert(path.isdir(fullPath)) ++ ++-- Test existence again, but explicitly check for correct casing ++local files = dir.getdirectories(path.dirname(path.tmpname())) ++local found = false ++for i, filename in ipairs(files) do ++ if filename == dirName then ++ found = true ++ break ++ end ++end ++assert(found, "dir was not found in directory, check casing: " .. newFileName) ++ ++ + -- Try to move a non-existant file (which should fail) + local fileName2 = 'blub' + local newFileName2 = 'snortsh' +@@ -89,6 +155,47 @@ file.delete( fileName ) + file.delete( newFileName ) + + ++ ++ ++-- Test rmtree ----------------------------------------- ++do ++ local dirName = path.tmpname() ++ os.remove(dirName) ++ assert(dir.makepath(dirName)) ++ assert(file.write(path.normpath(dirName .. "/file_base.txt"), "hello world")) ++ assert(dir.makepath(path.normpath(dirName .. "/sub1"))) ++ assert(file.write(path.normpath(dirName .. "/sub1/file_sub1.txt"), "hello world")) ++ assert(dir.makepath(path.normpath(dirName .. "/sub2"))) ++ assert(file.write(path.normpath(dirName .. "/sub2/file_sub2.txt"), "hello world")) ++ ++ ++ local linkTarget = path.tmpname() ++ os.remove(linkTarget) ++ assert(dir.makepath(linkTarget)) ++ local linkFile = path.normpath(linkTarget .. "/file.txt") ++ assert(file.write(linkFile, "hello world")) ++ ++ local linkSource = path.normpath(dirName .. "/link1") ++ assert(lfs.link(linkTarget, linkSource, true)) ++ ++ -- test: rmtree will not follow symlinks ++ local ok, err = dir.rmtree(linkSource) ++ asserteq(ok, false) ++ asserteq(err, "will not follow symlink") ++ ++ -- test: rmtree removes a tree without following symlinks in that tree ++ local ok, err = dir.rmtree(dirName) ++ asserteq(err, nil) ++ asserteq(ok, true) ++ ++ asserteq(path.exists(dirName), false) -- tree is gone, including symlink ++ assert(path.exists(linkFile), "expected linked-to file to still exist") -- symlink target file is still there ++ ++ -- cleanup ++ assert(dir.rmtree(linkTarget)) ++end ++ ++ + -- have NO idea why forcing the return code is necessary here (Windows 7 64-bit) + os.exit(0) + +diff --git a/extra/penlight/tests/test-executeex.lua b/extra/penlight/tests/test-executeex.lua +deleted file mode 100644 +index fcf9783..0000000 +--- a/extra/penlight/tests/test-executeex.lua ++++ /dev/null +@@ -1,31 +0,0 @@ +-local path = require 'pl.path' +-local utils = require 'pl.utils' +-local asserteq = require 'pl.test'.asserteq +- +-local echo_lineending = "\n" +-if path.is_windows then +- echo_lineending = " \n" +-end +- +-local function test_executeex(cmd, expected_successful, expected_retcode, expected_stdout, expected_stderr) +- local successful, retcode, stdout, stderr = utils.executeex(cmd) +- asserteq(successful, expected_successful) +- asserteq(retcode, expected_retcode) +- asserteq(stdout, expected_stdout) +- asserteq(stderr, expected_stderr) +-end +- +--- Check the return codes +-test_executeex("exit", true, 0, "", "") +-test_executeex("exit 0", true, 0, "", "") +-test_executeex("exit 1", false, 1, "", "") +-test_executeex("exit 13", false, 13, "", "") +-test_executeex("exit 255", false, 255, "", "") +-test_executeex("exit 256", true, 0, "", "") +-test_executeex("exit 257", false, 1, "", "") +-test_executeex("exit 3809", false, 225, "", "") +- +--- Check output strings +-test_executeex("echo stdout", true, 0, "stdout" .. echo_lineending, "") +-test_executeex("(echo stderr 1>&2)", true, 0, "", "stderr" .. echo_lineending) +-test_executeex("(echo stdout && (echo stderr 1>&2))", true, 0, "stdout" .. echo_lineending, "stderr" .. echo_lineending) +diff --git a/extra/penlight/tests/test-func.lua b/extra/penlight/tests/test-func.lua +index c8606d0..8c3f171 100644 +--- a/extra/penlight/tests/test-func.lua ++++ b/extra/penlight/tests/test-func.lua +@@ -11,19 +11,19 @@ function pprint (t) + end + + function test (e) +- local v = {} +- print('test',collect_values(e,v)) +- if #v > 0 then pprint(v) end +- local rep = repr(e) ++ local v = {} ++ print('test',collect_values(e,v)) ++ if #v > 0 then pprint(v) end ++ local rep = repr(e) + print(rep) + end + + function teste (e,rs,ve) +- local v = {} +- collect_values(e,v) +- if #v > 0 then asserteq(v,ve,nil,2) end +- local rep = repr(e) +- asserteq(rep,rs) ++ local v = {} ++ collect_values(e,v) ++ if #v > 0 then asserteq(v,ve,nil,1) end ++ local rep = repr(e) ++ asserteq(rep,rs, nil, 1) + end + + teste(_1+_2('hello'),'_1 + _2(_C1)',{"hello"}) +@@ -37,6 +37,28 @@ asserteq(instantiate(Or(Not(_1),_2))(true,true),true) + teste(_1() + _2() + _3(),'_1() + _2() + _3()',30) + asserteq(I(_1+_2)(10,20),30) + ++teste(_1() - -_2() % _3(), '_1() - - _2() % _3()') ++teste((_1() - -_2()) % _3(), '(_1() - - _2()) % _3()') ++ ++teste(_1() - _2() + _3(), '_1() - _2() + _3()') ++teste(_1() - (_2() + _3()), '_1() - (_2() + _3())') ++teste((_1() - _2()) + _3(), '_1() - _2() + _3()') ++ ++teste(_1() .. _2() .. _3(), '_1() .. _2() .. _3()') ++teste(_1() .. (_2() .. _3()), '_1() .. _2() .. _3()') ++teste((_1() .. _2()) .. _3(), '(_1() .. _2()) .. _3()') ++ ++teste(_1() ^ _2() ^ _3(), '_1() ^ _2() ^ _3()') ++teste(_1() ^ (_2() ^ _3()), '_1() ^ _2() ^ _3()') ++teste((_1() ^ _2()) ^ _3(), '(_1() ^ _2()) ^ _3()') ++ ++teste(-_1() * _2(), '- _1() * _2()') ++teste(-(_1() * _2()), '- (_1() * _2())') ++teste((-_1()) * _2(), '- _1() * _2()') ++teste(-_1() ^ _2(), '- _1() ^ _2()') ++teste(-(_1() ^ _2()), '- _1() ^ _2()') ++teste((-_1()) ^ _2(), '(- _1()) ^ _2()') ++ + asserteq(instantiate(_1+_2)(10,20),30) + + ls = List {1,2,3,4} +@@ -58,7 +80,7 @@ asserteq (map(_1:sub(1,2),{'one','four'}),{'on','fo'}) + --~ -- or you can do this using List:map + asserteq( List({'one','four'}):map(_1:sub(1,2)), List{'on','fo'}) + +---~ -- note that Len can't be represented generally by #, since this can only be overriden by userdata ++--~ -- note that Len can't be represented generally by #, since this can only be overridden by userdata + asserteq( map(Len(_1),{'one','four'}), {3,4} ) + + --~ -- simularly, 'and' and 'or' are not really operators in Lua, so we need a function notation for them +diff --git a/extra/penlight/tests/test-lapp.lua b/extra/penlight/tests/test-lapp.lua +index 6c0c7f9..40ef7ce 100644 +--- a/extra/penlight/tests/test-lapp.lua ++++ b/extra/penlight/tests/test-lapp.lua +@@ -47,16 +47,16 @@ Various flags and option types + + check(simple, + {'-o','in'}, +- {quiet=false,p=false,o='in',input=''}) ++ {quiet=false,p=false,o='in',input='', input_name="stdin"}) + + ---- value of flag may be separated by '=' or ':' + check(simple, + {'-o=in'}, +- {quiet=false,p=false,o='in',input=''}) ++ {quiet=false,p=false,o='in',input='', input_name="stdin"}) + + check(simple, + {'-o:in'}, +- {quiet=false,p=false,o='in',input=''}) ++ {quiet=false,p=false,o='in',input='', input_name="stdin"}) + + -- Check lapp.callback. + local calls = {} +@@ -118,6 +118,15 @@ check_error(extended,{'-n','x'},"unable to convert to number: x") + + check_error(extended,{'-n','12'},"n out of range") + ++local with_advanced_enum = [[ ++ -s (test1|test2()|%a) ++ -c (1-2|2-3|cool[]) ++]] ++ ++check(with_advanced_enum,{"-s", "test2()", "-c", "1-2"},{s='test2()',c='1-2'}) ++check(with_advanced_enum,{"-s", "test2()", "-c", "2-3"},{s='test2()',c='2-3'}) ++check(with_advanced_enum,{"-s", "%a", "-c", "2-3"},{s='%a',c='2-3'}) ++ + local with_dashes = [[ + --first-dash dash + --second-dash dash also +@@ -147,6 +156,23 @@ check (false_flag,{'-g','-f'},{f=false,g=true}) + check (false_flag,{'-g','--'},{f=true,g=true}) + check (false_flag,{'-g','--','-a','frodo'},{f=true,g=true; '-a','frodo'}) + ++ ++ ++local default_file_flag = [[ ++ -f (file-out default stdout) ++]] ++check (default_file_flag,{},{f="", f_name = "stdout"}) ++ ++ ++ ++local numbered_pos_args = [[ ++ (string) ++ (string) ++ <3arg3> (string) ++]] ++check (numbered_pos_args,{"1", "2", "3"},{arg1="1", arg2 = "2", _arg3 = "3"}) ++ ++ + local addtype = [[ + -l (intlist) List of items + ]] +diff --git a/extra/penlight/tests/test-lexer.lua b/extra/penlight/tests/test-lexer.lua +index 20a86a1..1c09b85 100644 +--- a/extra/penlight/tests/test-lexer.lua ++++ b/extra/penlight/tests/test-lexer.lua +@@ -137,3 +137,10 @@ asserteq(lexer.lineno(iter), 3) + iter() + iter() + asserteq(lexer.lineno(iter), 3) ++ ++do -- numbers without leading zero; ".123" ++ local s = 'hello = +.234' ++ test_scan(s, {space=true}, {number=true}, { ++ {'iden', 'hello'}, {'=', '='}, {'number', .234} ++ }) ++end +diff --git a/extra/penlight/tests/test-pylib.lua b/extra/penlight/tests/test-list2.lua +similarity index 66% +rename from tests/test-pylib.lua +rename to tests/test-list2.lua +index 0d7bb19..174b1c7 100644 +--- a/extra/penlight/tests/test-pylib.lua ++++ b/extra/penlight/tests/test-list2.lua +@@ -1,11 +1,7 @@ +--- test-pylib.lua + local List = require 'pl.List' +-local text = require 'pl.text' +-local Template = text.Template + local asserteq = require 'pl.test' . asserteq + +-l = List{10,20,30,40,50} +-s = List{1,2,3,4,5} ++local s = List{1,2,3,4,5} + + -- test using: lua pylist.lua + local lst = List() +@@ -19,7 +15,7 @@ lst:remove_value(40) + asserteq (lst,List{10,20,11,30,50}) + asserteq (lst:contains(11),true) + asserteq (lst:contains(40),false) +-local q=lst:pop() ++local _ = lst:pop() + asserteq( lst:index(30),4 ) + asserteq( lst:count(10),1 ) + lst:sort() +@@ -37,7 +33,7 @@ asserteq (lst:slice(2,4),{20,30,40}) + asserteq (lst:slice(-4,-2),{20,30,40}) + + lst = List.range(0,9) +-seq = List{0,1,2,3,4,5,6,7,8,9} ++local seq = List{0,1,2,3,4,5,6,7,8,9} + asserteq(List.range(4),{1,2,3,4}) + asserteq(List.range(0,8,2),{0,2,4,6,8}) + asserteq(List.range(0,1,0.2),{0,0.2,0.4,0.6,0.8,1},1e-9) +@@ -52,32 +48,13 @@ asserteq (List('abcd'),List{'a','b','c','d'}) + local caps = List() + List('abcd'):foreach(function(v) caps:append(v:upper()) end) + asserteq (caps,List{'A','B','C','D'}) +-ls = List{10,20,30,40} ++local ls = List{10,20,30,40} + ls:slice_assign(2,3,{21,31}) + asserteq (ls , List{10,21,31,40}) + asserteq (ls:remove(2), List{10,31,40}) + asserteq (ls:clear(), List{}) + asserteq (ls:len(), 0) + +--- strings --- +-require 'pl.stringx'.import() ---> convenient! +-s = '123' +-assert (s:isdigit()) +-assert (not s:isspace()) + s = 'here the dog is just a dog' +-assert (s:startswith('here')) +-assert (s:endswith('dog')) +-assert (s:count('dog') == 2) + assert (List.split(s) == List{'here', 'the', 'dog', 'is', 'just', 'a', 'dog'}) + assert (List.split('foo;bar;baz', ';') == List{'foo', 'bar', 'baz'}) +-s = ' here we go ' +-asserteq (s:lstrip() , 'here we go ') +-asserteq (s:rstrip() , ' here we go') +-asserteq (s:strip() , 'here we go') +-asserteq (('hello'):center(20,'+') , '+++++++hello++++++++') +- +-t = Template('${here} is the $answer') +-asserteq(t:substitute {here = 'one', answer = 'two'} , 'one is the two') +- +-asserteq (('hello dolly'):title() , 'Hello Dolly') +-asserteq (('h bk bonzo TOK fred m'):title() , 'H Bk Bonzo Tok Fred M') +diff --git a/extra/penlight/tests/test-map.lua b/extra/penlight/tests/test-map.lua +index 793c9c1..1c0bee6 100644 +--- a/extra/penlight/tests/test-map.lua ++++ b/extra/penlight/tests/test-map.lua +@@ -3,17 +3,132 @@ + local test = require 'pl.test' + local Map = require 'pl.Map' + local tablex = require 'pl.tablex' ++local Set = require 'pl.Set' ++local utils = require 'pl.utils' + + local asserteq = test.asserteq +- + local cmp = tablex.compare_no_order + ++ ++ ++-- construction, plain + local m = Map{alpha=1,beta=2,gamma=3} + +-assert (cmp(m:values(),{1,2,3})) ++assert(cmp( ++ m:values(), ++ {1, 2, 3} ++)) + +-assert (cmp(m:keys(),{'alpha','beta','gamma'})) ++assert(cmp( ++ m:keys(), ++ {'alpha', 'beta', 'gamma'} ++)) + +-asserteq (m:items(),{{'alpha',1},{'beta',2},{'gamma',3}}) ++asserteq( ++ m:items(), ++ { ++ {'alpha', 1}, ++ {'beta', 2}, ++ {'gamma', 3}, ++ } ++) + + asserteq (m:getvalues {'alpha','gamma'}, {1,3}) ++ ++ ++ ++-- construction, from a set ++local s = Set{'red','orange','green','blue'} ++m = Map(s) ++ ++asserteq( ++ m:items(), ++ { ++ {'blue', true}, ++ {'green', true}, ++ {'orange', true}, ++ {'red', true}, ++ } ++) ++ ++ ++-- iter() ++m = Map{alpha=1,beta=2,gamma=3} ++local t = {alpha=1,beta=2,gamma=3} ++for k,v in m:iter() do ++ asserteq(v, t[k]) ++ t[k] = nil ++end ++assert(next(t) == nil, "expected the table to be empty by now") ++ ++ ++ ++-- setdefault() ++m = Map{alpha=1,beta=2,gamma=3} ++local v = m:setdefault("charlie", 4) ++asserteq(v, 4) ++v = m:setdefault("alpha", 10) ++asserteq(v, 1) ++asserteq( ++ m:items(), ++ { ++ {'alpha', 1}, ++ {'beta', 2}, ++ {'charlie', 4}, ++ {'gamma', 3}, ++ } ++) ++v = m:set("alpha", false) ++v = m:setdefault("alpha", true) -- falsy value should not be altered ++asserteq(false, m:get("alpha")) ++ ++ ++ ++-- len() ++m = Map{alpha=1,beta=2,gamma=3} ++asserteq(3, m:len()) ++m = Map{} ++asserteq(0, m:len()) ++m:set("charlie", 4) ++asserteq(1, m:len()) ++ ++ ++ ++-- set() & get() ++m = Map{} ++m:set("charlie", 4) ++asserteq(4, m:get("charlie")) ++m:set("charlie", 5) ++asserteq(5, m:get("charlie")) ++m:set("charlie", nil) ++asserteq(nil, m:get("charlie")) ++ ++ ++ ++-- getvalues() ++m = Map{alpha=1,beta=2,gamma=3} ++local x = m:getvalues{"gamma", "beta"} ++asserteq({3, 2}, x) ++ ++ ++ ++-- __eq() -- equality ++local m1 = Map{alpha=1,beta=2,gamma=3} ++local m2 = Map{alpha=1,beta=2,gamma=3} ++assert(m1 == m2) ++m1 = Map() ++m2 = Map() ++assert(m1 == m2) ++ ++ ++ ++-- __tostring() ++m = Map() ++asserteq("{}", tostring(m)) ++m = Map{alpha=1} ++asserteq("{alpha=1}", tostring(m)) ++m = Map{alpha=1,beta=2} ++assert(({ -- test 2 versions, since we cannot rely on order ++ ["{alpha=1,beta=2}"] = true, ++ ["{beta=2,alpha=1}"] = true, ++ })[tostring(m)]) +diff --git a/extra/penlight/tests/test-orderedmap.lua b/extra/penlight/tests/test-orderedmap.lua +new file mode 100644 +index 0000000..1d0fc67 +--- /dev/null ++++ b/extra/penlight/tests/test-orderedmap.lua +@@ -0,0 +1,98 @@ ++local List = require 'pl.List' ++ ++local asserteq = require 'pl.test' . asserteq ++local asserteq2 = require 'pl.test' . asserteq2 ++local OrderedMap = require 'pl.OrderedMap' ++ ++ ++m = OrderedMap() ++m:set('one',1) ++m:set('two',2) ++m:set('three',3) ++ ++asserteq(m:values(),List{1,2,3}) ++ ++-- usually exercized like this: ++--for k,v in m:iter() do print(k,v) end ++ ++local fn = m:iter() ++asserteq2 ('one',1,fn()) ++asserteq2 ('two',2,fn()) ++asserteq2 ('three',3,fn()) ++ ++-- Keys overriding methods can be used. ++m:set('set', 4) ++asserteq(m:values(),List{1,2,3,4}) ++ ++local o1 = OrderedMap {{z=2},{beta=1},{name='fred'}} ++asserteq(tostring(o1),'{z=2,beta=1,name="fred"}') ++ ++-- order of keys is not preserved here! ++local o2 = OrderedMap {z=4,beta=1.1,name='alice',extra='dolly'} ++ ++o1:update(o2) ++asserteq(tostring(o1),'{z=4,beta=1.1,name="alice",extra="dolly"}') ++ ++o1:set('beta',nil) ++asserteq(o1,OrderedMap{{z=4},{name='alice'},{extra='dolly'}}) ++ ++local o3 = OrderedMap() ++o3:set('dog',10) ++o3:set('cat',20) ++o3:set('mouse',30) ++ ++asserteq(o3:keys(),{'dog','cat','mouse'}) ++ ++o3:set('dog',nil) ++ ++asserteq(o3:keys(),{'cat','mouse'}) ++ ++-- Vadim found a problem when clearing a key which did not exist already. ++-- The keys list would then contain the key, although the map would not ++o3:set('lizard',nil) ++ ++asserteq(o3:keys(),{'cat','mouse'}) ++asserteq(o3:values(), {20,30}) ++asserteq(tostring(o3),'{cat=20,mouse=30}') ++ ++-- copy constructor ++local o4 = OrderedMap(o3) ++ ++asserteq(o4,o3) ++ ++-- constructor throws an error if the argument is bad ++-- (errors same as OrderedMap:update) ++asserteq(false,pcall(function() ++ m = OrderedMap('string') ++end)) ++ ++---- changing order of key/value pairs ---- ++ ++o3 = OrderedMap{{cat=20},{mouse=30}} ++ ++o3:insert(1,'bird',5) -- adds key/value before specified position ++o3:insert(1,'mouse') -- moves key keeping old value ++asserteq(o3:keys(),{'mouse','bird','cat'}) ++asserteq(tostring(o3),'{mouse=30,bird=5,cat=20}') ++o3:insert(2,'cat',21) -- moves key and sets new value ++asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') ++-- if you don't specify a value for an unknown key, nothing happens to the map ++o3:insert(3,'alligator') ++asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') ++ ++---- short-cut notation ++ ++local o5 = OrderedMap() ++o5.alpha = 1 ++o5.beta = 2 ++o5.gamma = 3 ++ ++asserteq(o5,OrderedMap{{alpha=1},{beta=2},{gamma=3}}) ++ ++o5.alpha = 10 ++o5.beta = 20 ++o5.gamma = 30 ++o5.delta = 40 ++o5.checked = false ++ ++asserteq(o5,OrderedMap{{alpha=10},{beta=20},{gamma=30},{delta=40},{checked=false}}) +diff --git a/extra/penlight/tests/test-path.lua b/extra/penlight/tests/test-path.lua +index 43d9228..8023270 100644 +--- a/extra/penlight/tests/test-path.lua ++++ b/extra/penlight/tests/test-path.lua +@@ -2,83 +2,199 @@ local path = require 'pl.path' + asserteq = require 'pl.test'.asserteq + + function quote(s) +- return '"'..s..'"' ++ return '"'..s..'"' + end + + function print2(s1,s2) +- print(quote(s1),quote(s2)) ++ print(quote(s1),quote(s2)) + end + +-function testpath(pth,p1,p2,p3) +- local dir,rest = path.splitpath(pth) +- local name,ext = path.splitext(rest) +- asserteq(dir,p1) +- asserteq(name,p2) +- asserteq(ext,p3) ++function slash (p) ++ return (p:gsub('\\','/')) + end + +-testpath ([[/bonzo/dog_stuff/cat.txt]],[[/bonzo/dog_stuff]],'cat','.txt') +-testpath ([[/bonzo/dog/cat/fred.stuff]],'/bonzo/dog/cat','fred','.stuff') +-testpath ([[../../alice/jones]],'../../alice','jones','') +-testpath ([[alice]],'','alice','') +-testpath ([[/path-to/dog/]],[[/path-to/dog]],'','') ++-- path.currentdir ++do ++ local cp = path.currentdir() ++ path.chdir("docs") ++ asserteq(path.currentdir(), cp .. path.sep .. "docs") ++ path.chdir("..") ++ asserteq(path.currentdir(), cp) ++end ++ ++-- path.isdir ++asserteq( path.isdir( "docs" ), true ) ++asserteq( path.isdir( "docs/" ), true ) ++asserteq( path.isdir( "docs/index.html" ), false ) ++asserteq( path.isdir( path.currentdir() ), true) ++asserteq( path.isdir( "c:\\" ), path.is_windows ) ++asserteq( path.isdir( "c:/" ), path.is_windows ) ++ ++-- path.isfile ++asserteq( path.isfile( "docs" ), false ) ++asserteq( path.isfile( "docs/index.html" ), true ) ++ ++-- path.exists ++asserteq( path.exists( "docs"), "docs") ++asserteq( path.exists( "docs/index.html"), "docs/index.html") ++ ++ ++do -- path.splitpath & path.splitext ++ function testpath(pth,p1,p2,p3) ++ local dir,rest = path.splitpath(pth) ++ local name,ext = path.splitext(rest) ++ asserteq(dir,p1) ++ asserteq(name,p2) ++ asserteq(ext,p3) ++ end ++ ++ testpath ([[/bonzo/dog_stuff/cat.txt]],[[/bonzo/dog_stuff]],'cat','.txt') ++ testpath ([[/bonzo/dog/cat/fred.stuff]],'/bonzo/dog/cat','fred','.stuff') ++ testpath ([[../../alice/jones]],'../../alice','jones','') ++ testpath ([[alice]],'','alice','') ++ testpath ([[/path-to/dog/]],[[/path-to/dog]],'','') ++ ++ asserteq({path.splitpath("some/dir/myfile.txt")}, {"some/dir", "myfile.txt"}) ++ asserteq({path.splitpath("some/dir/")}, {"some/dir", ""}) ++ asserteq({path.splitpath("some_dir")}, {"", "some_dir"}) ++ ++ asserteq({path.splitext("/bonzo/dog_stuff/cat.txt")}, {"/bonzo/dog_stuff/cat", ".txt"}) ++ asserteq({path.splitext("cat.txt")}, {"cat", ".txt"}) ++ asserteq({path.splitext("cat")}, {"cat", ""}) ++ asserteq({path.splitext(".txt")}, {"", ".txt"}) ++ asserteq({path.splitext("")}, {"", ""}) ++end ++ ++ ++-- TODO: path.abspath ++ ++-- TODO: path.dirname ++ ++-- TODO: path.basename ++ ++-- TODO: path.extension ++ ++ ++do -- path.isabs ++ asserteq(path.isabs("/hello/path"), true) ++ asserteq(path.isabs("hello/path"), false) ++ asserteq(path.isabs("./hello/path"), false) ++ asserteq(path.isabs("../hello/path"), false) ++ if path.is_windows then ++ asserteq(path.isabs("c:/"), true) ++ asserteq(path.isabs("c:/hello/path"), true) ++ asserteq(path.isabs("c:"), false) ++ asserteq(path.isabs("c:hello/path"), false) ++ asserteq(path.isabs("c:./hello/path"), false) ++ asserteq(path.isabs("c:../hello/path"), false) ++ end ++end ++ ++ ++do -- path.join ++ assert(path.join("somepath",".") == "somepath"..path.sep..".") ++ assert(path.join(".","readme.txt") == "."..path.sep.."readme.txt") ++ assert(path.join("/a_dir", "abs_path/") == "/a_dir"..path.sep.."abs_path/") ++ assert(path.join("a_dir", "/abs_path/") == "/abs_path/") ++ assert(path.join("a_dir", "/abs_path/", "/abs_path2/") == "/abs_path2/") ++ assert(path.join("a_dir", "/abs_path/", "not_abs_path2/") == "/abs_path/not_abs_path2/") ++ assert(path.join("a_dir", "/abs_path/", "not_abs_path2/", "/abs_path3/", "not_abs_path4/") == "/abs_path3/not_abs_path4/") ++ assert(path.join("first","second","third") == "first"..path.sep.."second"..path.sep.."third") ++ assert(path.join("first","second","") == "first"..path.sep.."second"..path.sep) ++ assert(path.join("first","","third") == "first"..path.sep.."third") ++ assert(path.join("","second","third") == "second"..path.sep.."third") ++ assert(path.join("","") == "") ++end ++ ++ ++do -- path.normcase ++ if path.iswindows then ++ asserteq('c:\\hello\\world', 'c:\\hello\\world') ++ asserteq('C:\\Hello\\wORLD', 'c:\\hello\\world') ++ asserteq('c:/hello/world', 'c:\\hello\\world') ++ else ++ asserteq('/Hello/wORLD', '/Hello/wORLD') ++ end ++end ++ ++ ++do -- path.normpath ++ local norm = path.normpath ++ local p = norm '/a/b' + +-asserteq( path.isdir( "doc" ), true ) +-asserteq( path.isdir( "doc/config.ld" ), false ) ++ asserteq(norm '/a/fred/../b',p) ++ asserteq(norm '/a//b',p) + +-asserteq( path.isfile( "doc" ), false ) +-asserteq( path.isfile( "doc/config.ld" ), true ) ++ function testnorm(p1,p2) ++ asserteq(norm(p1):gsub('\\','/'), p2) ++ end + +-local norm = path.normpath +-local p = norm '/a/b' ++ testnorm('a/b/..','a') ++ testnorm('a/b/../..','.') ++ testnorm('a/b/../c/../../d','d') ++ testnorm('a/.','a') ++ testnorm('a/./','a') ++ testnorm('a/b/.././..','.') ++ testnorm('../../a/b','../../a/b') ++ testnorm('../../a/b/../../','../..') ++ testnorm('../../a/b/../c','../../a/c') ++ testnorm('./../../a/b/../c','../../a/c') ++ testnorm('a/..b', 'a/..b') ++ testnorm('./a', 'a') ++ testnorm('a/.', 'a') ++ testnorm('a/', 'a') ++ testnorm('/a', '/a') ++ testnorm('', ".") + +-asserteq(norm '/a/fred/../b',p) +-asserteq(norm '/a//b',p) ++ if path.is_windows then ++ testnorm('C://a', 'C:/a') ++ testnorm('C:/../a', 'C:/../a') ++ asserteq(norm [[\a\.\b]], p) ++ -- UNC paths ++ asserteq(norm [[\\bonzo\..\dog]], [[\\dog]]) ++ asserteq(norm [[\\?\c:\bonzo\dog\.\]], [[\\?\c:\bonzo\dog]]) ++ else ++ testnorm('//a', '//a') ++ testnorm('///a', '/a') ++ end + +-function testnorm(p1,p2) +- asserteq(norm(p1):gsub('\\','/'), p2) ++ asserteq(norm '1/2/../3/4/../5',norm '1/3/5') ++ asserteq(norm '1/hello/../3/hello/../HELLO',norm '1/3/HELLO') + end + +-testnorm('a/b/..','a') +-testnorm('a/b/../..','.') +-testnorm('a/b/../c/../../d','d') +-testnorm('a/.','a') +-testnorm('a/./','a') +-testnorm('a/b/.././..','.') +-testnorm('../../a/b','../../a/b') +-testnorm('../../a/b/../../','../..') +-testnorm('../../a/b/../c','../../a/c') +-testnorm('./../../a/b/../c','../../a/c') +-testnorm('a/..b', 'a/..b') +-testnorm('./a', 'a') +-testnorm('a/.', 'a') +-testnorm('a/', 'a') +-testnorm('/a', '/a') +- +-if path.is_windows then +- testnorm('C://a', 'C:/a') +- testnorm('C:/../a', 'C:/../a') +- asserteq(norm [[\a\.\b]], p) +- -- UNC paths +- asserteq(norm [[\\bonzo\..\dog]], [[\\dog]]) +- asserteq(norm [[\\?\c:\bonzo\dog\.\]], [[\\?\c:\bonzo\dog]]) +-else +- testnorm('//a', '//a') +- testnorm('///a', '/a') ++ ++do -- path.relpath ++ local testpath = '/a/B/c' ++ ++ function try (p,r) ++ asserteq(slash(path.relpath(p,testpath)),r) ++ end ++ ++ try('/a/B/c/one.lua','one.lua') ++ try('/a/B/c/bonZO/two.lua','bonZO/two.lua') ++ try('/a/B/three.lua','../three.lua') ++ try('/a/four.lua','../../four.lua') ++ try('one.lua','one.lua') ++ try('../two.lua','../two.lua') ++end ++ ++ ++-- TODO: path.tmpname ++ ++ ++do -- path.common_prefix ++ asserteq(slash(path.common_prefix("../anything","../anything/goes")),"../anything") ++ asserteq(slash(path.common_prefix("../anything/goes","../anything")),"../anything") ++ asserteq(slash(path.common_prefix("../anything/goes","../anything/goes")),"../anything") ++ asserteq(slash(path.common_prefix("../anything/","../anything/")),"../anything") ++ asserteq(slash(path.common_prefix("../anything","../anything")),"..") ++ asserteq(slash(path.common_prefix("/hello/world","/hello/world/filename.doc")),"/hello/world") ++ asserteq(slash(path.common_prefix("/hello/filename.doc","/hello/filename.doc")),"/hello") ++ if path.is_windows then ++ asserteq(path.common_prefix("c:\\hey\\there","c:\\hey"),"c:\\hey") ++ asserteq(path.common_prefix("c:/HEy/there","c:/hEy"),"c:\\hEy") -- normalized separators, original casing ++ end + end + +-asserteq(norm '1/2/../3/4/../5',norm '1/3/5') +- +-assert(path.join("somepath",".") == "somepath"..path.sep..".") +-assert(path.join(".","readme.txt") == "."..path.sep.."readme.txt") +-assert(path.join("/a_dir", "abs_path/") == "/a_dir"..path.sep.."abs_path/") +-assert(path.join("a_dir", "/abs_path/") == "/abs_path/") +-assert(path.join("a_dir", "/abs_path/", "/abs_path2/") == "/abs_path2/") +-assert(path.join("a_dir", "/abs_path/", "not_abs_path2/") == "/abs_path/not_abs_path2/") +-assert(path.join("a_dir", "/abs_path/", "not_abs_path2/", "/abs_path3/", "not_abs_path4/") == "/abs_path3/not_abs_path4/") +-assert(path.join("first","second","third") == "first"..path.sep.."second"..path.sep.."third") +-assert(path.join("first","second","") == "first"..path.sep.."second"..path.sep) +-assert(path.join("first","","third") == "first"..path.sep.."third") +-assert(path.join("","second","third") == "second"..path.sep.."third") +-assert(path.join("","") == "") + ++-- TODO: path.package_path +diff --git a/extra/penlight/tests/test-pretty-number.lua b/extra/penlight/tests/test-pretty-number.lua +deleted file mode 100644 +index 347a5cf..0000000 +--- a/extra/penlight/tests/test-pretty-number.lua ++++ /dev/null +@@ -1,33 +0,0 @@ +-local test = require 'pl.test' +-local pretty = require 'pl.pretty' +- +-function testm(x,s) +- test.asserteq(pretty.number(x,'M'),s) +-end +- +-testm(123,'123B') +-testm(1234,'1.2KiB') +-testm(10*1024,'10.0KiB') +-testm(1024*1024,'1.0MiB') +- +-function testn(x,s) +- test.asserteq(pretty.number(x,'N',2),s) +-end +- +-testn(123,'123') +-testn(1234,'1.23K') +-testn(10*1024,'10.24K') +-testn(1024*1024,'1.05M') +-testn(1024*1024*1024,'1.07B') +- +-function testc(x,s) +- test.asserteq(pretty.number(x,'T'),s) +-end +- +-testc(123,'123') +-testc(1234,'1,234') +-testc(12345,'12,345') +-testc(123456,'123,456') +-testc(1234567,'1,234,567') +-testc(12345678,'12,345,678') +- +diff --git a/extra/penlight/tests/test-pretty.lua b/extra/penlight/tests/test-pretty.lua +index 9d9277d..9da2584 100644 +--- a/extra/penlight/tests/test-pretty.lua ++++ b/extra/penlight/tests/test-pretty.lua +@@ -84,11 +84,49 @@ t2 = {} + t1[1],t1[2] = t2,t2 + asserteq( pretty.write(t1,""), [[{{},{}}]] ) + ++-- Check that write correctly print table with non number or string as keys ++ ++t1 = { [true] = "boolean", [false] = "untrue", a = "a", b = "b", [1] = 1, [0] = 0 } ++asserteq( pretty.write(t1,""), [[{1,["false"]="untrue",["true"]="boolean",a="a",b="b",[0]=0}]] ) ++ ++ + -- Check number formatting + asserteq(pretty.write({1/0, -1/0, 0/0, 1, 1/2}, ""), "{Inf,-Inf,NaN,1,0.5}") + +-if _VERSION == "Lua 5.3" then ++if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then + asserteq(pretty.write({1.0}, ""), "{1.0}") + else + asserteq(pretty.write({1.0}, ""), "{1}") + end ++ ++do -- issue #203, item 3 ++ local t = {}; t[t] = 1 ++ pretty.write(t) -- should not crash ++end ++ ++ ++do ++ local float = 1e100 ++ local max_int = 9007199254740991 -- 1 << 53 - 1 ++ local min_int = -9007199254740991 ++ ++ asserteq(pretty.write(float), "1.0e+100") ++ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then ++ --There is no way to portably format with %d before 5.3 ++ asserteq(pretty.write(min_int - 3), "-9007199254740994") ++ asserteq(pretty.write(max_int + 3), "9007199254740994") ++ asserteq(pretty.write(min_int), "-9007199254740991") ++ asserteq(pretty.write(max_int), "9007199254740991") ++ end ++end ++ ++-- pretty.write fails if an __index metatable raises an error #257 ++-- only applies to 5.3+ where iterators respect metamethods ++do ++ local t = setmetatable({},{ ++ __index = function(self, key) ++ error("oops... couldn't find " .. tostring(key)) ++ end ++ }) ++ asserteq(pretty.write(t), "{\n}") ++end +diff --git a/extra/penlight/tests/test-relpath.lua b/extra/penlight/tests/test-relpath.lua +deleted file mode 100644 +index 78d1ad3..0000000 +--- a/extra/penlight/tests/test-relpath.lua ++++ /dev/null +@@ -1,23 +0,0 @@ +-local path = require 'pl.path' +-local test = require 'pl.test' +- +-relpath = path.relpath +- +-path = '/a/b/c' +- +-function slash (p) +- return (p:gsub('\\','/')) +-end +- +-function try (p,r) +- test.asserteq(slash(relpath(p,path)),r) +-end +- +-try('/a/b/c/one.lua','one.lua') +-try('/a/b/c/bonzo/two.lua','bonzo/two.lua') +-try('/a/b/three.lua','../three.lua') +-try('/a/four.lua','../../four.lua') +-try('one.lua','one.lua') +-try('../two.lua','../two.lua') +- +- +diff --git a/extra/penlight/tests/test-seq.lua b/extra/penlight/tests/test-seq.lua +index 190d829..e59a4be 100644 +--- a/extra/penlight/tests/test-seq.lua ++++ b/extra/penlight/tests/test-seq.lua +@@ -68,7 +68,11 @@ asserteq(C(seq.map(L'#_',{'one','tw'})),{3,2}) + + --for l1,l2 in seq.last{10,20,30} do print(l1,l2) end + +-asserteq(C2(seq.last{10,20,30}),{{20,10},{30,20}} ) ++asserteq( C2(seq.last{10,20,30}),{{20,10},{30,20}} ) ++ ++asserteq( C2(seq.last{40}),{} ) ++ ++asserteq( C2(seq.last{}),{} ) + + asserteq( + seq{10,20,30}:map(L'_+1'):copy(), +diff --git a/extra/penlight/tests/test-set.lua b/extra/penlight/tests/test-set.lua +deleted file mode 100644 +index 7402971..0000000 +--- a/extra/penlight/tests/test-set.lua ++++ /dev/null +@@ -1,201 +0,0 @@ +-class = require 'pl.class' +-M = require 'pl.Map' +-S = require 'pl.Set' +-List = require 'pl.List' +- +-asserteq = require 'pl.test' . asserteq +-asserteq2 = require 'pl.test' . asserteq2 +-MultiMap = require 'pl.MultiMap' +-OrderedMap = require 'pl.OrderedMap' +- +-s1 = S{1,2} +-s2 = S{1,2} +--- equality +-asserteq(s1,s2) +--- union +-asserteq(S{1,2} + S{2,3}, S{1,2,3}) +-asserteq(S{1,2} + 3, S{1,2,3}) +--- intersection +-asserteq(S{1,2} * S{2,3}, S{2}) +--- difference +-fruit = S{'apple','banana','orange','apricots'} +-tropical = S{'banana','orange'} +- +-asserteq(fruit - tropical, S{'apple','apricots'}) +-asserteq(tropical - 'orange', S{'banana'}) +- +--- symmetric_difference +-asserteq(S{1,2} ^ S{2,3}, S{1,3}) +--- tostring - illustrative, because these assertions may or may not work, +--- due to no ordering in set elements +---asserteq(tostring(S{1,2}),'[1,2]') +---asserteq(tostring(S{1,S{2,3}}),'[1,[2,3]]') +- +-s3 = S() +-asserteq(S.isempty(s3),true) +- +-s4 = S{1,2,3} +- +--- subsets/supersets +-asserteq(s4 > s1,true) +- +-S.set(s3,'one',true) +-s3.two = true +-asserteq(s3,S{'one','two'}) +- +-m = M{one=1,two=2} +-asserteq(m,M{one=1,two=2}) +-m:update {three=3,four=4} +-asserteq(m,M{one=1,two=2,three=3,four=4}) +- +-class.Animal() +- +-function Animal:_init(name) +- self.name = name +-end +- +-function Animal:__tostring() +- return self.name..': '..self:speak() +-end +- +-class.Dog(Animal) +- +-function Dog:speak() +- return 'bark' +-end +- +-class.Cat(Animal) +- +-function Cat:_init(name,breed) +- self:super(name) -- must init base! +- self.breed = breed +-end +- +-function Cat:speak() +- return 'meow' +-end +- +-Lion = class(Cat) +- +-function Lion:speak() +- return 'roar' +-end +- +-fido = Dog('Fido') +-felix = Cat('Felix','Tabby') +-leo = Lion('Leo','African') +- +-asserteq(tostring(fido),'Fido: bark') +-asserteq(tostring(felix),'Felix: meow') +-asserteq(tostring(leo),'Leo: roar') +- +-assert(Dog:class_of(fido)) +-assert(fido:is_a(Dog)) +- +-assert(leo:is_a(Animal)) +- +-m = MultiMap() +-m:set('john',1) +-m:set('jane',3) +-m:set('john',2) +- +-ms = MultiMap{john={1,2},jane={3}} +- +-asserteq(m,ms) +- +-m = OrderedMap() +-m:set('one',1) +-m:set('two',2) +-m:set('three',3) +- +-asserteq(m:values(),List{1,2,3}) +- +--- usually exercized like this: +---for k,v in m:iter() do print(k,v) end +- +-fn = m:iter() +-asserteq2 ('one',1,fn()) +-asserteq2 ('two',2,fn()) +-asserteq2 ('three',3,fn()) +- +--- Keys overriding methods can be used. +-m:set('set', 4) +-asserteq(m:values(),List{1,2,3,4}) +- +-o1 = OrderedMap {{z=2},{beta=1},{name='fred'}} +-asserteq(tostring(o1),'{z=2,beta=1,name="fred"}') +- +--- order of keys is not preserved here! +-o2 = OrderedMap {z=4,beta=1.1,name='alice',extra='dolly'} +- +-o1:update(o2) +-asserteq(tostring(o1),'{z=4,beta=1.1,name="alice",extra="dolly"}') +- +-o1:set('beta',nil) +-asserteq(o1,OrderedMap{{z=4},{name='alice'},{extra='dolly'}}) +- +-o3 = OrderedMap() +-o3:set('dog',10) +-o3:set('cat',20) +-o3:set('mouse',30) +- +-asserteq(o3:keys(),{'dog','cat','mouse'}) +- +-o3:set('dog',nil) +- +-asserteq(o3:keys(),{'cat','mouse'}) +- +--- Vadim found a problem when clearing a key which did not exist already. +--- The keys list would then contain the key, although the map would not +-o3:set('lizard',nil) +- +-asserteq(o3:keys(),{'cat','mouse'}) +-asserteq(o3:values(), {20,30}) +-asserteq(tostring(o3),'{cat=20,mouse=30}') +- +--- copy constructor +-o4 = OrderedMap(o3) +- +-asserteq(o4,o3) +- +--- constructor throws an error if the argument is bad +--- (errors same as OrderedMap:update) +-asserteq(false,pcall(function() +- m = OrderedMap('string') +-end)) +- +----- changing order of key/value pairs ---- +- +-o3 = OrderedMap{{cat=20},{mouse=30}} +- +-o3:insert(1,'bird',5) -- adds key/value before specified position +-o3:insert(1,'mouse') -- moves key keeping old value +-asserteq(o3:keys(),{'mouse','bird','cat'}) +-asserteq(tostring(o3),'{mouse=30,bird=5,cat=20}') +-o3:insert(2,'cat',21) -- moves key and sets new value +-asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') +--- if you don't specify a value for an unknown key, nothing happens to the map +-o3:insert(3,'alligator') +-asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') +- +----- short-cut notation +- +-o5 = OrderedMap() +-o5.alpha = 1 +-o5.beta = 2 +-o5.gamma = 3 +- +-asserteq(o5,OrderedMap{{alpha=1},{beta=2},{gamma=3}}) +- +-o5.alpha = 10 +-o5.beta = 20 +-o5.gamma = 30 +-o5.delta = 40 +-o5.checked = false +- +-asserteq(o5,OrderedMap{{alpha=10},{beta=20},{gamma=30},{delta=40},{checked=false}}) +- +- +- +- +- +diff --git a/extra/penlight/tests/test-sip.lua b/extra/penlight/tests/test-sip.lua +index ea4c19a..cf4b344 100644 +--- a/extra/penlight/tests/test-sip.lua ++++ b/extra/penlight/tests/test-sip.lua +@@ -52,13 +52,13 @@ check('just a string', 'not that string') + local months={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"} + + local function adjust_year(res) +- if res.year < 100 then +- if res.year < 70 then +- res.year = res.year + 2000 +- else +- res.year = res.year + 1900 +- end +- end ++ if res.year < 100 then ++ if res.year < 70 then ++ res.year = res.year + 2000 ++ else ++ res.year = res.year + 1900 ++ end ++ end + end + + local shortdate = sip.compile('$d{day}/$d{month}/$d{year}') +@@ -71,17 +71,17 @@ local function dcheck (d1,d2) + end + + local function dates(str,tbl) +- local res = {} +- if shortdate(str,res) then +- dcheck(res,tbl) ++ local res = {} ++ if shortdate(str,res) then ++ dcheck(res,tbl) + elseif isodate(str,res) then + dcheck(res,tbl) +- elseif longdate(str,res) then +- res.month = tablex.find(months,res.month) +- dcheck(res,tbl) +- else +- assert(tbl == nil) +- end ++ elseif longdate(str,res) then ++ res.month = tablex.find(months,res.month) ++ dcheck(res,tbl) ++ else ++ assert(tbl == nil) ++ end + end + + dates ('10/12/2007',{year=2007,month=12,day=10}) +diff --git a/extra/penlight/tests/test-strict.lua b/extra/penlight/tests/test-strict.lua +index bb29082..12b0fad 100644 +--- a/extra/penlight/tests/test-strict.lua ++++ b/extra/penlight/tests/test-strict.lua +@@ -1,3 +1,4 @@ ++require 'pl.compat' -- require this one before loading strict + local strict = require 'pl.strict' + local test = require 'pl.test' + local app = require 'pl.app' +@@ -42,9 +43,103 @@ end,"variable 'sine' is not declared in 'math'") + + + +- +- +- +- +- +- ++-- module ++do ++ local testmodule = { ++ hello = function() return "supremacy" end ++ } ++ -- make strict and allow extra field "world" ++ strict.module("my_test", testmodule, { world = true }) ++ ++ test.asserteq(testmodule.hello(), "supremacy") ++ test.assertraise(function() ++ print(testmodule.not_allowed_key) ++ end, "variable 'not_allowed_key' is not declared in 'my_test'") ++ ++ test.asserteq(testmodule.world, nil) ++ testmodule.world = "supremacy" ++ test.asserteq(testmodule.world, "supremacy") ++ ++ ++ -- table with a __newindex method ++ local mod1 = strict.module("mod1", setmetatable( ++ { ++ hello = "world", ++ }, { ++ __newindex = function(self, key, value) ++ if key == "Lua" then ++ rawset(self, key, value) ++ end ++ end, ++ } ++ )) ++ test.asserteq(mod1.hello, "world") ++ mod1.Lua = "hello world" ++ test.asserteq(mod1.Lua, "hello world") ++ test.assertraise(function() ++ print(mod1.not_allowed_key) ++ end, "variable 'not_allowed_key' is not declared in 'mod1'") ++ ++ ++ -- table with a __index method ++ local mod1 = strict.module("mod1", setmetatable( ++ { ++ hello = "world", ++ }, { ++ __index = function(self, key) ++ if key == "Lua" then ++ return "rocks" ++ end ++ end, ++ } ++ )) ++ test.asserteq(mod1.hello, "world") ++ test.asserteq(mod1.Lua, "rocks") ++ test.assertraise(function() ++ print(mod1.not_allowed_key) ++ end, "variable 'not_allowed_key' is not declared in 'mod1'") ++ ++ ++ -- table with a __index table ++ local mod1 = strict.module("mod1", setmetatable( ++ { ++ hello = "world", ++ }, { ++ __index = { ++ Lua = "rocks!" ++ } ++ } ++ )) ++ test.asserteq(mod1.hello, "world") ++ test.asserteq(mod1.Lua, "rocks!") ++ test.assertraise(function() ++ print(mod1.not_allowed_key) ++ end, "variable 'not_allowed_key' is not declared in 'mod1'") ++ ++end ++ ++ ++do ++ -- closed_module ++ -- what does this do? this does not seem a usefull function??? ++ ++ local testmodule = { ++ hello = function() return "supremacy" end ++ } ++ local M = strict.closed_module(testmodule, "my_test") ++ ++ -- read acces to original is granted, but not to the new one ++ test.asserteq(testmodule.hello(), "supremacy") ++ test.assertraise(function() ++ print(M.hello()) ++ end, "variable 'hello' is not declared in 'my_test'") ++ ++ -- write access to both is granted ++ testmodule.world = "domination" ++ M.world = "domination" ++ ++ -- read acces to set field in original is granted, but not set ++ test.asserteq(testmodule.world, nil) ++ test.asserteq(M.world, "domination") ++ ++end +diff --git a/extra/penlight/tests/test-stringx.lua b/extra/penlight/tests/test-stringx.lua +deleted file mode 100644 +index 7802eb4..0000000 +--- a/extra/penlight/tests/test-stringx.lua ++++ /dev/null +@@ -1,354 +0,0 @@ +-local stringx = require 'pl.stringx' +-local utils = require 'pl.utils' +-local asserteq = require 'pl.test' . asserteq +-local T = require 'pl.test'.tuple +- +-local function FIX(s) +- io.stderr:write('FIX:' .. s .. '\n') +-end +- +- +--- isalpha +-asserteq(T(stringx.isalpha''), T(false)) +-asserteq(T(stringx.isalpha' '), T(false)) +-asserteq(T(stringx.isalpha'0'), T(false)) +-asserteq(T(stringx.isalpha'\0'), T(false)) +-asserteq(T(stringx.isalpha'azAZ'), T(true)) +-asserteq(T(stringx.isalpha'az9AZ'), T(false)) +- +--- isdigit +-asserteq(T(stringx.isdigit''), T(false)) +-asserteq(T(stringx.isdigit' '), T(false)) +-asserteq(T(stringx.isdigit'a'), T(false)) +-asserteq(T(stringx.isdigit'0123456789'), T(true)) +- +--- isalnum +-asserteq(T(stringx.isalnum''), T(false)) +-asserteq(T(stringx.isalnum' '), T(false)) +-asserteq(T(stringx.isalnum('azAZ01234567890')), T(true)) +- +--- isspace +-asserteq(T(stringx.isspace''), T(false)) +-asserteq(T(stringx.isspace' '), T(true)) +-asserteq(T(stringx.isspace' \r\n\f\t'), T(true)) +-asserteq(T(stringx.isspace' \r\n-\f\t'), T(false)) +- +--- islower +-asserteq(T(stringx.islower''), T(false)) +-asserteq(T(stringx.islower'az'), T(true)) +-asserteq(T(stringx.islower'aMz'), T(false)) +-asserteq(T(stringx.islower'a z'), T(true)) +- +--- startswith +-local startswith = stringx.startswith +-asserteq(T(startswith('', '')), T(true)) +-asserteq(T(startswith('', 'a')), T(false)) +-asserteq(T(startswith('a', '')), T(true)) +-asserteq(T(startswith('a', 'a')), T(true)) +-asserteq(T(startswith('a', 'b')), T(false)) +-asserteq(T(startswith('a', 'ab')), T(false)) +-asserteq(T(startswith('abc', 'ab')), T(true)) +-asserteq(T(startswith('abc', 'bc')), T(false)) -- off by one +-asserteq(T(startswith('abc', '.')), T(false)) -- Lua pattern char +-asserteq(T(startswith('a\0bc', 'a\0b')), T(true)) -- '\0' +- +-asserteq(startswith('abcfoo',{'abc','def'}),true) +-asserteq(startswith('deffoo',{'abc','def'}),true) +-asserteq(startswith('cdefoo',{'abc','def'}),false) +- +- +--- endswith +--- http://snippets.luacode.org/sputnik.lua?p=snippets/Check_string_ends_with_other_string_74 +-local endswith = stringx.endswith +-asserteq(T(endswith("", "")), T(true)) +-asserteq(T(endswith("", "a")), T(false)) +-asserteq(T(endswith("a", "")), T(true)) +-asserteq(T(endswith("a", "a")), T(true)) +-asserteq(T(endswith("a", "A")), T(false)) -- case sensitive +-asserteq(T(endswith("a", "aa")), T(false)) +-asserteq(T(endswith("abc", "")), T(true)) +-asserteq(T(endswith("abc", "ab")), T(false)) -- off by one +-asserteq(T(endswith("abc", "c")), T(true)) +-asserteq(T(endswith("abc", "bc")), T(true)) +-asserteq(T(endswith("abc", "abc")), T(true)) +-asserteq(T(endswith("abc", " abc")), T(false)) +-asserteq(T(endswith("abc", "a")), T(false)) +-asserteq(T(endswith("abc", ".")), T(false)) -- Lua pattern char +-asserteq(T(endswith("ab\0c", "b\0c")), T(true)) -- \0 +-asserteq(T(endswith("ab\0c", "b\0d")), T(false)) -- \0 +- +-asserteq(endswith('dollar.dot',{'.dot','.txt'}),true) +-asserteq(endswith('dollar.txt',{'.dot','.txt'}),true) +-asserteq(endswith('dollar.rtxt',{'.dot','.txt'}),false) +- +--- splitlines +-asserteq(stringx.splitlines(''), {}) +-asserteq(stringx.splitlines('a'), {'a'}) +-asserteq(stringx.splitlines('\n'), {''}) +-asserteq(stringx.splitlines('\n\n'), {'', ''}) +-asserteq(stringx.splitlines('\r\r'), {'', ''}) +-asserteq(stringx.splitlines('\r\n'), {''}) +-asserteq(stringx.splitlines('ab\ncd\n'), {'ab', 'cd'}) +-asserteq(stringx.splitlines('ab\ncd\n', true), {'ab\n', 'cd\n'}) +-asserteq(stringx.splitlines('\nab\r\r\ncd\n', true), {'\n', 'ab\r', '\r\n', 'cd\n'}) +- +--- expandtabs +----FIX[[raises error +-asserteq(T(stringx.expandtabs('',0)), T('')) +-asserteq(T(stringx.expandtabs('',1)), T('')) +-asserteq(T(stringx.expandtabs(' ',1)), T(' ')) +--- expandtabs now works like Python's str.expandtabs (up to next tab stop) +-asserteq(T(stringx.expandtabs(' \t ')), T((' '):rep(1+8))) +-asserteq(T(stringx.expandtabs(' \t ',2)), T(' ')) +---]] +- +--- lfind +-asserteq(T(stringx.lfind('', '')), T(1)) +-asserteq(T(stringx.lfind('a', '')), T(1)) +-asserteq(T(stringx.lfind('ab', 'b')), T(2)) +-asserteq(T(stringx.lfind('abc', 'cd')), T(nil)) +-asserteq(T(stringx.lfind('abcbc', 'bc')), T(2)) +-asserteq(T(stringx.lfind('ab..cd', '.')), T(3)) -- pattern char +-asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3)), T(4)) +-asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 4)), T(nil)) +-asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 5)), T(4)) +-asserteq(T(stringx.lfind('abcbcbbc', 'bc', nil, 5)), T(2)) +- +--- rfind +-asserteq(T(stringx.rfind('', '')), T(1)) +-asserteq(T(stringx.rfind('ab', '')), T(3)) +-asserteq(T(stringx.rfind('abc', 'cd')), T(nil)) +-asserteq(T(stringx.rfind('abcbc', 'bc')), T(4)) +-asserteq(T(stringx.rfind('abcbcb', 'bc')), T(4)) +-asserteq(T(stringx.rfind('ab..cd', '.')), T(4)) -- pattern char +-asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3)), T(7)) +-asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 4)), T(nil)) +-asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 5)), T(4)) +-asserteq(T(stringx.rfind('abcbcbbc', 'bc', nil, 5)), T(4)) +- +--- replace +-asserteq(T(stringx.replace('', '', '')), T('')) +-asserteq(T(stringx.replace(' ', '', '')), T(' ')) +-asserteq(T(stringx.replace(' ', '', ' ')), T(' ')) +-asserteq(T(stringx.replace(' ', ' ', '')), T('')) +-asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC')), T('aBCaBCaBC')) +-asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 1)), T('aBCabcabc')) +-asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 0)), T('abcabcabc')) +-asserteq(T(stringx.replace('abc', 'd', 'e')), T('abc')) +-asserteq(T(stringx.replace('a.b', '.', '%d')), T('a%db')) +- +--- split +-local split = stringx.split +-asserteq(split('', ''), {''}) +-asserteq(split('', 'z'), {}) --FIX:intended and specified behavior? +-asserteq(split('a', ''), {'a'}) --FIX:intended and specified behavior? +-asserteq(split('a', 'a'), {''}) +--- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. +--- If you need to split on a pattern, use utils.split() +--- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars +--- note that leading space is ignored by the default +-asserteq(split(' 1 2 3 '),{'1','2','3'}) +-asserteq(split('a*bb*c*ddd','*'),{'a','bb','c','ddd'}) +-asserteq(split('dog:fred:bonzo:alice',':',3), {'dog','fred','bonzo:alice'}) +-asserteq(split('///','/'),{'','','',''}) +--- capitalize +-asserteq(T(stringx.capitalize('')), T('')) +-asserteq(T(stringx.capitalize('abC deF1')), T('Abc Def1')) -- Python behaviour +- +--- count +-asserteq(T(stringx.count('', '')), T(0)) --infinite loop]] +-asserteq(T(stringx.count(' ', '')), T(2)) --infinite loop]] +-asserteq(T(stringx.count('a..c', '.')), T(2)) -- pattern chars +-asserteq(T(stringx.count('a1c', '%d')), T(0)) -- pattern chars +- +--- ljust +-asserteq(T(stringx.ljust('', 0)), T('')) +-asserteq(T(stringx.ljust('', 2)), T(' ')) +-asserteq(T(stringx.ljust('ab', 3)), T('ab ')) +-asserteq(T(stringx.ljust('ab', 3, '%')), T('ab%')) +-asserteq(T(stringx.ljust('abcd', 3)), T('abcd')) -- agrees with Python +- +--- rjust +-asserteq(T(stringx.rjust('', 0)), T('')) +-asserteq(T(stringx.rjust('', 2)), T(' ')) +-asserteq(T(stringx.rjust('ab', 3)), T(' ab')) +-asserteq(T(stringx.rjust('ab', 3, '%')), T('%ab')) +-asserteq(T(stringx.rjust('abcd', 3)), T('abcd')) -- agrees with Python +- +--- center +-asserteq(T(stringx.center('', 0)), T('')) +-asserteq(T(stringx.center('', 1)), T(' ')) +-asserteq(T(stringx.center('', 2)), T(' ')) +-asserteq(T(stringx.center('a', 1)), T('a')) +-asserteq(T(stringx.center('a', 2)), T('a ')) +-asserteq(T(stringx.center('a', 3)), T(' a ')) +- +- +--- ltrim +--- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +-local trim = stringx.lstrip +-asserteq(T(trim''), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim'a'), T'a') +-asserteq(T(trim' a'), T'a') +-asserteq(T(trim'a '), T'a ') +-asserteq(T(trim' a '), T'a ') +-asserteq(T(trim' a '), T'a ') +-asserteq(T(trim' ab cd '), T'ab cd ') +-asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b \r\t\n\f\v') +--- more +- +- +--- rtrim +--- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +-local trim = stringx.rstrip +-asserteq(T(trim''), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim'a'), T'a') +-asserteq(T(trim' a'), T' a') +-asserteq(T(trim'a '), T'a') +-asserteq(T(trim' a '), T' a') +-asserteq(T(trim' a '), T' a') +-asserteq(T(trim' ab cd '), T' ab cd') +-asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T' \t\r\n\f\va\000b') +--- more +- +- +--- trim +--- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +-local trim = stringx.strip +-asserteq(T(trim''), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim' '), T'') +-asserteq(T(trim'a'), T'a') +-asserteq(T(trim' a'), T'a') +-asserteq(T(trim'a '), T'a') +-asserteq(T(trim' a '), T'a') +-asserteq(T(trim' a '), T'a') +-asserteq(T(trim' ab cd '), T'ab cd') +-asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b') +--- more +- +- +--- partition +--- as per str.partition in Python, delimiter must be non-empty; +--- interpreted as a plain string +---asserteq(T(stringx.partition('', '')), T('', '', '')) -- error]] +---asserteq(T(stringx.partition('a', '')), T('', '', 'a')) --error]] +-asserteq(T(stringx.partition('a', 'a')), T('', 'a', '')) +-asserteq(T(stringx.partition('abc', 'b')), T('a', 'b', 'c')) +-asserteq(T(stringx.partition('abc', '.+')), T('abc','','')) +-asserteq(T(stringx.partition('a,b,c', ',')), T('a',',','b,c')) +--- rpartition +-asserteq(T(stringx.rpartition('a/b/c', '/')), T('a/b', '/', 'c')) +-asserteq(T(stringx.rpartition('abc', 'b')), T('a', 'b', 'c')) +- +- +--- at (works like s:sub(idx,idx), so negative indices allowed +-asserteq(T(stringx.at('a', 1)), T('a')) +-asserteq(T(stringx.at('ab', 2)), T('b')) +-asserteq(T(stringx.at('abcd', -1)), T('d')) +- +--- lines +-local function merge(it, ...) +- assert(select('#', ...) == 0) +- local ts = {} +- for val in it do ts[#ts+1] = val end +- return ts +-end +-asserteq(merge(stringx.lines('')), {''}) +-asserteq(merge(stringx.lines('ab')), {'ab'}) +-asserteq(merge(stringx.lines('ab\ncd')), {'ab', 'cd'}) +- +--- shorten +--- The returned string is always equal or less to the given size. +-asserteq(T(stringx.shorten('', 0)), T'') +-asserteq(T(stringx.shorten('a', 1)), T'a') +-asserteq(T(stringx.shorten('ab', 1)), T'.') --FIX:ok? +-asserteq(T(stringx.shorten('abc', 3)), T'abc') +-asserteq(T(stringx.shorten('abcd', 3)), T'...') +-asserteq(T(stringx.shorten('abcde', 5)), T'abcde') +-asserteq(T(stringx.shorten('abcde', 4)), T'a...') +-asserteq(T(stringx.shorten('abcde', 3)), T'...') +-asserteq(T(stringx.shorten('abcde', 2)), T'..') +-asserteq(T(stringx.shorten('abcde', 0)), T'') +-asserteq(T(stringx.shorten('', 0, true)), T'') +-asserteq(T(stringx.shorten('a', 1, true)), T'a') +-asserteq(T(stringx.shorten('ab', 1, true)), T'.') +-asserteq(T(stringx.shorten('abcde', 5, true)), T'abcde') +-asserteq(T(stringx.shorten('abcde', 4, true)), T'...e') +-asserteq(T(stringx.shorten('abcde', 3, true)), T'...') +-asserteq(T(stringx.shorten('abcde', 2, true)), T'..') +-asserteq(T(stringx.shorten('abcde', 0, true)), T'') +- +--- strip +-asserteq(stringx.strip(' hello '),'hello') +-asserteq(stringx.strip('--[hello] -- - ','-[] '),'hello') +-asserteq(stringx.rstrip('--[hello] -- - ','-[] '),'--[hello') +- +--- +- +-local assert_str_round_trip = function(s) +- +- local qs = stringx.quote_string(s) +- local compiled, err = utils.load("return "..qs) +- +- if not compiled then +- print( +- ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): +- format(s, qs, err) +- ) +- error() +- else +- compiled = compiled() +- end +- +- if compiled ~= s then +- print("strinx.quote_string assert Failed: String compiled but did not round trip.") +- print("input string:\t\t",s, #s) +- print("compiled string:\t", compiled, #compiled) +- print("output string:\t\t",qs, #qs) +- error() +- else +- -- print("input string:\t\t",s) +- -- print("compiled string:\t", compiled) +- -- print("output string:\t\t",qs) +- end +-end +- +-assert_str_round_trip( "normal string with nothing weird.") +-assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") +- +-assert_str_round_trip( "Unescapped quote \" in the middle") +-assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") +-assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) +-assert_str_round_trip( "[[Completely normal\n long quote. ]]") +-assert_str_round_trip( "String with a newline\nending with a closing bracket]") +-assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") +-assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") +-assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') +-assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") +-assert_str_round_trip( "This\tincludes\ttabs.") +-assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") +-assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") +- +-assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") +-assert_str_round_trip('"A quoted string looks like what?"') +-assert_str_round_trip( "'I think that it should be quoted, anyway.'") +-assert_str_round_trip( "[[Even if they're long quoted.]]") +-assert_str_round_trip( "]=]==]") +- +-assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") +-assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") +-assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") +-assert_str_round_trip( "") +-assert_str_round_trip( " ") +-assert_str_round_trip( "\n") --tricky. +-assert_str_round_trip( "\r") +-assert_str_round_trip( "\r\n") +-assert_str_round_trip( "\r1\n") +-assert_str_round_trip( "[[") +-assert_str_round_trip( "''") +-assert_str_round_trip( '""') +diff --git a/extra/penlight/tests/test-tablex.lua b/extra/penlight/tests/test-tablex.lua +index 59213d5..c8bbf01 100644 +--- a/extra/penlight/tests/test-tablex.lua ++++ b/extra/penlight/tests/test-tablex.lua +@@ -16,49 +16,70 @@ end + + + asserteq( +- copy {10,20,30}, +- {10,20,30} ++ copy {10,20,30}, ++ {10,20,30} + ) + + asserteq( +- deepcopy {10,20,{30,40}}, +- {10,20,{30,40}} ++ deepcopy {10,20,{30,40}}, ++ {10,20,{30,40}} ++) ++ ++local t = { ++ a = "hello", ++ b = { ++ c = "world" ++ } ++} ++t.b.d = t.b ++ ++local tcopy = { ++ a = "hello", ++ b = { ++ c = "world" ++ } ++} ++tcopy.b.d = tcopy.b ++ ++asserteq( ++ deepcopy(t), ++ tcopy + ) + + asserteq( +- pairmap(function(i,v) return v end,{10,20,30}), +- {10,20,30} ++ pairmap(function(i,v) return v end,{10,20,30}), ++ {10,20,30} + ) + + asserteq_no_order( +- pairmap(L'_',{fred=10,bonzo=20}), +- {'fred','bonzo'} ++ pairmap(L'_',{fred=10,bonzo=20}), ++ {'fred','bonzo'} + ) + + asserteq_no_order( +- pairmap(function(k,v) return v end,{fred=10,bonzo=20}), +- {10,20} ++ pairmap(function(k,v) return v end,{fred=10,bonzo=20}), ++ {10,20} + ) + + asserteq_no_order( +- pairmap(function(i,v) return v,i end,{10,20,30}), +- {10,20,30} ++ pairmap(function(i,v) return v,i end,{10,20,30}), ++ {10,20,30} + ) + + asserteq( +- pairmap(function(k,v) return {k,v},k end,{one=1,two=2}), +- {one={'one',1},two={'two',2}} ++ pairmap(function(k,v) return {k,v},k end,{one=1,two=2}), ++ {one={'one',1},two={'two',2}} + ) + -- same as above, using string lambdas + asserteq( +- pairmap(L'|k,v|{k,v},k',{one=1,two=2}), +- {one={'one',1},two={'two',2}} ++ pairmap(L'|k,v|{k,v},k',{one=1,two=2}), ++ {one={'one',1},two={'two',2}} + ) + + + asserteq( +- map(function(v) return v*v end,{10,20,30}), +- {100,400,900} ++ map(function(v) return v*v end,{10,20,30}), ++ {100,400,900} + ) + + -- extra arguments to map() are passed to the function; can use +@@ -81,13 +102,13 @@ asserteq( + + -- mapn operates over an arbitrary number of input tables (but use map2 for n=2) + asserteq( +- mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}), +- {111,222,333} ++ mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}), ++ {111,222,333} + ) + + asserteq( +- mapn(math.max, {1,20,300},{10,2,3},{100,200,100}), +- {100,200,300} ++ mapn(math.max, {1,20,300},{10,2,3},{100,200,100}), ++ {100,200,300} + ) + + asserteq( +@@ -186,3 +207,142 @@ asserteq(tablex.reduce('-', {}, 2), 2) + asserteq(tablex.reduce('-', {}), nil) + asserteq(tablex.reduce('-', {1,2,3,4,5}), -13) + asserteq(tablex.reduce('-', {1,2,3,4,5}, 1), -14) ++ ++ ++-- tablex.compare ++do ++ asserteq(tablex.compare({},{}, "=="), true) ++ asserteq(tablex.compare({1,2,3}, {1,2,3}, "=="), true) ++ asserteq(tablex.compare({1,"hello",3}, {1,2,3}, "=="), false) ++ asserteq(tablex.compare( ++ {1,2,3, hello = "world"}, ++ {1,2,3}, ++ function(v1, v2) return v1 == v2 end), ++ true) -- only compares the list part ++end ++ ++ ++-- tablex.rfind ++do ++ local rfind = tablex.rfind ++ local lst = { "Rudolph", "the", "red-nose", "raindeer" } ++ asserteq(rfind(lst, "Santa"), nil) ++ asserteq(rfind(lst, "raindeer", -2), nil) ++ asserteq(rfind(lst, "raindeer"), 4) ++ asserteq(rfind(lst, "Rudolph"), 1) ++ asserteq(rfind(lst, "the", -3), 2) ++ asserteq(rfind(lst, "the", -30), nil) ++ asserteq(rfind({10,10,10},10), 3) ++end ++ ++ ++-- tablex.find_if ++do ++ local fi = tablex.find_if ++ local lst = { "Rudolph", true, false, 15 } ++ asserteq({fi(lst, "==", "Rudolph")}, {1, true}) ++ asserteq({fi(lst, "==", true)}, {2, true}) ++ asserteq({fi(lst, "==", false)}, {3, true}) ++ asserteq({fi(lst, "==", 15)}, {4, true}) ++ ++ local cmp = function(v1, v2) return v1 == v2 and v2 end ++ asserteq({fi(lst, cmp, "Rudolph")}, {1, "Rudolph"}) ++ asserteq({fi(lst, cmp, true)}, {2, true}) ++ asserteq({fi(lst, cmp, false)}, {}) -- 'false' cannot be returned! ++ asserteq({fi(lst, cmp, 15)}, {4, 15}) ++end ++ ++ ++-- tablex.map_named_method ++do ++ local Car = {} ++ Car.__index = Car ++ function Car.new(car) ++ return setmetatable(car or {}, Car) ++ end ++ Car.speed = 0 ++ function Car:faster(increase) ++ self.speed = self.speed + (increase or 1) ++ return self.speed ++ end ++ function Car:slower(self, decrease) ++ self.speed = self.speed - (decrease or 1) ++ return self.speed ++ end ++ ++ local ferrari = Car.new{ name = "Ferrari" } ++ local lamborghini = Car.new{ name = "Lamborghini", speed = 50 } ++ local cars = { ferrari, lamborghini } ++ ++ asserteq(ferrari.speed, 0) ++ asserteq(lamborghini.speed, 50) ++ asserteq(tablex.map_named_method("faster", cars, 10), {10, 60}) ++ asserteq(ferrari.speed, 10) ++ asserteq(lamborghini.speed, 60) ++ ++end ++ ++ ++-- tablex.foreach ++do ++ local lst = { "one", "two", "three", hello = "world" } ++ tablex.foreach(lst, function(v, k, sep) ++ lst[k] = tostring(k) .. sep .. v ++ end, " = ") ++ asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "hello = world"}) ++end ++ ++ ++-- tablex.foreachi ++do ++ local lst = { "one", "two", "three", hello = "world" } ++ tablex.foreachi(lst, function(v, k, sep) ++ lst[k] = tostring(k) .. sep .. v ++ end, " = ") ++ asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "world"}) ++end ++ ++ ++-- tablex.new ++asserteq(tablex.new(3, "hi"), { "hi", "hi", "hi" }) ++ ++ ++-- tablex.search ++do ++ local t = { ++ penlight = { ++ battery = { ++ type = "AA", ++ capacity = "1500mah", ++ }, ++ }, ++ hello = { ++ world = { ++ also = "AA" ++ } ++ } ++ } ++ asserteq(tablex.search(t, "1500mah"), "penlight.battery.capacity") ++ asserteq(tablex.search(t, "AA", {t.penlight} ), "hello.world.also") ++ asserteq(tablex.search(t, "xxx"), nil) ++end ++ ++ ++-- tablex.readonly ++do ++ local ro = tablex.readonly { 1,2,3, hello = "world" } ++ asserteq(pcall(function() ro.hello = "hi there" end), false) ++ asserteq(getmetatable(ro), false) ++ ++ if not utils.lua51 then ++ asserteq(#ro, 3) ++ ++ local r = {} ++ for k,v in pairs(ro) do r[k] = v end ++ asserteq(r, { 1,2,3, hello = "world" }) ++ ++ r = {} ++ for k,v in ipairs(ro) do r[k] = v end ++ asserteq(r, { 1,2,3 }) ++ end ++end +diff --git a/extra/penlight/tests/test-move.lua b/extra/penlight/tests/test-tablex3.lua +similarity index 100% +rename from tests/test-move.lua +rename to tests/test-tablex3.lua +diff --git a/extra/penlight/tests/test-template.lua b/extra/penlight/tests/test-template.lua +index 3cda83d..9c9825a 100644 +--- a/extra/penlight/tests/test-template.lua ++++ b/extra/penlight/tests/test-template.lua +@@ -46,6 +46,29 @@ asserteq(subst([[ + ]]) + + ++-------------------------------------------------------------------------------- ++-- Regression tests for issue #451 (can't use % for escapes) ++asserteq(subst([[ ++% for i = 1,3 do ++ $(text[i]) ++% end ++]],{_parent=_G,_escape='%',text={'foo','bar','baz'}}),[[ ++ foo ++ bar ++ baz ++]]) ++ ++asserteq(subst([[ ++? for i = 1,3 do ++ %(text[i]) ++? end ++]],{_parent=_G,_escape='?',_inline_escape='%',text={'foo','bar','baz'}}),[[ ++ foo ++ bar ++ baz ++]]) ++-------------------------------------------------------------------------------- ++ + + -- handle templates with a lot of substitutions + asserteq(subst(("$(x)\n"):rep(300), {x = "y"}), ("y\n"):rep(300)) +@@ -161,7 +184,7 @@ local my_env = { + ipairs = ipairs, + T = {'one','two','three'} + } +-local t, err = template.compile(tmpl, { debug = true, newline = "" }) ++local t, err = template.compile(tmpl, { debug = true, newline = true }) + local res, err, code = t:render(my_env) + --print(res, err, code) + asserteq(res, [[some list: ONE,TWO,THREE]]) +@@ -174,7 +197,7 @@ local tmpl = [[ + header: $("hello" * 10) + ]] + +-local t, err = template.compile(tmpl, { debug = true, newline = "" }) ++local t, err = template.compile(tmpl, { debug = true, newline = true }) + local res, err, code = t:render() + --print(res, err, code) + assert(res == nil, "expected nil here because of the runtime error") +@@ -183,6 +206,22 @@ asserteq(type(utils.load(code)), "function") + + + ++-------------------------------------------------- ++-- Test template run-time, doesn't fail on table value ++-- table.concat fails if we insert a non-string (table) value ++local tmpl = [[ ++header: $(myParam) ++]] ++ ++local t, err = template.compile(tmpl, { debug = true, newline = true }) ++local myParam = {} ++local res, err, code = t:render( {myParam = myParam } ) -- insert a table ++--print(res, err, code) ++asserteq(res, "header: "..tostring(myParam)) ++asserteq(type(err), "nil") ++ ++ ++ + -------------------------------------------------- + -- Test template compile-time error + local tmpl = [[ +@@ -193,7 +232,7 @@ local my_env = { + ipairs = ipairs, + T = {'one','two','three'} + } +-local t, err, code = template.compile(tmpl, { debug = true, newline = "" }) ++local t, err, code = template.compile(tmpl, { debug = true, newline = true }) + --print(t, err, code) + assert(t==nil, "expected t to be nil here because of the syntax error") + asserteq(type(err), "string") +@@ -223,7 +262,63 @@ asserteq(code, [[return "
      \ +

      a paragraph

      \ +

      a paragraph

      \ +
    \ +-"]]) ++";]]) ++ ++ ++ ++-------------------------------------------------- ++-- Test template preserves line numbers, both when ++-- stripping and not stripping newlines ++local tmpl = [[ ++# local function foo(x) ++ a$(x)b$(x + 1)c ++# return x + 2 ++# end ++Hello ++there ++ ++ ++# foo(1) ++foo$(foo)bar ++# -- ++]] ++local expected_num = select(2, string.gsub(tmpl, "\n", "\n")) ++ ++-- Trailing newline, no newline stripping ++local t, err = template.compile(tmpl, { debug = true }) ++local res, err, code = t:render(my_env) ++--print(res, err, code) ++ ++assert(res, "rendering should not fail here") ++asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) ++ ++-- Trailing newline, with newline stripping ++local t, err = template.compile(tmpl, { debug = true, newline = true }) ++local res, err, code = t:render(my_env) ++--print(res, err, code) ++ ++assert(res, "rendering should not fail here") ++asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) ++ ++ ++tmpl = string.sub(code, 1, -2) -- Remove the trailing newline ++-- num_expected remains unchanged because the template will append a trailing newline ++ ++-- No trailing newline, no newline stripping ++local t, err = template.compile(tmpl, { debug = true }) ++local res, err, code = t:render(my_env) ++--print(res, err, code) ++ ++assert(res, "rendering should not fail here") ++asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) ++ ++-- No trailing newline, with newline stripping ++local t, err = template.compile(tmpl, { debug = true, newline = true }) ++local res, err, code = t:render(my_env) ++--print(res, err, code) ++ ++assert(res, "rendering should not fail here") ++asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) + + + print("template: success") +diff --git a/extra/penlight/tests/test-template2.lua b/extra/penlight/tests/test-template2.lua +new file mode 100644 +index 0000000..14e55a6 +--- /dev/null ++++ b/extra/penlight/tests/test-template2.lua +@@ -0,0 +1,76 @@ ++local T = require 'pl.text' ++local utils = require 'pl.utils' ++local Template = T.Template ++local asserteq = require 'pl.test'.asserteq ++local OrderedMap = require 'pl.OrderedMap' ++local template = require 'pl.template' ++ ++local t = [[ ++# for i = 1,3 do ++ print($(i+1)) ++# end ++]] ++ ++asserteq(template.substitute(t),[[ ++ print(2) ++ print(3) ++ print(4) ++]]) ++ ++t = [[ ++> for i = 1,3 do ++ print(${i+1}) ++> end ++]] ++ ++asserteq(template.substitute(t,{_brackets='{}',_escape='>'}),[[ ++ print(2) ++ print(3) ++ print(4) ++]]) ++ ++t = [[ ++#@ for i = 1,3 do ++ print(@{i+1}) ++#@ end ++]] ++ ++asserteq(template.substitute(t,{_brackets='{}',_escape='#@',_inline_escape='@'}),[[ ++ print(2) ++ print(3) ++ print(4) ++]]) ++ ++--- iteration using pairs is usually unordered. But using OrderedMap ++--- we can get the exact original ordering. ++ ++t = [[ ++# for k,v in pairs(T) do ++ "$(k)", -- $(v) ++# end ++]] ++ ++if utils.lua51 then ++ -- easy enough to define a general pairs in Lua 5.1 ++ local rawpairs = pairs ++ function pairs(t) ++ local mt = getmetatable(t) ++ local f = mt and mt.__pairs ++ if f then ++ return f(t) ++ else ++ return rawpairs(t) ++ end ++ end ++end ++ ++ ++local Tee = OrderedMap{{Dog = 'Bonzo'}, {Cat = 'Felix'}, {Lion = 'Leo'}} ++ ++-- note that the template will also look up global functions using _parent ++asserteq(template.substitute(t,{T=Tee,_parent=_G}),[[ ++ "Dog", -- Bonzo ++ "Cat", -- Felix ++ "Lion", -- Leo ++]]) ++ +diff --git a/extra/penlight/tests/test-text.lua b/extra/penlight/tests/test-text.lua +deleted file mode 100644 +index 9419b22..0000000 +--- a/extra/penlight/tests/test-text.lua ++++ /dev/null +@@ -1,177 +0,0 @@ +-local T = require 'pl.text' +-local utils = require 'pl.utils' +-local Template = T.Template +-local asserteq = require 'pl.test'.asserteq +-local OrderedMap = require 'pl.OrderedMap' +- +-local t1 = Template [[ +-while true do +- $contents +-end +-]] +- +-assert(t1:substitute {contents = 'print "hello"'},[[ +-while true do +- print "hello" +-end +-]]) +- +-assert(t1:indent_substitute {contents = [[ +-for i = 1,10 do +- gotcha(i) +-end +-]]},[[ +-while true do +- for i = 1,10 do +- gotcha(i) +- end +-end +-]]) +- +-asserteq(T.dedent [[ +- one +- two +- three +-]],[[ +-one +-two +-three +-]]) +-asserteq(T.fill ([[ +-It is often said of Lua that it does not include batteries. That is because the goal of Lua is to produce a lean expressive language that will be used on all sorts of machines, (some of which don't even have hierarchical filesystems). The Lua language is the equivalent of an operating system kernel; the creators of Lua do not see it as their responsibility to create a full software ecosystem around the language. That is the role of the community. +-]],20),[[ +-It is often said of Lua +-that it does not include +-batteries. That is because +-the goal of Lua is to +-produce a lean expressive +-language that will be +-used on all sorts of machines, +-(some of which don't +-even have hierarchical +-filesystems). The Lua +-language is the equivalent +-of an operating system +-kernel; the creators of +-Lua do not see it as their +-responsibility to create +-a full software ecosystem +-around the language. That +-is the role of the community. +-]]) +- +-local template = require 'pl.template' +- +-local t = [[ +-# for i = 1,3 do +- print($(i+1)) +-# end +-]] +- +-asserteq(template.substitute(t),[[ +- print(2) +- print(3) +- print(4) +-]]) +- +-t = [[ +-> for i = 1,3 do +- print(${i+1}) +-> end +-]] +- +-asserteq(template.substitute(t,{_brackets='{}',_escape='>'}),[[ +- print(2) +- print(3) +- print(4) +-]]) +- +-t = [[ +-#@ for i = 1,3 do +- print(@{i+1}) +-#@ end +-]] +- +-asserteq(template.substitute(t,{_brackets='{}',_escape='#@',_inline_escape='@'}),[[ +- print(2) +- print(3) +- print(4) +-]]) +- +---- iteration using pairs is usually unordered. But using OrderedMap +---- we can get the exact original ordering. +- +-t = [[ +-# for k,v in pairs(T) do +- "$(k)", -- $(v) +-# end +-]] +- +-if utils.lua51 then +- -- easy enough to define a general pairs in Lua 5.1 +- local rawpairs = pairs +- function pairs(t) +- local mt = getmetatable(t) +- local f = mt and mt.__pairs +- if f then +- return f(t) +- else +- return rawpairs(t) +- end +- end +-end +- +- +-local Tee = OrderedMap{{Dog = 'Bonzo'}, {Cat = 'Felix'}, {Lion = 'Leo'}} +- +--- note that the template will also look up global functions using _parent +-asserteq(template.substitute(t,{T=Tee,_parent=_G}),[[ +- "Dog", -- Bonzo +- "Cat", -- Felix +- "Lion", -- Leo +-]]) +- +--- for those with a fondness for Python-style % formatting... +-T.format_operator() +-asserteq('[%s]' % 'home', '[home]') +-asserteq('%s = %d' % {'fred',42},'fred = 42') +- +--- mostly works like string.format, except that %s forces use of tostring() +--- rather than throwing an error +-local List = require 'pl.List' +-asserteq('TBL:%s' % List{1,2,3},'TBL:{1,2,3}') +- +--- table with keys and format with $ +-asserteq('<$one>' % {one=1}, '<1>') +--- (second arg may also be a function, like os.getenv) +-function subst(k) +- if k == 'A' then return 'ay' +- elseif k == 'B' then return 'bee' +- else return '?' +- end +-end +-asserteq( +- '$A & $B' % subst,'ay & bee' +-) +- +-t = [[ +-a whole lot +-of love +-]] +- +-asserteq(T.indent(t,4),[[ +- a whole lot +- of love +-]]) +- +-asserteq(T.indent([[ +-easy +- +-enough! +-]],2,'*'),[[ +-**easy +-** +-**enough! +-]]) +- +- +diff --git a/extra/penlight/tests/test-types.lua b/extra/penlight/tests/test-types.lua +index 075b970..7cf313d 100644 +--- a/extra/penlight/tests/test-types.lua ++++ b/extra/penlight/tests/test-types.lua +@@ -7,33 +7,133 @@ local list = List() + local array = {10,20,30} + local map = {one=1,two=2} + +--- extened type() function ++-- extended type() function + asserteq(types.type(array),'table') + asserteq(types.type('hello'),'string') + -- knows about Lua file objects + asserteq(types.type(io.stdin),'file') ++local f = io.open("tests/test-types.lua") ++asserteq(types.type(f),'file') ++f:close() + -- and class names + asserteq(types.type(list),'List') ++-- tables with unknown metatable ++asserteq(types.type(setmetatable({},{})), "unknown table") ++-- userdata with unknown metatable ++if newproxy then ++ asserteq(types.type(newproxy(true)), "unknown userdata") ++end + + asserteq(types.is_integer(10),true) + asserteq(types.is_integer(10.1),false) ++asserteq(types.is_integer(-10),true) ++asserteq(types.is_integer(-10.1),false) + -- do note that for Lua < 5.3, 10.0 is the same as 10; an integer. + + asserteq(types.is_callable(asserteq),true) + asserteq(types.is_callable(List),true) ++do ++ local mt = setmetatable({}, { ++ __index = { ++ __call = function() return "ok" end ++ } ++ }) ++ asserteq(type(mt.__call), "function") -- __call is looked-up through another metatable ++ local nc = setmetatable({}, mt) ++ -- proof-of-pudding, let's call it. To verify Lua behaves the same on all engines ++ local success, result = pcall(function() return nc() end) ++ assert(result ~= "ok", "expected result to not be 'ok'") ++ asserteq(success, false) ++ -- real test now ++ asserteq(types.is_callable(nc), false) -- NOT callable, since __call is fetched using RAWget by Lua ++end + + asserteq(types.is_indexable(array),true) +-asserteq(types.is_iterable(array),true) + asserteq(types.is_indexable('hello'),nil) + asserteq(types.is_indexable(10),nil) ++if newproxy then ++ local v = newproxy(true) ++ local mt = getmetatable(v) ++ mt.__len = true ++ mt.__index = true ++ asserteq(types.is_indexable(v), true) ++end ++if newproxy then ++ local v = newproxy(true) ++ asserteq(types.is_indexable(v), nil) ++end ++ ++asserteq(types.is_iterable(array),true) ++asserteq(types.is_iterable(true),nil) ++asserteq(types.is_iterable(42),nil) ++asserteq(types.is_iterable("array"),nil) ++if newproxy then ++ local v = newproxy(true) ++ local mt = getmetatable(v) ++ mt.__pairs = true ++ asserteq(types.is_iterable(v), true) ++end ++if newproxy then ++ local v = newproxy(true) ++ asserteq(types.is_iterable(v), nil) ++end ++ ++asserteq(types.is_writeable(array),true) ++asserteq(types.is_writeable(true),nil) ++asserteq(types.is_writeable(42),nil) ++asserteq(types.is_writeable("array"),nil) ++if newproxy then ++ local v = newproxy(true) ++ local mt = getmetatable(v) ++ mt.__newindex = true ++ asserteq(types.is_writeable(v), true) ++end ++if newproxy then ++ local v = newproxy(true) ++ asserteq(types.is_writeable(v), nil) ++end + + asserteq(types.is_empty(nil),true) + asserteq(types.is_empty({}),true) ++asserteq(types.is_empty({[false] = false}),false) + asserteq(types.is_empty(""),true) + asserteq(types.is_empty(" ",true),true) ++asserteq(types.is_empty(" "),false) ++asserteq(types.is_empty(true),true) ++-- Numbers ++asserteq(types.is_empty(0), true) ++asserteq(types.is_empty(20), true) ++-- Booleans ++asserteq(types.is_empty(false), true) ++asserteq(types.is_empty(true), true) ++-- Functions ++asserteq(types.is_empty(print), true) ++-- Userdata ++--asserteq(types.is_empty(newproxy()), true) --newproxy was removed in Lua 5.2 + + -- a more relaxed kind of truthiness.... + asserteq(types.to_bool('yes'),true) + asserteq(types.to_bool('true'),true) ++asserteq(types.to_bool('y'),true) ++asserteq(types.to_bool('t'),true) ++asserteq(types.to_bool('YES'),true) ++asserteq(types.to_bool('1'),true) ++asserteq(types.to_bool('no'),false) ++asserteq(types.to_bool('false'),false) ++asserteq(types.to_bool('n'),false) ++asserteq(types.to_bool('f'),false) ++asserteq(types.to_bool('NO'),false) ++asserteq(types.to_bool('0'),false) + asserteq(types.to_bool(1),true) +-asserteq(types.to_bool(0),false) +\ No newline at end of file ++asserteq(types.to_bool(0),false) ++local de_fr = { 'ja', 'oui' } ++asserteq(types.to_bool('ja', de_fr),true) ++asserteq(types.to_bool('OUI', de_fr),true) ++local t_e = {} ++local t_ne = { "not empty" } ++asserteq(types.to_bool(t_e,nil,false),false) ++asserteq(types.to_bool(t_e,nil,true),false) ++asserteq(types.to_bool(t_ne,nil,false),false) ++asserteq(types.to_bool(t_ne,nil,true),true) ++asserteq(types.to_bool(coroutine.create(function() end),nil,true),true) ++asserteq(types.to_bool(coroutine.create(function() end),nil,false),false) +diff --git a/extra/penlight/tests/test-tzone.lua b/extra/penlight/tests/test-tzone.lua +deleted file mode 100644 +index 230fb18..0000000 +--- a/extra/penlight/tests/test-tzone.lua ++++ /dev/null +@@ -1,10 +0,0 @@ +-local Date = require 'pl.Date' +-local test = require 'pl.test' +-local df = Date.Format() +-local dl = df:parse '2008-07-05' +-local du = dl:toUTC() +- +-test.asserteq(dl,du) +- +- +- +diff --git a/extra/penlight/tests/test-url.lua b/extra/penlight/tests/test-url.lua +index 656415c..9d9af6a 100644 +--- a/extra/penlight/tests/test-url.lua ++++ b/extra/penlight/tests/test-url.lua +@@ -30,3 +30,8 @@ asserteq(url.unquote('%60%7E%21%40%23%24%25%5E%26%2A%28%29'), '`~!@#$%^&*()') + asserteq(url.unquote('%252'), '%2') + asserteq(url.unquote('2%52%1%%'), '2R%1%%') + asserteq(url.unquote('2R%251%25%25'), '2R%1%%') ++ ++asserteq(url.quote(true), true) ++asserteq(url.quote(42), 42) ++asserteq(url.unquote(true), true) ++asserteq(url.unquote(42), 42) +diff --git a/extra/penlight/tests/test-utils.lua b/extra/penlight/tests/test-utils.lua +index b115bea..cff400a 100644 +--- a/extra/penlight/tests/test-utils.lua ++++ b/extra/penlight/tests/test-utils.lua +@@ -3,10 +3,71 @@ local path = require 'pl.path' + local test = require 'pl.test' + local asserteq, T = test.asserteq, test.tuple + +---- escaping magic chars +-local escape = utils.escape +-asserteq(escape '[a]','%[a%]') +-asserteq(escape '$(bonzo)','%$%(bonzo%)') ++ ++local function quote(s) ++ if utils.is_windows then ++ return '"'..s..'"' ++ else ++ return "'"..s.."'" ++ end ++end ++ ++-- construct command to run external lua, we need to to be able to run some ++-- tests on the same lua engine, but also need to pass on the LuaCov flag ++-- if it was used, to make sure we report the proper coverage. ++local cmd = "-e " ++do ++ local i = 0 ++ while arg[i-1] do ++ local a = arg[i-1] ++ if a:find("package%.path") and a:sub(1,1) ~= "'" then ++ a = quote(a) ++ end ++ cmd = a .. " " .. cmd ++ i = i - 1 ++ end ++end ++ ++ ++--- quitting ++do ++ local luacode = quote("require([[pl.utils]]).quit([[hello world]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ if utils.is_windows then ++ asserteq(code, -1) ++ else ++ asserteq(code, 255) ++ end ++ asserteq(stdout, "") ++ asserteq(stderr, "hello world\n") ++ ++ local luacode = quote("require([[pl.utils]]).quit(2, [[hello world]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ asserteq(code, 2) ++ asserteq(stdout, "") ++ asserteq(stderr, "hello world\n") ++ ++ local luacode = quote("require([[pl.utils]]).quit(2, [[hello %s]], 42)") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ asserteq(code, 2) ++ asserteq(stdout, "") ++ asserteq(stderr, "hello 42\n") ++ ++ local luacode = quote("require([[pl.utils]]).quit(2)") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ asserteq(code, 2) ++ asserteq(stdout, "") ++ asserteq(stderr, "") ++end ++ ++----- importing module tables wholesale --- ++utils.import(math) ++asserteq(type(sin),"function") ++asserteq(type(abs),"function") + + --- useful patterns + local P = utils.patterns +@@ -14,6 +75,15 @@ asserteq(("+0.1e10"):match(P.FLOAT) ~= nil, true) + asserteq(("-23430"):match(P.INTEGER) ~= nil, true) + asserteq(("my_little_pony99"):match(P.IDEN) ~= nil, true) + ++--- escaping magic chars ++local escape = utils.escape ++asserteq(escape '[a]','%[a%]') ++asserteq(escape '$(bonzo)','%$%(bonzo%)') ++ ++--- choose ++asserteq(utils.choose(true, 1, 2), 1) ++asserteq(utils.choose(false, 1, 2), 2) ++ + --- splitting strings --- + local split = utils.split + asserteq(split("hello dolly"),{"hello","dolly"}) +@@ -22,6 +92,12 @@ asserteq(split("hello,dolly,",","),{"hello","dolly"}) + + local first,second = utils.splitv("hello:dolly",":") + asserteq(T(first,second),T("hello","dolly")) ++local first,second = utils.splitv("hello:dolly:parton",":", false, 2) ++asserteq(T(first,second),T("hello","dolly:parton")) ++local first,second,third = utils.splitv("hello=dolly:parton","[:=]") ++asserteq(T(first,second,third),T("hello","dolly","parton")) ++local first,second = utils.splitv("hello=dolly:parton","[:=]", false, 2) ++asserteq(T(first,second),T("hello","dolly:parton")) + + ----- table of values to table of strings + asserteq(utils.array_tostring{1,2,3},{"1","2","3"}) +@@ -117,12 +193,128 @@ else + asserteq(utils.quote_arg([['a\'b]]), [[''\''a\'\''b']]) + end + +------ importing module tables wholesale --- +-utils.import(math) +-asserteq(type(sin),"function") +-asserteq(type(abs),"function") ++-- packing and unpacking arguments in a nil-safe way ++local t = utils.pack(nil, nil, "hello", nil) ++asserteq(t.n, 4) -- the last nil does count as an argument ++ ++local arg1, arg2, arg3, arg4 = utils.unpack(t) ++assert(arg1 == nil) ++assert(arg2 == nil) ++asserteq("hello", arg3) ++assert(arg4 == nil) + + ++-- Assert arguments assert_arg ++local ok, err = pcall(function() ++ utils.assert_arg(4,'!@#$%^&*','string',require("pl.path").isdir,'not a directory') ++end) ++asserteq(ok, false) ++asserteq(err:match("(argument .+)$"), "argument 4: '!@#$%^&*' not a directory") ++ ++local ok, err = pcall(function() ++ utils.assert_arg(1, "hello", "table") ++end) ++asserteq(ok, false) ++asserteq(err:match("(argument .+)$"), "argument 1 expected a 'table', got a 'string'") ++ ++local ok, err = pcall(function() ++ return utils.assert_arg(1, "hello", "string") ++end) ++asserteq(ok, true) ++asserteq(err, "hello") ++ ++-- assert_string ++local success, err = pcall(utils.assert_string, 2, 5) ++asserteq(success, false) ++asserteq(err:match("(argument .+)$"), "argument 2 expected a 'string', got a 'number'") + ++local x = utils.assert_string(2, "5") ++asserteq(x, "5") + + ++do ++ -- printf -- without template ++ local luacode = quote("require([[pl.utils]]).printf([[hello world]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, true) ++ asserteq(code, 0) ++ asserteq(stdout, "hello world") ++ asserteq(stderr, "") ++ ++ -- printf -- with template ++ local luacode = quote("require([[pl.utils]]).printf([[hello %s]], [[world]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, true) ++ asserteq(code, 0) ++ asserteq(stdout, "hello world") ++ asserteq(stderr, "") ++ ++ -- printf -- with bad template ++ local luacode = quote("require([[pl.utils]]).printf(42)") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ asserteq(code, 1) ++ asserteq(stdout, "") ++ assert(stderr:find("argument 1 expected a 'string', got a 'number'")) ++end ++ ++do ++ -- on_error, raise -- default ++ utils.on_error("default") ++ local ok, err = utils.raise("some error") ++ asserteq(ok, nil) ++ asserteq(err, "some error") ++ local ok, err = pcall(utils.on_error, "bad one") ++ asserteq(ok, false) ++ asserteq(err, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'") ++ ++ -- on_error, raise -- error ++ utils.on_error("error") ++ local ok, err = pcall(utils.raise, "some error") ++ asserteq(ok, false) ++ asserteq(err, "some error") ++ local ok, err = pcall(utils.on_error, "bad one") ++ asserteq(ok, false) ++ assert(err:find("Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'")) ++ ++ -- on_error, raise -- quit ++ utils.on_error("quit") ++ local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.raise([[some error]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ if utils.is_windows then ++ asserteq(code, -1) ++ else ++ asserteq(code, 255) ++ end ++ asserteq(stdout, "") ++ asserteq(stderr, "some error\n") ++ ++ local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.on_error([[bad one]])") ++ local success, code, stdout, stderr = utils.executeex(cmd..luacode) ++ asserteq(success, false) ++ if utils.is_windows then ++ asserteq(code, -1) ++ else ++ asserteq(code, 255) ++ end ++ asserteq(stdout, "") ++ asserteq(stderr, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'\n") ++ ++ utils.on_error("default") -- cleanup by restoring behaviour after on_error + raise tests ++end ++ ++do ++ -- readlines ++ local f = utils.readlines("tests/test-utils.lua") ++ asserteq(type(f), "table") ++ local v = "some extraordinary string this is only in this file for test purposes so we can go and find it" ++ local found = false ++ for i, line in ipairs(f) do ++ if line:find(v) then ++ found = true ++ break ++ end ++ end ++ asserteq(found, true) ++end +diff --git a/extra/penlight/tests/test-utils2.lua b/extra/penlight/tests/test-utils2.lua +new file mode 100644 +index 0000000..897da3e +--- /dev/null ++++ b/extra/penlight/tests/test-utils2.lua +@@ -0,0 +1,53 @@ ++local path = require 'pl.path' ++local utils = require 'pl.utils' ++local asserteq = require 'pl.test'.asserteq ++ ++local echo_lineending = "\n" ++if path.is_windows then ++ echo_lineending = " \n" ++end ++ ++local function test_executeex(cmd, expected_successful, expected_retcode, expected_stdout, expected_stderr) ++--print("\n"..cmd) ++--print(os.execute(cmd)) ++--print(utils.executeex(cmd)) ++ local successful, retcode, stdout, stderr = utils.executeex(cmd) ++ asserteq(successful, expected_successful) ++ asserteq(retcode, expected_retcode) ++ asserteq(stdout, expected_stdout) ++ asserteq(stderr, expected_stderr) ++end ++ ++-- Check the return codes ++if utils.is_windows then ++ test_executeex("exit", true, 0, "", "") ++ test_executeex("exit 0", true, 0, "", "") ++ test_executeex("exit 1", false, 1, "", "") ++ test_executeex("exit 13", false, 13, "", "") ++ test_executeex("exit 255", false, 255, "", "") ++ test_executeex("exit 256", false, 256, "", "") ++ test_executeex("exit 257", false, 257, "", "") ++ test_executeex("exit 3809", false, 3809, "", "") ++ test_executeex("exit -1", false, -1, "", "") ++ test_executeex("exit -13", false, -13, "", "") ++ test_executeex("exit -255", false, -255, "", "") ++ test_executeex("exit -256", false, -256, "", "") ++ test_executeex("exit -257", false, -257, "", "") ++ test_executeex("exit -3809", false, -3809, "", "") ++else ++ test_executeex("exit", true, 0, "", "") ++ test_executeex("exit 0", true, 0, "", "") ++ test_executeex("exit 1", false, 1, "", "") ++ test_executeex("exit 13", false, 13, "", "") ++ test_executeex("exit 255", false, 255, "", "") ++ -- on posix anything other than 0-255 is undefined ++ test_executeex("exit 256", true, 0, "", "") ++ test_executeex("exit 257", false, 1, "", "") ++ test_executeex("exit 3809", false, 225, "", "") ++end ++ ++-- Check output strings ++test_executeex("echo stdout", true, 0, "stdout" .. echo_lineending, "") ++test_executeex("(echo stderr 1>&2)", true, 0, "", "stderr" .. echo_lineending) ++test_executeex("(echo stdout && (echo stderr 1>&2))", true, 0, "stdout" .. echo_lineending, "stderr" .. echo_lineending) ++ +diff --git a/extra/penlight/tests/test-fenv.lua b/extra/penlight/tests/test-utils3.lua +similarity index 94% +rename from tests/test-fenv.lua +rename to tests/test-utils3.lua +index f16b327..a635de5 100644 +--- a/extra/penlight/tests/test-fenv.lua ++++ b/extra/penlight/tests/test-utils3.lua +@@ -41,8 +41,8 @@ if not package.path:find '.[/\\]%?' then + end + + asserteq( +- package.searchpath('tests.test-fenv',package.path):gsub('\\','/'), +- './tests/test-fenv.lua' ++ package.searchpath('tests.test-utils3',package.path):gsub('\\','/'), ++ './tests/test-utils3.lua' + ) + + -- testing getfenv and setfenv for both interpreters +diff --git a/extra/penlight/tests/test-xml.lua b/extra/penlight/tests/test-xml.lua +index 5e32eab..e1b541e 100644 +--- a/extra/penlight/tests/test-xml.lua ++++ b/extra/penlight/tests/test-xml.lua +@@ -1,21 +1,34 @@ + local xml = require 'pl.xml' + local asserteq = require 'pl.test'.asserteq + local dump = require 'pl.pretty'.dump ++local path = require 'pl.path' ++local utils = require 'pl.utils' + + -- Prosody stanza.lua style XML building + + d = xml.new 'top' : addtag 'child' : text 'alice' : up() : addtag 'child' : text 'bob' + + d = xml.new 'children' : +- addtag 'child' : +- addtag 'name' : text 'alice' : up() : addtag 'age' : text '5' : up() : addtag('toy',{type='fluffy'}) : up() : +- up() : +- addtag 'child': +- addtag 'name' : text 'bob' : up() : addtag 'age' : text '6' : up() : addtag('toy',{type='squeaky'}) +- +-asserteq( +-xml.tostring(d,'',' '), +-[[ ++ addtag 'child' : ++ addtag 'name' : ++ text 'alice' : ++ up() : ++ addtag 'age' : ++ text '5' : ++ up() : ++ addtag('toy',{type='fluffy'}) : ++ up() : ++ up() : ++ addtag 'child': ++ addtag 'name' : ++ text 'bob' : ++ up() : ++ addtag 'age' : ++ text '6' : ++ up() : ++ addtag('toy',{type='squeaky'}) ++ ++asserteq(xml.tostring(d,'',' '), [[ + + + +@@ -38,7 +51,6 @@ d1 = children { + child {name 'alice', age '5', toy {type='fluffy'}}, + child {name 'bob', age '6', toy {type='squeaky'}} + } +- + assert(xml.compare(d,d1)) + + -- or we can use a template document to convert Lua data to LOM +@@ -133,8 +145,8 @@ t1 = [[ + + + match(t1,{ +- condition = "Clear", +- temp = "24", ++ condition = "Clear", ++ temp = "24", + } ) + + t2 = [[ +@@ -149,30 +161,30 @@ t2 = [[ + ]] + + local conditions = { +- { +- low = "60", +- high = "89", +- day = "Sat", +- condition = "Clear", +- }, +- { +- low = "53", +- high = "86", +- day = "Sun", +- condition = "Clear", +- }, +- { +- low = "57", +- high = "87", +- day = "Mon", +- condition = "Clear", +- }, +- { +- low = "60", +- high = "84", +- day = "Tue", +- condition = "Clear", +- } ++ { ++ low = "60", ++ high = "89", ++ day = "Sat", ++ condition = "Clear", ++ }, ++ { ++ low = "53", ++ high = "86", ++ day = "Sun", ++ condition = "Clear", ++ }, ++ { ++ low = "57", ++ high = "87", ++ day = "Mon", ++ condition = "Clear", ++ }, ++ { ++ low = "60", ++ high = "84", ++ day = "Tue", ++ condition = "Clear", ++ } + } + + match(t2,conditions) +@@ -195,9 +207,9 @@ match([[ + {{$value}} + + ]],{ +- {key="alpha", value = "1.3"}, +- {key="beta", value = "10"}, +- {key="name",value = "bozo"}, ++ {key="alpha", value = "1.3"}, ++ {key="beta", value = "10"}, ++ {key="name",value = "bozo"}, + }) + -- can be numerical indices + match([[ +@@ -205,9 +217,9 @@ match([[ + {{<1->$2}} + + ]],{ +- {"alpha","1.3"}, +- {"beta","10"}, +- {"name","bozo"}, ++ {"alpha","1.3"}, ++ {"beta","10"}, ++ {"name","bozo"}, + }) + -- _ is special; means 'this value is key of captured table' + match([[ +@@ -215,9 +227,9 @@ match([[ + {{<_->$1}} + + ]],{ +- alpha = {"1.3"}, +- beta = {"10"}, +- name = {"bozo"}, ++ alpha = {"1.3"}, ++ beta = {"10"}, ++ name = {"bozo"}, + }) + + -- the numerical index 0 is special: a capture of {[0]=val} becomes simply the value val +@@ -226,9 +238,9 @@ match([[ + {{<_->$0}} + + ]],{ +- alpha = "1.3", +- name = "bozo", +- beta = "10" ++ alpha = "1.3", ++ name = "bozo", ++ beta = "10" + }) + + -- this can of course also work with attributes, but then we don't want to collapse! +@@ -248,9 +260,9 @@ match([[ + {{<_- type='$1'>$2}} + + ]],{ +- alpha = {"number","1.3"}, +- beta = {"number","10"}, +- name = {"string","bozo"}, ++ alpha = {"number","1.3"}, ++ beta = {"number","10"}, ++ name = {"string","bozo"}, + }) + + d,err = parse [[ +@@ -279,16 +291,16 @@ res,err = d:match [[ + ]] + + asserteq(res,{ +- HOST = "windows-unknown-linux-gnu", +- COPYRIGHT = "Copyright (C) 1999-2009 ImageMagick Studio LLC", +- NAME = "ImageMagick", +- LIB_VERSION = "0x651", +- VERSION = "6.5.1", +- RELEASE_DATE = "2009-05-01", +- WEBSITE = "http://www.imagemagick.org", +- LIB_VERSION_NUMBER = "6,5,1,3", +- CC = "vs7", +- DELEGATES = "bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib" ++ HOST = "windows-unknown-linux-gnu", ++ COPYRIGHT = "Copyright (C) 1999-2009 ImageMagick Studio LLC", ++ NAME = "ImageMagick", ++ LIB_VERSION = "0x651", ++ VERSION = "6.5.1", ++ RELEASE_DATE = "2009-05-01", ++ WEBSITE = "http://www.imagemagick.org", ++ LIB_VERSION_NUMBER = "6,5,1,3", ++ CC = "vs7", ++ DELEGATES = "bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib" + }) + + -- short excerpt from +@@ -524,3 +536,17 @@ asserteq(xml.tostring(doc),[[ + dolly]]) + + ++-- parsing by file name ++ ++local filename = path.tmpname() ++utils.writefile(filename, '') ++doc = xml.parse(filename, true, true) ++os.remove(filename) ++asserteq(type(doc), 'table') ++asserteq(xml.tostring(doc, '', ' ', nil, true), [[ ++ ++ ++ ++]]) ++ ++ +Submodule pkg/optim 656c42a..a5ceed7: +diff --git a/pkg/optim/adam.lua b/pkg/optim/adam.lua +index bc80b5e..2e127e9 100644 +--- a/pkg/optim/adam.lua ++++ b/pkg/optim/adam.lua +@@ -1,4 +1,4 @@ +---[[ An implementation of Adam http://arxiv.org/pdf/1412.6980.pdf ++--[[ An implementation of Adam https://arxiv.org/abs/1412.6980 + + ARGS: + From f8b65945faf32e6941e0c352e9423fb9ac3da672 Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Thu, 17 Jul 2025 10:48:01 +0200 Subject: [PATCH 3/6] info about crosscompilation patch --- toolchain-changes.patch | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 toolchain-changes.patch diff --git a/toolchain-changes.patch b/toolchain-changes.patch new file mode 100644 index 000000000..f284c27ec --- /dev/null +++ b/toolchain-changes.patch @@ -0,0 +1,42 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 31884a9..5ee3e0d 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1,4 +1,11 @@ + cmake_minimum_required(VERSION 2.8) ++ ++if(CMAKE_CROSSCOMPILING) ++ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ldl") ++ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -ldl") ++endif() ++ ++ + ADD_SUBDIRECTORY(exe) + + # If this variable is defined, a pure cmake build is done end-to-end with no luarocks +diff --git a/exe/luajit-rocks b/exe/luajit-rocks +--- a/exe/luajit-rocks ++++ b/exe/luajit-rocks +@@ -1 +1 @@ +-Subproject commit 411f4b9d9c4be176d4aab965ebfce50911583e14 ++Subproject commit 411f4b9d9c4be176d4aab965ebfce50911583e14-dirty +diff --git a/install.sh b/install.sh +index ad65434..15a9ae2 100755 +--- a/install.sh ++++ b/install.sh +@@ -70,7 +70,14 @@ echo "Installing Lua version: ${TORCH_LUA_VERSION}" + mkdir -p install + mkdir -p build + cd build +-cmake .. -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 ++ ++export CC="/usr/bin/riscv64-linux-gnu-gcc" ++export CXX="riscv64-linux-gnu-g++" ++ ++ ++TOOLCHAIN_FILE="${THIS_DIR}/riscv64-toolchain.cmake" ++ ++cmake .. -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" -DHAVE_DLOPEN=1 -DCMAKE_HAVE_DLOPEN=1 -DLUA_USE_DLOPEN=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 + (make 2>&1 >>$PREFIX/install.log || exit 1) && (make install 2>&1 >>$PREFIX/install.log || exit 1) + cd .. + From 70875dd9a845b62372d1135d4a7ea76ed042c7ab Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Tue, 9 Sep 2025 21:13:48 +0200 Subject: [PATCH 4/6] changes to local repo for crosscompilation --- CMakeLists.txt | 3 + install.sh | 292 +++++++++------------------------------- riscv64-toolchain.cmake | 31 ++++- 3 files changed, 93 insertions(+), 233 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9017018a4..83bf54ad8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ cmake_minimum_required(VERSION 2.8) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + if(CMAKE_CROSSCOMPILING) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ldl") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -ldl") diff --git a/install.sh b/install.sh index b2b7b5691..5415b9dd6 100755 --- a/install.sh +++ b/install.sh @@ -1,248 +1,76 @@ #!/usr/bin/env bash -SKIP_RC=0 -BATCH_INSTALL=0 - THIS_DIR=$(cd $(dirname $0); pwd) -if [[ "$THIS_DIR" == *" "* ]]; then - echo "$THIS_DIR: Torch cannot install to a path containing whitespace. -Please try a different path, one without any spaces." - exit 1 -fi PREFIX=${PREFIX:-"${THIS_DIR}/install"} -TORCH_LUA_VERSION=${TORCH_LUA_VERSION:-"LUAJIT21"} # by default install LUAJIT21 - -while getopts 'bsh' x; do - case "$x" in - h) - echo "usage: $0 -This script will install Torch and related, useful packages into $PREFIX. - - -b Run without requesting any user input (will automatically add PATH to shell profile) - -s Skip adding the PATH to shell profile -" - exit 2 - ;; - b) - BATCH_INSTALL=1 - ;; - s) - SKIP_RC=1 - ;; - esac -done - - -# Scrub an anaconda/conda install, if exists, from the PATH. -# It has a malformed MKL library (as of 1/17/2015) -OLDPATH=$PATH -if [[ $(echo $PATH | grep conda) ]]; then - export PATH=$(echo $PATH | tr ':' '\n' | grep -v "conda[2-9]\?" | uniq | tr '\n' ':') -fi - -echo "Prefix set to $PREFIX" - -if [[ `uname` == 'Linux' ]]; then - export CMAKE_LIBRARY_PATH=$PREFIX/include:/opt/OpenBLAS/include:$PREFIX/lib:/opt/OpenBLAS/lib:$CMAKE_LIBRARY_PATH -fi -export CMAKE_PREFIX_PATH=$PREFIX +RISCV_PREFIX="${PREFIX}/riscv64-cmake" -git submodule update --init --recursive - -# If we're on OS X, use clang -if [[ `uname` == "Darwin" ]]; then - # make sure that we build with Clang. CUDA's compiler nvcc - # does not play nice with any recent GCC version. - export CC=clang - export CXX=clang++ -fi -# If we're on Arch linux, use gcc v5 -if [[ `uname -a` == *"ARCH"* ]]; then - path_to_gcc5=$(which gcc-5) - if [ -x "$path_to_gcc5" ]; then - export CC="$path_to_gcc5" - else - echo "Warning: GCC v5 not found. CUDA v8 is incompatible with GCC v6, if installation fails, consider running \$ pacman -S gcc5" - fi -fi +# Clean previous builds +echo "Cleaning previous build artifacts..." +find . -name "build" -type d -path "*/pkg/*" -exec rm -rf {} + 2>/dev/null +find . -name "*.so" -path "*/pkg/*" -delete 2>/dev/null +find . -name "*.so" -path "*/extra/*" -delete 2>/dev/null -echo "Installing Lua version: ${TORCH_LUA_VERSION}" -mkdir -p install -mkdir -p build -cd build - - -export CC="/usr/bin/riscv64-linux-gnu-gcc" -export CXX="riscv64-linux-gnu-g++" - -TOOLCHAIN_FILE="${THIS_DIR}/riscv64-toolchain.cmake" - -cmake .. -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" -DHAVE_DLOPEN=1 -DCMAKE_HAVE_DLOPEN=1 -DLUA_USE_DLOPEN=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 -(make 2>&1 >>$PREFIX/install.log || exit 1) && (make install 2>&1 >>$PREFIX/install.log || exit 1) -cd .. - -# Check for a CUDA install (using nvcc instead of nvidia-smi for cross-platform compatibility) -path_to_nvcc=$(which nvcc) -if [ $? == 1 ]; then { # look for it in /usr/local - if [[ -f /usr/local/cuda/bin/nvcc ]]; then { - path_to_nvcc=/usr/local/cuda/bin/nvcc - } - elif [[ -f /opt/cuda/bin/nvcc ]]; then { # default path for arch - path_to_nvcc=/opt/cuda/bin/nvcc - } fi -} fi - -# check if we are on mac and fix RPATH for local install -path_to_install_name_tool=$(which install_name_tool 2>/dev/null) -if [ -x "$path_to_install_name_tool" ] -then - if [ ${TORCH_LUA_VERSION} == "LUAJIT21" ] || [ ${TORCH_LUA_VERSION} == "LUAJIT20" ] ; then - install_name_tool -id ${PREFIX}/lib/libluajit.dylib ${PREFIX}/lib/libluajit.dylib - else - install_name_tool -id ${PREFIX}/lib/liblua.dylib ${PREFIX}/lib/liblua.dylib - fi -fi - -if [ -x "$path_to_nvcc" ] || [ -x "$path_to_nvidiasmi" ] -then - echo "Found CUDA on your machine. Installing CMake 3.6 modules to get up-to-date FindCUDA" - cd ${THIS_DIR}/cmake/3.6 && \ -(cmake -E make_directory build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX="${PREFIX}" \ - && make install) && echo "FindCuda bits of CMake 3.6 installed" || exit 1 +# Make sure native luarocks is available +if [ ! -f "$PREFIX/bin/luarocks" ]; then + echo "Error: Native luarocks not found. Please run './install.sh -b' first" + exit 1 fi - setup_lua_env_cmd=$($PREFIX/bin/luarocks path) eval "$setup_lua_env_cmd" -echo "Installing common Lua packages" -cd ${THIS_DIR}/extra/luafilesystem && $PREFIX/bin/luarocks make rockspecs/luafilesystem-1.6.3-1.rockspec || exit 1 -cd ${THIS_DIR}/extra/penlight && $PREFIX/bin/luarocks make penlight-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/extra/lua-cjson && $PREFIX/bin/luarocks make lua-cjson-2.1devel-1.rockspec || exit 1 - -echo "Installing core Torch packages" -cd ${THIS_DIR}/extra/luaffifb && $PREFIX/bin/luarocks make luaffi-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/sundown && $PREFIX/bin/luarocks make rocks/sundown-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/cwrap && $PREFIX/bin/luarocks make rocks/cwrap-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/paths && $PREFIX/bin/luarocks make rocks/paths-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/torch && $PREFIX/bin/luarocks make rocks/torch-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/dok && $PREFIX/bin/luarocks make rocks/dok-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/exe/trepl && $PREFIX/bin/luarocks make trepl-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/sys && $PREFIX/bin/luarocks make sys-1.1-0.rockspec || exit 1 -cd ${THIS_DIR}/pkg/xlua && $PREFIX/bin/luarocks make xlua-1.0-0.rockspec || exit 1 -cd ${THIS_DIR}/extra/moses && $PREFIX/bin/luarocks make rockspec/moses-1.6.1-1.rockspec || exit 1 -cd ${THIS_DIR}/extra/nn && $PREFIX/bin/luarocks make rocks/nn-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/extra/graph && $PREFIX/bin/luarocks make rocks/graph-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/extra/nngraph && $PREFIX/bin/luarocks make nngraph-scm-1.rockspec || exit 1 -cd ${THIS_DIR}/pkg/image && $PREFIX/bin/luarocks make image-1.1.alpha-0.rockspec || exit 1 -cd ${THIS_DIR}/pkg/optim && $PREFIX/bin/luarocks make optim-1.0.5-0.rockspec || exit 1 +mkdir -p "$RISCV_PREFIX/lib/lua/5.1" +mkdir -p "$RISCV_PREFIX/share/lua/5.1" -if [ -x "$path_to_nvcc" ] -then - echo "Found CUDA on your machine. Installing CUDA packages" - cd ${THIS_DIR}/extra/cutorch && $PREFIX/bin/luarocks make rocks/cutorch-scm-1.rockspec || exit 1 - cd ${THIS_DIR}/extra/cunn && $PREFIX/bin/luarocks make rocks/cunn-scm-1.rockspec || exit 1 -fi - -# Optional packages -echo "Installing optional Torch packages" -cd ${THIS_DIR}/pkg/gnuplot && $PREFIX/bin/luarocks make rocks/gnuplot-scm-1.rockspec -cd ${THIS_DIR}/exe/env && $PREFIX/bin/luarocks make env-scm-1.rockspec -cd ${THIS_DIR}/extra/nnx && $PREFIX/bin/luarocks make nnx-0.1-1.rockspec -cd ${THIS_DIR}/exe/qtlua && $PREFIX/bin/luarocks make rocks/qtlua-scm-1.rockspec -cd ${THIS_DIR}/pkg/qttorch && $PREFIX/bin/luarocks make rocks/qttorch-scm-1.rockspec -cd ${THIS_DIR}/extra/threads && $PREFIX/bin/luarocks make rocks/threads-scm-1.rockspec -cd ${THIS_DIR}/extra/argcheck && $PREFIX/bin/luarocks make rocks/argcheck-scm-1.rockspec - -# Optional CUDA packages -if [ -x "$path_to_nvcc" ] -then - echo "Found CUDA on your machine. Installing optional CUDA packages" - cd ${THIS_DIR}/extra/cudnn && $PREFIX/bin/luarocks make cudnn-scm-1.rockspec -fi - -export PATH=$OLDPATH # Restore anaconda distribution if we took it out. - -# Add C libs to LUA_CPATH -if [[ `uname` == "Darwin" ]]; then - CLIB_LUA_CPATH=$PREFIX/lib/?.dylib -else - CLIB_LUA_CPATH=$PREFIX/lib/?.so -fi - -cat <$PREFIX/bin/torch-activate -$setup_lua_env_cmd -export PATH=$PREFIX/bin:\$PATH -export LD_LIBRARY_PATH=$PREFIX/lib:\$LD_LIBRARY_PATH -export DYLD_LIBRARY_PATH=$PREFIX/lib:\$DYLD_LIBRARY_PATH -export LUA_CPATH='$CLIB_LUA_CPATH;'\$LUA_CPATH -EOF -chmod +x $PREFIX/bin/torch-activate - -if [[ $SKIP_RC == 1 ]]; then - exit 0 -fi - -RC_FILE=0 -DEFAULT=yes -if [[ $(echo $SHELL | grep bash) ]]; then - RC_FILE=$HOME/.bashrc -elif [[ $(echo $SHELL | grep zsh) ]]; then - RC_FILE=$HOME/.zshrc -else - echo " - -Non-standard shell $SHELL detected. You might want to -add the following lines to your shell profile: - -. $PREFIX/bin/torch-activate -" -fi - -WRITE_PATH_TO_PROFILE=0 -if [[ $BATCH_INSTALL == 0 ]]; then - if [ -f "$RC_FILE" ]; then - echo " - -Do you want to automatically prepend the Torch install location -to PATH and LD_LIBRARY_PATH in your $RC_FILE? (yes/no) -[$DEFAULT] >>> " - read input - if [[ $input == "" ]]; then - input=$DEFAULT - fi +# Cross-compilation environment +export CC="riscv64-linux-gnu-gcc" +export CXX="riscv64-linux-gnu-g++" +export AR="riscv64-linux-gnu-ar" +export RANLIB="riscv64-linux-gnu-ranlib" - is_yes() { - yesses={y,Y,yes,Yes,YES} - if [[ $yesses =~ $1 ]]; then - echo 1 - fi - } +export CFLAGS="-march=rv64gc -mabi=lp64d -fPIC -DNO_AVX -DNO_SSE" +export CXXFLAGS="-march=rv64gc -mabi=lp64d -fPIC -DNO_AVX -DNO_SSE" +export LDFLAGS="-latomic -lm -lpthread" - if [[ $(is_yes $input) ]]; then - WRITE_PATH_TO_PROFILE=1 - fi - fi +# Test cross-compiler +echo 'int main(){return 0;}' | $CC $CFLAGS -x c - -o test_riscv 2>/dev/null +if [ -f test_riscv ]; then + rm -f test_riscv else - if [[ "$RC_FILE" ]]; then - WRITE_PATH_TO_PROFILE=1 - fi + echo "ERROR: Cross-compiler not working. Install with: sudo apt install gcc-riscv64-linux-gnu" + exit 1 fi -if [[ $WRITE_PATH_TO_PROFILE == 1 ]]; then - echo " - -. $PREFIX/bin/torch-activate" >> "$RC_FILE" - echo " - -. $PREFIX/bin/torch-activate" >> "$HOME"/.profile - -else - echo " - -Not updating your shell profile. -You might want to -add the following lines to your shell profile: - -. $PREFIX/bin/torch-activate -" -fi +# +## Optional packages +#echo "Installing optional Torch packages" +#cd ${THIS_DIR}/pkg/gnuplot && $PREFIX/bin/luarocks make rocks/gnuplot-scm-1.rockspec +#cd ${THIS_DIR}/exe/env && $PREFIX/bin/luarocks make env-scm-1.rockspec +#cd ${THIS_DIR}/extra/nnx && $PREFIX/bin/luarocks make nnx-0.1-1.rockspec +#cd ${THIS_DIR}/exe/qtlua && $PREFIX/bin/luarocks make rocks/qtlua-scm-1.rockspec +#cd ${THIS_DIR}/pkg/qttorch && $PREFIX/bin/luarocks make rocks/qttorch-scm-1.rockspec +#cd ${THIS_DIR}/extra/threads && $PREFIX/bin/luarocks make rocks/threads-scm-1.rockspec +#cd ${THIS_DIR}/extra/argcheck && $PREFIX/bin/luarocks make rocks/argcheck-scm-1.rockspec + + +#echo "" +#echo "=== Building TH Library using bare cmake ===" +#cd pkg/torch/lib/TH +#mkdir -p build-riscv +#cd build-riscv +# +#cmake .. \ +# -DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \ +# -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ +# -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ +# -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ +# -DCMAKE_INSTALL_PREFIX="$RISCV_PREFIX" \ +# -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \ +# -DWITH_OPENMP=OFF -DWITH_SSE=OFF -DWITH_AVX=OFF \ +# -DTH_HAVE_X86_SIMD=OFF 2>&1 | tee /tmp/th-build.log +# +#make -j2 VERBOSE=1 2>&1 | tee -a /tmp/th-build.log && make install 2>&1 | tee -a /tmp/th-build.log +# +#cd "$THIS_DIR" +# +#setup_lua_env_cmd=$($PREFIX/bin/luarocks path) +#eval "$setup_lua_env_cmd" diff --git a/riscv64-toolchain.cmake b/riscv64-toolchain.cmake index e2b4c25aa..e5b7bd07a 100644 --- a/riscv64-toolchain.cmake +++ b/riscv64-toolchain.cmake @@ -1,5 +1,34 @@ + set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR riscv64) -set(CMAKE_C_COMPILER /usr/bin/riscv64-linux-gnu-gcc) +set(CMAKE_C_COMPILER /usr/bin/riscv64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER /usr/bin/riscv64-linux-gnu-g++) + +# Set position independent code by default +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Set the flags properly - don't reference CMAKE_C_FLAGS in its own definition +set(CMAKE_C_FLAGS "-march=rv64gc -mabi=lp64d -fPIC") +set(CMAKE_CXX_FLAGS "-march=rv64gc -mabi=lp64d -fPIC") + +# Force shared libraries +set(BUILD_SHARED_LIBS OFF) + +# For cross-compilation, we need to set the sysroot and find root path +set(CMAKE_FIND_ROOT_PATH /usr/riscv64-linux-gnu) + +# Search for programs in host directories +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# Search for libraries and headers in target directories +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# Disable PIE for cross-compilation (can cause linking issues) +set(CMAKE_EXE_LINKER_FLAGS "-no-pie") +set(CMAKE_SHARED_LINKER_FLAGS "") + +# Make sure we link against libatomic for RISC-V +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -latomic") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -latomic") \ No newline at end of file From 8a62f54c084e0415ef942d37f26d5c39328c9a71 Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Tue, 9 Sep 2025 21:22:59 +0200 Subject: [PATCH 5/6] changes to submodules and crosscompilation scripts --- changes-submodules.patch | 93070 --------------------------- install-x86.sh | 75 + riscv64_cross_compilation_setup.sh | 203 + test.lua | 22 + torch-static.patch | 733 + 5 files changed, 1033 insertions(+), 93070 deletions(-) delete mode 100644 changes-submodules.patch create mode 100755 install-x86.sh create mode 100755 riscv64_cross_compilation_setup.sh create mode 100644 test.lua create mode 100644 torch-static.patch diff --git a/changes-submodules.patch b/changes-submodules.patch deleted file mode 100644 index 2ed3153cb..000000000 --- a/changes-submodules.patch +++ /dev/null @@ -1,93070 +0,0 @@ -Submodule exe/qtlua 8b80419..b268c3b: -diff --git a/exe/qtlua/doc/qtgui.md b/exe/qtlua/doc/qtgui.md -index 9985c94..025006b 100644 ---- a/exe/qtlua/doc/qtgui.md -+++ b/exe/qtlua/doc/qtgui.md -@@ -345,7 +345,7 @@ for selecting an existing file. - * Argument `opt` are the [file dialog options](http://doc.trolltech.com/4.4/qfiledialog.html#Option-enum). - - The function returns a `qt.QString` containing the selected file name --and a `qt.QString` contaning the selected filter. -+and a `qt.QString` containing the selected filter. - -
    - ### qt.QFileDialog.getOpenFileNames([p,[c,[d,[f,[s,[opt]]]]]]) ### -diff --git a/exe/qtlua/doc/qtide.md b/exe/qtlua/doc/qtide.md -index b19b5b9..de71a2b 100644 ---- a/exe/qtlua/doc/qtide.md -+++ b/exe/qtlua/doc/qtide.md -@@ -67,7 +67,7 @@ Not yet implemented - - Starts the QLua Integrated Development Environment (IDE) - and ensure that the main window is visible. --This function is called implicitely when program `qlua` -+This function is called implicitly when program `qlua` - is executed with option `-ide`. - - The optional argument `style` is a string -@@ -98,7 +98,7 @@ a graphic console. - - ### qt.QLuaIde ### - --Object `qt.qLuaIde` represetns the global state of the IDE. -+Object `qt.qLuaIde` represents the global state of the IDE. - This is the unique instance of class `qt.QLuaIde` - which inherits [qt.QObject](qt.md#qobject). - Most of its capabilities are conveniently -diff --git a/exe/qtlua/packages/qtwidget/qtlualistener.cpp b/exe/qtlua/packages/qtwidget/qtlualistener.cpp -index 6f07e00..3c2097d 100644 ---- a/exe/qtlua/packages/qtwidget/qtlualistener.cpp -+++ b/exe/qtlua/packages/qtwidget/qtlualistener.cpp -@@ -21,8 +21,8 @@ f_enumerator(const char *s) - static const QMetaObject* qt() { return &staticQtMetaObject; } - }; - const QMetaObject *mo = QFakeObject::qt(); -- int index = mo->indexOfEnumerator(s); -- if (mo >= 0) -+ int index = (mo) ? mo->indexOfEnumerator(s) : -1; -+ if (index >= 0) - return mo->enumerator(index); - return QMetaEnum(); - } -diff --git a/exe/qtlua/packages/qtwidget/qtwidget.cpp b/exe/qtlua/packages/qtwidget/qtwidget.cpp -index 8567c69..29e3402 100644 ---- a/exe/qtlua/packages/qtwidget/qtwidget.cpp -+++ b/exe/qtlua/packages/qtwidget/qtwidget.cpp -@@ -38,8 +38,8 @@ - static QMetaEnum - f_enumerator(const char *s, const QMetaObject *mo) - { -- int index = mo->indexOfEnumerator(s); -- if (mo >= 0) -+ int index = (mo) ? mo->indexOfEnumerator(s) : -1; -+ if (index >= 0) - return mo->enumerator(index); - return QMetaEnum(); - } -Submodule extra/cunn 27d79db..1ae6aa0: -diff --git a/extra/cunn/README.md b/extra/cunn/README.md -index 22f490f..6d8609c 100644 ---- a/extra/cunn/README.md -+++ b/extra/cunn/README.md -@@ -87,7 +87,7 @@ local a = torch.CudaTensor(1000,1000):uniform() - a:add(1) - ``` - ... the GPU kernel to add 1 will only be scheduled for launch by `a:add(1)`. It might not have completed yet, or --even have reached the GPU, at the time that the `a:add(1)` instructions has completed -+even have reached the GPU, at the time that the `a:add(1)` returns - * therefore for running wall-clock timings, you should call `cutorch.synchronize()` before each timecheck - point: - ```lua -diff --git a/extra/cunn/lib/THCUNN/BatchNormalization.cu b/extra/cunn/lib/THCUNN/BatchNormalization.cu -index 125e3ff..e6717c7 100644 ---- a/extra/cunn/lib/THCUNN/BatchNormalization.cu -+++ b/extra/cunn/lib/THCUNN/BatchNormalization.cu -@@ -5,7 +5,7 @@ - - #include "THCDeviceTensor.cuh" - #include "THCDeviceTensorUtils.cuh" -- -+#include "THCDeviceUtils.cuh" - const int WARP_SIZE = 32; - - // The maximum number of threads in a block -@@ -80,7 +80,7 @@ template - static __device__ __forceinline__ T warpSum(T val) { - #if __CUDA_ARCH__ >= 300 - for (int i = 0; i < getMSB(WARP_SIZE); ++i) { -- val += __shfl_xor(val, 1 << i, WARP_SIZE); -+ val += WARP_SHFL_XOR(val, 1 << i, WARP_SIZE); - } - #else - __shared__ T values[MAX_BLOCK_SIZE]; -diff --git a/extra/cunn/lib/THCUNN/CMakeLists.txt b/extra/cunn/lib/THCUNN/CMakeLists.txt -index d4777bf..6bc5813 100644 ---- a/extra/cunn/lib/THCUNN/CMakeLists.txt -+++ b/extra/cunn/lib/THCUNN/CMakeLists.txt -@@ -29,6 +29,15 @@ IF(NOT CUDA_FOUND) - FIND_PACKAGE(CUDA 6.5 REQUIRED) - ENDIF() - -+IF ($ENV{TH_BINARY_BUILD}) -+ MESSAGE(STATUS "TH_BINARY_BUILD detected. Statically linking libstdc++") -+ SET(CMAKE_CXX_FLAGS "-static-libstdc++ ${CMAKE_CXX_FLAGS}") -+ IF (UNIX AND NOT APPLE) -+ # hiding statically linked library symbols, this flag is not available for the linker under MACOSX -+ SET(CMAKE_CXX_FLAGS "-Wl,--exclude-libs,libstdc++.a ${CMAKE_CXX_FLAGS}") -+ ENDIF(UNIX AND NOT APPLE) -+ENDIF() -+ - # Detect CUDA architecture and get best NVCC flags - IF(NOT COMMAND CUDA_SELECT_NVCC_ARCH_FLAGS OR MSVC) - INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/select_compute_arch.cmake) -@@ -46,6 +55,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.9.3") - endif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - -+if(CUDA_VERSION VERSION_GREATER "8.0") -+ LIST(APPEND CUDA_NVCC_FLAGS "-D__CUDA_NO_HALF_OPERATORS__") -+endif(CUDA_VERSION VERSION_GREATER "8.0") -+ - IF(MSVC) - LIST(APPEND CUDA_NVCC_FLAGS "-Xcompiler /wd4819") - ADD_DEFINITIONS(-DTH_EXPORTS) -diff --git a/extra/cunn/lib/THCUNN/FeatureLPPooling.cu b/extra/cunn/lib/THCUNN/FeatureLPPooling.cu -new file mode 100644 -index 0000000..4ad190f ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/FeatureLPPooling.cu -@@ -0,0 +1,653 @@ -+#include "THCUNN.h" -+#include "THCAtomics.cuh" -+#include "THCDeviceTensor.cuh" -+#include "THCDeviceTensorUtils.cuh" -+#include "THCDeviceUtils.cuh" -+#include "THCNumerics.cuh" -+#include "THCTensorTypeUtils.cuh" -+ -+#define OUTPUT_FEATURES_PER_THREAD 32 -+#define MAX_WARPS_PER_RUN 4 -+ -+namespace detail { -+ -+/// Various utilities for dealing with arrays of values which are -+/// maintained in thread-local registers. All accesses are done in such -+/// a way such that the index is statically known, which preserves the -+/// compiler's ability to allocate the values to registers, as opposed -+/// to local memory. -+template -+struct RegisterUtils { -+ /// Register shifting: move elements towards the beginning of the -+ /// array (towards 0) by `Shift` places: -+ /// arr[i] = arr[i + Shift] -+ /// The `Shift` elements at the end are left unchanged. -+ template -+ __device__ __forceinline__ static void shiftLeft(T arr[N]) { -+ // e.g., N = 5, Shift = 2: -+ // 0 1 2 3 4 becomes => -+ // 2 3 4 3 4 (last are unchanged) -+#pragma unroll -+ for (int i = 0; i < N - Shift; ++i) { -+ arr[i] = arr[i + Shift]; -+ } -+ } -+}; -+ -+template -+__device__ __forceinline__ -+int getDim1Point(const THCDeviceTensor& input) { -+ int threadPoint = blockIdx.x * blockDim.x + threadIdx.x; -+ return threadPoint / input.getSize(3); -+} -+ -+template -+__device__ __forceinline__ -+int getDim2Point(const THCDeviceTensor& input) { -+ int threadPoint = blockIdx.x * blockDim.x + threadIdx.x; -+ return threadPoint % input.getSize(3); -+} -+ -+__device__ __forceinline__ -+int getStartOutputFeature() { -+ return blockIdx.y * OUTPUT_FEATURES_PER_THREAD; -+} -+ -+template -+__device__ __forceinline__ -+int getEndOutputFeature(const THCDeviceTensor& output) { -+ return min((blockIdx.y + 1) * OUTPUT_FEATURES_PER_THREAD, output.getSize(1)); -+} -+ -+__device__ __forceinline__ -+int getBatch() { -+ return blockIdx.z; -+} -+ -+// All of these functions that follow are MathOps; they are template -+// parameters so L2 can be more efficiently implemented -+// template -+// typedef T (*MathOp)(const T in, const T arg); -+ -+template -+__device__ __forceinline__ T power2(const T in, const T power) { -+ return THCNumerics::mul(in, in); -+} -+ -+template -+__device__ __forceinline__ T root2(const T in, const T power) { -+ return THCNumerics::sqrt(in); -+} -+ -+template -+__device__ __forceinline__ T powerGrad2(const T in, const T power) { -+ return in; -+} -+ -+template -+__device__ __forceinline__ T powerN(const T in, const T power) { -+ return THCNumerics::pow(in, power); -+} -+ -+template -+__device__ __forceinline__ T rootN(const T in, const T power) { -+ const T invPower = THCNumerics::cinv(power); -+ return THCNumerics::pow(in, invPower); -+} -+ -+template -+__device__ __forceinline__ T powerGradN(const T in, const T power) { -+ return THCNumerics::pow(in, -+ THCNumerics::sub(power, -+ ScalarConvert::to(1))); -+} -+ -+// Input is of the form: -+// [batch][feature dim][optional dim 1][optional dim 2] -+template -+__global__ void -+featureLPPoolingUpdateOutput(const THCDeviceTensor input, -+ THCDeviceTensor output, -+ T power) { -+ // What non-feature points is this thread handling? -+ int dim1Point = getDim1Point(input); -+ int dim2Point = getDim2Point(input); -+ -+ if (dim1Point >= input.getSize(2) || dim2Point >= input.getSize(3)) { -+ // This thread in the warp is out of bounds -+ return; -+ } -+ -+ // What feature points is this thread handling? -+ int startOutputFeature = getStartOutputFeature(); -+ int endOutputFeature = getEndOutputFeature(output); -+ int startInputFeature = startOutputFeature * Stride; -+ -+ // What batch points is this thread handling? -+ int batch = getBatch(); -+ -+ // If stride >= width, then there is no loaded data reuse. -+ // If stride > 1 and stride < width, then shift by stride, since we -+ // can reuse Width - Stride elements from the previous round. -+ // e.g., width = 5, stride = 2, -+ // output 0 uses input 0 1 2 3 4 -+ // output 1 uses input 2 3 4 5 6 (inputs 2 - 4 are reused, i.e., 5 - -+ // 2 elements are reused, and we have to shift the array by 2) -+ // -+ // e.g., width = 5, stride = 3, -+ // output 0 uses input 0 1 2 3 4 -+ // output 1 uses input 3 4 5 6 7 (inputs 3 - 4 are reused, i.e., 5 - 3 -+ // elements are reused, and we have to shift the array by 3) -+ -+ // Valid only pooling: load Width elements from input (Width - -+ // Stride is handled here, at the top of the loop we handle the -+ // remaining Stride elements). We already verified that the input is -+ // larger than the width. -+ // `in` will contain the input values ^ power. -+ T in[Width]; -+ -+#pragma unroll -+ for (int i = 0; i < Width - Stride; ++i) { -+ const T data = -+ input[batch][startInputFeature + i][dim1Point][dim2Point]; -+ in[i] = PowerFunc(data, power); -+ } -+ -+ for (int outputFeature = startOutputFeature; -+ outputFeature < endOutputFeature; -+ ++outputFeature) { -+ // If Stride < Width, we're loading Stride new values starting at -+ // Width - Stride -+ // If Stride >= Width, we're loading Width new values starting at 0 -+ if (Stride < Width) { -+ int nextInputFeature = outputFeature * Stride + Width - Stride; -+ -+#pragma unroll -+ for (int i = 0; i < Stride; ++i) { -+ const T data = -+ input[batch][nextInputFeature + i][dim1Point][dim2Point]; -+ in[Width - Stride + i] = PowerFunc(data, power); -+ } -+ } else { -+ int nextInputFeature = outputFeature * Stride; -+ -+#pragma unroll -+ for (int i = 0; i < Width; ++i) { -+ T data = input[batch][nextInputFeature + i][dim1Point][dim2Point]; -+ in[i] = PowerFunc(data, power); -+ } -+ } -+ -+ // Calculate the new output feature -+ T val = ScalarConvert::to(0); -+ for (int i = 0; i < Width; ++i) { -+ val = THCNumerics::add(val, in[i]); -+ } -+ -+ val = RootFunc(val, power); -+ output[batch][outputFeature][dim1Point][dim2Point] = val; -+ -+ if (Stride < Width) { -+ // Shift registers for calculating the next point -+ RegisterUtils::shiftLeft(in); -+ } -+ } -+} -+ -+// forward pass: f(a, ..., z) = (a^p + ... + z^p)^(1 / p) -+// for bprop: -+// partial df(a, ... z)/da = a^(p - 1) * (a^p + ... + z^p)^((1 / p) - 1) = -+// a^(p - 1) * 1/(f(a, ..., z)^(p - 1)) = (a / f(a, ..., z))^(p - 1) -+// -+// example: for p = 2, df(a, ..., z)/da = a / f(a, ..., z) -+// example: for p = 3, df(a, ..., z)/da = (a / f(a, ..., z))^2 -+// -+// PowerGradFunc implements x^(p - 1) -+template -+__global__ void -+featureLPPoolingUpdateGradInput(const THCDeviceTensor gradOutput, -+ const THCDeviceTensor input, -+ const THCDeviceTensor output, -+ THCDeviceTensor gradInput, -+ T power) { -+ // What non-feature points is this thread handling? -+ int dim1Point = getDim1Point(input); -+ int dim2Point = getDim2Point(input); -+ -+ if (dim1Point >= input.getSize(2) || dim2Point >= input.getSize(3)) { -+ // This thread in the warp is out of bounds -+ return; -+ } -+ -+ // What feature points is this thread handling? [start, end) -+ int startOutputFeature = getStartOutputFeature(); -+ int endOutputFeature = getEndOutputFeature(output); -+ -+ // What is the first input point that the output features depend -+ // upon? [start, end) -+ int startInputFeature = startOutputFeature * Stride; -+ int endInputFeature = endOutputFeature * Stride; -+ -+ // What batch points is this thread handling? -+ int batch = getBatch(); -+ -+ // atomicAdd into gradInput is slow, avoid it where possible. -+ // We can do this because there is a range of gradInput elements -+ // that we are updating exclusively. This is how we find it -+ // -+ // width = 3 stride = 1 example: -+ // ------------------------------ -+ // startOutputFeature for this thread -+ // | -+ // | -+ // previous thread's output feature -+ // | | -+ // | | gradOutput -+ // __v____v___________________ -+ // | | | | | | -+ // --------------------------- -+ // |\ \_____ -+ // | \__ \ gradInput -+ // __v____v____v_____________ -+ // | | | | | | -+ // --------------------------- -+ // A A -+ // | | -+ // startInputFeature -+ // | -+ // exclusiveStartInputFeature -+ // -+ // exclusiveStartInputFeature is the first input feature that we can -+ // write into exclusively; the one right before it overlaps with -+ // updates from a previous thread and thus has to use atomicAdd. -+ int exclusiveStartInputFeature = -+ startInputFeature == 0 ? -+ // no thread is before ourselves -+ 0 : -+ // there is a thread before ourselves -+ startInputFeature + (Width - 1) * Stride; -+ -+ // Similarly, exclusiveEndInputFeature is the last input feature -+ // that we can write into exclusively, since we might be overlapping -+ // with the following thread -+ int exclusiveEndInputFeature = -+ endOutputFeature == output.getSize(1) ? -+ // no thread is after ourselves -+ endInputFeature + (Width - 1) * Stride : -+ // there is a thread after ourselves -+ endInputFeature; -+ -+ // As with updateOutput preload input elements, except no need to -+ // transform them -+ T in[Width]; -+#pragma unroll -+ for (int i = 0; i < Width - Stride; ++i) { -+ in[i] = input[batch][startInputFeature + i][dim1Point][dim2Point]; -+ } -+ -+ for (int outputFeature = startOutputFeature; -+ outputFeature < endOutputFeature; -+ ++outputFeature) { -+ // As with updateOutput load the subsequent input elements that we -+ // need, except no need to transform them -+ // -+ // If Stride < Width, we're loading Stride new values starting at -+ // Width - Stride -+ // If Stride >= Width, we're loading Width new values starting at 0 -+ if (Stride < Width) { -+ int nextInputFeature = outputFeature * Stride + Width - Stride; -+ -+#pragma unroll -+ for (int i = 0; i < Stride; ++i) { -+ in[Width - Stride + i] = -+ input[batch][nextInputFeature + i][dim1Point][dim2Point]; -+ } -+ } else { -+ int nextInputFeature = outputFeature * Stride; -+ -+#pragma unroll -+ for (int i = 0; i < Width; ++i) { -+ in[i] = input[batch][nextInputFeature + i][dim1Point][dim2Point]; -+ } -+ } -+ -+ // A given output feature gradient contributes to `Width` input -+ // gradients -+ const T gradOut = -+ gradOutput[batch][outputFeature][dim1Point][dim2Point]; -+ -+ // Load output (f(x_is)). It is possible that this is zero, in -+ // which case we'll ignore this point. -+ T out = output[batch][outputFeature][dim1Point][dim2Point]; -+ if (THCNumerics::eq(out, ScalarConvert::to(0))) { -+ continue; -+ } -+ -+ int curStartInputFeature = outputFeature * Stride; -+ int curEndInputFeature = outputFeature * Stride + Width - 1; -+ -+ if (curStartInputFeature >= exclusiveStartInputFeature && -+ curEndInputFeature < exclusiveEndInputFeature) { -+ // This thread is exclusively responsible for updating these -+ // input points, so we need not make the addition atomic -+ for (int i = 0; i < Width; ++i) { -+ int inputFeature = outputFeature * Stride + i; -+ -+ // Calculate grad * (x_i / f(x_is))^(p - 1) -+ const T val = THCNumerics::mul( -+ gradOut, -+ PowerGradFunc(THCNumerics::div(in[i], out), power)); -+ -+ gradInput[batch][inputFeature][dim1Point][dim2Point] = -+ THCNumerics::add( -+ gradInput[batch][inputFeature][dim1Point][dim2Point], val); -+ } -+ } else { -+ // Handle start and end boundary cases: potential overlap with -+ // other threads -+ for (int i = 0; i < Width; ++i) { -+ int inputFeature = outputFeature * Stride + i; -+ -+ // Calculate grad * (x_i / f(x_is))^(p - 1) -+ T val = THCNumerics::mul( -+ gradOut, -+ PowerGradFunc(THCNumerics::div(in[i], out), power)); -+ -+ // We don't overlap other threads for this range -+ if (inputFeature >= exclusiveStartInputFeature && -+ inputFeature < exclusiveEndInputFeature) { -+ gradInput[batch][inputFeature][dim1Point][dim2Point] -+ = THCNumerics::add( -+ gradInput[batch][inputFeature][dim1Point][dim2Point], val); -+ } else { -+ // We are potentially overlapping with threads handling -+ // features before ourselves, so these need to be added atomically -+ atomicAdd(&gradInput[batch][inputFeature][dim1Point][dim2Point], -+ val); -+ } -+ } -+ } -+ -+ if (Stride < Width) { -+ // Shift registers for calculating the next point -+ RegisterUtils::shiftLeft(in); -+ } -+ } -+} -+ -+} // namespace detail -+ -+inline int lpPoolingOutputSize(int inputSize, int width, int stride) { -+ return ((inputSize - width) / stride) + 1; -+} -+ -+template -+bool -+runFeatureLPPoolingUpdateOutput(THCState* state, -+ const THCDeviceTensor& input, -+ THCDeviceTensor& output, -+ float power, int width, int stride) { -+ cudaStream_t stream = -+ THCState_getCurrentStream(state); -+ const cudaDeviceProp* deviceProperties = -+ THCState_getCurrentDeviceProperties(state); -+ -+ int outputFeatures = ((input.getSize(1) - width) / stride) + 1; -+ -+ THAssert(input.getSize(0) == output.getSize(0)); -+ THAssert(outputFeatures == output.getSize(1)); -+ THAssert(input.getSize(1) >= width); -+ -+ THAssert(input.getSize(2) == output.getSize(2)); -+ THAssert(input.getSize(3) == output.getSize(3)); -+ THAssert(power > 0.0f); -+ THAssert(width >= 1); -+ THAssert(stride >= 1); -+ -+ // Split non-features among threads and grid x -+ int totalNonFeatureSize = input.getSize(2) * input.getSize(3); -+ int numWarps = -+ min(THCCeilDiv(totalNonFeatureSize, deviceProperties->warpSize), -+ MAX_WARPS_PER_RUN); -+ int blockSize = deviceProperties->warpSize * numWarps; -+ -+ // Split non-features among grid x -+ int nonFeatureSizeBlocks = THCCeilDiv(totalNonFeatureSize, blockSize); -+ -+ // Split features among grid y, up to a maximum number of features per thread -+ int featureBlocks = THCCeilDiv(outputFeatures, OUTPUT_FEATURES_PER_THREAD); -+ -+ // Split batch among grid z. -+ dim3 grid(nonFeatureSizeBlocks, featureBlocks, input.getSize(0)); -+ dim3 block(blockSize); -+ -+#define L2_STRIDE_CASE(STRIDE, WIDTH) \ -+ case STRIDE: \ -+ detail:: \ -+ featureLPPoolingUpdateOutput<<>>( \ -+ input, output, \ -+ ScalarConvert::to(power)); \ -+ return true; -+ -+#define L2_WIDTH_CASE(WIDTH) \ -+ case WIDTH: \ -+ switch (stride) { \ -+ L2_STRIDE_CASE(1, WIDTH); \ -+ L2_STRIDE_CASE(2, WIDTH); \ -+ L2_STRIDE_CASE(3, WIDTH); \ -+ L2_STRIDE_CASE(4, WIDTH); \ -+ } -+ -+#define LP_STRIDE_CASE(STRIDE, WIDTH) \ -+ case STRIDE: \ -+ detail:: \ -+ featureLPPoolingUpdateOutput<<>>( \ -+ input, output, \ -+ ScalarConvert::to(power)); \ -+ return true; -+ -+#define LP_WIDTH_CASE(WIDTH) \ -+ case WIDTH: \ -+ switch (stride) { \ -+ LP_STRIDE_CASE(1, WIDTH); \ -+ LP_STRIDE_CASE(2, WIDTH); \ -+ LP_STRIDE_CASE(3, WIDTH); \ -+ LP_STRIDE_CASE(4, WIDTH); \ -+ } -+ -+ if (power == 2.0f) { -+ switch (width) { -+ L2_WIDTH_CASE(2); -+ L2_WIDTH_CASE(3); -+ L2_WIDTH_CASE(4); -+ L2_WIDTH_CASE(5); -+ L2_WIDTH_CASE(6); -+ L2_WIDTH_CASE(7); -+ L2_WIDTH_CASE(8); -+ L2_WIDTH_CASE(9); -+ L2_WIDTH_CASE(10); -+ L2_WIDTH_CASE(11); -+ L2_WIDTH_CASE(12); -+ L2_WIDTH_CASE(13); -+ L2_WIDTH_CASE(14); -+ L2_WIDTH_CASE(15); -+ L2_WIDTH_CASE(16); -+ } -+ } else { -+ switch (width) { -+ LP_WIDTH_CASE(2); -+ LP_WIDTH_CASE(3); -+ LP_WIDTH_CASE(4); -+ LP_WIDTH_CASE(5); -+ LP_WIDTH_CASE(6); -+ LP_WIDTH_CASE(7); -+ LP_WIDTH_CASE(8); -+ LP_WIDTH_CASE(9); -+ LP_WIDTH_CASE(10); -+ LP_WIDTH_CASE(11); -+ LP_WIDTH_CASE(12); -+ LP_WIDTH_CASE(13); -+ LP_WIDTH_CASE(14); -+ LP_WIDTH_CASE(15); -+ LP_WIDTH_CASE(16); -+ } -+ } -+ -+ // Otherwise, we have an unhandled width and/or stride. -+ return false; -+ -+#undef L2_STRIDE_CASE -+#undef L2_WIDTH_CASE -+#undef LP_STRIDE_CASE -+#undef LP_WIDTH_CASE -+} -+ -+template -+bool -+runFeatureLPPoolingUpdateGradInput(THCState* state, -+ const THCDeviceTensor& gradOutput, -+ const THCDeviceTensor& input, -+ const THCDeviceTensor& output, -+ THCDeviceTensor& gradInput, -+ float power, int width, int stride) { -+ cudaStream_t stream = -+ THCState_getCurrentStream(state); -+ const cudaDeviceProp* deviceProperties = -+ THCState_getCurrentDeviceProperties(state); -+ -+ for (int i = 0; i < 4; ++i) { -+ THAssert(gradOutput.getSize(i) == output.getSize(i)); -+ THAssert(gradInput.getSize(i) == input.getSize(i)); -+ } -+ -+ int outputFeatures = ((input.getSize(1) - width) / stride) + 1; -+ -+ THAssert(gradInput.getSize(0) == gradOutput.getSize(0)); -+ THAssert(outputFeatures == gradOutput.getSize(1)); -+ THAssert(gradInput.getSize(1) >= width); -+ -+ THAssert(gradInput.getSize(2) == gradOutput.getSize(2)); -+ THAssert(gradInput.getSize(3) == gradOutput.getSize(3)); -+ THAssert(power > 0.0f); -+ THAssert(width >= 1); -+ THAssert(stride >= 1); -+ -+ // Different threads are potentially adding into overlapping input -+ // points, so we must clear out gradInput before continuing. -+ gradInput.zero(stream); -+ -+ // Split non-features among threads and grid x -+ int totalNonFeatureSize = input.getSize(2) * input.getSize(3); -+ int numWarps = -+ min(THCCeilDiv(totalNonFeatureSize, deviceProperties->warpSize), -+ MAX_WARPS_PER_RUN); -+ int blockSize = deviceProperties->warpSize * numWarps; -+ -+ // Split non-features among grid x -+ int nonFeatureSizeBlocks = THCCeilDiv(totalNonFeatureSize, blockSize); -+ -+ // Split features among grid y, up to a maximum number of features per thread -+ int featureBlocks = THCCeilDiv(outputFeatures, OUTPUT_FEATURES_PER_THREAD); -+ -+ // Split batch among grid z. -+ dim3 grid(nonFeatureSizeBlocks, featureBlocks, input.getSize(0)); -+ dim3 block(blockSize); -+ -+#define L2_STRIDE_CASE(STRIDE, WIDTH) \ -+ case STRIDE: \ -+ detail:: \ -+ featureLPPoolingUpdateGradInput< \ -+ T, WIDTH, STRIDE, detail::powerGrad2><<>>( \ -+ gradOutput, input, output, gradInput, \ -+ ScalarConvert::to(power)); \ -+ return true; -+ -+#define L2_WIDTH_CASE(WIDTH) \ -+ case WIDTH: \ -+ switch (stride) { \ -+ L2_STRIDE_CASE(1, WIDTH); \ -+ L2_STRIDE_CASE(2, WIDTH); \ -+ L2_STRIDE_CASE(3, WIDTH); \ -+ L2_STRIDE_CASE(4, WIDTH); \ -+ } -+ -+#define LP_STRIDE_CASE(STRIDE, WIDTH) \ -+ case STRIDE: \ -+ detail:: \ -+ featureLPPoolingUpdateGradInput< \ -+ T, WIDTH, STRIDE, detail::powerGradN><<>>( \ -+ gradOutput, input, output, gradInput, \ -+ ScalarConvert::to(power)); \ -+ return true; -+ -+#define LP_WIDTH_CASE(WIDTH) \ -+ case WIDTH: \ -+ switch (stride) { \ -+ LP_STRIDE_CASE(1, WIDTH); \ -+ LP_STRIDE_CASE(2, WIDTH); \ -+ LP_STRIDE_CASE(3, WIDTH); \ -+ LP_STRIDE_CASE(4, WIDTH); \ -+ } -+ -+ if (power == 2.0f) { -+ switch (width) { -+ L2_WIDTH_CASE(2); -+ L2_WIDTH_CASE(3); -+ L2_WIDTH_CASE(4); -+ L2_WIDTH_CASE(5); -+ L2_WIDTH_CASE(6); -+ L2_WIDTH_CASE(7); -+ L2_WIDTH_CASE(8); -+ L2_WIDTH_CASE(9); -+ L2_WIDTH_CASE(10); -+ L2_WIDTH_CASE(11); -+ L2_WIDTH_CASE(12); -+ L2_WIDTH_CASE(13); -+ L2_WIDTH_CASE(14); -+ L2_WIDTH_CASE(15); -+ L2_WIDTH_CASE(16); -+ } -+ } else { -+ switch (width) { -+ LP_WIDTH_CASE(2); -+ LP_WIDTH_CASE(3); -+ LP_WIDTH_CASE(4); -+ LP_WIDTH_CASE(5); -+ LP_WIDTH_CASE(6); -+ LP_WIDTH_CASE(7); -+ LP_WIDTH_CASE(8); -+ LP_WIDTH_CASE(9); -+ LP_WIDTH_CASE(10); -+ LP_WIDTH_CASE(11); -+ LP_WIDTH_CASE(12); -+ LP_WIDTH_CASE(13); -+ LP_WIDTH_CASE(14); -+ LP_WIDTH_CASE(15); -+ LP_WIDTH_CASE(16); -+ } -+ } -+ -+ // Otherwise, we have an unhandled width and/or stride. -+ return false; -+ -+#undef L2_STRIDE_CASE -+#undef L2_WIDTH_CASE -+#undef LP_STRIDE_CASE -+#undef LP_WIDTH_CASE -+} -+ -+#include "generic/FeatureLPPooling.cu" -+#include "THCGenerateFloatTypes.h" -diff --git a/extra/cunn/lib/THCUNN/LogSigmoid.cu b/extra/cunn/lib/THCUNN/LogSigmoid.cu -index bb9753a..dbdfea5 100644 ---- a/extra/cunn/lib/THCUNN/LogSigmoid.cu -+++ b/extra/cunn/lib/THCUNN/LogSigmoid.cu -@@ -7,7 +7,9 @@ template - struct logSigmoid_updateOutput_functor - { - __device__ void operator()(T *output, const T *input) const { -- *output = -THCNumerics::log(1.f + THCNumerics::exp(- *input)); -+ const T max = fmaxType(0.f, - *input); -+ const T z = THCNumerics::exp(-max) + THCNumerics::exp(-*input -max); -+ *output = -(max + THCNumerics::log(z)); - } - }; - -@@ -15,8 +17,15 @@ template - struct logSigmoid_updateGradInput_functor - { - __device__ void operator()(T *gradInput, const T *input, const T *gradOutput) const { -- const T z = THCNumerics::exp(- *input); -- *gradInput = *gradOutput * z / (1.f + z); -+ const T max = fmaxType(0.f, -*input); -+ const T z = THCNumerics::exp(-max) + THCNumerics::exp(-*input -max); -+ T max_deriv = 0.f; -+ T sign = -1.f; -+ if (*input < 0.f){ -+ max_deriv = -1.f; -+ sign = 1.f; -+ } -+ *gradInput = *gradOutput * (-max_deriv - sign*((z - 1.f)/z)); - } - }; - -@@ -25,11 +34,14 @@ template <> - struct logSigmoid_updateOutput_functor { - __device__ __forceinline__ void operator()(half* output, const half *input) const { - #ifdef CUDA_HALF_INSTRUCTIONS -- const half one = __float2half(1.f); -- *output = __hneg(THCNumerics::log(one + THCNumerics::exp(__hneg(*input)))); -+ const half max = fmaxType(__float2half(0.f), __hneg(*input)); -+ const half z = THCNumerics::exp(__hneg(max)) + THCNumerics::exp(__hneg(*input) - max); -+ *output = __hneg(max + THCNumerics::log(z)); - #else - float in = __half2float(*input); -- *output = __float2half(-THCNumerics::log(1.f + THCNumerics::exp(-in))); -+ float max = fmaxType(0.f, -in); -+ float z = THCNumerics::exp(-max) + THCNumerics::exp(-in - max); -+ *output = __float2half(-(max + THCNumerics::log(z))); - #endif - } - }; -@@ -39,12 +51,28 @@ struct logSigmoid_updateGradInput_functor { - __device__ __forceinline__ void operator()(half* gradInput, const half *input, const half *gradOutput) const { - #ifdef CUDA_HALF_INSTRUCTIONS - const half one = __float2half(1.f); -- const half in_exp = THCNumerics::exp(__hneg(*input)); -- *gradInput = hdiv(__hmul(*gradOutput, in_exp), __hadd(one, in_exp)); -+ const half zero = __float2half(0.f); -+ const half max = fmaxType(zero, __hneg(*input)); -+ const half z = THCNumerics::exp(__hneg(max)) + THCNumerics::exp(__hneg(*input) - max); -+ half max_deriv = zero; -+ half sign = __hneg(one); -+ if(*input < zero){ -+ max_deriv = __hneg(one); -+ sign = one; -+ } -+ *gradInput = __hmul(*gradOutput, (__hneg(max_deriv) - __hmul(sign, __hdiv(z - one, z)))); - #else -- const float in_exp = THCNumerics::exp(-(__half2float(*input))); -+ const float in = __half2float(*input); -+ const float max = fmaxType(0.f, -in); -+ const float z = THCNumerics::exp(-max) + THCNumerics::exp(-in - max); - const float go = __half2float(*gradOutput); -- *gradInput = __float2half(go * in_exp / (1.f + in_exp)); -+ float max_deriv = 0.f; -+ float sign = -1.f; -+ if(in < 0.f){ -+ max_deriv = -1.f; -+ sign = 1.f; -+ } -+ *gradInput = __float2half(go * (-max_deriv - sign*((z - 1.f)/z))); - #endif - } - }; -diff --git a/extra/cunn/lib/THCUNN/LookupTable.cu b/extra/cunn/lib/THCUNN/LookupTable.cu -index e626632..116639b 100644 ---- a/extra/cunn/lib/THCUNN/LookupTable.cu -+++ b/extra/cunn/lib/THCUNN/LookupTable.cu -@@ -105,7 +105,7 @@ __global__ void cunn_LookupTable_accGradParametersKernel( - int idx = blockIdx.x * 4 + threadIdx.y; - - // Each warp is responsible for an input into the LookupTable. -- // If the preceeding input has the same as this input, then the warp -+ // If the preceding input has the same as this input, then the warp - // exits immediately. The warp also processes subsequent inputs with the - // same value. - // -diff --git a/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu -new file mode 100644 -index 0000000..77d9811 ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/SpatialFullDilatedConvolution.cu -@@ -0,0 +1,8 @@ -+#include "THCUNN.h" -+#include "im2col.h" -+ -+#include "THCHalf.h" -+#include "THCHalfAutoNumerics.cuh" -+ -+#include "generic/SpatialFullDilatedConvolution.cu" -+#include "THCGenerateFloatTypes.h" -diff --git a/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu b/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu -index f584dcf..979d370 100644 ---- a/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu -+++ b/extra/cunn/lib/THCUNN/VolumetricAveragePooling.cu -@@ -9,8 +9,12 @@ - - template - __global__ void cuda_VolumetricAveragePooling_updateOutput( -- THCDeviceTensor input, THCDeviceTensor output, -- int kT, int kH, int kW, int dT, int dH, int dW, Acctype normFactor, int offsetZ) -+ THCDeviceTensor input, -+ THCDeviceTensor output, -+ int kT, int kH, int kW, -+ int dT, int dH, int dW, -+ int padT, int padH, int padW, -+ bool count_include_pad, int offsetZ) - { - int oCol = blockIdx.x * blockDim.x + threadIdx.x; - int oRow = blockIdx.y * blockDim.y + threadIdx.y; -@@ -21,32 +25,40 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( - { - Acctype sum = 0.0; - -- int iColumn = oCol * dW; -- int iRow = oRow * dH; -- int iFrame = oFrame * dT; -+ int tstart = oFrame * dT - padT; -+ int hstart = oRow * dH - padH; -+ int wstart = oCol * dW - padW; -+ int tend = min(tstart + kT, input.getSize(1) + padT); -+ int hend = min(hstart + kH, input.getSize(2) + padH); -+ int wend = min(wstart + kW, input.getSize(3) + padW); -+ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); -+ tstart = max(tstart, 0); -+ hstart = max(hstart, 0); -+ wstart = max(wstart, 0); -+ tend = min(tend, input.getSize(1)); -+ hend = min(hend, input.getSize(2)); -+ wend = min(wend, input.getSize(3)); -+ -+ Acctype divide_factor; -+ if (count_include_pad) -+ divide_factor = static_cast(pool_size); -+ else -+ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); - -- for (int frame = 0; frame < kT; ++frame) -+ int ti, hi, wi; -+ for (ti = tstart; ti < tend; ++ti) - { -- if (iFrame + frame < input.getSize(1)) -+ for (hi = hstart; hi < hend; ++hi) - { -- for (int row = 0; row < kH; ++row) -+ for (wi = wstart; wi < wend; ++wi) - { -- if (iRow + row < input.getSize(2)) -- { -- for (int column = 0; column < kW; ++column) -- { -- if (iColumn + column < input.getSize(3)) -- { -- Dtype val = input[slice][iFrame + frame][iRow + row][iColumn + column]; -- sum += val; -- } -- } -- } -+ Dtype val = input[slice][ti][hi][wi]; -+ sum += val; - } - } - } - -- output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum * normFactor); -+ output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum / divide_factor); - } - } - -@@ -54,9 +66,13 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( - // performance reasons. - // - template --__global__ void cuda_VolumetricAveragePooling_updateOutput( -- THCDeviceTensor input, THCDeviceTensor output, -- int kT, int kH, int dT, int dH, int dW, Acctype normFactor, int offsetZ) -+__global__ void cuda_VolumetricAveragePooling_updateOutput_fixedKW( -+ THCDeviceTensor input, -+ THCDeviceTensor output, -+ int kT, int kH, -+ int dT, int dH, int dW, -+ int padT, int padH, int padW, -+ bool count_include_pad, int offsetZ) - { - int oCol = blockIdx.x * blockDim.x + threadIdx.x; - int oRow = blockIdx.y * blockDim.y + threadIdx.y; -@@ -67,45 +83,54 @@ __global__ void cuda_VolumetricAveragePooling_updateOutput( - { - Acctype sum = 0.0; - -- int iColumn = oCol * dW; -- int iRow = oRow * dH; -- int iFrame = oFrame * dT; -+ int tstart = oFrame * dT - padT; -+ int hstart = oRow * dH - padH; -+ int wstart = oCol * dW - padW; -+ int tend = min(tstart + kT, input.getSize(1) + padT); -+ int hend = min(hstart + kH, input.getSize(2) + padH); -+ int wend = min(wstart + KERNEL_WIDTH, input.getSize(3) + padW); -+ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); -+ tstart = max(tstart, 0); -+ hstart = max(hstart, 0); -+ wstart = max(wstart, 0); -+ tend = min(tend, input.getSize(1)); -+ hend = min(hend, input.getSize(2)); -+ wend = min(wend, input.getSize(3)); - -- for (int frame = 0; frame < kT; ++frame) -+ Acctype divide_factor; -+ if (count_include_pad) -+ divide_factor = static_cast(pool_size); -+ else -+ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); -+ -+ int ti, hi, wi; -+ for (ti = tstart; ti < tend; ++ti) - { -- if (iFrame + frame < input.getSize(1)) -+ for (hi = hstart; hi < hend; ++hi) - { -- for (int row = 0; row < kH; ++row) -+ for (wi = wstart; wi < wend; ++wi) - { -- if (iRow + row < input.getSize(2)) -- { -- for (int column = 0; column < KERNEL_WIDTH; ++column) -- { -- if (iColumn + column < input.getSize(3)) -- { -- Dtype val = input[slice][iFrame + frame][iRow + row][iColumn + column]; -- sum += val; -- } -- } -- } -+ Dtype val = input[slice][ti][hi][wi]; -+ sum += val; - } - } - } - -- output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum * normFactor); -+ output[slice][oFrame][oRow][oCol] = ScalarConvert::to(sum / divide_factor); - } - } - --#define LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(KW) case KW: \ -- cuda_VolumetricAveragePooling_updateOutput<<>>( \ -- cudaInput, cudaOutput, kT, kH, dT, dH, dW, normFactor, offsetZ); \ -+#define LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(KW) case KW: \ -+ cuda_VolumetricAveragePooling_updateOutput_fixedKW<<>>( \ -+ cudaInput, cudaOutput, kT, kH, dT, dH, dW, padT, padH, padW, count_include_pad, offsetZ); \ - break - - template - __global__ void cuda_VolumetricAveragePooling_updateGradInput_Stride1( - THCDeviceTensor gradOutput, - THCDeviceTensor gradInput, -- int kT, int kH, int kW, Acctype normFactor, int offsetZ) -+ int kT, int kH, int kW, -+ Acctype normFactor, int offsetZ) - { - int iCol = blockIdx.x * blockDim.x + threadIdx.x; - int iRow = blockIdx.y * blockDim.y + threadIdx.y; -@@ -148,7 +173,10 @@ template - __global__ void cuda_VolumetricAveragePooling_updateGradInput_atomicAdd( - THCDeviceTensor gradOutput, - THCDeviceTensor gradInput, -- int kT, int kH, int kW, int dT, int dH, int dW, int offsetZ) -+ int kT, int kH, int kW, -+ int dT, int dH, int dW, -+ int padT, int padH, int padW, -+ bool count_include_pad, int offsetZ) - { - int oCol = blockIdx.x * blockDim.x + threadIdx.x; - int oRow = blockIdx.y * blockDim.y + threadIdx.y; -@@ -158,13 +186,33 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput_atomicAdd( - // guard against over-tiled threads - if (oRow < gradOutput.getSize(2) && oCol < gradOutput.getSize(3)) - { -+ int tstart = oFrame * dT - padT; -+ int hstart = oRow * dH - padH; -+ int wstart = oCol * dW - padW; -+ int tend = min(tstart + kT, gradInput.getSize(1) + padT); -+ int hend = min(hstart + kH, gradInput.getSize(2) + padH); -+ int wend = min(wstart + kW, gradInput.getSize(3) + padW); -+ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); -+ tstart = max(tstart, 0); -+ hstart = max(hstart, 0); -+ wstart = max(wstart, 0); -+ tend = min(tend, gradInput.getSize(1)); -+ hend = min(hend, gradInput.getSize(2)); -+ wend = min(wend, gradInput.getSize(3)); -+ -+ Acctype divide_factor; -+ if (count_include_pad) -+ divide_factor = static_cast(pool_size); -+ else -+ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); -+ - Dtype val = ScalarConvert::to( -- ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / (kT * kH * kW)); -- for (int iFrame = oFrame * dT; iFrame < oFrame * dT + kT; ++iFrame) -+ ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / divide_factor); -+ for (int iFrame = tstart; iFrame < tend; ++iFrame) - { -- for (int iRow = oRow * dH; iRow < oRow * dH + kH; ++iRow) -+ for (int iRow = hstart; iRow < hend; ++iRow) - { -- for (int iCol = oCol * dW; iCol < oCol * dW + kW; ++iCol) -+ for (int iCol = wstart; iCol < wend; ++iCol) - { - atomicAdd(&gradInput[slice][iFrame][iRow][iCol], val); - } -@@ -178,7 +226,9 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput( - THCDeviceTensor gradOutput, - THCDeviceTensor gradInput, - int kT, int kH, int kW, -- int dT, int dH, int dW, int offsetZ) -+ int dT, int dH, int dW, -+ int padT, int padH, int padW, -+ bool count_include_pad, int offsetZ) - { - int oCol = blockIdx.x * blockDim.x + threadIdx.x; - int oRow = blockIdx.y * blockDim.y + threadIdx.y; -@@ -188,13 +238,33 @@ __global__ void cuda_VolumetricAveragePooling_updateGradInput( - // guard against over-tiled threads - if (oRow < gradOutput.getSize(2) && oCol < gradOutput.getSize(3)) - { -+ int tstart = oFrame * dT - padT; -+ int hstart = oRow * dH - padH; -+ int wstart = oCol * dW - padW; -+ int tend = min(tstart + kT, gradInput.getSize(1) + padT); -+ int hend = min(hstart + kH, gradInput.getSize(2) + padH); -+ int wend = min(wstart + kW, gradInput.getSize(3) + padW); -+ int pool_size = (tend - tstart) * (hend - hstart) * (wend - wstart); -+ tstart = max(tstart, 0); -+ hstart = max(hstart, 0); -+ wstart = max(wstart, 0); -+ tend = min(tend, gradInput.getSize(1)); -+ hend = min(hend, gradInput.getSize(2)); -+ wend = min(wend, gradInput.getSize(3)); -+ -+ Acctype divide_factor; -+ if (count_include_pad) -+ divide_factor = static_cast(pool_size); -+ else -+ divide_factor = static_cast((tend - tstart) * (hend - hstart) * (wend - wstart)); -+ - Dtype val = ScalarConvert::to( -- ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / (kT * kH * kW)); -- for (int iFrame = oFrame * dT; iFrame < oFrame * dT + kT; ++iFrame) -+ ScalarConvert::to(gradOutput[slice][oFrame][oRow][oCol]) / divide_factor); -+ for (int iFrame = tstart; iFrame < tend; ++iFrame) - { -- for (int iRow = oRow * dH; iRow < oRow * dH + kH; ++iRow) -+ for (int iRow = hstart; iRow < hend; ++iRow) - { -- for (int iCol = oCol * dW; iCol < oCol * dW + kW; ++iCol) -+ for (int iCol = wstart; iCol < wend; ++iCol) - { - gradInput[slice][iFrame][iRow][iCol] = val; - } -diff --git a/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu b/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu -index 93c4c0f..556b5bc 100644 ---- a/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu -+++ b/extra/cunn/lib/THCUNN/VolumetricFullConvolution.cu -@@ -1,6 +1,5 @@ - #include "THCUNN.h" - #include "common.h" --#include "vol2col.h" - #include "THCHalf.h" - #include "THCHalfAutoNumerics.cuh" - -diff --git a/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu -new file mode 100644 -index 0000000..47173f2 ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/VolumetricFullDilatedConvolution.cu -@@ -0,0 +1,8 @@ -+#include "THCUNN.h" -+#include "common.h" -+#include "vol2col.h" -+#include "THCHalf.h" -+#include "THCHalfAutoNumerics.cuh" -+ -+#include "generic/VolumetricFullDilatedConvolution.cu" -+#include "THCGenerateFloatTypes.h" -diff --git a/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu b/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu -new file mode 100644 -index 0000000..9300450 ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/generic/FeatureLPPooling.cu -@@ -0,0 +1,267 @@ -+#ifndef THC_GENERIC_FILE -+#define THC_GENERIC_FILE "generic/FeatureLPPooling.cu" -+#else -+ -+#include "../common.h" -+ -+// non-batch mode: -+// [feature dim] -+// [feature dim][opt dim 1] -+// [feature dim][opt dim 1][opt dim 2] -+// -+// batch mode: -+// [batch dim][feature dim] -+// [batch dim][feature dim][opt dim 1] -+// [batch dim][feature dim][opt dim 1][opt dim 2] -+THCDeviceTensor -+THNN_(FeatureLPPooling_upcast)(THCState* state, THCTensor* t, bool batchMode) { -+ int inputDim = THCTensor_(nDimension)(state, t); -+ -+ if (inputDim == 1) { -+ // [feature dim] -+ return toDeviceTensor(state, t). -+ upcastOuter<2>().upcastInner<4>(); -+ } else if (inputDim == 2) { -+ if (batchMode) { -+ // [batch dim][feature dim] -+ return toDeviceTensor(state, t). -+ upcastInner<4>(); -+ } else { -+ // [feature dim][opt dim 1] -+ return toDeviceTensor(state, t). -+ upcastOuter<3>().upcastInner<4>(); -+ } -+ } else if (inputDim == 3) { -+ if (batchMode) { -+ // [batch dim][feature dim][opt dim 1] -+ return toDeviceTensor(state, t). -+ upcastInner<4>(); -+ } else { -+ // [feature dim][opt dim 1][opt dim 2] -+ return toDeviceTensor(state, t). -+ upcastOuter<4>(); -+ } -+ } else { -+ // inputDim == 4 -+ // [batch dim][feature dim][opt dim 1][opt dim 2] -+ THAssert(batchMode); -+ return toDeviceTensor(state, t); -+ } -+} -+ -+// Resizes `toResize` based on the output size for `src` as an input -+// tensor -+void -+THNN_(FeatureLPPooling_resizeForOutput)(THCState* state, -+ THCTensor* toResize, -+ THCTensor* input, -+ bool batchMode, -+ int width, -+ int stride) { -+ int inputDim = THCTensor_(nDimension)(state, input); -+ THAssert(inputDim >= 1 && inputDim <= 4); -+ -+ long outSize = -+ lpPoolingOutputSize(THCTensor_(size)(state, input, 0), width, stride); -+ if (batchMode) { -+ THAssert(inputDim > 1); -+ outSize = -+ lpPoolingOutputSize(THCTensor_(size)(state, input, 1), width, stride); -+ } else { -+ THAssert(inputDim < 4); -+ } -+ -+ if (inputDim == 1) { -+ THCTensor_(resize1d)(state, toResize, outSize); -+ } else if (inputDim == 2) { -+ if (batchMode) { -+ THCTensor_(resize2d)( -+ state, toResize, THCTensor_(size)(state, input, 0), outSize); -+ } else { -+ THCTensor_(resize2d)( -+ state, toResize, outSize, THCTensor_(size)(state, input, 1)); -+ } -+ } else if (inputDim == 3) { -+ if (batchMode) { -+ THCTensor_(resize3d)( -+ state, -+ toResize, -+ THCTensor_(size)(state, input, 0), outSize, -+ THCTensor_(size)(state, input, 2)); -+ } else { -+ THCTensor_(resize3d)( -+ state, -+ toResize, -+ outSize, THCTensor_(size)(state, input, 1), -+ THCTensor_(size)(state, input, 2)); -+ } -+ } else if (inputDim == 4) { -+ THCTensor_(resize4d)( -+ state, -+ toResize, -+ THCTensor_(size)(state, input, 0), outSize, -+ THCTensor_(size)(state, input, 2), THCTensor_(size)(state, input, 3)); -+ } -+} -+ -+// Makes `toResize` the same size/dimensionality as `src` -+void -+THNN_(FeatureLPPooling_resize)(THCState* state, -+ THCTensor* toResize, -+ THCTensor* src) { -+ int inputDim = THCTensor_(nDimension)(state, src); -+ THAssert(inputDim >= 1 && inputDim <= 4); -+ -+ if (inputDim == 1) { -+ THCTensor_(resize1d)(state, -+ toResize, -+ THCTensor_(size)(state, src, 0)); -+ } else if (inputDim == 2) { -+ THCTensor_(resize2d)( -+ state, -+ toResize, -+ THCTensor_(size)(state, src, 0), -+ THCTensor_(size)(state, src, 1)); -+ } else if (inputDim == 3) { -+ THCTensor_(resize3d)( -+ state, -+ toResize, -+ THCTensor_(size)(state, src, 0), -+ THCTensor_(size)(state, src, 1), -+ THCTensor_(size)(state, src, 2)); -+ } else if (inputDim == 4) { -+ THCTensor_(resize4d)( -+ state, -+ toResize, -+ THCTensor_(size)(state, src, 0), -+ THCTensor_(size)(state, src, 1), -+ THCTensor_(size)(state, src, 2), -+ THCTensor_(size)(state, src, 3)); -+ } -+} -+ -+void THNN_(FeatureLPPooling_updateOutput)(THCState* state, -+ THCTensor* inputTH, -+ THCTensor* outputTH, -+ accreal power, -+ int width, -+ int stride, -+ bool batchMode) { -+ THCUNN_assertSameGPU(state, 2, inputTH, outputTH); -+ -+ int inputDim = THCTensor_(nDimension)(state, inputTH); -+ -+ if (batchMode) { -+ THArgCheck(inputDim >= 2 && inputDim <= 4, 2, -+ "input must be 2-4 dimensions for batch mode"); -+ } else { -+ THArgCheck(inputDim >= 1 && inputDim <= 3, 2, -+ "input must be 1-3 dimensions for non-batch mode"); -+ } -+ -+ THArgCheck(TensorUtils::canUse32BitIndexMath(state, inputTH), 2, -+ "input tensor must fit into 32-bit index math"); -+ -+ THCDeviceTensor::DataType, 4> input; -+ THCDeviceTensor::DataType, 4> output; -+ -+ input = THNN_(FeatureLPPooling_upcast)(state, inputTH, batchMode); -+ -+ // Make sure the feature dimension is properly sized -+ THArgCheck(input.getSize(1) >= width, 2, -+ "input: feature dimension must be >= width"); -+ -+ // Make sure that width and stride are within range -+ THArgCheck(width >= 2 && width <= 16, 5, -+ "width must be between 2 - 16"); -+ -+ THArgCheck(stride >= 1 && stride <= 4, 6, -+ "stride must be between 1 - 4"); -+ -+ THNN_(FeatureLPPooling_resizeForOutput)( -+ state, outputTH, inputTH, batchMode, width, stride); -+ -+ output = THNN_(FeatureLPPooling_upcast)(state, outputTH, batchMode); -+ -+ bool found = runFeatureLPPoolingUpdateOutput(state, -+ input, -+ output, -+ power, -+ width, -+ stride); -+ THAssert(found); -+} -+ -+void THNN_(FeatureLPPooling_updateGradInput)(THCState* state, -+ THCTensor* gradOutputTH, -+ THCTensor* inputTH, -+ THCTensor* outputTH, -+ THCTensor* gradInputTH, -+ accreal power, -+ int width, -+ int stride, -+ bool batchMode) { -+ THArgCheck(TensorUtils::canUse32BitIndexMath(state, gradOutputTH), 2, -+ "output gradient tensor must fit into 32-bit index math"); -+ THArgCheck(TensorUtils::canUse32BitIndexMath(state, inputTH), 3, -+ "input tensor must fit into 32-bit index math"); -+ THCUNN_assertSameGPU(state, 4, gradOutputTH, inputTH, outputTH, gradInputTH); -+ -+ int inputDim = THCTensor_(nDimension)(state, inputTH); -+ -+ if (batchMode) { -+ THArgCheck(inputDim >= 2 && inputDim <= 4, 2, -+ "input must be 2-4 dimensions for batch mode"); -+ } else { -+ THArgCheck(inputDim >= 1 && inputDim <= 3, 2, -+ "input must be 1-3 dimensions for non-batch mode"); -+ } -+ -+ THCDeviceTensor::DataType, 4> gradOutput; -+ THCDeviceTensor::DataType, 4> input; -+ THCDeviceTensor::DataType, 4> output; -+ THCDeviceTensor::DataType, 4> gradInput; -+ -+ input = THNN_(FeatureLPPooling_upcast)(state, inputTH, batchMode); -+ -+ // Make sure the feature dimension is properly sized -+ THArgCheck(input.getSize(1) >= width, 3, -+ "input: feature dimension must be >= width"); -+ -+ // Make sure that width and stride are within range -+ THArgCheck(width >= 2 && width <= 16, 7, -+ "width must be between 2 - 16"); -+ -+ THArgCheck(stride >= 1 && stride <= 4, 8, -+ "stride must be between 1 - 4"); -+ -+ gradOutput = THNN_(FeatureLPPooling_upcast)(state, gradOutputTH, batchMode); -+ output = THNN_(FeatureLPPooling_upcast)(state, outputTH, batchMode); -+ -+ for (int i = 0; i < 4; ++i) { -+ THAssertMsg(output.getSize(i) == gradOutput.getSize(i), -+ "output and gradOutput sizes do not match"); -+ } -+ -+ // Make sure that the input sizes produce the output sizes -+ THArgCheck(lpPoolingOutputSize(input.getSize(1), width, stride) == -+ output.getSize(1), 3, -+ "input and output sizes do not match with respect to " -+ "width and stride"); -+ -+ // Resize `gradInput` based on `input` -+ THNN_(FeatureLPPooling_resize)(state, gradInputTH, inputTH); -+ gradInput = THNN_(FeatureLPPooling_upcast)(state, gradInputTH, batchMode); -+ -+ bool found = runFeatureLPPoolingUpdateGradInput(state, -+ gradOutput, -+ input, -+ output, -+ gradInput, -+ power, -+ width, -+ stride); -+ THAssert(found); -+} -+ -+#endif -diff --git a/extra/cunn/lib/THCUNN/generic/SparseLinear.cu b/extra/cunn/lib/THCUNN/generic/SparseLinear.cu -index 07eda62..dc4c6d4 100644 ---- a/extra/cunn/lib/THCUNN/generic/SparseLinear.cu -+++ b/extra/cunn/lib/THCUNN/generic/SparseLinear.cu -@@ -156,7 +156,7 @@ void THNN_(SparseLinear_accGradParameters)( - THCTensor_(select)(state, sel, input, 1, 0); // rowInds - THCTensor_(select)(state, cols, input, 1, 1); // colInds - THCTensor_(cadd)(state, buf, sel, batchnum, cols); // colInds * buatchdim + rowInds -- THCTensor_(sort)(state, buf, inds, buf, 0, 0); // Indicies are now in ind -+ THCTensor_(sort)(state, buf, inds, buf, 0, 0); // Indices are now in ind - THCTensor_(indexSelect)(state, buf, input, 0, inds); - - THCTensor_(resize1d)(state, values, nnz); -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu -index 4a0563d..06bf1f8 100644 ---- a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu -+++ b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionLocal.cu -@@ -266,7 +266,7 @@ void THNN_(SpatialConvolutionLocal_updateGradInput)( - col2im( - THCState_getCurrentStream(state), - THCTensor_(data)(state, fgradInput_n), -- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, -+ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, - 1, 1, THCTensor_(data)(state, gradInput_n) - ); - -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu -index b4ae8e5..4db0406 100644 ---- a/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu -+++ b/extra/cunn/lib/THCUNN/generic/SpatialConvolutionMM.cu -@@ -302,7 +302,7 @@ void THNN_(SpatialConvolutionMM_updateGradInput)( - col2im( - THCState_getCurrentStream(state), - THCTensor_(data)(state, gradColumns), -- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, -+ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, - 1, 1, THCTensor_(data)(state, gradInput_n) - ); - } -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu -index 1fc365f..68077ed 100644 ---- a/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu -+++ b/extra/cunn/lib/THCUNN/generic/SpatialDepthWiseConvolution.cu -@@ -388,7 +388,7 @@ void THNN_(SpatialDepthWiseConvolution_updateGradInput)( - col2im( - THCState_getCurrentStream(state), - THCTensor_(data)(state, gradColumns), -- 1, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, -+ 1, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, - 1, 1, THCTensor_(data)(state, gradInput_i) - ); - } -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu -index 01c97c9..ff764f6 100644 ---- a/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu -+++ b/extra/cunn/lib/THCUNN/generic/SpatialDilatedConvolution.cu -@@ -296,7 +296,7 @@ void THNN_(SpatialDilatedConvolution_updateGradInput)( - col2im( - THCState_getCurrentStream(state), - THCTensor_(data)(state, gradColumns), -- nInputPlane, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, -+ nInputPlane, inputHeight, inputWidth, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, - dilationH, dilationW, - THCTensor_(data)(state, gradInput_n) - ); -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu -index 76abb90..af9a473 100644 ---- a/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu -+++ b/extra/cunn/lib/THCUNN/generic/SpatialFullConvolution.cu -@@ -2,65 +2,6 @@ - #define THC_GENERIC_FILE "generic/SpatialFullConvolution.cu" - #else - --static inline void THNN_(SpatialFullConvolution_shapeCheck)( -- THCState *state, -- THCTensor *input, THCTensor *gradOutput, -- THCTensor *weight, THCTensor *bias, -- int kH, int kW, int dH, int dW, int padH, int padW, -- int adjH, int adjW) { -- THArgCheck(kW > 0 && kH > 0, 9, -- "kernel size should be greater than zero, but got kH: %d kW: %d", kH, kW); -- THArgCheck(dW > 0 && dH > 0, 11, -- "stride should be greater than zero, but got dH: %d dW: %d", dH, dW); -- THArgCheck(adjW < dW && adjH < dH, 15, -- "output adjustment must be smaller than stride, but got adjH: %d adjW: %d dH: %d dW: %d", -- adjH, adjW, dH, dW); -- THArgCheck(THCTensor_(isContiguous)(state, weight), 4, -- "weight tensor has to be contiguous"); -- THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, -- "bias tensor has to be contiguous"); -- THCUNN_argCheck(state, weight->nDimension == 2 || weight->nDimension == 4, 5, weight, -- "2D or 4D weight tensor expected, but got: %s"); -- -- if (bias != NULL) { -- THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); -- } -- -- int ndim = input->nDimension; -- int dimf = 0; -- int dimh = 1; -- int dimw = 2; -- -- if (ndim == 4) { -- dimf++; -- dimh++; -- dimw++; -- } -- -- THCUNN_argCheck(state, ndim == 3 || ndim == 4, 2, input, -- "3D or 4D input tensor expected but got: %s"); -- -- long nInputPlane = weight->size[0]; -- long inputHeight = input->size[dimh]; -- long inputWidth = input->size[dimw]; -- long nOutputPlane = weight->size[1]; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- -- if (outputWidth < 1 || outputHeight < 1) -- THError("Given input size: (%d x %d x %d). " -- "Calculated output size: (%d x %d x %d). Output size is too small", -- nInputPlane,inputHeight,inputWidth,nOutputPlane,outputHeight,outputWidth); -- -- THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); -- -- if (gradOutput != NULL) { -- THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); -- THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); -- THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); -- } --} -- - void THNN_(SpatialFullConvolution_updateOutput)( - THCState *state, - THCTensor *input, -@@ -74,133 +15,9 @@ void THNN_(SpatialFullConvolution_updateOutput)( - int padW, int padH, - int adjW, int adjH) - { -- -- int nInputPlane = THCTensor_(size)(state, weight, 0); -- int nOutputPlane = THCTensor_(size)(state, weight, 1); -- -- THCUNN_assertSameGPU(state, 6, input, output, weight, -- bias, columns, ones); -- THNN_(SpatialFullConvolution_shapeCheck) -- (state, input, NULL, weight, bias, kH, kW, dH, dW, padH, padW, adjH, adjW); -- -- input = THCTensor_(newContiguous)(state, input); -- weight = THCTensor_(newContiguous)(state, weight); -- bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; -- -- int batch = 1; -- if (input->nDimension == 3) { -- // Force batch -- batch = 0; -- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -- } -- -- long inputWidth = input->size[3]; -- long inputHeight = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Resize output -- THCTensor_(resize4d)(state, output, batchSize, nOutputPlane, outputHeight, outputWidth); -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); -- -- // Define a buffer of ones, for bias accumulation -- // Note: this buffer can be shared with other modules, it only ever gets increased, -- // and always contains ones. -- if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { -- // Resize plane and fill with ones... -- THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); -- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -- } -- -- // Helpers -- THCTensor *input_n = THCTensor_(new)(state); -- THCTensor *output_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per output: -- THCTensor_(select)(state, input_n, input, 0, elt); -- THCTensor_(select)(state, output_n, output, 0, elt); -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m = weight->size[1] * weight->size[2] * weight->size[3]; -- long n = columns->size[1]; -- long k = weight->size[0]; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 'n', 't', -- n, m, k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, input_n), n, -- THCTensor_(data)(state, weight), m, -- ScalarConvert::to(0), -- THCTensor_(data)(state, columns), n -- ); -- -- // Unpack columns back into input: -- col2im( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, columns), -- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, -- 1, 1, THCTensor_(data)(state, output_n) -- ); -- -- // Do Bias after: -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m_ = nOutputPlane; -- long n_ = outputHeight * outputWidth; -- long k_ = 1; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- if (bias) { -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 't', 'n', -- n_, m_, k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, ones), k_, -- THCTensor_(data)(state, bias), k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, output_n), n_ -- ); -- } -- } -- -- // Free -- THCTensor_(free)(state, input_n); -- THCTensor_(free)(state, output_n); -- -- // Resize output -- if (batch == 0) { -- THCTensor_(resize3d)(state, output, nOutputPlane, outputHeight, outputWidth); -- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, weight); -- if (bias) THCTensor_(free)(state, bias); -- -+ THNN_(SpatialFullDilatedConvolution_updateOutput)( -+ state, input, output, weight, bias, columns, ones, -+ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH); - } - - void THNN_(SpatialFullConvolution_updateGradInput)( -@@ -215,98 +32,9 @@ void THNN_(SpatialFullConvolution_updateGradInput)( - int padW, int padH, - int adjW, int adjH) - { -- int nInputPlane = THCTensor_(size)(state, weight, 0); -- int nOutputPlane = THCTensor_(size)(state, weight, 1); -- -- THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, -- gradColumns, gradInput); -- THNN_(SpatialFullConvolution_shapeCheck) -- (state, input, gradOutput, weight, NULL, kH, kW, dH, dW, padH, padW, adjH, adjW); -- -- input = THCTensor_(newContiguous)(state, input); -- gradOutput = THCTensor_(newContiguous)(state, gradOutput); -- weight = THCTensor_(newContiguous)(state, weight); -- int batch = 1; -- if (input->nDimension == 3) { -- // Force batch -- batch = 0; -- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -- THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); -- } -- -- long inputWidth = input->size[3]; -- long inputHeight = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Resize output -- THCTensor_(resize4d)(state, gradInput, batchSize, nInputPlane, inputHeight, inputWidth); -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH, inputHeight*inputWidth); -- -- // Helpers -- THCTensor *gradInput_n = THCTensor_(new)(state); -- THCTensor *gradOutput_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per sample: -- THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); -- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -- -- // Extract columns: -- im2col( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, gradOutput_n), -- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, -- 1, 1, THCTensor_(data)(state, gradColumns) -- ); -- -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m = weight->size[0]; -- long n = gradColumns->size[1]; -- long k = weight->size[1] * weight->size[2] * weight->size[3]; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 'n', 'n', -- n, m, k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradColumns), n, -- THCTensor_(data)(state, weight), k, -- ScalarConvert::to(0), -- THCTensor_(data)(state, gradInput_n), n -- ); -- } -- -- -- // Free -- THCTensor_(free)(state, gradInput_n); -- THCTensor_(free)(state, gradOutput_n); -- -- // Resize output -- if (batch == 0) { -- THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); -- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -- THCTensor_(resize3d)(state, gradInput, nInputPlane, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, gradOutput); -- THCTensor_(free)(state, weight); -+ THNN_(SpatialFullDilatedConvolution_updateGradInput)( -+ state, input, gradOutput, gradInput, weight, gradColumns, -+ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH); - } - - -@@ -324,139 +52,10 @@ void THNN_(SpatialFullConvolution_accGradParameters)( - int adjW, int adjH, - accreal scale_) - { -- real scale = ScalarConvert::to(scale_); -- int nInputPlane = THCTensor_(size)(state, gradWeight, 0); -- int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); -- -- THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, -- gradBias, columns, ones); -- THNN_(SpatialFullConvolution_shapeCheck) -- (state, input, gradOutput, gradWeight, gradBias, kH, kW, dH, dW, padH, padW, adjH, adjW); -- -- THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); -- if (gradBias) -- THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); -- input = THCTensor_(newContiguous)(state, input); -- gradOutput = THCTensor_(newContiguous)(state, gradOutput); -- int batch = 1; -- if (input->nDimension == 3) { -- // Force batch -- batch = 0; -- THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -- THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); -- } -- -- long inputWidth = input->size[3]; -- long inputHeight = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Define a buffer of ones, for bias accumulation -- if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { -- // Resize plane and fill with ones... -- THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); -- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -- } -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); -- -- // Helpers -- THCTensor *input_n = THCTensor_(new)(state); -- THCTensor *gradOutput_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per output: -- THCTensor_(select)(state, input_n, input, 0, elt); -- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -- -- // Extract columns: -- im2col( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, gradOutput_n), -- nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, -- 1, 1, THCTensor_(data)(state, columns) -- ); -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long n = columns->size[0]; // nOutputPlane * kh * kw -- long m = input_n->size[0]; // nInputPlane -- long k = columns->size[1]; // inputHeight * inputWidth -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 't', 'n', -- n, m, k, -- scale, -- THCTensor_(data)(state, columns), k, -- THCTensor_(data)(state, input_n), k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradWeight), n -- ); -- -- // Do Bias: -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m_ = nOutputPlane; -- long k_ = outputHeight * outputWidth; -- -- // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) -- if (gradBias) { -- #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemv( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemv( -- #endif -- state, -- 't', -- k_, m_, -- scale, -- THCTensor_(data)(state, gradOutput_n), k_, -- THCTensor_(data)(state, ones), 1, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradBias), 1 -- ); -- #endif -- #ifdef THC_REAL_IS_HALF -- THCudaBlas_Hgemm( -- state, -- 't', 'n', -- m_, 1, k_, -- scale, -- THCTensor_(data)(state, gradOutput_n), k_, -- THCTensor_(data)(state, ones), k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradBias), m_ -- ); -- #endif -- } -- } -- -- // Free -- THCTensor_(free)(state, input_n); -- THCTensor_(free)(state, gradOutput_n); -- -- // Resize -- if (batch == 0) { -- THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); -- THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, gradOutput); -+ THNN_(SpatialFullDilatedConvolution_accGradParameters)( -+ state, input, gradOutput, gradWeight, gradBias, -+ columns, ones, -+ kW, kH, dW, dH, padW, padH, 1, 1, adjW, adjH, scale_); - } - - #endif -diff --git a/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu -new file mode 100644 -index 0000000..aafd07e ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/generic/SpatialFullDilatedConvolution.cu -@@ -0,0 +1,469 @@ -+#ifndef THC_GENERIC_FILE -+#define THC_GENERIC_FILE "generic/SpatialFullDilatedConvolution.cu" -+#else -+ -+static inline void THNN_(SpatialFullDilatedConvolution_shapeCheck)( -+ THCState *state, -+ THCTensor *input, THCTensor *gradOutput, -+ THCTensor *weight, THCTensor *bias, -+ int kH, int kW, int dH, int dW, int padH, int padW, -+ int dilationH, int dilationW, -+ int adjH, int adjW) { -+ THArgCheck(kW > 0 && kH > 0, 9, -+ "kernel size should be greater than zero, but got kH: %d kW: %d", kH, kW); -+ THArgCheck(dW > 0 && dH > 0, 11, -+ "stride should be greater than zero, but got dH: %d dW: %d", dH, dW); -+ THArgCheck(dilationW > 0 && dilationH > 0, 15, -+ "dilation should be greater than zero, but got dilationH: %d, dilationW: %d", -+ dilationH, dilationW); -+ THArgCheck((adjW < dW || adjW < dilationW) && (adjH < dH || adjH < dilationH), 15, -+ "output padding must be smaller than either stride or dilation, but got adjH: %d adjW: %d dH: %d dW: %d dilationH: %d dilationW: %d", -+ adjH, adjW, dH, dW, dilationH, dilationW); -+ THArgCheck(THCTensor_(isContiguous)(state, weight), 4, -+ "weight tensor has to be contiguous"); -+ THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, -+ "bias tensor has to be contiguous"); -+ THCUNN_argCheck(state, weight->nDimension == 2 || weight->nDimension == 4, 5, weight, -+ "2D or 4D weight tensor expected, but got: %s"); -+ -+ if (bias != NULL) { -+ THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); -+ } -+ -+ int ndim = input->nDimension; -+ int dimf = 0; -+ int dimh = 1; -+ int dimw = 2; -+ -+ if (ndim == 4) { -+ dimf++; -+ dimh++; -+ dimw++; -+ } -+ -+ THCUNN_argCheck(state, ndim == 3 || ndim == 4, 2, input, -+ "3D or 4D input tensor expected but got: %s"); -+ -+ long nInputPlane = weight->size[0]; -+ long inputHeight = input->size[dimh]; -+ long inputWidth = input->size[dimw]; -+ long nOutputPlane = weight->size[1]; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ if (outputWidth < 1 || outputHeight < 1) -+ THError("Given input size: (%d x %d x %d). " -+ "Calculated output size: (%d x %d x %d). Output size is too small", -+ nInputPlane,inputHeight,inputWidth,nOutputPlane,outputHeight,outputWidth); -+ -+ THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); -+ -+ if (gradOutput != NULL) { -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); -+ } -+} -+ -+void THNN_(SpatialFullDilatedConvolution_updateOutput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *output, -+ THCTensor *weight, -+ THCTensor *bias, -+ THCTensor *columns, -+ THCTensor *ones, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH) -+{ -+ -+ int nInputPlane = THCTensor_(size)(state, weight, 0); -+ int nOutputPlane = THCTensor_(size)(state, weight, 1); -+ -+ THCUNN_assertSameGPU(state, 6, input, output, weight, -+ bias, columns, ones); -+ THNN_(SpatialFullDilatedConvolution_shapeCheck) -+ (state, input, NULL, weight, bias, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); -+ -+ input = THCTensor_(newContiguous)(state, input); -+ weight = THCTensor_(newContiguous)(state, weight); -+ bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; -+ -+ int batch = 1; -+ if (input->nDimension == 3) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -+ } -+ -+ long inputWidth = input->size[3]; -+ long inputHeight = input->size[2]; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Resize output -+ THCTensor_(resize4d)(state, output, batchSize, nOutputPlane, outputHeight, outputWidth); -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); -+ -+ // Define a buffer of ones, for bias accumulation -+ // Note: this buffer can be shared with other modules, it only ever gets increased, -+ // and always contains ones. -+ if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { -+ // Resize plane and fill with ones... -+ THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); -+ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -+ } -+ -+ // Helpers -+ THCTensor *input_n = THCTensor_(new)(state); -+ THCTensor *output_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per output: -+ THCTensor_(select)(state, input_n, input, 0, elt); -+ THCTensor_(select)(state, output_n, output, 0, elt); -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m = weight->size[1] * weight->size[2] * weight->size[3]; -+ long n = columns->size[1]; -+ long k = weight->size[0]; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 'n', 't', -+ n, m, k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, input_n), n, -+ THCTensor_(data)(state, weight), m, -+ ScalarConvert::to(0), -+ THCTensor_(data)(state, columns), n -+ ); -+ -+ // Unpack columns back into input: -+ col2im( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, columns), -+ nOutputPlane, outputHeight, outputWidth, inputHeight, inputWidth, kH, kW, padH, padW, dH, dW, -+ dilationH, dilationW, THCTensor_(data)(state, output_n) -+ ); -+ -+ // Do Bias after: -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m_ = nOutputPlane; -+ long n_ = outputHeight * outputWidth; -+ long k_ = 1; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ if (bias) { -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 't', 'n', -+ n_, m_, k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, ones), k_, -+ THCTensor_(data)(state, bias), k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, output_n), n_ -+ ); -+ } -+ } -+ -+ // Free -+ THCTensor_(free)(state, input_n); -+ THCTensor_(free)(state, output_n); -+ -+ // Resize output -+ if (batch == 0) { -+ THCTensor_(resize3d)(state, output, nOutputPlane, outputHeight, outputWidth); -+ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, weight); -+ if (bias) THCTensor_(free)(state, bias); -+ -+} -+ -+void THNN_(SpatialFullDilatedConvolution_updateGradInput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradInput, -+ THCTensor *weight, -+ THCTensor *gradColumns, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH) -+{ -+ int nInputPlane = THCTensor_(size)(state, weight, 0); -+ int nOutputPlane = THCTensor_(size)(state, weight, 1); -+ -+ THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, -+ gradColumns, gradInput); -+ THNN_(SpatialFullDilatedConvolution_shapeCheck) -+ (state, input, gradOutput, weight, NULL, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); -+ -+ input = THCTensor_(newContiguous)(state, input); -+ gradOutput = THCTensor_(newContiguous)(state, gradOutput); -+ weight = THCTensor_(newContiguous)(state, weight); -+ int batch = 1; -+ if (input->nDimension == 3) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -+ THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); -+ } -+ -+ long inputWidth = input->size[3]; -+ long inputHeight = input->size[2]; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Resize output -+ THCTensor_(resize4d)(state, gradInput, batchSize, nInputPlane, inputHeight, inputWidth); -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH, inputHeight*inputWidth); -+ -+ // Helpers -+ THCTensor *gradInput_n = THCTensor_(new)(state); -+ THCTensor *gradOutput_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per sample: -+ THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); -+ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -+ -+ // Extract columns: -+ im2col( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, gradOutput_n), -+ nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, -+ dilationH, dilationW, THCTensor_(data)(state, gradColumns) -+ ); -+ -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m = weight->size[0]; -+ long n = gradColumns->size[1]; -+ long k = weight->size[1] * weight->size[2] * weight->size[3]; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 'n', 'n', -+ n, m, k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradColumns), n, -+ THCTensor_(data)(state, weight), k, -+ ScalarConvert::to(0), -+ THCTensor_(data)(state, gradInput_n), n -+ ); -+ } -+ -+ -+ // Free -+ THCTensor_(free)(state, gradInput_n); -+ THCTensor_(free)(state, gradOutput_n); -+ -+ // Resize output -+ if (batch == 0) { -+ THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); -+ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -+ THCTensor_(resize3d)(state, gradInput, nInputPlane, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, gradOutput); -+ THCTensor_(free)(state, weight); -+} -+ -+ -+void THNN_(SpatialFullDilatedConvolution_accGradParameters)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradWeight, -+ THCTensor *gradBias, -+ THCTensor *columns, -+ THCTensor *ones, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH, -+ accreal scale_) -+{ -+ real scale = ScalarConvert::to(scale_); -+ int nInputPlane = THCTensor_(size)(state, gradWeight, 0); -+ int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); -+ -+ THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, -+ gradBias, columns, ones); -+ THNN_(SpatialFullDilatedConvolution_shapeCheck) -+ (state, input, gradOutput, gradWeight, gradBias, kH, kW, dH, dW, padH, padW, dilationH, dilationW, adjH, adjW); -+ -+ THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); -+ if (gradBias) -+ THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); -+ input = THCTensor_(newContiguous)(state, input); -+ gradOutput = THCTensor_(newContiguous)(state, gradOutput); -+ int batch = 1; -+ if (input->nDimension == 3) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize4d)(state, input, 1, input->size[0], input->size[1], input->size[2]); -+ THCTensor_(resize4d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2]); -+ } -+ -+ long inputWidth = input->size[3]; -+ long inputHeight = input->size[2]; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Define a buffer of ones, for bias accumulation -+ if (ones->nDimension != 2 || ones->size[0]*ones->size[1] < outputHeight*outputWidth) { -+ // Resize plane and fill with ones... -+ THCTensor_(resize2d)(state, ones, outputHeight, outputWidth); -+ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -+ } -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH, inputHeight*inputWidth); -+ -+ // Helpers -+ THCTensor *input_n = THCTensor_(new)(state); -+ THCTensor *gradOutput_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per output: -+ THCTensor_(select)(state, input_n, input, 0, elt); -+ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -+ -+ // Extract columns: -+ im2col( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, gradOutput_n), -+ nOutputPlane, outputHeight, outputWidth, kH, kW, padH, padW, dH, dW, -+ dilationH, dilationW, THCTensor_(data)(state, columns) -+ ); -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long n = columns->size[0]; // nOutputPlane * kh * kw -+ long m = input_n->size[0]; // nInputPlane -+ long k = columns->size[1]; // inputHeight * inputWidth -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 't', 'n', -+ n, m, k, -+ scale, -+ THCTensor_(data)(state, columns), k, -+ THCTensor_(data)(state, input_n), k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradWeight), n -+ ); -+ -+ // Do Bias: -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m_ = nOutputPlane; -+ long k_ = outputHeight * outputWidth; -+ -+ // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) -+ if (gradBias) { -+ #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemv( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemv( -+ #endif -+ state, -+ 't', -+ k_, m_, -+ scale, -+ THCTensor_(data)(state, gradOutput_n), k_, -+ THCTensor_(data)(state, ones), 1, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradBias), 1 -+ ); -+ #endif -+ #ifdef THC_REAL_IS_HALF -+ THCudaBlas_Hgemm( -+ state, -+ 't', 'n', -+ m_, 1, k_, -+ scale, -+ THCTensor_(data)(state, gradOutput_n), k_, -+ THCTensor_(data)(state, ones), k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradBias), m_ -+ ); -+ #endif -+ } -+ } -+ -+ // Free -+ THCTensor_(free)(state, input_n); -+ THCTensor_(free)(state, gradOutput_n); -+ -+ // Resize -+ if (batch == 0) { -+ THCTensor_(resize3d)(state, gradOutput, nOutputPlane, outputHeight, outputWidth); -+ THCTensor_(resize3d)(state, input, nInputPlane, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, gradOutput); -+} -+ -+#endif -diff --git a/extra/cunn/lib/THCUNN/generic/THCUNN.h b/extra/cunn/lib/THCUNN/generic/THCUNN.h -index e770dff..df186b1 100644 ---- a/extra/cunn/lib/THCUNN/generic/THCUNN.h -+++ b/extra/cunn/lib/THCUNN/generic/THCUNN.h -@@ -123,6 +123,26 @@ TH_API void THNN_(ELU_updateGradInput)( - accreal alpha, - bool inplace); - -+TH_API void THNN_(FeatureLPPooling_updateOutput)( -+ THCState* state, -+ THCTensor* inputTH, -+ THCTensor* outputTH, -+ accreal power, -+ int width, -+ int stride, -+ bool batchMode); -+ -+TH_API void THNN_(FeatureLPPooling_updateGradInput)( -+ THCState* state, -+ THCTensor* gradOutputTH, -+ THCTensor* inputTH, -+ THCTensor* outputTH, -+ THCTensor* gradInputTH, -+ accreal power, -+ int width, -+ int stride, -+ bool batchMode); -+ - TH_API void THNN_(HardTanh_updateOutput)( - THCState *state, - THCTensor *input, -@@ -743,6 +763,48 @@ TH_API void THNN_(SpatialDilatedConvolution_accGradParameters)( - int dilationW, int dilationH, - accreal scale); - -+TH_API void THNN_(SpatialFullDilatedConvolution_updateOutput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *output, -+ THCTensor *weight, -+ THCTensor *bias, // [OPTIONAL] -+ THCTensor *columns, -+ THCTensor *ones, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH); -+ -+TH_API void THNN_(SpatialFullDilatedConvolution_updateGradInput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradInput, -+ THCTensor *weight, -+ THCTensor *gradColumns, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH); -+ -+TH_API void THNN_(SpatialFullDilatedConvolution_accGradParameters)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradWeight, -+ THCTensor *gradBias, // [OPTIONAL] -+ THCTensor *columns, -+ THCTensor *ones, -+ int kW, int kH, -+ int dW, int dH, -+ int padW, int padH, -+ int dilationW, int dilationH, -+ int adjW, int adjH, -+ accreal scale); -+ - TH_API void THNN_(SpatialDilatedMaxPooling_updateOutput)( - THCState *state, - THCTensor *input, -@@ -1177,7 +1239,10 @@ TH_API void THNN_(VolumetricAveragePooling_updateOutput)( - THCTensor *input, - THCTensor *output, - int kT, int kW, int kH, -- int dT, int dW, int dH); -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ bool ceil_mode, -+ bool count_include_pad); - - TH_API void THNN_(VolumetricAveragePooling_updateGradInput)( - THCState *state, -@@ -1185,7 +1250,10 @@ TH_API void THNN_(VolumetricAveragePooling_updateGradInput)( - THCTensor *gradOutput, - THCTensor *gradInput, - int kT, int kW, int kH, -- int dT, int dW, int dH); -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ bool ceil_mode, -+ bool count_include_pad); - - TH_API void THNN_(VolumetricConvolution_updateOutput)( - THCState *state, -@@ -1259,6 +1327,46 @@ TH_API void THNN_(VolumetricDilatedConvolution_accGradParameters)( - int dilationT, int dilationW, int dilationH, - accreal scale); - -+TH_API void THNN_(VolumetricFullDilatedConvolution_updateOutput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *output, -+ THCTensor *weight, -+ THCTensor *bias, // [OPTIONAL] -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH); -+ -+TH_API void THNN_(VolumetricFullDilatedConvolution_updateGradInput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradInput, -+ THCTensor *weight, -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH); -+ -+TH_API void THNN_(VolumetricFullDilatedConvolution_accGradParameters)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradWeight, -+ THCTensor *gradBias, // [OPTIONAL] -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH, -+ accreal scale); -+ - TH_API void THNN_(VolumetricDilatedMaxPooling_updateOutput)( - THCState *state, - THCTensor *input, -diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu b/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu -index 7a6c595..828a0f6 100644 ---- a/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu -+++ b/extra/cunn/lib/THCUNN/generic/VolumetricAveragePooling.cu -@@ -6,12 +6,11 @@ static inline void THNN_(VolumetricAveragePooling_shapeCheck)( - THCState *state, - THCTensor *input, - THCTensor *gradOutput, -- int kT, -- int kW, -- int kH, -- int dT, -- int dW, -- int dH) { -+ int kT, int kW, int kH, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ bool ceil_mode) -+{ - int inputSlices; - int inputTime; - int inputHeight; -@@ -66,11 +65,42 @@ static inline void THNN_(VolumetricAveragePooling_shapeCheck)( - THArgCheck(false, 2, "4D or 5D tensor expected, but got: %d", input->nDimension); - } - -- int outputTime = (inputTime - kT) / dT + 1; -- int outputHeight = (inputHeight - kH) / dH + 1; -- int outputWidth = (inputWidth - kW) / dW + 1; -+ // The second argument is the index of padH. -+ THArgCheck(kT/2 >= padT && kW/2 >= padW && kH/2 >= padH, 11, -+ "pad should not be greater than half of kernel size, but got " -+ "padT = %d, padW = %d, padH = %d, kT = %d, kW = %d, kH = %d", -+ padT, padW, padH, kT, kW, kH); -+ -+ int outputTime; -+ int outputHeight; -+ int outputWidth; -+ -+ if (ceil_mode) -+ { -+ outputTime = ceil(float(inputTime - kT + 2*padT) / float(dT)) + 1; -+ outputHeight = ceil(float(inputHeight - kH + 2*padH) / float(dH)) + 1; -+ outputWidth = ceil(float(inputWidth - kW + 2*padW) / float(dW)) + 1; -+ } -+ else -+ { -+ outputTime = floor(float(inputTime - kT + 2*padT) / float(dT)) + 1; -+ outputHeight = floor(float(inputHeight - kH + 2*padH) / float(dH)) + 1; -+ outputWidth = floor(float(inputWidth - kW + 2*padW) / float(dW)) + 1; -+ } -+ if (padT || padW || padH) -+ { -+ // ensure that the last pooling starts inside the image -+ // needed to avoid problems in ceil mode -+ if ((outputTime - 1)*dT >= inputTime + padT) -+ --outputTime; -+ if ((outputHeight - 1)*dH >= inputHeight + padH) -+ --outputHeight; -+ if ((outputWidth - 1)*dW >= inputWidth + padW) -+ --outputWidth; -+ } - -- if (gradOutput != NULL) { -+ if (gradOutput != NULL) -+ { - THCUNN_check_dim_size(state, gradOutput, ndim, dimN, inputSlices); - THCUNN_check_dim_size(state, gradOutput, ndim, dimt, outputTime); - THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); -@@ -83,7 +113,10 @@ void THNN_(VolumetricAveragePooling_updateOutput)( - THCTensor *input, - THCTensor *output, - int kT, int kW, int kH, -- int dT, int dW, int dH) -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ bool ceil_mode, -+ bool count_include_pad) - { - int batchSize; - int inputSlices; -@@ -103,7 +136,8 @@ void THNN_(VolumetricAveragePooling_updateOutput)( - } - - THNN_(VolumetricAveragePooling_shapeCheck) -- (state, input, NULL, kT, kW, kH, dT, dW, dH); -+ (state, input, NULL, kT, kW, kH, dT, dW, dH, -+ padT, padW, padH, ceil_mode); - - if (THCTensor_(nDimension)(state, input) == 4) - { -@@ -124,9 +158,33 @@ void THNN_(VolumetricAveragePooling_updateOutput)( - inputWidth = THCTensor_(size)(state, input, 4); - } - -- int outputTime = (inputTime - kT) / dT + 1; -- int outputHeight = (inputHeight - kH) / dH + 1; -- int outputWidth = (inputWidth - kW) / dW + 1; -+ int outputTime; -+ int outputHeight; -+ int outputWidth; -+ -+ if (ceil_mode) -+ { -+ outputTime = ceil(float(inputTime - kT + 2*padT) / float(dT)) + 1; -+ outputHeight = ceil(float(inputHeight - kH + 2*padH) / float(dH)) + 1; -+ outputWidth = ceil(float(inputWidth - kW + 2*padW) / float(dW)) + 1; -+ } -+ else -+ { -+ outputTime = floor(float(inputTime - kT + 2*padT) / float(dT)) + 1; -+ outputHeight = floor(float(inputHeight - kH + 2*padH) / float(dH)) + 1; -+ outputWidth = floor(float(inputWidth - kW + 2*padW) / float(dW)) + 1; -+ } -+ if (padT || padH || padW) -+ { -+ // ensure that the last pooling starts inside the image -+ // needed to avoid problems in ceil mode -+ if ((outputTime - 1)*dT >= inputTime + padT) -+ --outputTime; -+ if ((outputHeight - 1)*dH >= inputHeight + padH) -+ --outputHeight; -+ if ((outputWidth - 1)*dW >= inputWidth + padW) -+ --outputWidth; -+ } - - if (input->nDimension == 4) /* 4D */ - { -@@ -164,7 +222,6 @@ void THNN_(VolumetricAveragePooling_updateOutput)( - THCCeilDiv(outputHeight, static_cast(block.y)), - totalZ > 65535 ? 65535 : totalZ); - -- accreal normFactor = ScalarConvert::to(1) / static_cast(kT * kH * kW); - switch (kW) - { - LAUNCH_UPDATE_OUTPUT_KERNEL_WIDTH(1); -@@ -180,7 +237,8 @@ void THNN_(VolumetricAveragePooling_updateOutput)( - cudaOutput, - kT, kH, kW, - dT, dH, dW, -- normFactor, -+ padT, padH, padW, -+ count_include_pad, - offsetZ - ); - break; -@@ -198,11 +256,14 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( - THCTensor *gradOutput, - THCTensor *gradInput, - int kT, int kW, int kH, -- int dT, int dW, int dH) -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ bool ceil_mode, -+ bool count_include_pad) - { -- - THNN_(VolumetricAveragePooling_shapeCheck) -- (state, input, gradOutput, kT, kW, kH, dT, dW, dH); -+ (state, input, gradOutput, kT, kW, kH, dT, dW, dH, -+ padT, padW, padH, ceil_mode); - bool kernelsOverlap = (dT < kT) || (dH < kH) || (dW < kW); - - // Resize and initialize result tensor. -@@ -266,7 +327,8 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( - - // Optimizing for stride 1 is probably only of limited value, but this - // specialization yields 3x speedup over the atomicAdd implementation. -- if (dT == 1 && dH == 1 && dW == 1) -+ // Padding must be 0, otherwise, pool size may change. -+ if (dT == 1 && dH == 1 && dW == 1 && padT == 0 && padH == 0 && padW == 0) - { - int totalZ = inputTime * inputSlices * batchSize; - int offsetZ = 0; -@@ -286,20 +348,21 @@ void THNN_(VolumetricAveragePooling_updateGradInput)( - int totalZ = outputTime * inputSlices * batchSize; - int offsetZ = 0; - while (totalZ > 0) { -- - dim3 grid(THCCeilDiv(outputWidth, static_cast(block.x)), - THCCeilDiv(outputHeight, static_cast(block.y)), - totalZ > 65535 ? 65535 : totalZ); - if (kernelsOverlap) -- { -- cuda_VolumetricAveragePooling_updateGradInput_atomicAdd<<>>( -- cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, offsetZ); -- } -+ { -+ cuda_VolumetricAveragePooling_updateGradInput_atomicAdd<<>>( -+ cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, -+ padT, padH, padW, count_include_pad, offsetZ); -+ } - else -- { -- cuda_VolumetricAveragePooling_updateGradInput<<>>( -- cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, offsetZ); -- } -+ { -+ cuda_VolumetricAveragePooling_updateGradInput<<>>( -+ cudaGradOutput, cudaGradInput, kT, kH, kW, dT, dH, dW, -+ padT, padH, padW, count_include_pad, offsetZ); -+ } - THCudaCheck(cudaGetLastError()); - totalZ -= 65535; - offsetZ += 65535; -diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu -index 45bb0f6..1203733 100644 ---- a/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu -+++ b/extra/cunn/lib/THCUNN/generic/VolumetricDilatedConvolution.cu -@@ -310,6 +310,7 @@ void THNN_(VolumetricDilatedConvolution_updateGradInput)( - THCState_getCurrentStream(state), - THCTensor_(data)(state, gradColumns), - nInputPlane, inputDepth, inputHeight, inputWidth, -+ outputDepth, outputHeight, outputWidth, - kT, kH, kW, padT, padH, padW, dT, dH, dW, - dilationT, dilationH, dilationW, - THCTensor_(data)(state, gradInput_n) -diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu -index 9dd266c..9837a2d 100644 ---- a/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu -+++ b/extra/cunn/lib/THCUNN/generic/VolumetricFullConvolution.cu -@@ -2,75 +2,6 @@ - #define THC_GENERIC_FILE "generic/VolumetricFullConvolution.cu" - #else - --static inline void THNN_(VolumetricFullConvolution_shapeCheck)( -- THCState *state, -- THCTensor *input, -- THCTensor *gradOutput, -- THCTensor *weight, -- THCTensor *bias, -- int dT, int dW, int dH, -- int padT, int padW, int padH, -- int adjT, int adjW, int adjH) { -- THCUNN_argCheck(state, input->nDimension == 4 || input->nDimension == 5, 2, input, -- "4D or 5D (batch mode) tensor expected for input, but got: %s"); -- // number of input & output planes and kernel size is indirectly defined by the weight tensor -- THCUNN_argCheck(state, weight->nDimension == 5, 4, weight, -- "5D (nOutputPlane x nInputPlane x kT x kH x kW) tensor " -- "expected for weight, but got: %s"); -- THArgCheck(THCTensor_(isContiguous)(state, weight), 4, -- "weight tensor has to be contiguous"); -- THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, -- "bias tensor has to be contiguous"); -- THArgCheck(dT > 0 && dW > 0 && dH > 0, 8, -- "stride should be greater than zero, but got dT: %d dH: %d dW: %d", dT, dH, dW); -- THArgCheck(adjT < dT && adjW < dW && adjH < dH, 14, -- "output adjustment must be smaller than stride, but got " -- "adjT: %d adjH: %d adjW: %d dT: %d dH: %d dW: %d", -- adjT, adjH, adjW, dT, dH, dW); -- -- int ndim = input->nDimension; -- int nInputPlane = THCTensor_(size)(state, weight, 0); -- int nOutputPlane = THCTensor_(size)(state, weight, 1); -- const int kT = (int)weight->size[2]; -- const int kH = (int)weight->size[3]; -- const int kW = (int)weight->size[4]; -- -- if (bias != NULL) { -- THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); -- } -- -- int dimf = 0; -- int dimd = 1; -- int dimh = 2; -- int dimw = 3; -- -- if (ndim == 5) { -- dimf++; -- dimd++; -- dimh++; -- dimw++; -- } -- -- long inputWidth = input->size[dimw]; -- long inputHeight = input->size[dimh]; -- long inputDepth = input->size[dimd]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; -- -- if (outputDepth < 1 || outputWidth < 1 || outputHeight < 1) -- THError("Given input size: (%dx%dx%dx%d). Calculated output size: (%dx%dx%dx%d). Output size is too small", -- nInputPlane,inputDepth,inputHeight,inputWidth,nOutputPlane,outputDepth,outputHeight,outputWidth); -- -- THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); -- if (gradOutput != NULL) { -- THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); -- THCUNN_check_dim_size(state, gradOutput, ndim, dimd, outputDepth); -- THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); -- THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); -- } --} -- - void THNN_(VolumetricFullConvolution_updateOutput)( - THCState *state, - THCTensor *input, -@@ -83,144 +14,9 @@ void THNN_(VolumetricFullConvolution_updateOutput)( - int padT, int padW, int padH, - int adjT, int adjW, int adjH) - { -- -- THCTensor *columns = finput; -- THCTensor *ones = fgradInput; -- -- int nInputPlane = THCTensor_(size)(state, weight, 0); -- int nOutputPlane = THCTensor_(size)(state, weight, 1); -- const int kT = (int)weight->size[2]; -- const int kH = (int)weight->size[3]; -- const int kW = (int)weight->size[4]; -- -- THCUNN_assertSameGPU(state, 6, input, output, weight, -- bias, columns, ones); -- THNN_(VolumetricFullConvolution_shapeCheck)( -- state, input, NULL, weight, bias, -- dT, dW, dH, padT, padW, padH, -- adjT, adjW, adjH); -- -- input = THCTensor_(newContiguous)(state, input); -- weight = THCTensor_(newContiguous)(state, weight); -- bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; -- -- int batch = 1; -- if (input->nDimension == 4) { -- // Force batch -- batch = 0; -- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -- } -- -- long inputWidth = input->size[4]; -- long inputHeight = input->size[3]; -- long inputDepth = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Resize output -- THCTensor_(resize5d)(state, output, batchSize, nOutputPlane, outputDepth, outputHeight, outputWidth); -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -- -- // Define a buffer of ones, for bias accumulation -- // Note: this buffer can be shared with other modules, it only ever gets increased, -- // and always contains ones. -- if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { -- // Resize plane and fill with ones... -- THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); -- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -- } -- -- // Helpers -- THCTensor *input_n = THCTensor_(new)(state); -- THCTensor *output_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per output: -- THCTensor_(select)(state, input_n, input, 0, elt); -- THCTensor_(select)(state, output_n, output, 0, elt); -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; -- long n = columns->size[1]; -- long k = weight->size[0]; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 'n', 't', -- n, m, k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, input_n), n, -- THCTensor_(data)(state, weight), m, -- ScalarConvert::to(0), -- THCTensor_(data)(state, columns), n -- ); -- -- // Unpack columns back into input: -- col2vol( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, columns), -- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, -- 1,1,1, -- THCTensor_(data)(state, output_n) -- ); -- -- // Do Bias after: -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m_ = nOutputPlane; -- long n_ = outputDepth * outputHeight * outputWidth; -- long k_ = 1; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- if (bias) { -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 't', 'n', -- n_, m_, k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, ones), k_, -- THCTensor_(data)(state, bias), k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, output_n), n_ -- ); -- } -- } -- -- // Free -- THCTensor_(free)(state, input_n); -- THCTensor_(free)(state, output_n); -- -- // Resize output -- if (batch == 0) { -- THCTensor_(resize4d)(state, output, nOutputPlane, outputDepth, outputHeight, outputWidth); -- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, weight); -- if (bias) THCTensor_(free)(state, bias); -- -+ THNN_(VolumetricFullDilatedConvolution_updateOutput)( -+ state, input, output, weight, bias, finput, fgradInput, -+ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH); - } - - void THNN_(VolumetricFullConvolution_updateGradInput)( -@@ -235,109 +31,9 @@ void THNN_(VolumetricFullConvolution_updateGradInput)( - int padT, int padW, int padH, - int adjT, int adjW, int adjH) - { -- THCTensor *gradColumns = finput; -- -- int nInputPlane = THCTensor_(size)(state, weight, 0); -- int nOutputPlane = THCTensor_(size)(state, weight, 1); -- const int kT = (int)weight->size[2]; -- const int kH = (int)weight->size[3]; -- const int kW = (int)weight->size[4]; -- -- THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, -- gradColumns, gradInput); -- THNN_(VolumetricFullConvolution_shapeCheck)( -- state, input, gradOutput, weight, NULL, -- dT, dW, dH, padT, padW, padH, -- adjT, adjW, adjH); -- -- input = THCTensor_(newContiguous)(state, input); -- gradOutput = THCTensor_(newContiguous)(state, gradOutput); -- weight = THCTensor_(newContiguous)(state, weight); -- -- int batch = 1; -- if (input->nDimension == 4) { -- // Force batch -- batch = 0; -- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -- THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); -- } -- -- long inputWidth = input->size[4]; -- long inputHeight = input->size[3]; -- long inputDepth = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Resize output -- THCTensor_(resize5d)(state, gradInput, batchSize, nInputPlane, inputDepth, inputHeight, inputWidth); -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -- -- // Helpers -- THCTensor *gradInput_n = THCTensor_(new)(state); -- THCTensor *gradOutput_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per sample: -- THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); -- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -- -- // Extract columns: -- vol2col( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, gradOutput_n), -- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, -- 1,1,1, -- THCTensor_(data)(state, gradColumns) -- ); -- -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m = weight->size[0]; -- long n = gradColumns->size[1]; -- long k = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 'n', 'n', -- n, m, k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradColumns), n, -- THCTensor_(data)(state, weight), k, -- ScalarConvert::to(0), -- THCTensor_(data)(state, gradInput_n), n -- ); -- } -- -- -- // Free -- THCTensor_(free)(state, gradInput_n); -- THCTensor_(free)(state, gradOutput_n); -- -- // Resize output -- if (batch == 0) { -- THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); -- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -- THCTensor_(resize4d)(state, gradInput, nInputPlane, inputDepth, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, gradOutput); -- THCTensor_(free)(state, weight); -+ THNN_(VolumetricFullDilatedConvolution_updateGradInput)( -+ state, input, gradOutput, gradInput, weight, finput, fgradInput, -+ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH); - } - - -@@ -354,152 +50,9 @@ void THNN_(VolumetricFullConvolution_accGradParameters)( - int adjT, int adjW, int adjH, - accreal scale_) - { -- real scale = ScalarConvert::to(scale_); -- THCTensor *columns = finput; -- THCTensor *ones = fgradInput; -- -- int nInputPlane = THCTensor_(size)(state, gradWeight, 0); -- int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); -- const int kT = (int)gradWeight->size[2]; -- const int kH = (int)gradWeight->size[3]; -- const int kW = (int)gradWeight->size[4]; -- -- THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, -- gradBias, columns, ones); -- THNN_(VolumetricFullConvolution_shapeCheck)( -- state, input, gradOutput, gradWeight, -- gradBias, dT, dW, dH, padT, padW, padH, -- adjT, adjW, adjH); -- -- THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); -- if (gradBias) -- THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); -- -- input = THCTensor_(newContiguous)(state, input); -- gradOutput = THCTensor_(newContiguous)(state, gradOutput); -- -- int batch = 1; -- if (input->nDimension == 4) { -- // Force batch -- batch = 0; -- THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -- THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); -- } -- -- long inputWidth = input->size[4]; -- long inputHeight = input->size[3]; -- long inputDepth = input->size[2]; -- long outputWidth = (inputWidth - 1) * dW - 2*padW + kW + adjW; -- long outputHeight = (inputHeight - 1) * dH - 2*padH + kH + adjH; -- long outputDepth = (inputDepth - 1) * dT - 2*padT + kT + adjT; -- -- // Batch size + input planes -- long batchSize = input->size[0]; -- -- // Define a buffer of ones, for bias accumulation -- if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { -- // Resize plane and fill with ones... -- THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); -- THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -- } -- -- // Resize temporary columns -- THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -- -- // Helpers -- THCTensor *input_n = THCTensor_(new)(state); -- THCTensor *gradOutput_n = THCTensor_(new)(state); -- -- // For each elt in batch, do: -- for (int elt = 0; elt < batchSize; elt ++) { -- // Matrix mulitply per output: -- THCTensor_(select)(state, input_n, input, 0, elt); -- THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -- -- // Extract columns: -- vol2col( -- THCState_getCurrentStream(state), -- THCTensor_(data)(state, gradOutput_n), -- nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, -- 1,1,1, -- THCTensor_(data)(state, columns) -- ); -- -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long n = columns->size[0]; // nOutputPlane * kt * kh * kw -- long m = input_n->size[0]; // nInputPlane -- long k = columns->size[1]; // inputHeight * inputWidth -- -- // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemm( -- #elif defined(THC_REAL_IS_HALF) -- THCudaBlas_Hgemm( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemm( -- #endif -- state, -- 't', 'n', -- n, m, k, -- scale, -- THCTensor_(data)(state, columns), k, -- THCTensor_(data)(state, input_n), k, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradWeight), n -- ); -- -- // Do Bias: -- // M,N,K are dims of matrix A and B -- // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -- long m_ = nOutputPlane; -- long k_ = outputDepth * outputHeight * outputWidth; -- -- // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) -- if (gradBias) { -- #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) -- #ifdef THC_REAL_IS_FLOAT -- THCudaBlas_Sgemv( -- #elif defined(THC_REAL_IS_DOUBLE) -- THCudaBlas_Dgemv( -- #endif -- state, -- 't', -- k_, m_, -- scale, -- THCTensor_(data)(state, gradOutput_n), k_, -- THCTensor_(data)(state, ones), 1, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradBias), 1 -- ); -- #endif -- #ifdef THC_REAL_IS_HALF -- THCudaBlas_Hgemm( -- state, -- 't', 'n', -- m_, 1, k_, -- scale, -- THCTensor_(data)(state, gradOutput_n), k_, -- THCTensor_(data)(state, ones), k_, -- ScalarConvert::to(1), -- THCTensor_(data)(state, gradBias), m_ -- ); -- #endif -- } -- } -- -- // Free -- THCTensor_(free)(state, input_n); -- THCTensor_(free)(state, gradOutput_n); -- -- // Resize -- if (batch == 0) { -- THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); -- THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -- } -- -- THCTensor_(free)(state, input); -- THCTensor_(free)(state, gradOutput); -+ THNN_(VolumetricFullDilatedConvolution_accGradParameters)( -+ state, input, gradOutput, gradWeight, gradBias, finput, fgradInput, -+ dT, dW, dH, padT, padW, padH, 1, 1, 1, adjT, adjW, adjH, scale_); - } - - #endif -diff --git a/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu b/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu -new file mode 100644 -index 0000000..94fcc6c ---- /dev/null -+++ b/extra/cunn/lib/THCUNN/generic/VolumetricFullDilatedConvolution.cu -@@ -0,0 +1,516 @@ -+#ifndef THC_GENERIC_FILE -+#define THC_GENERIC_FILE "generic/VolumetricFullDilatedConvolution.cu" -+#else -+ -+static inline void THNN_(VolumetricFullDilatedConvolution_shapeCheck)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *weight, -+ THCTensor *bias, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH) { -+ THCUNN_argCheck(state, input->nDimension == 4 || input->nDimension == 5, 2, input, -+ "4D or 5D (batch mode) tensor expected for input, but got: %s"); -+ // number of input & output planes and kernel size is indirectly defined by the weight tensor -+ THCUNN_argCheck(state, weight->nDimension == 5, 4, weight, -+ "5D (nOutputPlane x nInputPlane x kT x kH x kW) tensor " -+ "expected for weight, but got: %s"); -+ THArgCheck(THCTensor_(isContiguous)(state, weight), 4, -+ "weight tensor has to be contiguous"); -+ THArgCheck(!bias || THCTensor_(isContiguous)(state, bias), 5, -+ "bias tensor has to be contiguous"); -+ THArgCheck(dT > 0 && dW > 0 && dH > 0, 8, -+ "stride should be greater than zero, but got dT: %d dH: %d dW: %d", dT, dH, dW); -+ THArgCheck(dilationT > 0 && dilationW > 0 && dilationH > 0, 15, -+ "dilation should be greater than zero, but got dilationT: %d, dilationH: %d, dilationW: %d", -+ dilationT, dilationH, dilationW); -+ THArgCheck((adjT < dT || adjT < dilationT) -+ && (adjW < dW || adjW < dilationW) -+ && (adjH < dH || adjH < dilationH), 15, -+ "output padding must be smaller than either stride or dilation," -+ " but got adjT: %d adjH: %d adjW: %d dT: %d dH: %d dW: %d " -+ "dilationT: %d dilationH: %d dilationW: %d", -+ adjT, adjH, adjW, dT, dH, dW, dilationT, dilationH, dilationW); -+ -+ int ndim = input->nDimension; -+ int nInputPlane = THCTensor_(size)(state, weight, 0); -+ int nOutputPlane = THCTensor_(size)(state, weight, 1); -+ const int kT = (int)weight->size[2]; -+ const int kH = (int)weight->size[3]; -+ const int kW = (int)weight->size[4]; -+ -+ if (bias != NULL) { -+ THCUNN_check_dim_size(state, bias, 1, 0, weight->size[1]); -+ } -+ -+ int dimf = 0; -+ int dimd = 1; -+ int dimh = 2; -+ int dimw = 3; -+ -+ if (ndim == 5) { -+ dimf++; -+ dimd++; -+ dimh++; -+ dimw++; -+ } -+ -+ long inputWidth = input->size[dimw]; -+ long inputHeight = input->size[dimh]; -+ long inputDepth = input->size[dimd]; -+ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ if (outputDepth < 1 || outputWidth < 1 || outputHeight < 1) -+ THError("Given input size: (%dx%dx%dx%d). Calculated output size: (%dx%dx%dx%d). Output size is too small", -+ nInputPlane,inputDepth,inputHeight,inputWidth,nOutputPlane,outputDepth,outputHeight,outputWidth); -+ -+ THCUNN_check_dim_size(state, input, ndim, dimf, nInputPlane); -+ if (gradOutput != NULL) { -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimf, nOutputPlane); -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimd, outputDepth); -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimh, outputHeight); -+ THCUNN_check_dim_size(state, gradOutput, ndim, dimw, outputWidth); -+ } -+} -+ -+void THNN_(VolumetricFullDilatedConvolution_updateOutput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *output, -+ THCTensor *weight, -+ THCTensor *bias, -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH) -+{ -+ -+ THCTensor *columns = finput; -+ THCTensor *ones = fgradInput; -+ -+ int nInputPlane = THCTensor_(size)(state, weight, 0); -+ int nOutputPlane = THCTensor_(size)(state, weight, 1); -+ const int kT = (int)weight->size[2]; -+ const int kH = (int)weight->size[3]; -+ const int kW = (int)weight->size[4]; -+ -+ THCUNN_assertSameGPU(state, 6, input, output, weight, -+ bias, columns, ones); -+ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( -+ state, input, NULL, weight, bias, -+ dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, -+ adjT, adjW, adjH); -+ -+ input = THCTensor_(newContiguous)(state, input); -+ weight = THCTensor_(newContiguous)(state, weight); -+ bias = bias ? THCTensor_(newContiguous)(state, bias) : bias; -+ -+ int batch = 1; -+ if (input->nDimension == 4) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -+ } -+ -+ long inputWidth = input->size[4]; -+ long inputHeight = input->size[3]; -+ long inputDepth = input->size[2]; -+ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Resize output -+ THCTensor_(resize5d)(state, output, batchSize, nOutputPlane, outputDepth, outputHeight, outputWidth); -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -+ -+ // Define a buffer of ones, for bias accumulation -+ // Note: this buffer can be shared with other modules, it only ever gets increased, -+ // and always contains ones. -+ if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { -+ // Resize plane and fill with ones... -+ THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); -+ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -+ } -+ -+ // Helpers -+ THCTensor *input_n = THCTensor_(new)(state); -+ THCTensor *output_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per output: -+ THCTensor_(select)(state, input_n, input, 0, elt); -+ THCTensor_(select)(state, output_n, output, 0, elt); -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; -+ long n = columns->size[1]; -+ long k = weight->size[0]; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 'n', 't', -+ n, m, k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, input_n), n, -+ THCTensor_(data)(state, weight), m, -+ ScalarConvert::to(0), -+ THCTensor_(data)(state, columns), n -+ ); -+ -+ // Unpack columns back into input: -+ col2vol( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, columns), -+ nOutputPlane, outputDepth, outputHeight, outputWidth, -+ inputDepth, inputHeight, inputWidth, -+ kT, kH, kW, padT, padH, padW, dT, dH, dW, -+ dilationT, dilationH, dilationW, -+ THCTensor_(data)(state, output_n) -+ ); -+ -+ // Do Bias after: -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m_ = nOutputPlane; -+ long n_ = outputDepth * outputHeight * outputWidth; -+ long k_ = 1; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ if (bias) { -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 't', 'n', -+ n_, m_, k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, ones), k_, -+ THCTensor_(data)(state, bias), k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, output_n), n_ -+ ); -+ } -+ } -+ -+ // Free -+ THCTensor_(free)(state, input_n); -+ THCTensor_(free)(state, output_n); -+ -+ // Resize output -+ if (batch == 0) { -+ THCTensor_(resize4d)(state, output, nOutputPlane, outputDepth, outputHeight, outputWidth); -+ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, weight); -+ if (bias) THCTensor_(free)(state, bias); -+ -+} -+ -+void THNN_(VolumetricFullDilatedConvolution_updateGradInput)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradInput, -+ THCTensor *weight, -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH) -+{ -+ THCTensor *gradColumns = finput; -+ -+ int nInputPlane = THCTensor_(size)(state, weight, 0); -+ int nOutputPlane = THCTensor_(size)(state, weight, 1); -+ const int kT = (int)weight->size[2]; -+ const int kH = (int)weight->size[3]; -+ const int kW = (int)weight->size[4]; -+ -+ THCUNN_assertSameGPU(state, 5, input, gradOutput, weight, -+ gradColumns, gradInput); -+ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( -+ state, input, gradOutput, weight, NULL, -+ dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, -+ adjT, adjW, adjH); -+ -+ input = THCTensor_(newContiguous)(state, input); -+ gradOutput = THCTensor_(newContiguous)(state, gradOutput); -+ weight = THCTensor_(newContiguous)(state, weight); -+ -+ int batch = 1; -+ if (input->nDimension == 4) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -+ THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); -+ } -+ -+ long inputWidth = input->size[4]; -+ long inputHeight = input->size[3]; -+ long inputDepth = input->size[2]; -+ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Resize output -+ THCTensor_(resize5d)(state, gradInput, batchSize, nInputPlane, inputDepth, inputHeight, inputWidth); -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, gradColumns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -+ -+ // Helpers -+ THCTensor *gradInput_n = THCTensor_(new)(state); -+ THCTensor *gradOutput_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per sample: -+ THCTensor_(select)(state, gradInput_n, gradInput, 0, elt); -+ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -+ -+ // Extract columns: -+ vol2col( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, gradOutput_n), -+ nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, -+ dilationT, dilationH, dilationW, -+ THCTensor_(data)(state, gradColumns) -+ ); -+ -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m = weight->size[0]; -+ long n = gradColumns->size[1]; -+ long k = weight->size[1] * weight->size[2] * weight->size[3] * weight->size[4]; -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 'n', 'n', -+ n, m, k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradColumns), n, -+ THCTensor_(data)(state, weight), k, -+ ScalarConvert::to(0), -+ THCTensor_(data)(state, gradInput_n), n -+ ); -+ } -+ -+ -+ // Free -+ THCTensor_(free)(state, gradInput_n); -+ THCTensor_(free)(state, gradOutput_n); -+ -+ // Resize output -+ if (batch == 0) { -+ THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); -+ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -+ THCTensor_(resize4d)(state, gradInput, nInputPlane, inputDepth, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, gradOutput); -+ THCTensor_(free)(state, weight); -+} -+ -+ -+void THNN_(VolumetricFullDilatedConvolution_accGradParameters)( -+ THCState *state, -+ THCTensor *input, -+ THCTensor *gradOutput, -+ THCTensor *gradWeight, -+ THCTensor *gradBias, -+ THCTensor *finput, -+ THCTensor *fgradInput, -+ int dT, int dW, int dH, -+ int padT, int padW, int padH, -+ int dilationT, int dilationW, int dilationH, -+ int adjT, int adjW, int adjH, -+ accreal scale_) -+{ -+ real scale = ScalarConvert::to(scale_); -+ THCTensor *columns = finput; -+ THCTensor *ones = fgradInput; -+ -+ int nInputPlane = THCTensor_(size)(state, gradWeight, 0); -+ int nOutputPlane = THCTensor_(size)(state, gradWeight, 1); -+ const int kT = (int)gradWeight->size[2]; -+ const int kH = (int)gradWeight->size[3]; -+ const int kW = (int)gradWeight->size[4]; -+ -+ THCUNN_assertSameGPU(state, 6, input, gradOutput, gradWeight, -+ gradBias, columns, ones); -+ THNN_(VolumetricFullDilatedConvolution_shapeCheck)( -+ state, input, gradOutput, gradWeight, -+ gradBias, dT, dW, dH, padT, padW, padH, dilationT, dilationW, dilationH, -+ adjT, adjW, adjH); -+ -+ THArgCheck(THCTensor_(isContiguous)(state, gradWeight), 4, "gradWeight needs to be contiguous"); -+ if (gradBias) -+ THArgCheck(THCTensor_(isContiguous)(state, gradBias), 5, "gradBias needs to be contiguous"); -+ -+ input = THCTensor_(newContiguous)(state, input); -+ gradOutput = THCTensor_(newContiguous)(state, gradOutput); -+ -+ int batch = 1; -+ if (input->nDimension == 4) { -+ // Force batch -+ batch = 0; -+ THCTensor_(resize5d)(state, input, 1, input->size[0], input->size[1], input->size[2], input->size[3]); -+ THCTensor_(resize5d)(state, gradOutput, 1, gradOutput->size[0], gradOutput->size[1], gradOutput->size[2], gradOutput->size[3]); -+ } -+ -+ long inputWidth = input->size[4]; -+ long inputHeight = input->size[3]; -+ long inputDepth = input->size[2]; -+ long outputDepth = (inputDepth - 1) * dT - 2*padT + (dilationT * (kT - 1) + 1) + adjT; -+ long outputHeight = (inputHeight - 1) * dH - 2*padH + (dilationH * (kH - 1) + 1) + adjH; -+ long outputWidth = (inputWidth - 1) * dW - 2*padW + (dilationW * (kW - 1) + 1) + adjW; -+ -+ // Batch size + input planes -+ long batchSize = input->size[0]; -+ -+ // Define a buffer of ones, for bias accumulation -+ if (ones->nDimension != 3 || ones->size[0]*ones->size[1]*ones->size[2] < outputDepth*outputHeight*outputWidth) { -+ // Resize plane and fill with ones... -+ THCTensor_(resize3d)(state, ones, outputDepth, outputHeight, outputWidth); -+ THCTensor_(fill)(state, ones, ScalarConvert::to(1)); -+ } -+ -+ // Resize temporary columns -+ THCTensor_(resize2d)(state, columns, nOutputPlane*kW*kH*kT, inputDepth*inputHeight*inputWidth); -+ -+ // Helpers -+ THCTensor *input_n = THCTensor_(new)(state); -+ THCTensor *gradOutput_n = THCTensor_(new)(state); -+ -+ // For each elt in batch, do: -+ for (int elt = 0; elt < batchSize; elt ++) { -+ // Matrix mulitply per output: -+ THCTensor_(select)(state, input_n, input, 0, elt); -+ THCTensor_(select)(state, gradOutput_n, gradOutput, 0, elt); -+ -+ // Extract columns: -+ vol2col( -+ THCState_getCurrentStream(state), -+ THCTensor_(data)(state, gradOutput_n), -+ nOutputPlane, outputDepth, outputHeight, outputWidth, kT, kH, kW, padT, padH, padW, dT, dH, dW, -+ dilationT, dilationH, dilationW, -+ THCTensor_(data)(state, columns) -+ ); -+ -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long n = columns->size[0]; // nOutputPlane * kt * kh * kw -+ long m = input_n->size[0]; // nInputPlane -+ long k = columns->size[1]; // inputHeight * inputWidth -+ -+ // Do GEMM (note: this is a bit confusing because gemm assumes column-major matrices) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemm( -+ #elif defined(THC_REAL_IS_HALF) -+ THCudaBlas_Hgemm( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemm( -+ #endif -+ state, -+ 't', 'n', -+ n, m, k, -+ scale, -+ THCTensor_(data)(state, columns), k, -+ THCTensor_(data)(state, input_n), k, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradWeight), n -+ ); -+ -+ // Do Bias: -+ // M,N,K are dims of matrix A and B -+ // (see http://docs.nvidia.com/cuda/cublas/#cublas-lt-t-gt-gemm) -+ long m_ = nOutputPlane; -+ long k_ = outputDepth * outputHeight * outputWidth; -+ -+ // Do GEMV (note: this is a bit confusing because gemv assumes column-major matrices) -+ if (gradBias) { -+ #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) -+ #ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_Sgemv( -+ #elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_Dgemv( -+ #endif -+ state, -+ 't', -+ k_, m_, -+ scale, -+ THCTensor_(data)(state, gradOutput_n), k_, -+ THCTensor_(data)(state, ones), 1, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradBias), 1 -+ ); -+ #endif -+ #ifdef THC_REAL_IS_HALF -+ THCudaBlas_Hgemm( -+ state, -+ 't', 'n', -+ m_, 1, k_, -+ scale, -+ THCTensor_(data)(state, gradOutput_n), k_, -+ THCTensor_(data)(state, ones), k_, -+ ScalarConvert::to(1), -+ THCTensor_(data)(state, gradBias), m_ -+ ); -+ #endif -+ } -+ } -+ -+ // Free -+ THCTensor_(free)(state, input_n); -+ THCTensor_(free)(state, gradOutput_n); -+ -+ // Resize -+ if (batch == 0) { -+ THCTensor_(resize4d)(state, gradOutput, nOutputPlane, outputDepth, outputHeight, outputWidth); -+ THCTensor_(resize4d)(state, input, nInputPlane, inputDepth, inputHeight, inputWidth); -+ } -+ -+ THCTensor_(free)(state, input); -+ THCTensor_(free)(state, gradOutput); -+} -+ -+#endif -diff --git a/extra/cunn/lib/THCUNN/im2col.h b/extra/cunn/lib/THCUNN/im2col.h -index ba57263..060525f 100644 ---- a/extra/cunn/lib/THCUNN/im2col.h -+++ b/extra/cunn/lib/THCUNN/im2col.h -@@ -104,13 +104,10 @@ __global__ void col2im_kernel(const int n, const Dtype* data_col, - template - void col2im(cudaStream_t stream, const Dtype* data_col, const int channels, - const int height, const int width, -+ const int output_height, const int output_width, - const int patch_h, const int patch_w, const int pad_h, - const int pad_w, const int stride_h, const int stride_w, - const int dilation_h, const int dilation_w, Dtype* data_im) { -- int height_col = (height + 2 * pad_h - (dilation_h * (patch_h - 1) + 1)) -- / stride_h + 1; -- int width_col = (width + 2 * pad_w - (dilation_w * (patch_w - 1) + 1)) -- / stride_w + 1; - int num_kernels = channels * height * width; - // To avoid involving atomic operations, we will launch one kernel per - // bottom dimension, and then in the kernel add up the top dimensions. -@@ -118,7 +115,7 @@ void col2im(cudaStream_t stream, const Dtype* data_col, const int channels, - num_kernels, data_col, height, width, channels, - patch_h, patch_w, pad_h, pad_w, stride_h, stride_w, - dilation_h, dilation_w, -- height_col, width_col, data_im -+ output_height, output_width, data_im - ); - THCudaCheck(cudaGetLastError()); - } -diff --git a/extra/cunn/lib/THCUNN/vol2col.h b/extra/cunn/lib/THCUNN/vol2col.h -index 15b110e..12c4838 100644 ---- a/extra/cunn/lib/THCUNN/vol2col.h -+++ b/extra/cunn/lib/THCUNN/vol2col.h -@@ -120,14 +120,12 @@ __global__ void vol2im_kernel(const int n, const Dtype* data_col, - template - void col2vol(cudaStream_t stream, const Dtype* data_col, const int channels, - const int depth, const int height, const int width, -+ const int output_depth, const int output_height, const int output_width, - const int patch_t, const int patch_h, const int patch_w, - const int pad_t, const int pad_h, const int pad_w, - const int stride_t, const int stride_h, const int stride_w, - const int dilation_t, const int dilation_h, const int dilation_w, - Dtype* data_vol) { -- int depth_col = (depth + 2 * pad_t - (dilation_t * (patch_t - 1) + 1)) / stride_t + 1; -- int height_col = (height + 2 * pad_h - (dilation_h * (patch_h - 1) + 1)) / stride_h + 1; -- int width_col = (width + 2 * pad_w - (dilation_w * (patch_w - 1) + 1)) / stride_w + 1; - int num_kernels = channels * depth * height * width; - // To avoid involving atomic operations, we will launch one kernel per - // bottom dimension, and then in the kernel add up the top dimensions. -@@ -135,7 +133,7 @@ void col2vol(cudaStream_t stream, const Dtype* data_col, const int channels, - num_kernels, data_col, depth, height, width, channels, - patch_t, patch_h, patch_w, pad_t, pad_h, pad_w, stride_t, stride_h, stride_w, - dilation_t, dilation_h, dilation_w, -- depth_col, height_col, width_col, data_vol -+ output_depth, output_height, output_width, data_vol - ); - THCudaCheck(cudaGetLastError()); - } -diff --git a/extra/cunn/test.lua b/extra/cunn/test.lua -index 7903aa6..02b63d4 100644 ---- a/extra/cunn/test.lua -+++ b/extra/cunn/test.lua -@@ -978,9 +978,9 @@ local function BatchNormalization_backward(moduleName, mode, inputSize, backward - end - end - --local function testBatchNormalization(name, dim, k) -+local function testBatchNormalization(name, dim, k, batchsize) - local function inputSize() -- local inputSize = { torch.random(2,32), torch.random(1, k) } -+ local inputSize = { batchsize or torch.random(2,32), torch.random(1, k) } - for i=1,dim do - table.insert(inputSize, torch.random(1,k)) - end -@@ -1005,6 +1005,7 @@ end - - function cunntest.BatchNormalization() - testBatchNormalization('BatchNormalization', 0, 128) -+ testBatchNormalization('BatchNormalization', 0, 128, 1) -- test batchsize=1 - end - - function cunntest.SpatialBatchNormalization() -@@ -4437,6 +4438,84 @@ function cunntest.SpatialUpSamplingBilinear_backward_batch() - end - end - -+function cunntest.UpSampling_forward_batch() -+ local minibatch = torch.random(1, 10) -+ local f = torch.random(3, 10) -+ local d = torch.random(3, 10) -+ local h = torch.random(3, 10) -+ local w = torch.random(3, 10) -+ local scale = torch.random(2,5) -+ -+ for k, typename in ipairs(typenames) do -+ for _,mode in pairs({'nearest','linear'}) do -+ for dim = 4,5 do -+ local input -+ if (dim == 4) then -+ input = torch.randn(minibatch, f, h, w):type(typename) -+ else -+ input = torch.randn(minibatch, f, d, h, w):type(typename) -+ end -+ -+ local ctype = t2cpu[typename] -+ input = makeNonContiguous(input:type(ctype)) -+ local sconv = nn.UpSampling(scale, mode):type(ctype) -+ local groundtruth = sconv:forward(input) -+ -+ input = makeNonContiguous(input:type(typename)) -+ local gconv = sconv:clone():type(typename) -+ local rescuda = gconv:forward(input) -+ -+ local error = rescuda:double() - groundtruth:double() -+ mytester:assertlt(error:abs():max(), precision_forward_type(precision_forward, typename), -+ string.format('error on state (forward) with %s', typename)) -+ end -+ end -+ end -+end -+ -+function cunntest.UpSampling_backward_batch() -+ local minibatch = torch.random(1, 10) -+ local f = torch.random(3, 10) -+ local d = torch.random(3, 10) -+ local h = torch.random(3, 10) -+ local w = torch.random(3, 10) -+ local scale = torch.random(2,4) -+ -+ for k, typename in ipairs(typenames) do -+ for _,mode in pairs({'nearest','linear'}) do -+ for dim = 4,5 do -+ local input, gradOutput -+ if (dim == 4) then -+ input = torch.randn(minibatch, f, h, w):type(typename) -+ gradOutput = torch.randn(minibatch, f, h*scale, w*scale):type(typename) -+ else -+ input = torch.randn(minibatch, f, d, h, w):type(typename) -+ gradOutput = torch.randn(minibatch, f, d*scale, h*scale, w*scale):type(typename) -+ end -+ -+ local ctype = t2cpu[typename] -+ input = makeNonContiguous(input:type(ctype)) -+ gradOutput = makeNonContiguous(gradOutput:type(ctype)) -+ local sconv = nn.UpSampling(scale, mode):type(ctype) -+ sconv:forward(input) -+ sconv:zeroGradParameters() -+ local groundgrad = sconv:backward(input, gradOutput) -+ -+ input = makeNonContiguous(input:type(typename)) -+ gradOutput = makeNonContiguous(gradOutput:type(typename)) -+ local gconv = sconv:clone():type(typename) -+ gconv:forward(input) -+ gconv:zeroGradParameters() -+ local rescuda = gconv:backward(input, gradOutput) -+ -+ local error = rescuda:double() - groundgrad:double() -+ mytester:assertlt(error:abs():max(), precision_backward_type(precision_backward, typename), -+ string.format('error on state (backward) with %s', typename)) -+ end -+ end -+ end -+end -+ - function cunntest.l1cost() - local size = math.random(300,500) - -@@ -5203,6 +5282,149 @@ function cunntest.VolumetricAveragePooling_backward() - end - end - -+function cunntest.FeatureLPPooling_forward() -+ for tries = 1, 5 do -+ local batch_mode = {true, false} -+ batch_mode = batch_mode[math.random(1, 2)] -+ local power = {2, 3} -+ power = power[math.random(1, 2)] -+ -+ local dims = math.random(1, 3) -+ -+ if batch_mode then -+ dims = dims + 1 -+ end -+ -+ local width = torch.random(2, 16) -+ local stride = torch.random(1, 4) -+ -+ local output_size = torch.random(1, 100) -+ local input_size = (output_size - 1) * stride + width -+ -+ local baseInput = nil -+ if dims == 1 then -+ baseInput = torch.Tensor(input_size):uniform() -+ elseif dims == 2 then -+ if batch_mode then -+ baseInput = torch.Tensor(math.random(1, 5), input_size):uniform() -+ else -+ baseInput = torch.Tensor(input_size, math.random(1, 5)):uniform() -+ end -+ elseif dims == 3 then -+ if batch_mode then -+ baseInput = torch.Tensor(math.random(1, 5), input_size, -+ math.random(1, 5)):uniform() -+ else -+ baseInput = torch.Tensor(input_size, math.random(1, 5), -+ math.random(1, 5)):uniform() -+ end -+ else -+ baseInput = torch.Tensor(math.random(1, 5), input_size, -+ math.random(1, 5), math.random(1, 5)):uniform() -+ end -+ -+ for k, typename in ipairs(typenames) do -+ local input = baseInput:type(typename) -+ -+ local ctype = t2cpu[typename] -+ input = makeNonContiguous(input:type(ctype)) -+ local sconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(ctype) -+ local groundtruth = sconv:forward(input) -+ -+ input = makeNonContiguous(input:type(typename)) -+ local gconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(typename) -+ local rescuda = gconv:forward(input) -+ -+ local error = rescuda:double() - groundtruth:double() -+ mytester:assertlt(error:abs():max(), -+ precision_forward_type(precision_forward, typename), -+ string.format('error on state (forward) with %s', typename)) -+ end -+ end -+end -+ -+function cunntest.FeatureLPPooling_backward() -+ for tries = 1, 5 do -+ local batch_mode = {true, false} -+ batch_mode = batch_mode[math.random(1, 2)] -+ local power = {2, 3} -+ power = power[math.random(1, 2)] -+ -+ local dims = math.random(1, 3) -+ -+ if batch_mode then -+ dims = dims + 1 -+ end -+ -+ local width = torch.random(2, 16) -+ local stride = torch.random(1, 4) -+ -+ local output_size = torch.random(1, 100) -+ local input_size = (output_size - 1) * stride + width -+ -+ local baseInput = nil -+ local baseGradOutput = nil -+ -+ if dims == 1 then -+ baseInput = torch.Tensor(input_size):uniform() -+ baseGradOutput = torch.Tensor(output_size):uniform() -+ elseif dims == 2 then -+ local a = math.random(1, 5) -+ if batch_mode then -+ baseInput = torch.Tensor(a, input_size):uniform() -+ baseGradOutput = torch.Tensor(a, output_size):uniform() -+ else -+ baseInput = torch.Tensor(input_size, a):uniform() -+ baseGradOutput = torch.Tensor(output_size, a):uniform() -+ end -+ elseif dims == 3 then -+ local a = math.random(1, 5) -+ local b = math.random(1, 5) -+ if batch_mode then -+ baseInput = torch.Tensor(a, input_size, b):uniform() -+ baseGradOutput = torch.Tensor(a, output_size, b):uniform() -+ else -+ baseInput = torch.Tensor(input_size, a, b):uniform() -+ baseGradOutput = torch.Tensor(output_size, a, b):uniform() -+ end -+ else -+ local a = math.random(1, 5) -+ local b = math.random(1, 5) -+ local c = math.random(1, 5) -+ baseInput = torch.Tensor(a, input_size, b, c):uniform() -+ baseGradOutput = torch.Tensor(a, output_size, b, c):uniform() -+ end -+ -+ for k, typename in ipairs(typenames) do -+ local input = baseInput:type(typename) -+ local gradOutput = baseGradOutput:type(typename) -+ local ctype = t2cpu[typename] -+ input = makeNonContiguous(input:type(ctype)) -+ gradOutput = makeNonContiguous(gradOutput:type(ctype)) -+ -+ local sconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(ctype) -+ if ceil_mode then sconv:ceil() end -+ sconv:forward(input) -+ sconv:zeroGradParameters() -+ local groundgrad = sconv:backward(input, gradOutput) -+ -+ input = makeNonContiguous(input:type(typename)) -+ gradOutput = makeNonContiguous(gradOutput:type(typename)) -+ local gconv = nn.FeatureLPPooling(width, stride, power, batch_mode):type(typename) -+ if ceil_mode then gconv:ceil() end -+ -+ gconv:forward(input) -+ gconv:zeroGradParameters() -+ local rescuda = gconv:backward(input, gradOutput) -+ -+ local error = rescuda:double() - groundgrad:double() -+ -+ mytester:assertlt(error:abs():max(), precision_backward_type(precision_backward, typename), -+ string.format('error on state (backward) with %s', typename)) -+ end -+ end -+end -+ - function cunntest.CMul_forward_batch() - local bs = math.random(8,32) - local nini = math.random(1,100) -Submodule extra/cutorch caf84f3..5e9d86c: -diff --git a/extra/cutorch/lib/THC/CMakeLists.txt b/extra/cutorch/lib/THC/CMakeLists.txt -index 1ea6039..c07a1e4 100644 ---- a/extra/cutorch/lib/THC/CMakeLists.txt -+++ b/extra/cutorch/lib/THC/CMakeLists.txt -@@ -41,6 +41,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.9.3") - endif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - -+ -+if(CUDA_VERSION VERSION_GREATER "8.0") -+ LIST(APPEND CUDA_NVCC_FLAGS "-D__CUDA_NO_HALF_OPERATORS__") -+endif(CUDA_VERSION VERSION_GREATER "8.0") -+ - IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - IF(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.7" OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "4.7" ) - SET(CXX_VERSION "c++11") -@@ -90,6 +95,10 @@ ENDIF(MAGMA_FOUND) - IF ($ENV{TH_BINARY_BUILD}) - MESSAGE(STATUS "TH_BINARY_BUILD detected. Statically linking libstdc++") - SET(CMAKE_CXX_FLAGS "-static-libstdc++ ${CMAKE_CXX_FLAGS}") -+ IF (UNIX AND NOT APPLE) -+ # hiding statically linked library symbols, this flag is not available for the linker under MACOSX -+ SET(CMAKE_CXX_FLAGS "-Wl,--exclude-libs,libstdc++.a ${CMAKE_CXX_FLAGS}") -+ ENDIF(UNIX AND NOT APPLE) - ENDIF() - - IF(APPLE) -@@ -226,6 +235,21 @@ ELSE() - - IF(USE_MAGMA) - TARGET_LINK_LIBRARIES(THC ${MAGMA_LIBRARIES}) -+ IF ($ENV{TH_BINARY_BUILD}) -+ # because magma is linked statically and it wants a BLAS, -+ # we need to link the BLAS lib against THC. Usually TH will -+ # load a BLAS library and it's all fine, but in the binary builds, -+ # TH uses static linkage to MKL, so it doesn't have all symbols that -+ # magma needs. So in this case, explicitly find a BLAS and link against it -+ # just like in TH -+ SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../TH/cmake ${CMAKE_MODULE_PATH}) -+ FIND_PACKAGE(BLAS) -+ IF(BLAS_FOUND) -+ TARGET_LINK_LIBRARIES(THC "${BLAS_LIBRARIES};${BLAS_LIBRARIES};${BLAS_LIBRARIES}") -+ ELSE(BLAS_FOUND) -+ MESSAGE(FATAL_ERROR "Binary build needs blas to be found here") -+ ENDIF(BLAS_FOUND) -+ ENDIF($ENV{TH_BINARY_BUILD}) - ENDIF(USE_MAGMA) - - IF(NOT THC_SO_VERSION) -diff --git a/extra/cutorch/lib/THC/THCApply.cuh b/extra/cutorch/lib/THC/THCApply.cuh -index a47e303..e49a153 100644 ---- a/extra/cutorch/lib/THC/THCApply.cuh -+++ b/extra/cutorch/lib/THC/THCApply.cuh -@@ -109,16 +109,16 @@ inline bool getApplyGrid(THCState* state, ptrdiff_t totalElements, dim3& grid) { - return false; - } - -- // Assume a reasonable number of SMs if no state is available -- int numSM = -- state ? THCState_getCurrentDeviceProperties(state)->multiProcessorCount : 15; -- -- // 16 warps per block * 4 per SM gives 64 warps per SM at maximum, -- // which seems to be a good sweetspot for latency hiding -- grid = dim3(min((long long) THCCeilDiv(totalElements, -- (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK), -- 4LL * numSM)); -+ if(THCState_getCurrentDeviceProperties(state)->major < 3){ -+ grid = dim3(min((long long) THCCeilDiv(totalElements, -+ (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK), (long long) 64*1024-1)); -+ return true; -+ } -+ -+ grid = dim3((long long) THCCeilDiv(totalElements, -+ (ptrdiff_t) THC_APPLY_THREADS_PER_BLOCK) ); - return true; -+ - } - - template major >= 5){ -+ THCublasCheck(cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH)); -+ THCublasCheck(cublasGemmEx(handle, opa, opb, -+ i_m, i_n, i_k, &fAlpha, -+ a, CUDA_R_16F, i_lda, b, CUDA_R_16F, -+ i_ldb, &fBeta, c, CUDA_R_16F, i_ldc, -+ CUDA_R_32F, CUBLAS_GEMM_DFALT_TENSOR_OP)); -+ THCublasCheck(cublasSetMathMode(handle, CUBLAS_DEFAULT_MATH)); -+ }else{ -+ THCublasCheck(cublasSgemmEx(handle, opa, opb, -+ i_m, i_n, i_k, &fAlpha, -+ a, CUDA_R_16F, i_lda, b, CUDA_R_16F, -+ i_ldb, &fBeta, c, CUDA_R_16F, i_ldc)); -+ } -+#endif -+ return; - } -- -- return; -- } - THError("Cublas_Hgemm only supports m, n, k, lda, ldb, ldc" - "with th bound [val] <= %d", INT_MAX); - } -@@ -344,6 +354,31 @@ void THCudaBlas_SgemmBatched(THCState *state, char transa, char transb, long m, - (int)batchCount)); - } - -+#if CUDA_VERSION >= 8000 -+void THCudaBlas_SgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, -+ float alpha, const float *a, long lda, long strideA, const float *b, long ldb, long strideB, -+ float beta, float *c, long ldc, long strideC, long batchCount) -+{ -+ if( (m >= INT_MAX) || (n >= INT_MAX) || (k >= INT_MAX) || (lda >= INT_MAX) || (ldb >= INT_MAX) || (ldc >= INT_MAX) || (batchCount >= INT_MAX) ) -+ -+ { -+ THError("Cublas_SgemmStridedBatched only supports m, n, k, lda, ldb, ldc, batchCount" -+ "with the bound [val] <= %d", INT_MAX); -+ } -+ -+ adjustLd(transa, transb, m, n, k, &lda, &ldb, &ldc); -+ cublasOperation_t opa = convertTransToCublasOperation(transa); -+ cublasOperation_t opb = convertTransToCublasOperation(transb); -+ -+ cublasHandle_t handle = THCState_getCurrentBlasHandle(state); -+ cublasSetStream(handle, THCState_getCurrentStream(state)); -+ THCublasCheck(cublasSgemmStridedBatched(handle, -+ opa, opb, (int)m, (int)n, (int)k, -+ &alpha, a, (int)lda, strideA, b, (int)ldb, strideB, &beta, c, (int)ldc, strideC, -+ (int)batchCount)); -+} -+#endif -+ - void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, long n, long k, - double alpha, const double *a[], long lda, const double *b[], long ldb, - double beta, double *c[], long ldc, long batchCount) -@@ -366,6 +401,30 @@ void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, - (int)batchCount)); - } - -+#if CUDA_VERSION >= 8000 -+void THCudaBlas_DgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, -+ double alpha, const double *a, long lda, long strideA, const double *b, long ldb, long strideB, -+ double beta, double *c, long ldc, long strideC, long batchCount) -+{ -+ if( (m >= INT_MAX) || (n >= INT_MAX) || (k >= INT_MAX) || (lda >= INT_MAX) || (ldb >= INT_MAX) || (ldc >= INT_MAX) || (batchCount >= INT_MAX) ) -+ { -+ THError("Cublas_DgemmBatched only supports m, n, k, lda, ldb, ldc, batchCount" -+ "with the bound [val] <= %d", INT_MAX); -+ } -+ -+ adjustLd(transa, transb, m, n, k, &lda, &ldb, &ldc); -+ cublasOperation_t opa = convertTransToCublasOperation(transa); -+ cublasOperation_t opb = convertTransToCublasOperation(transb); -+ -+ cublasHandle_t handle = THCState_getCurrentBlasHandle(state); -+ cublasSetStream(handle, THCState_getCurrentStream(state)); -+ THCublasCheck(cublasDgemmStridedBatched(handle, -+ opa, opb, (int)m, (int)n, (int)k, -+ &alpha, a, (int)lda, strideA, b, (int)ldb, strideB, &beta, c, (int)ldc, strideC, -+ (int)batchCount)); -+} -+#endif -+ - /* Inverse */ - void THCudaBlas_Sgetrf(THCState *state, int n, float **a, int lda, int *pivot, int *info, int batchSize) { - if( (n >= INT_MAX) || (lda >= INT_MAX) || (batchSize >= INT_MAX) ) -diff --git a/extra/cutorch/lib/THC/THCBlas.h b/extra/cutorch/lib/THC/THCBlas.h -index 25246b1..6398eba 100644 ---- a/extra/cutorch/lib/THC/THCBlas.h -+++ b/extra/cutorch/lib/THC/THCBlas.h -@@ -31,7 +31,14 @@ THC_API void THCudaBlas_SgemmBatched(THCState *state, char transa, char transb, - THC_API void THCudaBlas_DgemmBatched(THCState *state, char transa, char transb, long m, long n, long k, - double alpha, const double *a[], long lda, const double *b[], long ldb, - double beta, double *c[], long ldc, long batchCount); -- -+#if CUDA_VERSION >= 8000 -+THC_API void THCudaBlas_SgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, -+ float alpha, const float *a, long lda, long strideA, const float *b, long ldb, long strideB, -+ float beta, float *c, long ldc, long strideC, long batchCount); -+THC_API void THCudaBlas_DgemmStridedBatched(THCState *state, char transa, char transb, long m, long n, long k, -+ double alpha, const double *a, long lda, long strideA, const double *b, long ldb, long strideB, -+ double beta, double *c, long ldc, long strideC, long batchCount); -+#endif - /* Inverse */ - THC_API void THCudaBlas_Sgetrf(THCState *state, int n, float **a, int lda, int *pivot, int *info, int batchSize); - THC_API void THCudaBlas_Dgetrf(THCState *state, int n, double **a, int lda, int *pivot, int *info, int batchSize); -diff --git a/extra/cutorch/lib/THC/THCCachingHostAllocator.h b/extra/cutorch/lib/THC/THCCachingHostAllocator.h -index 05513ac..8fb0ec9 100644 ---- a/extra/cutorch/lib/THC/THCCachingHostAllocator.h -+++ b/extra/cutorch/lib/THC/THCCachingHostAllocator.h -@@ -22,7 +22,7 @@ - THC_API THAllocator THCCachingHostAllocator; - - // Records an event in the specified stream. The allocation 'ptr' will not be --// re-used until the event has occured. -+// re-used until the event has occurred. - THC_API cudaError_t THCCachingHostAllocator_recordEvent(void *ptr, THCStream *stream); - - // Releases cached pinned memory allocations via cudaHostFree -diff --git a/extra/cutorch/lib/THC/THCDeviceUtils.cuh b/extra/cutorch/lib/THC/THCDeviceUtils.cuh -index 8052860..4ae2bee 100644 ---- a/extra/cutorch/lib/THC/THCDeviceUtils.cuh -+++ b/extra/cutorch/lib/THC/THCDeviceUtils.cuh -@@ -43,7 +43,6 @@ __device__ __forceinline__ unsigned int ACTIVE_MASK() - #endif - } - -- - __device__ __forceinline__ int WARP_BALLOT(int predicate, unsigned int mask = 0xffffffff) - { - #if CUDA_VERSION >= 9000 -diff --git a/extra/cutorch/lib/THC/THCHalf.h b/extra/cutorch/lib/THC/THCHalf.h -index d5bd5c1..bb21b9d 100644 ---- a/extra/cutorch/lib/THC/THCHalf.h -+++ b/extra/cutorch/lib/THC/THCHalf.h -@@ -15,7 +15,7 @@ - - #if CUDA_VERSION >= 9000 - #ifndef __cplusplus -- typedef __half_raw half; -+typedef __half_raw half; - #endif - #endif - -diff --git a/extra/cutorch/lib/THC/THCNumerics.cuh b/extra/cutorch/lib/THC/THCNumerics.cuh -index ba86e8f..a36ff14 100644 ---- a/extra/cutorch/lib/THC/THCNumerics.cuh -+++ b/extra/cutorch/lib/THC/THCNumerics.cuh -@@ -44,6 +44,7 @@ struct THCNumerics { - static inline __host__ __device__ bool eq(char a, char b) { return a == b; } - static inline __host__ __device__ bool ne(char a, char b) { return a != b; } - -+ static inline __host__ __device__ char neg(char a) { return -a; } - static inline __host__ __device__ char add(char a, char b) { return a + b; } - static inline __host__ __device__ char mul(char a, char b) { return a * b; } - static inline __host__ __device__ char sub(char a, char b) { return a - b; } -@@ -63,6 +64,7 @@ struct THCNumerics { - static inline __host__ __device__ bool eq(short a, short b) { return a == b; } - static inline __host__ __device__ bool ne(short a, short b) { return a != b; } - -+ static inline __host__ __device__ short neg(short a) { return -a; } - static inline __host__ __device__ short add(short a, short b) { return a + b; } - static inline __host__ __device__ short mul(short a, short b) { return a * b; } - static inline __host__ __device__ short sub(short a, short b) { return a - b; } -@@ -82,6 +84,7 @@ struct THCNumerics { - static inline __host__ __device__ bool eq(int a, int b) { return a == b; } - static inline __host__ __device__ bool ne(int a, int b) { return a != b; } - -+ static inline __host__ __device__ int neg(int a) { return -a; } - static inline __host__ __device__ int add(int a, int b) { return a + b; } - static inline __host__ __device__ int mul(int a, int b) { return a * b; } - static inline __host__ __device__ int sub(int a, int b) { return a - b; } -@@ -101,6 +104,7 @@ struct THCNumerics { - static inline __host__ __device__ bool eq(long a, long b) { return a == b; } - static inline __host__ __device__ bool ne(long a, long b) { return a != b; } - -+ static inline __host__ __device__ long neg(long a) { return -a; } - static inline __host__ __device__ long add(long a, long b) { return a + b; } - static inline __host__ __device__ long mul(long a, long b) { return a * b; } - static inline __host__ __device__ long sub(long a, long b) { return a - b; } -diff --git a/extra/cutorch/lib/THC/THCReduce.cuh b/extra/cutorch/lib/THC/THCReduce.cuh -index b7df49b..cae6cf1 100644 ---- a/extra/cutorch/lib/THC/THCReduce.cuh -+++ b/extra/cutorch/lib/THC/THCReduce.cuh -@@ -10,6 +10,7 @@ - - #include "THCTensorTypeUtils.cuh" - #include "THCReduceApplyUtils.cuh" -+#include "THCNumerics.cuh" - - // Threads per thread block - #define THC_NONCONTIG_REDUCE_BLOCK_SIZE 32 * 16 -@@ -23,7 +24,9 @@ __device__ __forceinline__ IndexType getReduceNoncontigDimSliceIndex() { - // Kernel that handles an entire reduction of a slice of a tensor per each thread - template - #if __CUDA_ARCH__ >= 350 -@@ -35,17 +38,18 @@ kernelReduceNoncontigDim_shared(TensorInfo out, - IndexType reductionStride, - IndexType reductionSize, - IndexType totalSlices, -- T init, -+ AccT init, - ModifyOp modifyOp, -- ReduceOp reduceOp) { -+ ReduceOp reduceOp, -+ ReduceAccOp reduceAccOp) { - - IndexType sliceIndex = blockIdx.x * blockDim.x + threadIdx.x; - IndexType sliceStride = gridDim.x * blockDim.x; - -- __shared__ T local_reduce[THC_NONCONTIG_REDUCE_BLOCK_SIZE]; -- T* shmem = &local_reduce[threadIdx.x + threadIdx.y * blockDim.x]; -+ __shared__ AccT local_reduce[THC_NONCONTIG_REDUCE_BLOCK_SIZE]; -+ AccT* shmem = &local_reduce[threadIdx.x + threadIdx.y * blockDim.x]; - T load_reg[4]; -- T local_reg; -+ AccT local_reg; - - for(;sliceIndex out, - load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); - load_reg[2] = modifyOp(in.data[inOffset + (i + blockDim.y * 2) * reductionStride]); - load_reg[3] = modifyOp(in.data[inOffset + (i + blockDim.y * 3) * reductionStride]); -- -- local_reg = reduceOp(local_reg, -- reduceOp( -- reduceOp(load_reg[0], load_reg[1]), -- reduceOp(load_reg[2], load_reg[3]) -- ) -- ); -- -+ local_reg = reduceOp(local_reg, load_reg[0]); -+ local_reg = reduceOp(local_reg, load_reg[1]); -+ local_reg = reduceOp(local_reg, load_reg[2]); -+ local_reg = reduceOp(local_reg, load_reg[3]); - }else if(i + blockDim.y * 2 < reductionSize){ - load_reg[0] = modifyOp(in.data[inOffset + (i + blockDim.y * 0) * reductionStride]); - load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); - load_reg[2] = modifyOp(in.data[inOffset + (i + blockDim.y * 2) * reductionStride]); -- -- local_reg = reduceOp( -- reduceOp(load_reg[0], load_reg[1]), -- reduceOp(load_reg[2], local_reg) -- ); -- -- }else if( (i + blockDim.y) < reductionSize){ -+ local_reg = reduceOp(local_reg, load_reg[0]); -+ local_reg = reduceOp(local_reg, load_reg[1]); -+ local_reg = reduceOp(local_reg, load_reg[2]); -+ }else if( (i + blockDim.y) < reductionSize){ - load_reg[0] = modifyOp(in.data[inOffset + (i + blockDim.y * 0) * reductionStride]); - load_reg[1] = modifyOp(in.data[inOffset + (i + blockDim.y * 1) * reductionStride]); -- local_reg = reduceOp( -- local_reg, reduceOp(load_reg[0], load_reg[1]) -- ); -- -+ local_reg = reduceOp(local_reg, load_reg[0]); -+ local_reg = reduceOp(local_reg, load_reg[1]); - }else if(i + blockDim.y * 0 < reductionSize){ - local_reg = reduceOp(local_reg, modifyOp(in.data[inOffset + i * reductionStride])); - } -@@ -100,15 +95,15 @@ kernelReduceNoncontigDim_shared(TensorInfo out, - while(dimy > 1){ - __syncthreads(); - if( threadIdx.y == 0 && (dimy%2 != 0) ){ -- *shmem = reduceOp(*shmem, *(shmem + (dimy-1) * blockDim.x) ); -+ *shmem = reduceAccOp(*shmem, *(shmem + (dimy-1) * blockDim.x) ); - } - if(threadIdx.y < dimy/2){ -- *shmem = reduceOp(*shmem, *(shmem + (dimy/2)*blockDim.x) ); -+ *shmem = reduceAccOp(*shmem, *(shmem + (dimy/2)*blockDim.x) ); - } - dimy /= 2; - } - if(threadIdx.y == 0) -- out.data[outOffset] = *shmem; -+ out.data[outOffset] = ScalarConvert::to(*shmem); - } - } - -@@ -116,7 +111,9 @@ kernelReduceNoncontigDim_shared(TensorInfo out, - // Kernel that handles an entire reduction of a slice of a tensor per each thread - template - #if __CUDA_ARCH__ >= 350 -@@ -128,9 +125,10 @@ kernelReduceNoncontigDim(TensorInfo out, - IndexType reductionStride, - IndexType reductionSize, - IndexType totalSlices, -- T init, -+ AccT init, - ModifyOp modifyOp, -- ReduceOp reduceOp) { -+ ReduceOp reduceOp, -+ ReduceAccOp reduceAccOp) { - const IndexType sliceIndex = getReduceNoncontigDimSliceIndex(); - - if (sliceIndex >= totalSlices) { -@@ -146,7 +144,7 @@ kernelReduceNoncontigDim(TensorInfo out, - - // For each point in reductionSize, reduce into `r` - IndexType inOffset = inBaseOffset; -- T r = init; -+ AccT r = init; - - for (IndexType i = 0; i < reductionSize; ++i) { - r = reduceOp(r, modifyOp(in.data[inOffset])); -@@ -154,7 +152,7 @@ kernelReduceNoncontigDim(TensorInfo out, - } - - // Write out reduced value -- out.data[outOffset] = r; -+ out.data[outOffset] = ScalarConvert::to(r); - } - - template -@@ -167,7 +165,9 @@ __device__ __forceinline__ IndexType getReduceContigDimSliceIndex() { - // each block - template - __global__ void -@@ -175,9 +175,10 @@ kernelReduceContigDim(TensorInfo out, - TensorInfo in, - IndexType reductionSize, - IndexType totalSlices, -- T init, -+ AccT init, - ModifyOp modifyOp, -- ReduceOp reduceOp) { -+ ReduceOp reduceOp, -+ ReduceAccOp reduceAccOp) { - const IndexType sliceIndex = getReduceContigDimSliceIndex(); - - if (sliceIndex >= totalSlices) { -@@ -195,7 +196,7 @@ kernelReduceContigDim(TensorInfo out, - // Each thread in the block will reduce some subset of elements in - // the slice. The elements are guaranteed contiguous starting at - // `inBaseOffset`. -- T r = init; -+ AccT r = init; - for (IndexType i = threadIdx.x; i < reductionSize; i += blockDim.x) { - r = reduceOp(r, modifyOp(in.data[inBaseOffset + i])); - } -@@ -203,12 +204,12 @@ kernelReduceContigDim(TensorInfo out, - // Reduce within the block - // FIXME: extern name - extern __shared__ char smemChar[]; -- T* smem = (T*) smemChar; -- r = reduceBlock(smem, blockDim.x, r, reduceOp, init); -+ AccT* smem = (AccT*) smemChar; -+ r = reduceBlock(smem, blockDim.x, r, reduceAccOp, init); - - if (threadIdx.x == 0) { - // Write out reduced value -- out.data[outOffset] = r; -+ out.data[outOffset] = ScalarConvert::to(r); - } - } - -@@ -254,13 +255,18 @@ inline bool getContigReduceGrid(ptrdiff_t elements, dim3& grid) { - - // Performs a reduction out[..., 0, ...] = reduce_i(modify(in[..., i, ...])) for - // all in where i and the out's 0 are indexed at dimension `dim` --template -+template - bool THC_reduceDim(THCState* state, - TensorType* out, - TensorType* in, - const ModifyOp& modifyOp, - const ReduceOp& reduceOp, -- typename TensorUtils::DataType init, -+ const ReduceAccOp& reduceAccOp, -+ AccT init, - int dim, - int keepdim) { - ptrdiff_t inElements = TensorUtils::getNumElements(state, in); -@@ -292,7 +298,7 @@ bool THC_reduceDim(THCState* state, - } - - block = getContigReduceBlock(outElements, reductionSize); -- smemSize = sizeof(typename TensorUtils::DataType) * block.x; -+ smemSize = sizeof(AccT) * block.x; - } else { - if (!getNoncontigReduceGrid(outElements, grid)) { - return false; -@@ -335,27 +341,31 @@ bool THC_reduceDim(THCState* state, - // index can be similarly collapsed. That is what this unrolling is for. - #define HANDLE_CASE(TYPE, OUT, IN) \ - if (contigReduction) { \ -- kernelReduceContigDim::DataType, \ -+ AccT, \ - TYPE, OUT, IN> \ - <<>>( \ - outInfo, inInfo, reductionSize, \ -- (TYPE) outElements, init, modifyOp, reduceOp); \ -+ (TYPE) outElements, init, modifyOp, reduceOp, reduceAccOp); \ - } else { \ - if(block.y == 1){ \ -- kernelReduceNoncontigDim::DataType, \ -+ AccT, \ - TYPE, OUT, IN> \ - <<>>( \ - outInfo, inInfo, reductionStride, reductionSize, \ -- (TYPE) outElements, init, modifyOp, reduceOp); \ -+ (TYPE) outElements, init, modifyOp, reduceOp, reduceAccOp); \ - }else{ \ -- kernelReduceNoncontigDim_shared::DataType, \ -+ AccT, \ - TYPE, OUT, IN> \ - <<>>( \ - outInfo, inInfo, reductionStride, reductionSize, \ -- (TYPE) outElements, init, modifyOp, reduceOp); \ -+ (TYPE) outElements, init, modifyOp, reduceOp, \ -+ reduceAccOp); \ - } \ - } \ - -@@ -409,7 +419,6 @@ bool THC_reduceDim(THCState* state, - getTensorInfo(state, in); - inInfo.reduceDim(dim); - inInfo.collapseDims(); -- - HANDLE_OUT_CASE(unsigned int, outInfo.dims, inInfo.dims); - } else { - TensorInfo::DataType, -diff --git a/extra/cutorch/lib/THC/THCScanUtils.cuh b/extra/cutorch/lib/THC/THCScanUtils.cuh -index ce9619d..9a487ca 100644 ---- a/extra/cutorch/lib/THC/THCScanUtils.cuh -+++ b/extra/cutorch/lib/THC/THCScanUtils.cuh -@@ -2,6 +2,7 @@ - #define THC_SCAN_UTILS_INC - - #include "THCAsmUtils.cuh" -+#include "THCDeviceUtils.cuh" - - // Collection of in-kernel scan / prefix sum utilities - -diff --git a/extra/cutorch/lib/THC/THCTensorConv.cu b/extra/cutorch/lib/THC/THCTensorConv.cu -index c8c1ad6..b8f3263 100644 ---- a/extra/cutorch/lib/THC/THCTensorConv.cu -+++ b/extra/cutorch/lib/THC/THCTensorConv.cu -@@ -663,7 +663,7 @@ THC_API void THCudaTensor_conv2DRevgerm(THCState *state, THCudaTensor *output, f - float *output_data = THCudaTensor_data(state, output); - - // kernel is called multiple times -- // (the arbitrary split below is just here to make sure we dont go over 256 threads) -+ // (the arbitrary split below is just here to make sure we don't go over 256 threads) - for (int sl=0; sl dst, - - template - struct LinearIndexCalcData { -+ // sizes for the Tensor dims (from the Tensor, for bounds checking) -+ IndexType baseSizes[Dims]; - // sizes for Tensor dims (either from the Tensor, or the size of the adv indexer at that dim) - IndexType sizes[Dims]; - // strides for the Tensor we are indexing into -@@ -373,6 +375,7 @@ __device__ __forceinline__ long calculateOffset( - indexAtDim = index - nextIndex * sizeAtDim; - } - -+ assert(indexAtDim < data.baseSizes[dim]); - offset += indexAtDim * strideAtDim; - index = nextIndex; - } -diff --git a/extra/cutorch/lib/THC/THCTensorMath.cuh b/extra/cutorch/lib/THC/THCTensorMath.cuh -index ae8f5db..202090e 100644 ---- a/extra/cutorch/lib/THC/THCTensorMath.cuh -+++ b/extra/cutorch/lib/THC/THCTensorMath.cuh -@@ -26,6 +26,24 @@ __global__ void THCTensor_copyToDiagonal(T* a, T* b, ptrdiff_t start, ptrdiff_t - #define CAT_ARRAY_BATCH_SIZE 1024 - #define CAT_ARRAY_MAX_INPUT_DIMS 4 - -+inline bool getCatGrid(THCState* state, ptrdiff_t nTensors, dim3& grid) { -+ int curDevice = -1; -+ cudaGetDevice(&curDevice); -+ -+ if (curDevice == -1) { -+ return false; -+ } -+ -+ // Assume a reasonable number of SMs if no state is available -+ int numSM = -+ state ? THCState_getCurrentDeviceProperties(state)->multiProcessorCount : 15; -+ //X dim of grid for cat array cooperates on a single tensor in the cat. -+ //Given half of the GPU, full utilization will always occur. -+ grid = dim3( 2LL * numSM, (long long) nTensors ); -+ -+ return true; -+} -+ - // Similar to any other IndexToOffset calculation for copying along a given dimension. - template - struct CatArrIndexToOffset { -@@ -77,6 +95,9 @@ struct OutputTensorSizeStride { - * - * The most important assumption made is that the input tensors are contiguous. - */ -+ -+ -+ - template - __global__ void CatArrayBatchedCopy( - T* output, -@@ -84,19 +105,26 @@ __global__ void CatArrayBatchedCopy( - OutputTensorSizeStride os, - const int concatDim, - IndexType dimStride) { -- T* data = inputs[blockIdx.y].input; -- IndexType offset = inputs[blockIdx.y].offset; -- IndexType dimSize = inputs[blockIdx.y].dimSize; -- IndexType nElements = inputs[blockIdx.y].nElements; -- IndexType dataOffset = offset * dimStride; -- -- for (IndexType linearIndex = blockIdx.x * blockDim.x + threadIdx.x; -- linearIndex < nElements; -- linearIndex += gridDim.x * blockDim.x) { -+ -+ IndexType tid = blockIdx.x * blockDim.x + threadIdx.x; -+ IndexType nElements = inputs[blockIdx.y].nElements; -+ -+ if(tid >= nElements) return; -+ -+ T* data = inputs[blockIdx.y].input; -+ IndexType offset = inputs[blockIdx.y].offset; -+ IndexType dimSize = inputs[blockIdx.y].dimSize; -+ IndexType dataOffset = offset * dimStride; -+ -+ IndexType stride = gridDim.x * blockDim.x; -+ -+ while( tid < nElements){ - IndexType elementOffset = CatArrIndexToOffset::compute( -- os.outputSize, os.outputStride, dimSize, concatDim, linearIndex); -- output[dataOffset + elementOffset] = data[linearIndex]; -- } -+ os.outputSize, os.outputStride, dimSize, concatDim, tid); -+ output[dataOffset + elementOffset] = data[tid]; -+ -+ tid += stride; -+ } - } - - #endif -diff --git a/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh b/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh -index 6ab010a..a37645d 100644 ---- a/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh -+++ b/extra/cutorch/lib/THC/THCTensorMathPointwise.cuh -@@ -264,60 +264,47 @@ struct TensorMulOp { - }; - #endif // CUDA_HALF_TENSOR - --template -+template - struct TensorPowOp { - TensorPowOp(T v) : val(v) {} - __device__ __forceinline__ void operator()(T* out, T* in) { -- *out = powf((float) *in, (float) val); -+ if (StaticExp == 1) { -+ *out = *in; -+ } else if (StaticExp == 2) { -+ *out = THCNumerics::mul(*in, *in); -+ } else if (StaticExp == 3) { -+ *out = THCNumerics::mul(*in, *in); -+ *out = THCNumerics::mul(*out, *in); -+ } else if (StaticExp == -1) { -+ *out = THCNumerics::cinv(*in); -+ } else if (StaticExp == -2) { -+ *out = THCNumerics::mul(*in, *in); -+ *out = THCNumerics::cinv(*out); -+ } else { -+ *out = THCNumerics::pow(*in, val); -+ } - } - - __device__ __forceinline__ void operator()(T* v) { -- *v = powf((float) *v, (float) val); -+ if (StaticExp == 1) { -+ *v = *v; -+ } else if (StaticExp == 2) { -+ *v = THCNumerics::mul(*v, *v); -+ } else if (StaticExp == 3) { -+ *v = THCNumerics::mul(THCNumerics::mul(*v, *v), *v); -+ } else if (StaticExp == -1) { -+ *v = THCNumerics::cinv(*v); -+ } else if (StaticExp == -2) { -+ *v = THCNumerics::mul(*v, *v); -+ *v = THCNumerics::cinv(*v); -+ } else { -+ *v = THCNumerics::pow(*v, val); -+ } - } - - const T val; - }; - --template <> --struct TensorPowOp { -- TensorPowOp(double v) : val(v) {} -- -- __device__ __forceinline__ void operator()(double* out, double* in) { -- *out = pow(*in, val); -- } -- -- __device__ __forceinline__ void operator()(double* v) { -- *v = pow(*v, val); -- } -- -- const double val; --}; -- --#ifdef CUDA_HALF_TENSOR --template <> --struct TensorPowOp { -- TensorPowOp(half v) : val(v) {} -- -- __device__ __forceinline__ void operator()(half* out, half* in) { -- // No fp16 pow function yet -- float fin = __half2float(*in); -- float fval = __half2float(val); -- float fout = powf(fin, fval); -- *out = __float2half(fout); -- } -- -- __device__ __forceinline__ void operator()(half* v) { -- // No fp16 pow function yet -- float fv = __half2float(*v); -- float fval = __half2float(val); -- float fout = powf(fv, fval); -- *v = __float2half(fout); -- } -- -- const half val; --}; --#endif // CUDA_HALF_TENSOR -- - template - struct TensorTPowOp { - TensorTPowOp(T v) : val(v) {} -diff --git a/extra/cutorch/lib/THC/THCTensorMode.cuh b/extra/cutorch/lib/THC/THCTensorMode.cuh -index b67ac2a..4d062e7 100644 ---- a/extra/cutorch/lib/THC/THCTensorMode.cuh -+++ b/extra/cutorch/lib/THC/THCTensorMode.cuh -@@ -245,7 +245,7 @@ __global__ void computeMode( - // Finally, we need to find the "an" index of the mode in the input Tensor. The API does - // not constrain which index we pick, so it can be any of the indices that contain the mode. - // We will do a reduction to find the index. We go back to using the (index, flag) buffer -- // arrangment. First, we mark indices that are equal to the mode, i.e B[i] = true if -+ // arrangement. First, we mark indices that are equal to the mode, i.e B[i] = true if - // input[i] == mode, and initialize C[i] to be the index - // - // Again we reduce 2 elements in the thread's registers prior to the block-wide reduction -diff --git a/extra/cutorch/lib/THC/THCTensorRandom.cuh b/extra/cutorch/lib/THC/THCTensorRandom.cuh -index 96263da..8d9f6cc 100644 ---- a/extra/cutorch/lib/THC/THCTensorRandom.cuh -+++ b/extra/cutorch/lib/THC/THCTensorRandom.cuh -@@ -92,7 +92,7 @@ condDiv(T *q, long *J, long inputsize, T q_max) { - // Normalizes the L1 norm of every row to 1; used by multinomial - template - __global__ void renormRowsL1(T* dist, long rows, long cols) { -- extern __shared__ __align__(sizeof(T)) unsigned char my_smem[]; -+ extern __shared__ unsigned char my_smem[]; - T *smem = reinterpret_cast(my_smem); - - for (long row = blockIdx.x; row < rows; row += gridDim.x) { -@@ -153,7 +153,7 @@ sampleMultinomialOnce(long* dest, - int categories, - T* sampled, - T* dist) { -- extern __shared__ __align__(sizeof(AccT)) unsigned char my_smem[]; -+ extern __shared__ unsigned char my_smem[]; - __shared__ bool found; - - // Shared Memory hold blockdim.x T for holding the cumulative sum, -diff --git a/extra/cutorch/lib/THC/generic/THCTensorIndex.cu b/extra/cutorch/lib/THC/generic/THCTensorIndex.cu -index a9ed28e..ac42cf5 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorIndex.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorIndex.cu -@@ -535,6 +535,7 @@ void THCTensor_(calculateAdvancedIndexingOffsets)( - { \ - LinearIndexCalcData data; \ - for (int i = 0; i < DIMS; ++i) { \ -+ data.baseSizes[i] = THCTensor_(size)(state, indexed, i); \ - data.sizes[i] = indexers[i] != NULL ? \ - THCudaLongTensor_nElement(state, indexers[i]) : \ - THCTensor_(size)(state, indexed, i); \ -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMath.cu b/extra/cutorch/lib/THC/generic/THCTensorMath.cu -index 0eed5a9..ceb6f2d 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMath.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMath.cu -@@ -43,6 +43,14 @@ THCTensor_(zeros)(THCState *state, THCTensor *r_, THLongStorage *size) - THCTensor_(zero)(state, r_); - } - -+THC_API void -+THCTensor_(zerosLike)(THCState *state, THCTensor *r_, THCTensor *input) -+{ -+ THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, r_, input)); -+ THCTensor_(resizeAs)(state, r_, input); -+ THCTensor_(zero)(state, r_); -+} -+ - THC_API void - THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size) - { -@@ -51,6 +59,14 @@ THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size) - THCTensor_(fill)(state, r_, ScalarConvert::to(1)); - } - -+THC_API void -+THCTensor_(onesLike)(THCState *state, THCTensor *r_, THCTensor *input) -+{ -+ THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, r_, input)); -+ THCTensor_(resizeAs)(state, r_, input); -+ THCTensor_(fill)(state, r_, ScalarConvert::to(1)); -+} -+ - THC_API void - THCTensor_(reshape)(THCState *state, THCTensor *r_, THCTensor *t, THLongStorage *size) - { -@@ -191,7 +207,7 @@ void THCTensor_(catArray)(THCState *state, THCTensor *result, - - // Template Declarations for dim = 1, 2, 3, 4 - #define HANDLE_CASE(DIMS) \ -- CatArrayBatchedCopy<<stream>>>(data, d_inputs, param, cat_dimension, param.outputStride[cat_dimension]); -+ CatArrayBatchedCopy<<stream>>>(data, d_inputs, param, cat_dimension, param.outputStride[cat_dimension]); - - // Now we loop - offset = 0; -@@ -227,15 +243,12 @@ void THCTensor_(catArray)(THCState *state, THCTensor *result, - // is based on. - dim3 applyBlock = getApplyBlock(); - -- // We also re-use the applyGrid - but note that we use the maximum number of -- // elements for a given tensor in this grouping to determine the count -- dim3 applyGrid; -- getApplyGrid(state, cohortMax, applyGrid); -+ //Get grid where x dim fills half gpu and y dim is number of tensors. -+ //This will have cating two tensors fill the entire grid, but prevent -+ //many threads from needlessly load meta data if their sizes is small. -+ dim3 catGrid; -+ getCatGrid(state, j, catGrid); - -- // Next, we set our grid's y component to be the number of tensors in -- // the batch. This will allow the kernel to determine which input -- // tensor it is responsible for copying -- applyGrid.y = j; - - switch (maxDim) { - case 1: -@@ -376,6 +389,28 @@ void THCTensor_(diag)(THCState *state, THCTensor *self_, THCTensor *src_, long k - THCudaCheck(cudaGetLastError()); - } - -+void THCTensor_(eye)(THCState *state, THCTensor *self_, long n, long m) -+{ -+ THCAssertSameGPU(THCTensor_(checkGPU)(state, 1, self_)); -+ THArgCheck(n > 0, 1, "invalid argument"); -+ -+ if(m <= 0) -+ m = n; -+ -+ THCTensor_(resize2d)(state, self_, n, m); -+ THCTensor_(zero)(state, self_); -+ -+ long sz = THMin(n, m); -+ long stride = THCTensor_(stride)(state, self_, 0) + -+ THCTensor_(stride)(state, self_, 1); -+ -+ THCTensor *diag = THCTensor_(newWithStorage1d)(state, self_->storage, -+ self_->storageOffset, sz, stride); -+ -+ THCTensor_(fill)(state, diag, ScalarConvert::to(1)); -+ THCTensor_(free)(state, diag); -+} -+ - accreal THCTensor_(trace)(THCState *state, THCTensor *src_) { - THCAssertSameGPU(THCTensor_(checkGPU)(state, 1, src_)); - THArgCheck((src_->nDimension == 2), 1, "expected a matrix"); -@@ -449,4 +484,15 @@ void THCTensor_(range)(THCState *state, THCTensor *r_, accreal xmin, accreal xma - THCudaCheck(cudaGetLastError()); - } - -+void THCTensor_(arange)(THCState* state, THCTensor *r_, accreal xmin, accreal xmax, accreal step) { -+#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) -+ int m = fmod(xmax - xmin, step) == 0; -+#else -+ int m = (xmax - xmin) % step == 0; -+#endif -+ if (m) -+ xmax -= step; -+ THCTensor_(range)(state, r_, xmin, xmax, step); -+} -+ - #endif -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMath.h b/extra/cutorch/lib/THC/generic/THCTensorMath.h -index aae6775..26a7a49 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMath.h -+++ b/extra/cutorch/lib/THC/generic/THCTensorMath.h -@@ -6,7 +6,9 @@ THC_API void THCTensor_(fill)(THCState *state, THCTensor *self, real value); - THC_API void THCTensor_(zero)(THCState *state, THCTensor *self); - - THC_API void THCTensor_(zeros)(THCState *state, THCTensor *r_, THLongStorage *size); -+THC_API void THCTensor_(zerosLike)(THCState *state, THCTensor *r_, THCTensor* input); - THC_API void THCTensor_(ones)(THCState *state, THCTensor *r_, THLongStorage *size); -+THC_API void THCTensor_(onesLike)(THCState *state, THCTensor *r_, THCTensor* input); - THC_API void THCTensor_(reshape)(THCState *state, THCTensor *r_, THCTensor *t, THLongStorage *size); - THC_API ptrdiff_t THCTensor_(numel)(THCState *state, THCTensor *t); - THC_API void THCTensor_(cat)(THCState *state, THCTensor *result, THCTensor *ta, THCTensor *tb, int dimension); -@@ -16,6 +18,7 @@ THC_API void THCTensor_(nonzero)(THCState* state, THCudaLongTensor *tensor, THCT - THC_API void THCTensor_(tril)(THCState *state, THCTensor *self, THCTensor *src, long k); - THC_API void THCTensor_(triu)(THCState *state, THCTensor *self, THCTensor *src, long k); - THC_API void THCTensor_(diag)(THCState *state, THCTensor *self, THCTensor *src, long k); -+THC_API void THCTensor_(eye)(THCState *state, THCTensor *self, long n, long m); - THC_API accreal THCTensor_(trace)(THCState *state, THCTensor *self); - - #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) -@@ -26,5 +29,6 @@ THC_API void THCTensor_(logspace)(THCState *state, THCTensor *r_, real a, real b - #endif - - THC_API void THCTensor_(range)(THCState *state, THCTensor *r_, accreal xmin, accreal xmax, accreal step); -+THC_API void THCTensor_(arange)(THCState *state, THCTensor *r_, accreal xmin, accreal xmax, accreal step); - - #endif -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu b/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu -index 61c255a..8dc542e 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMathBlas.cu -@@ -492,13 +492,15 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, - ldc = result_->stride[2]; - } - -- if (batch1->stride[transpose_result ? 2 : 1] == 1) -+ if (batch1->stride[transpose_result ? 2 : 1] == 1 && -+ batch1->stride[transpose_result ? 1 : 2] != 0) - { - transpose_batch1 = 'n'; - batch1_ = batch1; - lda = batch1_->stride[transpose_result ? 1 : 2]; - } -- else if (batch1->stride[transpose_result ? 1 : 2] == 1) -+ else if (batch1->stride[transpose_result ? 1 : 2] == 1 && -+ batch1->stride[transpose_result ? 2 : 1] != 0) - { - transpose_batch1 = 't'; - batch1_ = batch1; -@@ -511,13 +513,15 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, - lda = batch1_->stride[1]; - } - -- if (batch2->stride[transpose_result ? 2 : 1] == 1) -+ if (batch2->stride[transpose_result ? 2 : 1] == 1 && -+ batch2->stride[transpose_result ? 1 : 2] != 0) - { - transpose_batch2 = 'n'; - batch2_ = batch2; - ldb = batch2_->stride[transpose_result ? 1 : 2]; - } -- else if (batch2->stride[transpose_result ? 1 : 2] == 1) -+ else if (batch2->stride[transpose_result ? 1 : 2] == 1 && -+ batch2->stride[transpose_result ? 2 : 1] != 0) - { - transpose_batch2 = 't'; - batch2_ = batch2; -@@ -529,14 +533,14 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, - batch2_ = THCTensor_(newContiguous)(state, batch2); - ldb = batch2_->stride[1]; - } -- - long num_batches = result_->size[0]; - - #if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) - // Compute pointers to matrices in each batch. -+#if CUDA_VERSION < 8000 - size_t matrices_size = num_batches * sizeof(real*); - -- // Copy pointers to device. -+// Copy pointers to device. - const real **d_matrices1, **d_matrices2; - real **d_result_matrices; - THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, matrices_size)); -@@ -555,7 +559,6 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, - createBatchGemmBuffer<<>>( - (const real**)d_result_matrices, THCTensor_(data)(state,result_), - result_->stride[0], num_batches); -- - #ifdef THC_REAL_IS_FLOAT - THCudaBlas_SgemmBatched( - state, -@@ -589,6 +592,38 @@ THCTensor_(baddbmm)(THCState *state, THCTensor *result, real beta, THCTensor *t, - THCudaFree(state, d_matrices1); - THCudaFree(state, d_matrices2); - THCudaFree(state, d_result_matrices); -+ -+#else -+#ifdef THC_REAL_IS_FLOAT -+ THCudaBlas_SgemmStridedBatched( -+ state, -+ transpose_batch1, -+ transpose_batch2, -+ result_->size[transpose_result ? 2 : 1], -+ result_->size[transpose_result ? 1 : 2], -+ batch1_->size[transpose_result ? 1 : 2], -+ alpha, -+ THCTensor_(data)(state, batch1_), lda, batch1_->stride[0], -+ THCTensor_(data)(state, batch2_), ldb, batch2_->stride[0], -+ beta, -+ THCTensor_(data)(state, result_), ldc, result_->stride[0], -+ num_batches); -+#elif defined(THC_REAL_IS_DOUBLE) -+ THCudaBlas_DgemmStridedBatched( -+ state, -+ transpose_batch1, -+ transpose_batch2, -+ result_->size[transpose_result ? 2 : 1], -+ result_->size[transpose_result ? 1 : 2], -+ batch1_->size[transpose_result ? 1 : 2], -+ alpha, -+ THCTensor_(data)(state, batch1_), lda, batch1_->stride[0], -+ THCTensor_(data)(state, batch2_), ldb, batch2_->stride[0], -+ beta, -+ THCTensor_(data)(state, result_), ldc, result_->stride[0], -+ num_batches); -+#endif -+#endif - - #elif defined(THC_REAL_IS_HALF) - // Currently no HgemmBatched in Cublas -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu b/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu -index c35a83e..5e6b782 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMathMagma.cu -@@ -347,10 +347,10 @@ THC_API void THCTensor_(gesvd2)(THCState *state, THCTensor *ru_, THCTensor *rs_, - - THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) - { --#ifdef USE_MAGMA - THArgCheck(a->nDimension == 2, 2, "A should be 2 dimensional"); - THArgCheck(a->size[0] == a->size[1], 2, "A should be square"); - -+#ifdef USE_MAGMA - int info; - int n = a->size[0]; - int lwork = n * magma_get_sgetri_nb(n); -@@ -391,37 +391,23 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) - magma_free_pinned(ipiv); - THCTensor_(freeCopyTo)(state, input, ra_); - #else -- THArgCheck(a->nDimension == 2, 2, "A should be 2 dimensional"); -- THArgCheck(a->size[0] == a->size[1], 2, "A should be square"); -- - int n = a->size[0]; - - // input -- THCTensor *input = THCTensor_(newColumnMajor)(state, ra_, a); -- // output -- THCTensor *output = THCTensor_(newColumnMajor)(state, ra_, a); -+ THCTensor *input = THCTensor_(newColumnMajor)(state, a, a); -+ THCTensor_(resizeNd)(state, ra_, 2, input->size, input->stride); - -- size_t matrices_size = sizeof(real*); -- -- real **matrices1 = (real **)THAlloc(matrices_size); -- const real **matrices1_const = (const real **)THAlloc(matrices_size); -- real **matrices2 = (real **)THAlloc(matrices_size); -- matrices1[0] = THCTensor_(data)(state, input); -- matrices1_const[0] = THCTensor_(data)(state, input); -- matrices2[0] = THCTensor_(data)(state, output); -+ real *matrices1[1] = { THCTensor_(data)(state, input) }; -+ real *matrices2[1] = { THCTensor_(data)(state, ra_) }; - - // Copy pointers to device. - real **d_matrices1, **d_matrices2; -- const real **d_matrices1_const; -- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, matrices_size)); -- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1_const, matrices_size)); -- THCudaCheck(THCudaMalloc(state, (void**)&d_matrices2, matrices_size)); -+ THCudaCheck(THCudaMalloc(state, (void**)&d_matrices1, sizeof(real*))); -+ THCudaCheck(THCudaMalloc(state, (void**)&d_matrices2, sizeof(real*))); - -- THCudaCheck(cudaMemcpyAsync(d_matrices1, matrices1, matrices_size, -- cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); -- THCudaCheck(cudaMemcpyAsync(d_matrices1_const, matrices1_const, matrices_size, -+ THCudaCheck(cudaMemcpyAsync(d_matrices1, matrices1, sizeof(real*), - cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); -- THCudaCheck(cudaMemcpyAsync(d_matrices2, matrices2, matrices_size, -+ THCudaCheck(cudaMemcpyAsync(d_matrices2, matrices2, sizeof(real*), - cudaMemcpyHostToDevice, THCState_getCurrentStream(state))); - int info; - int *info_gpu; -@@ -446,11 +432,13 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) - - // Inverse - #if defined(THC_REAL_IS_FLOAT) -- THCudaBlas_Sgetri(state, n, d_matrices1_const, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); -+ THCudaBlas_Sgetri(state, n, (const real**)d_matrices1, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); - #else -- THCudaBlas_Dgetri(state, n, d_matrices1_const, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); -+ THCudaBlas_Dgetri(state, n, (const real**)d_matrices1, n, ipiv_gpu, d_matrices2, n, info_gpu, 1); - #endif - -+ THCudaCheck(cudaMemcpy(&info, info_gpu, sizeof(int), cudaMemcpyDeviceToHost)); -+ - if (info > 0) - THError("CUBLAS getri : U(%d,%d) is 0, U is singular", info, info); - else if (info < 0) -@@ -460,10 +448,9 @@ THC_API void THCTensor_(getri)(THCState *state, THCTensor *ra_, THCTensor *a) - THCudaCheck(THCudaFree(state, info_gpu)); - - THCudaCheck(THCudaFree(state, d_matrices1)); -- THCudaCheck(THCudaFree(state, d_matrices1_const)); - THCudaCheck(THCudaFree(state, d_matrices2)); - -- THCTensor_(freeCopyTo)(state, output, input); -+ THCTensor_(free)(state, input); - #endif - } - -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu -index cdf4b82..a2c714a 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.cu -@@ -46,7 +46,6 @@ IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(rsqrt, THCNumerics::rsqrt, Real) - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( ceil, THCNumerics::ceil, Real) - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(floor, THCNumerics::floor, Real) - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC(trunc, THCNumerics::trunc, Real) --IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( neg, THCNumerics::neg, Real) - - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( acos, THCNumerics::acos, Real) - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( cosh, THCNumerics::cosh, Real) -@@ -61,6 +60,13 @@ IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( cinv, THCNumerics::cinv, Real) - - #endif - -+#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) || \ -+ defined(THC_REAL_IS_SHORT) || defined(THC_REAL_IS_INT) || defined(THC_REAL_IS_LONG) -+ -+IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( neg, THCNumerics::neg, Real) -+ -+#endif -+ - IMPLEMENT_CUDA_TENSOR_BASIC_FUNC( abs, THCNumerics::abs, Real) - - #undef IMPLEMENT_CUDA_TENSOR_BASIC_FUNC_ -@@ -160,14 +166,60 @@ void THCTensor_(sigmoid)(THCState* state, THCTensor* self_, THCTensor* src) { - void THCTensor_(pow)(THCState *state, THCTensor *self_, THCTensor *src, real value) { - THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self_, src)); - if (self_ == src) { -- if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -- THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ if (THCNumerics::eq(value, ScalarConvert::to(1))) { -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(2))) { -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(3))) { -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(-1))) { -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(-2))) { -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else { -+ // fallback implementation using pow -+ if (!THC_pointwiseApply1(state, self_, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } - } - } else { - THCTensor_(resizeAs)(state, self_, src); - -- if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -- THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ if (THCNumerics::eq(value, ScalarConvert::to(1))) { -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(2))) { -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(3))) { -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(-1))) { -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else if (THCNumerics::eq(value, ScalarConvert::to(-2))) { -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } -+ } else { -+ // fallback implementation using pow -+ if (!THC_pointwiseApply2(state, self_, src, TensorPowOp(value))) { -+ THArgCheck(false, 2, CUTORCH_DIM_WARNING); -+ } - } - } - -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h -index 17171c0..cba627c 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h -+++ b/extra/cutorch/lib/THC/generic/THCTensorMathPointwise.h -@@ -30,11 +30,17 @@ THC_API void THCTensor_(trunc)(THCState *state, THCTensor *self, THCTensor *src) - THC_API void THCTensor_(frac)(THCState *state, THCTensor *self, THCTensor *src); - THC_API void THCTensor_(lerp)(THCState *state, THCTensor *result, THCTensor *a, THCTensor *b, real w); - --THC_API void THCTensor_(neg)(THCState *state, THCTensor *self, THCTensor *src); - THC_API void THCTensor_(cinv)(THCState *state, THCTensor *self, THCTensor *src); - - #endif - -+#if defined(THC_REAL_IS_FLOAT) || defined(THC_REAL_IS_DOUBLE) || defined(THC_REAL_IS_HALF) || \ -+ defined(THC_REAL_IS_SHORT) || defined(THC_REAL_IS_INT) || defined(THC_REAL_IS_LONG) -+ -+THC_API void THCTensor_(neg)(THCState *state, THCTensor *self, THCTensor *src); -+ -+#endif -+ - THC_API void THCTensor_(abs)(THCState *state, THCTensor *self, THCTensor *src); - THC_API void THCTensor_(sign)(THCState *state, THCTensor *self, THCTensor *src); - THC_API void THCTensor_(clamp)(THCState *state, THCTensor *self, THCTensor *src, real min_value, real max_value); -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu b/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu -index 846e7fd..a72562e 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMathReduce.cu -@@ -7,8 +7,9 @@ THCTensor_(sum)(THCState* state, THCTensor *self, THCTensor *src, long dimension - THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); - if (!THC_reduceDim(state, self, src, - thrust::identity(), -- ReduceAdd(), -- ScalarConvert::to(0), -+ ReduceAdd(), -+ ReduceAdd(), -+ ScalarConvert::to(0), - dimension, - keepdim)) { - THArgCheck(false, 2, CUTORCH_DIM_WARNING); -@@ -22,8 +23,9 @@ THCTensor_(prod)(THCState* state, THCTensor *self, THCTensor *src, long dimensio - THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); - if (!THC_reduceDim(state, self, src, - thrust::identity(), -- ReduceMultiply(), -- ScalarConvert::to(1), -+ ReduceMultiply(), -+ ReduceMultiply(), -+ ScalarConvert::to(1), - dimension, - keepdim)) { - THArgCheck(false, 2, CUTORCH_DIM_WARNING); -@@ -161,23 +163,23 @@ THCTensor_(norm)(THCState *state, THCTensor* self, THCTensor* src, real value, l - THCAssertSameGPU(THCTensor_(checkGPU)(state, 2, self, src)); - if (THCNumerics::eq(value, ScalarConvert::to(0.0))) { - THC_reduceDim(state, self, src, -- TensorNonZeroOp(), ReduceAdd(), -- ScalarConvert::to(0.0), dimension, keepdim); -+ TensorNonZeroOp(), ReduceAdd(), ReduceAdd(), -+ ScalarConvert::to(0.0), dimension, keepdim); - } else if (THCNumerics::eq(value, ScalarConvert::to(1.0))) { - THC_reduceDim(state, self, src, -- TensorNormOp(value), ReduceAdd(), -- ScalarConvert::to(0.0), dimension, keepdim); -+ TensorNormOp(value), ReduceAdd(), ReduceAdd(), -+ ScalarConvert::to(0.0), dimension, keepdim); - - } else if (THCNumerics::eq(value, ScalarConvert::to(2.0))) { - THC_reduceDim(state, self, src, -- TensorNormOp(value), ReduceAdd(), -- ScalarConvert::to(0.0), dimension, keepdim); -+ TensorNormOp(value), ReduceAdd(), ReduceAdd(), -+ ScalarConvert::to(0.0), dimension, keepdim); - THCTensor_(pow)(state, self, self, ScalarConvert::to(0.5)); - - } else { - THC_reduceDim(state, self, src, -- TensorNormOp(value), ReduceAdd(), -- ScalarConvert::to(0.0), dimension, keepdim); -+ TensorNormOp(value), ReduceAdd(), ReduceAdd(), -+ ScalarConvert::to(0.0), dimension, keepdim); - THCTensor_(pow)(state, self, self, THCNumerics::cinv(value)); - } - -diff --git a/extra/cutorch/lib/THC/generic/THCTensorMode.cu b/extra/cutorch/lib/THC/generic/THCTensorMode.cu -index e5a17f2..4cbcac0 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorMode.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorMode.cu -@@ -190,6 +190,10 @@ THC_API void THCTensor_(mode)(THCState *state, - if (sliceSize == 1) { - THCTensor_(copy)(state, values, input); - THCudaLongTensor_fill(state, indices, TH_INDEX_BASE); -+ if (!keepdim) { -+ THCTensor_(squeeze1d)(state, values, values, dimension); -+ THCudaLongTensor_squeeze1d(state, indices, indices, dimension); -+ } - return; - } - -diff --git a/extra/cutorch/lib/THC/generic/THCTensorRandom.cu b/extra/cutorch/lib/THC/generic/THCTensorRandom.cu -index b85c5d2..0da1e06 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorRandom.cu -+++ b/extra/cutorch/lib/THC/generic/THCTensorRandom.cu -@@ -34,6 +34,28 @@ THC_API void THCTensor_(normal)(THCState* state, THCTensor *self_, double mean, - THCTensor_(freeCopyTo)(state, self, self_); - }; - -+THC_API void THCTensor_(normal_means)(THCState *state, THCTensor *self, THCTensor *means, double stddev) { -+ THCTensor_(resizeAs)(state, self, means); -+ THCTensor_(normal)(state, self, 0, stddev); -+ THCTensor_(cadd)(state, self, self, ScalarConvert::to(1), means); -+} -+ -+THC_API void THCTensor_(normal_stddevs)(THCState *state, THCTensor *self, double mean, THCTensor *stddevs) -+{ -+ THCTensor_(resizeAs)(state, self, stddevs); -+ THCTensor_(normal)(state, self, 0, 1); -+ THCTensor_(cmul)(state, self, self, stddevs); -+ THCTensor_(add)(state, self, self, ScalarConvert::to(mean)); -+} -+ -+THC_API void THCTensor_(normal_means_stddevs)(THCState *state, THCTensor *self, THCTensor *means, THCTensor *stddevs) -+{ -+ THCTensor_(resizeAs)(state, self, means); -+ THCTensor_(normal)(state, self, 0, 1); -+ THCTensor_(cmul)(state, self, self, stddevs); -+ THCTensor_(cadd)(state, self, self, ScalarConvert::to(1), means); -+} -+ - THC_API void THCTensor_(logNormal)(THCState* state, THCTensor *self_, double mean, double stdv) - { - -diff --git a/extra/cutorch/lib/THC/generic/THCTensorRandom.h b/extra/cutorch/lib/THC/generic/THCTensorRandom.h -index ad23e78..22f5a92 100644 ---- a/extra/cutorch/lib/THC/generic/THCTensorRandom.h -+++ b/extra/cutorch/lib/THC/generic/THCTensorRandom.h -@@ -8,6 +8,9 @@ THC_API void THCTensor_(uniform)(struct THCState *state, THCTensor *self, double - THC_API void THCTensor_(rand)(THCState *state, THCTensor *r_, THLongStorage *size); - THC_API void THCTensor_(randn)(THCState *state, THCTensor *r_, THLongStorage *size); - THC_API void THCTensor_(normal)(struct THCState *state, THCTensor *self, double mean, double stdv); -+THC_API void THCTensor_(normal_means)(struct THCState *state, THCTensor *self, THCTensor *means, double stddev); -+THC_API void THCTensor_(normal_stddevs)(struct THCState *state, THCTensor *self, double mean, THCTensor *stddevs); -+THC_API void THCTensor_(normal_means_stddevs)(struct THCState *state, THCTensor *self, THCTensor *means, THCTensor *stddevs); - THC_API void THCTensor_(logNormal)(struct THCState *state, THCTensor *self, double mean, double stdv); - THC_API void THCTensor_(exponential)(struct THCState *state, THCTensor *self, double lambda); - THC_API void THCTensor_(cauchy)(struct THCState *state, THCTensor *self, double median, double sigma); -Submodule extra/lua-cjson e8972ac..718f272: -diff --git a/extra/lua-cjson/LICENSE b/extra/lua-cjson/LICENSE -index 747a8bf..4883748 100644 ---- a/extra/lua-cjson/LICENSE -+++ b/extra/lua-cjson/LICENSE -@@ -1,4 +1,4 @@ --Copyright (c) 2010-2012 Mark Pulford -+Copyright (c) 2010-2012 Mark Pulford - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the -diff --git a/extra/lua-cjson/README.adoc b/extra/lua-cjson/README.adoc -index e50534c..d84701a 100644 ---- a/extra/lua-cjson/README.adoc -+++ b/extra/lua-cjson/README.adoc -@@ -1,5 +1,5 @@ - = Lua CJSON = --Mark Pulford -+Mark Pulford - - The Lua CJSON module provides JSON support for Lua. - -@@ -20,7 +20,7 @@ Please read +manual.adoc+ for installation instructions and the API - manual. - - The current stable version of this software is available from the --http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. -+https://kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. - - Feel free to email me if you have any patches, suggestions, or comments. - -diff --git a/extra/lua-cjson/fpconv.c b/extra/lua-cjson/fpconv.c -index 3ecb8f7..0e21748 100644 ---- a/extra/lua-cjson/fpconv.c -+++ b/extra/lua-cjson/fpconv.c -@@ -1,6 +1,6 @@ - /* fpconv - Floating point conversion routines - * -- * Copyright (c) 2011-2012 Mark Pulford -+ * Copyright (c) 2011-2012 Mark Pulford - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the -diff --git a/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec b/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec -index 154e333..4f6db08 100644 ---- a/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec -+++ b/extra/lua-cjson/lua-cjson-2.1devel-1.rockspec -@@ -2,7 +2,7 @@ package = "lua-cjson" - version = "2.1devel-1" - - source = { -- url = "http://www.kyne.com.au/~mark/software/download/lua-cjson-2.1devel.zip", -+ url = "http://www.kyne.au/~mark/software/download/lua-cjson-2.1devel.zip", - } - - description = { -@@ -15,7 +15,7 @@ description = { - (infinity, NaN,..) - - No dependencies on other libraries - ]], -- homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", -+ homepage = "http://www.kyne.au/~mark/software/lua-cjson.php", - license = "MIT" - } - -diff --git a/extra/lua-cjson/lua-cjson.spec b/extra/lua-cjson/lua-cjson.spec -index 3d3cb16..8a7a5cd 100644 ---- a/extra/lua-cjson/lua-cjson.spec -+++ b/extra/lua-cjson/lua-cjson.spec -@@ -9,8 +9,8 @@ Summary: A fast JSON encoding/parsing module for Lua - - Group: Development/Libraries - License: MIT --URL: http://www.kyne.com.au/~mark/software/lua-cjson/ --Source0: http://www.kyne.com.au/~mark/software/lua-cjson/download/lua-cjson-%{version}.tar.gz -+URL: http://www.kyne.au/~mark/software/lua-cjson/ -+Source0: http://www.kyne.au/~mark/software/lua-cjson/download/lua-cjson-%{version}.tar.gz - BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) - - BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} -@@ -57,24 +57,24 @@ rm -rf "$RPM_BUILD_ROOT" - - - %changelog --* Thu Mar 1 2012 Mark Pulford - 2.1.0-1 -+* Thu Mar 1 2012 Mark Pulford - 2.1.0-1 - - Update for 2.1.0 - --* Sun Jan 22 2012 Mark Pulford - 2.0.0-1 -+* Sun Jan 22 2012 Mark Pulford - 2.0.0-1 - - Update for 2.0.0 - - Install lua2json / json2lua utilities - --* Wed Nov 27 2011 Mark Pulford - 1.0.4-1 -+* Wed Nov 27 2011 Mark Pulford - 1.0.4-1 - - Update for 1.0.4 - --* Wed Sep 15 2011 Mark Pulford - 1.0.3-1 -+* Wed Sep 15 2011 Mark Pulford - 1.0.3-1 - - Update for 1.0.3 - --* Sun May 29 2011 Mark Pulford - 1.0.2-1 -+* Sun May 29 2011 Mark Pulford - 1.0.2-1 - - Update for 1.0.2 - --* Sun May 10 2011 Mark Pulford - 1.0.1-1 -+* Sun May 10 2011 Mark Pulford - 1.0.1-1 - - Update for 1.0.1 - --* Sun May 1 2011 Mark Pulford - 1.0-1 -+* Sun May 1 2011 Mark Pulford - 1.0-1 - - Initial package -diff --git a/extra/lua-cjson/lua/cjson/util.lua b/extra/lua-cjson/lua/cjson/util.lua -index 5bb0d7d..13c889d 100644 ---- a/extra/lua-cjson/lua/cjson/util.lua -+++ b/extra/lua-cjson/lua/cjson/util.lua -@@ -2,7 +2,7 @@ local json = require "cjson" - - -- Various common routines used by the Lua CJSON package - -- ---- Mark Pulford -+-- Mark Pulford - - -- Determine with a Lua table can be treated as an array. - -- Explicitly returns "not an array" for very sparse arrays. -diff --git a/extra/lua-cjson/lua_cjson.c b/extra/lua-cjson/lua_cjson.c -index 22f33f1..3d1123e 100644 ---- a/extra/lua-cjson/lua_cjson.c -+++ b/extra/lua-cjson/lua_cjson.c -@@ -1,6 +1,6 @@ - /* Lua CJSON - JSON support for Lua - * -- * Copyright (c) 2010-2012 Mark Pulford -+ * Copyright (c) 2010-2012 Mark Pulford - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the -diff --git a/extra/lua-cjson/manual.adoc b/extra/lua-cjson/manual.adoc -index 83303a3..0be3d02 100644 ---- a/extra/lua-cjson/manual.adoc -+++ b/extra/lua-cjson/manual.adoc -@@ -1,5 +1,5 @@ - = Lua CJSON 2.1devel Manual = --Mark Pulford -+Mark Pulford - :revdate: August 2016 - - Overview -@@ -21,7 +21,7 @@ Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for - details. - - The current stable version of this software is available from the --http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. -+https://kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. - - Feel free to email me if you have any patches, suggestions, or comments. - -@@ -29,8 +29,8 @@ Feel free to email me if you have any patches, suggestions, or comments. - Installation - ------------ - --Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, Lua 5.3, --or http://www.luajit.org[LuaJIT] to build. -+Lua CJSON requires either https://lua.org[Lua] 5.1, Lua 5.2, Lua 5.3, -+or https://luajit.org[LuaJIT] to build. - - The build method can be selected from 4 options: - -@@ -63,7 +63,7 @@ cp cjson.so $LUA_MODULE_DIRECTORY - CMake - ~~~~~ - --http://www.cmake.org[CMake] can generate build configuration for many -+https://cmake.org[CMake] can generate build configuration for many - different platforms (including Unix and Windows). - - First, generate the makefile for your platform using CMake. If CMake is -@@ -88,14 +88,14 @@ make - cp cjson.so $LUA_MODULE_DIRECTORY - - Review the --http://www.cmake.org/cmake/help/documentation.html[CMake documentation] -+https://cmake.org/documentation/[CMake documentation] - for further details. - - - RPM - ~~~ - --Linux distributions using http://rpm.org[RPM] can create a package via -+Linux distributions using https://rpm.org[RPM] can create a package via - the included RPM spec file. Ensure the +rpm-build+ package (or similar) - has been installed. - -@@ -109,7 +109,7 @@ rpm -Uvh $LUA_CJSON_RPM - LuaRocks - ~~~~~~~~ - --http://luarocks.org[LuaRocks] can be used to install and manage Lua -+https://luarocks.org[LuaRocks] can be used to install and manage Lua - modules on a wide range of platforms (including Windows). - - First, extract the Lua CJSON source package. -@@ -125,7 +125,7 @@ LuaRocks does not support platform specific configuration for Solaris. - On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in - the rockspec before building this module. - --Review the http://luarocks.org/en/Documentation[LuaRocks documentation] -+Review the https://github.com/luarocks/luarocks/wiki/Documentation[LuaRocks documentation] - for further details. - - -@@ -151,7 +151,7 @@ Built-in floating point conversion - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Lua CJSON may be built with David Gay's --http://www.netlib.org/fp/[floating point conversion routines]. This can -+https://netlib.org/fp/[floating point conversion routines]. This can - increase overall performance by up to 50% on some platforms when - converting a large amount of numeric data. However, this option reduces - portability and is disabled by default. -@@ -380,8 +380,8 @@ ensure all Lua strings passed to +cjson.encode+ are UTF-8. - Base64 is commonly used to encode binary data as the most efficient - encoding under UTF-8 can only reduce the encoded size by a further - ~8%. Lua Base64 routines can be found in the --http://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and --http://www.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. -+https://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and -+https://web.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. - ========= - - Lua CJSON uses a heuristic to determine whether to encode a Lua table as -@@ -606,8 +606,8 @@ Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. - References - ---------- - --- http://tools.ietf.org/html/rfc4627[RFC 4627] --- http://www.json.org/[JSON website] -+- https://datatracker.ietf.org/doc/html/rfc4627[RFC 4627] -+- https://json.org/json-en.html[JSON website] - - - // vi:ft=asciidoc tw=72: -diff --git a/extra/lua-cjson/performance.adoc b/extra/lua-cjson/performance.adoc -index a36412d..e44a703 100644 ---- a/extra/lua-cjson/performance.adoc -+++ b/extra/lua-cjson/performance.adoc -@@ -1,6 +1,6 @@ - JSON module performance comparison under Lua - ============================================ --Mark Pulford -+Mark Pulford - :revdate: January 22, 2012 - - This performance comparison aims to provide a guide of relative -@@ -26,7 +26,7 @@ http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]:: - https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]:: - - C wrapper for the YAJL library - --http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]:: -+http://www.kyne.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]:: - - C implementation with no dependencies on other libraries - - -diff --git a/extra/lua-cjson/strbuf.c b/extra/lua-cjson/strbuf.c -index ac779e4..7c59e1b 100644 ---- a/extra/lua-cjson/strbuf.c -+++ b/extra/lua-cjson/strbuf.c -@@ -1,6 +1,6 @@ - /* strbuf - String buffer routines - * -- * Copyright (c) 2010-2012 Mark Pulford -+ * Copyright (c) 2010-2012 Mark Pulford - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the -diff --git a/extra/lua-cjson/strbuf.h b/extra/lua-cjson/strbuf.h -index d861108..1b980c3 100644 ---- a/extra/lua-cjson/strbuf.h -+++ b/extra/lua-cjson/strbuf.h -@@ -1,6 +1,6 @@ - /* strbuf - String buffer routines - * -- * Copyright (c) 2010-2012 Mark Pulford -+ * Copyright (c) 2010-2012 Mark Pulford - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the -diff --git a/extra/lua-cjson/tests/bench.lua b/extra/lua-cjson/tests/bench.lua -index 648020b..52a491a 100755 ---- a/extra/lua-cjson/tests/bench.lua -+++ b/extra/lua-cjson/tests/bench.lua -@@ -5,7 +5,7 @@ - -- - -- Your Mileage May Vary. - -- ---- Mark Pulford -+-- Mark Pulford - - local json_module = os.getenv("JSON_MODULE") or "cjson" - -diff --git a/extra/lua-cjson/tests/test.lua b/extra/lua-cjson/tests/test.lua -index 9690db4..5b9b98a 100755 ---- a/extra/lua-cjson/tests/test.lua -+++ b/extra/lua-cjson/tests/test.lua -@@ -2,7 +2,7 @@ - - -- Lua CJSON tests - -- ---- Mark Pulford -+-- Mark Pulford - -- - -- Note: The output of this script is easier to read with "less -S" - -Submodule extra/luaffifb 610ce4d..a1cb731: -diff --git a/extra/luaffifb/call_x64.h b/extra/luaffifb/call_x64.h -index 5cf89a0..afbf084 100644 ---- a/extra/luaffifb/call_x64.h -+++ b/extra/luaffifb/call_x64.h -@@ -19,110 +19,118 @@ - */ - - static const unsigned char build_actionlist[2157] = { -- 72,139,141,233,255,72,137,132,253,36,233,255,221,133,233,255,217,133,233, -- 255,252,243,15,126,133,233,255,252,243,15,90,133,233,255,221,156,253,36,233, -- 255,217,156,253,36,233,255,102,15,214,132,253,36,233,255,252,242,15,90,192, -- 102,15,214,132,253,36,233,255,252,242,15,90,192,102,15,126,132,253,36,233, -- 255,85,72,137,229,65,84,72,129,252,236,239,102,15,214,69,252,240,102,15,214, -- 77,232,102,15,214,85,224,102,15,214,93,216,102,15,214,101,208,102,15,214, -- 109,200,102,15,214,117,192,102,15,214,125,184,72,137,125,176,72,137,117,168, -- 72,137,85,160,72,137,77,152,76,137,69,144,76,137,77,136,255,73,188,237,237, -- 255,72,199,194,237,72,199,198,237,76,137,231,232,251,1,0,255,72,199,194,237, -- 72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251,1,0,255,72, -- 199,194,237,72,199,198,237,76,137,231,232,251,1,0,72,186,237,237,72,199,198, -- 252,255,252,255,252,255,252,255,76,137,231,232,251,1,1,255,72,137,8,72,199, -- 198,252,254,252,255,252,255,252,255,76,137,231,232,251,1,2,255,72,186,237, -- 237,72,199,198,0,0,0,0,76,137,231,232,251,1,1,255,72,137,8,255,102,15,214, -- 0,255,217,24,255,217,88,4,255,102,15,214,64,8,255,76,137,231,232,251,1,3, -- 255,15,182,201,72,137,206,76,137,231,232,251,1,4,255,15,182,201,255,15,190, -- 201,255,72,137,206,76,137,231,232,251,1,5,255,15,183,201,255,15,191,201,255, -- 72,137,206,76,137,231,232,251,1,6,255,72,185,237,237,72,199,194,237,72,199, -- 198,237,76,137,231,232,251,1,7,255,72,199,194,237,72,199,198,252,254,252, -- 255,252,255,252,255,76,137,231,232,251,1,0,72,185,237,237,72,199,194,252, -- 255,252,255,252,255,252,255,72,199,198,252,254,252,255,252,255,252,255,76, -- 137,231,232,251,1,8,72,137,68,36,32,72,199,198,252,252,252,255,252,255,252, -- 255,76,137,231,232,251,1,9,72,139,68,36,32,255,72,199,194,237,72,199,198, -- 252,254,252,255,252,255,252,255,76,137,231,232,251,1,0,72,185,237,237,72, -- 199,194,252,255,252,255,252,255,252,255,72,199,198,252,254,252,255,252,255, -- 252,255,76,137,231,232,251,1,10,137,68,36,32,72,199,198,252,252,252,255,252, -- 255,252,255,76,137,231,232,251,1,9,139,68,36,32,255,72,199,198,252,254,252, -- 255,252,255,252,255,76,137,231,232,251,1,9,255,72,199,198,252,255,252,255, -- 252,255,252,255,76,137,231,232,251,1,11,255,72,199,198,252,255,252,255,252, -- 255,252,255,76,137,231,232,251,1,12,255,137,68,36,32,72,199,198,252,253,252, -- 255,252,255,252,255,76,137,231,232,251,1,9,139,68,36,32,255,72,199,198,252, -- 255,252,255,252,255,252,255,76,137,231,232,251,1,13,255,72,199,198,252,255, -- 252,255,252,255,252,255,76,137,231,232,251,1,14,255,72,137,68,36,32,72,199, -- 198,252,253,252,255,252,255,252,255,76,137,231,232,251,1,9,72,139,68,36,32, -- 255,72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251,1,15,72, -- 137,68,36,32,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251, -- 1,9,72,139,68,36,32,255,72,199,198,252,255,252,255,252,255,252,255,76,137, -- 231,232,251,1,16,102,15,214,68,36,32,72,199,198,252,253,252,255,252,255,252, -- 255,76,137,231,232,251,1,9,255,252,242,15,90,68,36,32,255,252,243,15,126, -- 68,36,32,255,72,199,198,252,255,252,255,252,255,252,255,76,137,231,232,251, -- 1,17,102,15,214,68,36,32,72,199,198,252,253,252,255,252,255,252,255,76,137, -- 231,232,251,1,9,252,243,15,126,68,36,32,255,72,199,198,252,255,252,255,252, -- 255,252,255,76,137,231,232,251,1,18,102,15,214,68,36,32,102,15,214,76,36, -- 40,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251,1,9,252, -- 243,15,126,68,36,32,252,243,15,126,76,36,40,255,72,139,141,233,72,199,194, -- 252,255,252,255,252,255,252,255,76,137,230,72,137,207,232,251,1,18,72,131, -- 252,236,4,72,199,198,252,253,252,255,252,255,252,255,76,137,231,232,251,1, -- 9,255,76,139,101,252,248,72,137,252,236,93,194,236,255,85,72,137,229,65,84, -- 65,85,73,137,252,252,76,137,231,232,251,1,19,73,137,197,72,129,252,248,239, -- 255,15,141,244,248,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,248, -- 2,15,142,244,247,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,255,15, -- 141,244,247,102,184,0,0,72,190,237,237,76,137,231,232,251,1,20,255,248,1, -- 255,72,193,224,4,72,41,196,72,129,252,236,239,255,72,186,237,237,72,199,198, -- 0,0,0,0,76,137,231,232,251,1,1,72,131,252,236,16,255,72,185,237,237,72,199, -- 194,237,72,199,198,237,76,137,231,232,251,1,8,255,72,185,237,237,72,199,194, -- 237,72,199,198,237,76,137,231,232,251,1,21,255,72,185,237,237,72,199,194, -- 237,72,199,198,237,76,137,231,232,251,1,10,255,72,199,198,237,76,137,231, -- 232,251,1,12,255,15,182,192,255,15,190,192,255,15,183,192,255,15,191,192, -- 255,72,199,198,237,76,137,231,232,251,1,12,131,252,248,0,15,149,208,15,182, -- 192,255,72,199,198,237,76,137,231,232,251,1,11,255,72,199,198,237,76,137, -- 231,232,251,1,15,255,72,199,198,237,76,137,231,232,251,1,13,255,72,199,198, -- 237,76,137,231,232,251,1,14,255,72,199,198,237,76,137,231,232,251,1,16,255, -- 72,199,198,237,76,137,231,232,251,1,18,255,252,243,15,126,193,255,72,141, -- 132,253,36,233,72,131,252,236,4,72,199,194,237,76,137,230,72,137,199,232, -- 251,1,18,255,72,199,198,237,76,137,231,232,251,1,17,255,72,199,198,237,76, -- 137,231,232,251,1,17,137,4,36,217,4,36,255,137,20,36,217,4,36,255,72,137, -- 224,72,129,192,239,73,137,192,72,199,193,237,76,137,252,234,72,199,198,237, -- 76,137,231,232,251,1,22,255,72,137,224,72,129,192,239,73,137,192,72,199,193, -- 237,76,137,252,234,72,199,198,237,76,137,231,232,251,1,23,255,72,137,224, -- 72,129,192,239,73,137,193,73,199,192,237,72,199,193,237,76,137,252,234,72, -- 199,198,237,76,137,231,232,251,1,24,255,72,185,237,237,139,1,72,137,199,232, -- 251,1,25,255,72,131,196,32,255,252,243,15,126,188,253,36,233,255,252,243, -- 15,126,180,253,36,233,255,252,243,15,126,172,253,36,233,255,252,243,15,126, -- 164,253,36,233,255,252,243,15,126,156,253,36,233,255,252,243,15,126,148,253, -- 36,233,255,252,243,15,126,140,253,36,233,255,252,243,15,126,132,253,36,233, -- 255,76,139,140,253,36,233,255,76,139,132,253,36,233,255,72,139,140,253,36, -- 233,255,72,139,148,253,36,233,255,72,139,180,253,36,233,255,72,139,60,36, -- 255,72,129,196,239,255,176,8,255,232,251,1,26,72,131,252,236,48,255,72,137, -- 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237, -- 76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76,139,109,252, -- 240,76,139,101,252,248,72,137,252,236,93,195,255,72,137,68,36,32,232,251, -- 1,27,72,185,237,237,137,1,72,139,68,36,32,72,137,198,76,137,231,232,251,1, -- 28,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195, -- 255,72,137,68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199, -- 198,0,0,0,0,76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76, -- 139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,102,15,214,68, -- 36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237,76, -- 137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0,0,0,76,139,109,252,240, -- 76,139,101,252,248,72,137,252,236,93,195,255,102,15,214,76,36,40,102,15,214, -- 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199,198,237, -- 76,137,231,232,251,1,1,72,139,76,36,40,72,137,72,8,72,139,76,36,32,72,137, -- 8,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195, -- 255,232,251,1,27,72,185,237,237,137,1,184,0,0,0,0,76,139,109,252,240,76,139, -- 101,252,248,72,137,252,236,93,195,255,15,182,192,137,68,36,32,232,251,1,27, -- 72,185,237,237,137,1,139,68,36,32,72,137,198,76,137,231,232,251,1,4,184,1, -- 0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,137, -- 68,36,32,232,251,1,27,72,185,237,237,137,1,139,68,36,32,72,137,198,76,137, -- 231,232,251,1,5,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137, -+ 72,139,141,233,255,72,137,132,253,36,233,255,221.0,133,233,255,217.0,133, -+ 233,255,252,243.0,15.0,126,133,233,255,252,243.0,15.0,90,133,233,255,221.0, -+ 156,253,36,233,255,217.0,156,253,36,233,255,102.0,15.0,214,132,253,36,233, -+ 255,252,242.0,15.0,90,192,102.0,15.0,214,132,253,36,233,255,252,242.0,15.0, -+ 90,192,102.0,15.0,126,132,253,36,233,255,85,72,137,229,65,84,72,129.0,252, -+ 236,239,102.0,15.0,214,69,252,240,102.0,15.0,214,77,232,102.0,15.0,214,85, -+ 224,102.0,15.0,214,93,216,102.0,15.0,214,101,208,102.0,15.0,214,109,200,102.0, -+ 15.0,214,117,192,102.0,15.0,214,125,184,72,137,125,176,72,137,117,168,72, -+ 137,85,160,72,137,77,152,76,137,69,144,76,137,77,136,255,73,188,237,237,255, -+ 72,199.0,194,237,72,199.0,198,237,76,137,231,232,251,1,0,255,72,199.0,194, -+ 237,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, -+ 1,0,255,72,199.0,194,237,72,199.0,198,237,76,137,231,232,251,1,0,72,186,237, -+ 237,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, -+ 1,1,255,72,137,8,72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137, -+ 231,232,251,1,2,255,72,186,237,237,72,199.0,198,0,0.0,0.0,0.0,76,137,231, -+ 232,251,1,1,255,72,137,8,255,102.0,15.0,214,0,255,217.0,24,255,217.0,88,4, -+ 255,102.0,15.0,214,64,8,255,76,137,231,232,251,1,3,255,15.0,182,201,72,137, -+ 206,76,137,231,232,251,1,4,255,15.0,182,201,255,15.0,190,201,255,72,137,206, -+ 76,137,231,232,251,1,5,255,15.0,183,201,255,15.0,191,201,255,72,137,206,76, -+ 137,231,232,251,1,6,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237, -+ 76,137,231,232,251,1,7,255,72,199.0,194,237,72,199.0,198,252,254,252,255.0, -+ 252,255.0,252,255.0,76,137,231,232,251,1,0,72,185,237,237,72,199.0,194,252, -+ 255,252,255.0,252,255.0,252,255.0,72,199.0,198,252,254,252,255.0,252,255.0, -+ 252,255.0,76,137,231,232,251,1,8,72,137,68,36,32,72,199.0,198,252,252,252, -+ 255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,72,139,68,36,32,255,72,199.0, -+ 194,237,72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137,231,232, -+ 251,1,0,72,185,237,237,72,199.0,194,252,255,252,255.0,252,255.0,252,255.0, -+ 72,199.0,198,252,254,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,10, -+ 137,68,36,32,72,199.0,198,252,252,252,255.0,252,255.0,252,255.0,76,137,231, -+ 232,251,1,9,139,68,36,32,255,72,199.0,198,252,254,252,255.0,252,255.0,252, -+ 255.0,76,137,231,232,251,1,9,255,72,199.0,198,252,255,252,255.0,252,255.0, -+ 252,255.0,76,137,231,232,251,1,11,255,72,199.0,198,252,255,252,255.0,252, -+ 255.0,252,255.0,76,137,231,232,251,1,12,255,137,68,36,32,72,199.0,198,252, -+ 253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,139,68,36,32,255, -+ 72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,13, -+ 255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232,251, -+ 1,14,255,72,137,68,36,32,72,199.0,198,252,253,252,255.0,252,255.0,252,255.0, -+ 76,137,231,232,251,1,9,72,139,68,36,32,255,72,199.0,198,252,255,252,255.0, -+ 252,255.0,252,255.0,76,137,231,232,251,1,15,72,137,68,36,32,72,199.0,198, -+ 252,253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,72,139,68,36, -+ 32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232, -+ 251,1,16,102.0,15.0,214,68,36,32,72,199.0,198,252,253,252,255.0,252,255.0, -+ 252,255.0,76,137,231,232,251,1,9,255,252,242.0,15.0,90,68,36,32,255,252,243.0, -+ 15.0,126,68,36,32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0, -+ 76,137,231,232,251,1,17,102.0,15.0,214,68,36,32,72,199.0,198,252,253,252, -+ 255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,252,243.0,15.0,126,68,36, -+ 32,255,72,199.0,198,252,255,252,255.0,252,255.0,252,255.0,76,137,231,232, -+ 251,1,18,102.0,15.0,214,68,36,32,102.0,15.0,214,76,36,40,72,199.0,198,252, -+ 253,252,255.0,252,255.0,252,255.0,76,137,231,232,251,1,9,252,243.0,15.0,126, -+ 68,36,32,252,243.0,15.0,126,76,36,40,255,72,139,141,233,72,199.0,194,252, -+ 255,252,255.0,252,255.0,252,255.0,76,137,230,72,137,207,232,251,1,18,72,131.0, -+ 252,236,4,72,199.0,198,252,253,252,255.0,252,255.0,252,255.0,76,137,231,232, -+ 251,1,9,255,76,139,101,252,248,72,137,252,236,93,194,236,255,85,72,137,229, -+ 65,84,65,85,73,137,252,252,76,137,231,232,251,1,19,73,137,197,72,129.0,252, -+ 248,239,255,15.0,141,244,248.0,102,184,0,0.0,72,190,237,237,76,137,231,232, -+ 251,1,20,248,2,15.0,142,244,247.0,102,184,0,0.0,72,190,237,237,76,137,231, -+ 232,251,1,20,255,15.0,141,244,247.0,102,184,0,0.0,72,190,237,237,76,137,231, -+ 232,251,1,20,255,248,1,255,72,193.0,224,4,72,41,196,72,129.0,252,236,239, -+ 255,72,186,237,237,72,199.0,198,0,0.0,0.0,0.0,76,137,231,232,251,1,1,72,131.0, -+ 252,236,16,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137,231, -+ 232,251,1,8,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137,231, -+ 232,251,1,21,255,72,185,237,237,72,199.0,194,237,72,199.0,198,237,76,137, -+ 231,232,251,1,10,255,72,199.0,198,237,76,137,231,232,251,1,12,255,15.0,182, -+ 192,255,15.0,190,192,255,15.0,183,192,255,15.0,191,192,255,72,199.0,198,237, -+ 76,137,231,232,251,1,12,131.0,252,248,0,15.0,149.0,208,15.0,182,192,255,72, -+ 199.0,198,237,76,137,231,232,251,1,11,255,72,199.0,198,237,76,137,231,232, -+ 251,1,15,255,72,199.0,198,237,76,137,231,232,251,1,13,255,72,199.0,198,237, -+ 76,137,231,232,251,1,14,255,72,199.0,198,237,76,137,231,232,251,1,16,255, -+ 72,199.0,198,237,76,137,231,232,251,1,18,255,252,243.0,15.0,126,193,255,72, -+ 141,132,253,36,233,72,131.0,252,236,4,72,199.0,194,237,76,137,230,72,137, -+ 199,232,251,1,18,255,72,199.0,198,237,76,137,231,232,251,1,17,255,72,199.0, -+ 198,237,76,137,231,232,251,1,17,137,4,36,217.0,4,36,255,137,20,36,217.0,4, -+ 36,255,72,137,224,72,129.0,192,239,73,137,192,72,199.0,193,237,76,137,252, -+ 234,72,199.0,198,237,76,137,231,232,251,1,22,255,72,137,224,72,129.0,192, -+ 239,73,137,192,72,199.0,193,237,76,137,252,234,72,199.0,198,237,76,137,231, -+ 232,251,1,23,255,72,137,224,72,129.0,192,239,73,137,193,73,199.0,192,237, -+ 72,199.0,193,237,76,137,252,234,72,199.0,198,237,76,137,231,232,251,1,24, -+ 255,72,185,237,237,139,1,72,137,199,232,251,1,25,255,72,131.0,196,32,255, -+ 252,243.0,15.0,126,188,253,36,233,255,252,243.0,15.0,126,180,253,36,233,255, -+ 252,243.0,15.0,126,172,253,36,233,255,252,243.0,15.0,126,164,253,36,233,255, -+ 252,243.0,15.0,126,156,253,36,233,255,252,243.0,15.0,126,148,253,36,233,255, -+ 252,243.0,15.0,126,140,253,36,233,255,252,243.0,15.0,126,132,253,36,233,255, -+ 76,139,140,253,36,233,255,76,139,132,253,36,233,255,72,139,140,253,36,233, -+ 255,72,139,148,253,36,233,255,72,139,180,253,36,233,255,72,139,60,36,255, -+ 72,129.0,196,239,255,176,8,255,232,251,1,26,72,131.0,252,236,48,255,72,137, -+ 68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237,237,72,199.0,198,237, -+ 76,137,231,232,251,1,1,72,139,76,36,32,72,137,8,184,1,0.0,0.0,0.0,76,139, -+ 109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,72,137,68,36,32, -+ 232,251,1,27,72,185,237,237,137,1,72,139,68,36,32,72,137,198,76,137,231,232, -+ 251,1,28,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, -+ 236,93,195,255,72,137,68,36,32,232,251,1,27,72,185,237,237,137,1,72,186,237, -+ 237,72,199.0,198,0,0.0,0.0,0.0,76,137,231,232,251,1,1,72,139,76,36,32,72, -+ 137,8,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, -+ 236,93,195,255,102.0,15.0,214,68,36,32,232,251,1,27,72,185,237,237,137,1, -+ 72,186,237,237,72,199.0,198,237,76,137,231,232,251,1,1,72,139,76,36,32,72, -+ 137,8,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137,252, -+ 236,93,195,255,102.0,15.0,214,76,36,40,102.0,15.0,214,68,36,32,232,251,1, -+ 27,72,185,237,237,137,1,72,186,237,237,72,199.0,198,237,76,137,231,232,251, -+ 1,1,72,139,76,36,40,72,137,72,8,72,139,76,36,32,72,137,8,184,1,0.0,0.0,0.0, -+ 76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,232,251,1, -+ 27,72,185,237,237,137,1,184,0,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252, -+ 248,72,137,252,236,93,195,255,15.0,182,192,137,68,36,32,232,251,1,27,72,185, -+ 237,237,137,1,139,68,36,32,72,137,198,76,137,231,232,251,1,4,184,1,0.0,0.0, -+ 0.0,76,139,109,252,240,76,139,101,252,248,72,137,252,236,93,195,255,137,68, -+ 36,32,232,251,1,27,72,185,237,237,137,1,139,68,36,32,72,137,198,76,137,231, -+ 232,251,1,5,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139,101,252,248,72,137, - 252,236,93,195,255,137,68,36,32,232,251,1,27,72,185,237,237,137,1,139,68, -- 36,32,72,137,198,76,137,231,232,251,1,6,184,1,0,0,0,76,139,109,252,240,76, -- 139,101,252,248,72,137,252,236,93,195,255,252,243,15,90,192,102,15,214,68, -- 36,32,232,251,1,27,72,185,237,237,137,1,252,243,15,126,68,36,32,76,137,231, -- 232,251,1,3,184,1,0,0,0,76,139,109,252,240,76,139,101,252,248,72,137,252, -- 236,93,195,255 -+ 36,32,72,137,198,76,137,231,232,251,1,6,184,1,0.0,0.0,0.0,76,139,109,252, -+ 240,76,139,101,252,248,72,137,252,236,93,195,255,252,243.0,15.0,90,192,102.0, -+ 15.0,214,68,36,32,232,251,1,27,72,185,237,237,137,1,252,243.0,15.0,126,68, -+ 36,32,76,137,231,232,251,1,3,184,1,0.0,0.0,0.0,76,139,109,252,240,76,139, -+ 101,252,248,72,137,252,236,93,195,255 - }; - - static const char *const globnames[] = { -@@ -228,6 +236,8 @@ void compile_globals(struct jit* jit, lua_State* L) - * stack - */ - -+ -+ - compile(Dst, L, NULL, LUA_NOREF); - } - -@@ -355,7 +365,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int - reg->off += 8; - } else { - dasm_put(Dst, 1, reg->off); -+#if defined __amd64__ || defined _WIN64 -+ /* The parameters to a function on stack are always 8 byte aligned. */ -+ reg->off += 8; -+#else - reg->off += 4; -+#endif - } - } - -diff --git a/extra/luaffifb/call_x86.dasc b/extra/luaffifb/call_x86.dasc -index 4a72b98..404683b 100755 ---- a/extra/luaffifb/call_x86.dasc -+++ b/extra/luaffifb/call_x86.dasc -@@ -506,7 +506,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int - reg->off += 8; - } else { - | mov ecx, [rbp + reg->off] -+#if defined __amd64__ || defined _WIN64 -+ /* The parameters to a function on stack are always 8 byte aligned. */ -+ reg->off += 8; -+#else - reg->off += 4; -+#endif - } - } - -diff --git a/extra/luaffifb/call_x86.h b/extra/luaffifb/call_x86.h -index 4611a3c..05094ab 100755 ---- a/extra/luaffifb/call_x86.h -+++ b/extra/luaffifb/call_x86.h -@@ -20,94 +20,101 @@ - - static const unsigned char build_actionlist[1915] = { - 139,141,233,255,139,141,233,139,149,233,255,137,132,253,36,233,255,137,132, -- 253,36,233,137,148,253,36,233,255,221,133,233,255,217,133,233,255,252,243, -- 15,126,133,233,255,252,243,15,90,133,233,255,221,156,253,36,233,255,217,156, -- 253,36,233,255,102,15,214,132,253,36,233,255,252,242,15,90,192,102,15,214, -- 132,253,36,233,255,252,242,15,90,192,102,15,126,132,253,36,233,255,85,137, -- 229,87,129,252,236,239,255,137,77,252,248,137,85,252,244,255,191,237,255, -- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,0,255,199,68,36,8,237, -- 199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,0,255,199, -- 68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,0,199,68,36,8,237,199,68, -- 36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,1,255,137,8,199, -- 68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,2,255,199,68, -- 36,8,237,199,68,36,4,0,0,0,0,137,60,36,232,251,1,1,255,137,8,137,80,4,255, -- 137,8,255,102,15,214,0,255,217,24,255,217,88,4,255,221,24,255,221,88,8,255, -- 221,92,36,4,137,60,36,232,251,1,3,255,15,182,201,137,76,36,4,137,60,36,232, -- 251,1,4,255,15,182,201,255,15,190,201,255,137,76,36,4,137,60,36,232,251,1, -- 5,255,15,183,201,255,15,191,201,255,137,76,36,4,137,60,36,232,251,1,6,255, -- 199,68,36,12,0,0,0,0,199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1, -- 7,255,199,68,36,8,237,199,68,36,4,252,254,252,255,252,255,252,255,137,60, -- 36,232,251,1,0,199,68,36,12,237,199,68,36,8,252,255,252,255,252,255,252,255, -- 199,68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,8,137,68, -- 36,32,199,68,36,4,252,252,252,255,252,255,252,255,137,60,36,232,251,1,9,139, -- 68,36,32,255,199,68,36,8,237,199,68,36,4,252,254,252,255,252,255,252,255, -- 137,60,36,232,251,1,0,199,68,36,12,237,199,68,36,8,252,255,252,255,252,255, -- 252,255,199,68,36,4,252,254,252,255,252,255,252,255,137,60,36,232,251,1,10, -- 137,68,36,32,199,68,36,4,252,252,252,255,252,255,252,255,137,60,36,232,251, -- 1,9,139,68,36,32,255,199,68,36,4,252,254,252,255,252,255,252,255,137,60,36, -- 232,251,1,9,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232, -- 251,1,11,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251, -- 1,12,255,137,68,36,32,199,68,36,4,252,253,252,255,252,255,252,255,137,60, -- 36,232,251,1,9,139,68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255, -- 137,60,36,232,251,1,13,255,199,68,36,4,252,255,252,255,252,255,252,255,137, -- 60,36,232,251,1,14,255,137,68,36,32,137,84,36,36,199,68,36,4,252,253,252, -- 255,252,255,252,255,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199, -- 68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,15,137,68,36, -- 32,199,68,36,4,252,253,252,255,252,255,252,255,137,60,36,232,251,1,9,139, -- 68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251, -- 1,16,255,221,92,36,32,199,68,36,4,252,253,252,255,252,255,252,255,137,60, -- 36,232,251,1,9,221,68,36,32,255,199,68,36,4,252,255,252,255,252,255,252,255, -- 137,60,36,232,251,1,17,137,68,36,32,137,84,36,36,199,68,36,4,252,253,252, -- 255,252,255,252,255,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199, -- 68,36,4,252,255,252,255,252,255,252,255,137,60,36,232,251,1,18,102,15,214, -- 68,36,32,102,15,214,76,36,40,199,68,36,4,252,253,252,255,252,255,252,255, -- 137,60,36,232,251,1,9,252,243,15,126,68,36,32,252,243,15,126,76,36,40,255, -- 139,141,233,199,68,36,8,252,255,252,255,252,255,252,255,137,124,36,4,137, -- 12,36,232,251,1,18,131,252,236,4,199,68,36,4,252,253,252,255,252,255,252, -- 255,137,60,36,232,251,1,9,255,139,125,252,252,137,252,236,93,194,236,255, -- 85,137,229,87,86,139,189,233,131,252,236,16,137,60,36,232,251,1,19,137,198, -- 129,252,248,239,255,15,141,244,248,102,184,0,0,199,68,36,4,237,137,60,36, -- 232,251,1,20,248,2,15,142,244,247,102,184,0,0,199,68,36,4,237,137,60,36,232, -- 251,1,20,255,15,141,244,247,102,184,0,0,199,68,36,4,237,137,60,36,232,251, -- 1,20,255,248,1,255,193,224,4,41,196,129,252,236,239,255,199,68,36,8,237,199, -- 68,36,4,0,0,0,0,137,60,36,232,251,1,1,131,252,236,16,255,199,68,36,12,237, -- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,8,255,199,68,36,12,237, -- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,21,255,199,68,36,12,237, -- 199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,10,255,199,68,36,4,237, -- 137,60,36,232,251,1,12,255,15,182,192,255,15,190,192,255,15,183,192,255,15, -- 191,192,255,199,68,36,4,237,137,60,36,232,251,1,12,131,252,248,0,15,149,208, -- 15,182,192,255,199,68,36,4,237,137,60,36,232,251,1,11,255,199,68,36,4,237, -- 137,60,36,232,251,1,15,255,199,68,36,4,237,137,60,36,232,251,1,13,255,199, -- 68,36,4,237,137,60,36,232,251,1,14,255,199,68,36,4,237,137,60,36,232,251, -- 1,16,255,199,68,36,4,237,137,60,36,232,251,1,18,255,252,243,15,126,193,255, -- 141,132,253,36,233,131,252,236,4,199,68,36,8,237,137,124,36,4,137,4,36,232, -- 251,1,18,255,199,68,36,4,237,137,60,36,232,251,1,17,255,199,68,36,4,237,137, -- 60,36,232,251,1,17,137,4,36,217,4,36,255,137,20,36,217,4,36,255,137,224,129, -- 192,239,137,68,36,12,137,116,36,8,199,68,36,4,237,137,60,36,232,251,1,22, -- 255,185,237,139,1,137,4,36,232,251,1,23,255,131,196,32,255,139,148,253,36, -- 233,255,139,12,36,255,129,196,239,255,232,251,1,24,131,252,236,48,255,137, -- 68,36,32,232,251,1,25,185,237,137,1,199,68,36,8,237,199,68,36,4,237,137,60, -- 36,232,251,1,1,139,76,36,32,137,8,184,1,0,0,0,139,117,252,248,139,125,252, -- 252,137,252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68, -- 36,32,137,68,36,4,137,60,36,232,251,1,26,184,1,0,0,0,139,117,252,248,139, -- 125,252,252,137,252,236,93,195,255,137,84,36,36,137,68,36,32,232,251,1,25, -- 185,237,137,1,199,68,36,8,237,199,68,36,4,0,0,0,0,137,60,36,232,251,1,1,139, -- 76,36,36,139,84,36,32,137,72,4,137,16,184,1,0,0,0,139,117,252,248,139,125, -- 252,252,137,252,236,93,195,255,137,68,36,32,137,84,36,36,232,251,1,25,185, -- 237,137,1,199,68,36,8,237,199,68,36,4,237,137,60,36,232,251,1,1,139,76,36, -- 32,137,8,139,76,36,36,137,72,4,184,1,0,0,0,139,117,252,248,139,125,252,252, -- 137,252,236,93,195,255,131,252,236,4,232,251,1,25,185,237,137,1,184,1,0,0, -- 0,139,117,252,248,139,125,252,252,137,252,236,93,195,255,232,251,1,25,185, -- 237,137,1,184,0,0,0,0,139,117,252,248,139,125,252,252,137,252,236,93,195, -- 255,15,182,192,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68, -- 36,4,137,60,36,232,251,1,4,184,1,0,0,0,139,117,252,248,139,125,252,252,137, -- 252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137, -- 68,36,4,137,60,36,232,251,1,5,184,1,0,0,0,139,117,252,248,139,125,252,252, -+ 253,36,233,137,148,253,36,233,255,221.0,133,233,255,217.0,133,233,255,252, -+ 243.0,15.0,126,133,233,255,252,243.0,15.0,90,133,233,255,221.0,156,253,36, -+ 233,255,217.0,156,253,36,233,255,102.0,15.0,214,132,253,36,233,255,252,242.0, -+ 15.0,90,192,102.0,15.0,214,132,253,36,233,255,252,242.0,15.0,90,192,102.0, -+ 15.0,126,132,253,36,233,255,85,137,229,87,129.0,252,236,239,255,137,77,252, -+ 248,137,85,252,244,255,191,237,255,199.0,68,36,8,237,199.0,68,36,4,237,137, -+ 60,36,232,251,1,0,255,199.0,68,36,8,237,199.0,68,36,4,252,255,252,255.0,252, -+ 255.0,252,255.0,137,60,36,232,251,1,0,255,199.0,68,36,8,237,199.0,68,36,4, -+ 237,137,60,36,232,251,1,0,199.0,68,36,8,237,199.0,68,36,4,252,255,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,1,255,137,8,199.0,68,36,4,252,254, -+ 252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,2,255,199.0,68,36,8,237, -+ 199.0,68,36,4,0,0.0,0.0,0.0,137,60,36,232,251,1,1,255,137,8,137,80,4,255, -+ 137,8,255,102.0,15.0,214,0,255,217.0,24,255,217.0,88,4,255,221.0,24,255,221.0, -+ 88,8,255,221.0,92,36,4,137,60,36,232,251,1,3,255,15.0,182,201,137,76,36,4, -+ 137,60,36,232,251,1,4,255,15.0,182,201,255,15.0,190,201,255,137,76,36,4,137, -+ 60,36,232,251,1,5,255,15.0,183,201,255,15.0,191,201,255,137,76,36,4,137,60, -+ 36,232,251,1,6,255,199.0,68,36,12,0,0.0,0.0,0.0,199.0,68,36,8,237,199.0,68, -+ 36,4,237,137,60,36,232,251,1,7,255,199.0,68,36,8,237,199.0,68,36,4,252,254, -+ 252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,0,199.0,68,36,12,237,199.0, -+ 68,36,8,252,255,252,255.0,252,255.0,252,255.0,199.0,68,36,4,252,254,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,8,137,68,36,32,199.0,68,36,4,252, -+ 252,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36,32,255, -+ 199.0,68,36,8,237,199.0,68,36,4,252,254,252,255.0,252,255.0,252,255.0,137, -+ 60,36,232,251,1,0,199.0,68,36,12,237,199.0,68,36,8,252,255,252,255.0,252, -+ 255.0,252,255.0,199.0,68,36,4,252,254,252,255.0,252,255.0,252,255.0,137,60, -+ 36,232,251,1,10,137,68,36,32,199.0,68,36,4,252,252,252,255.0,252,255.0,252, -+ 255.0,137,60,36,232,251,1,9,139,68,36,32,255,199.0,68,36,4,252,254,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,9,255,199.0,68,36,4,252,255,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,11,255,199.0,68,36,4,252,255,252, -+ 255.0,252,255.0,252,255.0,137,60,36,232,251,1,12,255,137,68,36,32,199.0,68, -+ 36,4,252,253,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36, -+ 32,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232, -+ 251,1,13,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36, -+ 232,251,1,14,255,137,68,36,32,137,84,36,36,199.0,68,36,4,252,253,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,9,139,68,36,32,139,84,36,36,255,199.0, -+ 68,36,4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,15,137, -+ 68,36,32,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137,60,36,232, -+ 251,1,9,139,68,36,32,255,199.0,68,36,4,252,255,252,255.0,252,255.0,252,255.0, -+ 137,60,36,232,251,1,16,255,221.0,92,36,32,199.0,68,36,4,252,253,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,9,221.0,68,36,32,255,199.0,68,36, -+ 4,252,255,252,255.0,252,255.0,252,255.0,137,60,36,232,251,1,17,137,68,36, -+ 32,137,84,36,36,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137,60, -+ 36,232,251,1,9,139,68,36,32,139,84,36,36,255,199.0,68,36,4,252,255,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,18,102.0,15.0,214,68,36,32,102.0, -+ 15.0,214,76,36,40,199.0,68,36,4,252,253,252,255.0,252,255.0,252,255.0,137, -+ 60,36,232,251,1,9,252,243.0,15.0,126,68,36,32,252,243.0,15.0,126,76,36,40, -+ 255,139,141,233,199.0,68,36,8,252,255,252,255.0,252,255.0,252,255.0,137,124, -+ 36,4,137,12,36,232,251,1,18,131.0,252,236,4,199.0,68,36,4,252,253,252,255.0, -+ 252,255.0,252,255.0,137,60,36,232,251,1,9,255,139,125,252,252,137,252,236, -+ 93,194,236,255,85,137,229,87,86,139,189,233,131.0,252,236,16,137,60,36,232, -+ 251,1,19,137,198,129.0,252,248,239,255,15.0,141,244,248.0,102,184,0,0.0,199.0, -+ 68,36,4,237,137,60,36,232,251,1,20,248,2,15.0,142,244,247.0,102,184,0,0.0, -+ 199.0,68,36,4,237,137,60,36,232,251,1,20,255,15.0,141,244,247.0,102,184,0, -+ 0.0,199.0,68,36,4,237,137,60,36,232,251,1,20,255,248,1,255,193.0,224,4,41, -+ 196,129.0,252,236,239,255,199.0,68,36,8,237,199.0,68,36,4,0,0.0,0.0,0.0,137, -+ 60,36,232,251,1,1,131.0,252,236,16,255,199.0,68,36,12,237,199.0,68,36,8,237, -+ 199.0,68,36,4,237,137,60,36,232,251,1,8,255,199.0,68,36,12,237,199.0,68,36, -+ 8,237,199.0,68,36,4,237,137,60,36,232,251,1,21,255,199.0,68,36,12,237,199.0, -+ 68,36,8,237,199.0,68,36,4,237,137,60,36,232,251,1,10,255,199.0,68,36,4,237, -+ 137,60,36,232,251,1,12,255,15.0,182,192,255,15.0,190,192,255,15.0,183,192, -+ 255,15.0,191,192,255,199.0,68,36,4,237,137,60,36,232,251,1,12,131.0,252,248, -+ 0,15.0,149.0,208,15.0,182,192,255,199.0,68,36,4,237,137,60,36,232,251,1,11, -+ 255,199.0,68,36,4,237,137,60,36,232,251,1,15,255,199.0,68,36,4,237,137,60, -+ 36,232,251,1,13,255,199.0,68,36,4,237,137,60,36,232,251,1,14,255,199.0,68, -+ 36,4,237,137,60,36,232,251,1,16,255,199.0,68,36,4,237,137,60,36,232,251,1, -+ 18,255,252,243.0,15.0,126,193,255,141,132,253,36,233,131.0,252,236,4,199.0, -+ 68,36,8,237,137,124,36,4,137,4,36,232,251,1,18,255,199.0,68,36,4,237,137, -+ 60,36,232,251,1,17,255,199.0,68,36,4,237,137,60,36,232,251,1,17,137,4,36, -+ 217.0,4,36,255,137,20,36,217.0,4,36,255,137,224,129.0,192,239,137,68,36,12, -+ 137,116,36,8,199.0,68,36,4,237,137,60,36,232,251,1,22,255,185,237,139,1,137, -+ 4,36,232,251,1,23,255,131.0,196,32,255,139,148,253,36,233,255,139,12,36,255, -+ 129.0,196,239,255,232,251,1,24,131.0,252,236,48,255,137,68,36,32,232,251, -+ 1,25,185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,237,137,60,36,232,251, -+ 1,1,139,76,36,32,137,8,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252, - 137,252,236,93,195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36, -- 32,137,68,36,4,137,60,36,232,251,1,6,184,1,0,0,0,139,117,252,248,139,125, -- 252,252,137,252,236,93,195,255,221,92,36,4,232,251,1,25,185,237,137,1,137, -- 60,36,232,251,1,3,184,1,0,0,0,139,117,252,248,139,125,252,252,137,252,236, -- 93,195,255 -+ 32,137,68,36,4,137,60,36,232,251,1,26,184,1,0.0,0.0,0.0,139,117,252,248,139, -+ 125,252,252,137,252,236,93,195,255,137,84,36,36,137,68,36,32,232,251,1,25, -+ 185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,0,0.0,0.0,0.0,137,60,36,232, -+ 251,1,1,139,76,36,36,139,84,36,32,137,72,4,137,16,184,1,0.0,0.0,0.0,139,117, -+ 252,248,139,125,252,252,137,252,236,93,195,255,137,68,36,32,137,84,36,36, -+ 232,251,1,25,185,237,137,1,199.0,68,36,8,237,199.0,68,36,4,237,137,60,36, -+ 232,251,1,1,139,76,36,32,137,8,139,76,36,36,137,72,4,184,1,0.0,0.0,0.0,139, -+ 117,252,248,139,125,252,252,137,252,236,93,195,255,131.0,252,236,4,232,251, -+ 1,25,185,237,137,1,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137, -+ 252,236,93,195,255,232,251,1,25,185,237,137,1,184,0,0.0,0.0,0.0,139,117,252, -+ 248,139,125,252,252,137,252,236,93,195,255,15.0,182,192,137,68,36,32,232, -+ 251,1,25,185,237,137,1,139,68,36,32,137,68,36,4,137,60,36,232,251,1,4,184, -+ 1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93,195,255,137, -+ 68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68,36,4,137,60,36,232, -+ 251,1,5,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93, -+ 195,255,137,68,36,32,232,251,1,25,185,237,137,1,139,68,36,32,137,68,36,4, -+ 137,60,36,232,251,1,6,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137, -+ 252,236,93,195,255,221.0,92,36,4,232,251,1,25,185,237,137,1,137,60,36,232, -+ 251,1,3,184,1,0.0,0.0,0.0,139,117,252,248,139,125,252,252,137,252,236,93, -+ 195,255 - }; - - static const char *const globnames[] = { -@@ -211,6 +218,8 @@ void compile_globals(struct jit* jit, lua_State* L) - * stack - */ - -+ -+ - compile(Dst, L, NULL, LUA_NOREF); - } - -@@ -338,7 +347,12 @@ static void get_int(Dst_DECL, const struct ctype* ct, struct reg_alloc* reg, int - reg->off += 8; - } else { - dasm_put(Dst, 0, reg->off); -+#if defined __amd64__ || defined _WIN64 -+ /* The parameters to a function on stack are always 8 byte aligned. */ -+ reg->off += 8; -+#else - reg->off += 4; -+#endif - } - } - -diff --git a/extra/luaffifb/ffi.h b/extra/luaffifb/ffi.h -index dabdc9b..49584b1 100644 ---- a/extra/luaffifb/ffi.h -+++ b/extra/luaffifb/ffi.h -@@ -105,7 +105,7 @@ static char* luaL_prepbuffsize(luaL_Buffer* B, size_t sz) { - } - return luaL_prepbuffer(B); - } --#elif LUA_VERSION_NUM == 503 -+#elif LUA_VERSION_NUM >= 503 - static void (lua_remove)(lua_State *L, int idx) { - lua_remove(L, idx); - } -diff --git a/extra/luaffifb/test.c b/extra/luaffifb/test.c -index 20f5d13..2e8c612 100644 ---- a/extra/luaffifb/test.c -+++ b/extra/luaffifb/test.c -@@ -718,3 +718,10 @@ void test_call_pppppiiifii(void* p1, void* p2, void* p3, void* p4, void* p5, int - sprintf(buf, "%p %p %p %p %p %d %d %d %0.1f %d %d", p1, p2, p3, p4, p5, i1, i2, i3, f4, i5, i6); - } - -+typedef int (*cb_t)(char i1, char i2, char i3, char i4, char i5, char i6, char i7, char i8, char i9, char i10); -+EXPORT void test_callback_cccccccccc(cb_t func); -+void test_callback_cccccccccc(cb_t func) -+{ -+ func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); -+} -+ -diff --git a/extra/luaffifb/test.lua b/extra/luaffifb/test.lua -index 8a9b718..5151857 100644 ---- a/extra/luaffifb/test.lua -+++ b/extra/luaffifb/test.lua -@@ -925,6 +925,8 @@ void test_call_pppppiiiiii(void* p1, void* p2, void* p3, void* p4, void* p5, int - void test_call_pppppffffff(void* p1, void* p2, void* p3, void* p4, void* p5, float f1, float f2, float f3, float f4, float f5, float f6); - void test_call_pppppiifiii(void* p1, void* p2, void* p3, void* p4, void* p5, int i1, int i2, float f3, int i4, int i5, int i6); - void test_call_pppppiiifii(void* p1, void* p2, void* p3, void* p4, void* p5, int i1, int i2, int i3, float i4, int i5, int i6); -+typedef int (*cb_t)(char i1, char i2, char i3, char i4, char i5, char i6, char i7, char i8, char i9, char i10); -+void test_callback_cccccccccc(cb_t func); - ]] - - ffi.C.test_call_echo("input") -@@ -947,6 +949,15 @@ assert(ffi.C.buf == "0x1 0x2 0x3 0x4 0x5 6 7 8.5 9 10 11") - ffi.C.test_call_pppppiiifii(ptr(1), ptr(2), ptr(3), ptr(4), ptr(5), 6, 7, 8, 9.5, 10, 11) - assert(ffi.C.buf == "0x1 0x2 0x3 0x4 0x5 6 7 8 9.5 10 11") - -+ -+local function callback(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10) -+ local t = {i1, i2, i3, i4, i5, i6, i7, i8, i9, i10} -+ for i, v in ipairs(t) do -+ assert(i == v) -+ end -+end -+ffi.C.test_callback_cccccccccc(callback) -+ - local sum = ffi.C.add_dc(ffi.new('complex', 1, 2), ffi.new('complex', 3, 5)) - assert(ffi.istype('complex', sum)) - -Submodule extra/luafilesystem 3c4e563..0951178: -diff --git a/extra/luafilesystem/.github/workflows/ci.yml b/extra/luafilesystem/.github/workflows/ci.yml -new file mode 100644 -index 0000000..2e8364e ---- /dev/null -+++ b/extra/luafilesystem/.github/workflows/ci.yml -@@ -0,0 +1,78 @@ -+name: ci -+ -+on: -+ pull_request: -+ push: -+ branches: -+ - master -+ -+jobs: -+ TestMatrix: -+ strategy: -+ matrix: -+ lua-version: ["5.4", "5.3", "5.2", "5.1", "luajit"] -+ os: ["ubuntu-latest"] -+ libflag: ["-shared --coverage"] -+ include: -+ - os: "macos-latest" -+ lua-version: "5.4" -+ libflag: "-bundle -undefined dynamic_lookup -all_load --coverage" -+ - os: "windows-latest" -+ toolchain: "msvc" -+ lua-version: "5.4" -+ - os: "windows-latest" -+ lua-version: "luajit" -+ runs-on: ${{ matrix.os }} -+ -+ steps: -+ - uses: actions/checkout@master -+ -+ - name: Setup MSVC -+ # 'luarocks/gh-actions-lua' step requires msvc to build PUC-Rio Lua -+ # versions on Windows (LuaJIT will be built using MinGW/gcc). -+ if: ${{ matrix.toolchain == 'msvc' }} -+ uses: ilammy/msvc-dev-cmd@v1 -+ -+ - uses: luarocks/gh-actions-lua@master -+ with: -+ luaVersion: ${{ matrix.lua-version }} -+ -+ - uses: luarocks/gh-actions-luarocks@master -+ -+ - name: Prep luacov -+ run: | -+ luarocks install luacov -+ -+ - name: Prep luacov-coveralls -+ # TODO Windows coverage -+ if: ${{ matrix.os != 'windows-latest' }} -+ run: | -+ pip install --user cpp-coveralls -+ # install luacov-coveralls, but avoid installing luafilesystem -+ luarocks install lua-path -+ luarocks install dkjson -+ luarocks install luacov-coveralls --server=https://luarocks.org/dev --deps-mode=none -+ -+ - name: Unix Build -+ if: ${{ matrix.os != 'windows-latest' }} -+ run: | -+ luarocks make CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="${{ matrix.libflag }}" -+ -+ - name: Windows Build -+ # TODO Windows coverage -+ if: ${{ matrix.os == 'windows-latest' }} -+ run: | -+ luarocks make -+ -+ - name: Test -+ run: | -+ lua -lluacov tests/test.lua -+ -+ - name: Coverage -+ # TODO Windows coverage -+ if: ${{ matrix.os != 'windows-latest' }} -+ run: | -+ export MY_PYTHON_VER=$(python -c 'import sys; print(".".join(sys.version.split(".")[0:2]))') -+ export PATH="/Users/runner/Library/Python/$MY_PYTHON_VER/bin:$PATH" -+ coveralls -b . -i src --dump c.report.json -+ luacov-coveralls -j c.report.json -v -t ${{ secrets.GITHUB_TOKEN }} -diff --git a/extra/luafilesystem/.travis.yml b/extra/luafilesystem/.travis.yml -deleted file mode 100644 -index 04d46ac..0000000 ---- a/extra/luafilesystem/.travis.yml -+++ /dev/null -@@ -1,35 +0,0 @@ --language: c -- --sudo: false -- --env: -- - LUA="lua 5.1" -- - LUA="lua 5.2" -- - LUA="lua 5.3" -- - LUA="luajit 2.0" -- --before_install: -- - pip install --user cpp-coveralls hererocks -- - hererocks env --$LUA --luarocks latest -- - export PATH="$PWD/env/bin:$PATH" -- - luarocks install Lua-cURL --server=https://luarocks.org/dev -- - luarocks install lua-path -- - luarocks install lua-cjson -- - luarocks install luacov -- # install luacov-coveralls, but avoids installing luafilesystem -- - luarocks install luacov-coveralls --server=https://luarocks.org/dev --deps-mode=none -- --install: -- - luarocks make rockspecs/luafilesystem-cvs-3.rockspec CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="-shared --coverage" -- --script: -- - lua -lluacov tests/test.lua -- --after_success: -- - coveralls -b . -i src --dump c.report.json -- - luacov-coveralls -j c.report.json -v -- --notifications: -- email: -- on_success: change -- on_failure: always -diff --git a/extra/luafilesystem/LICENSE b/extra/luafilesystem/LICENSE -index 8475345..07ccdb1 100644 ---- a/extra/luafilesystem/LICENSE -+++ b/extra/luafilesystem/LICENSE -@@ -1,4 +1,5 @@ --Copyright © 2003-2014 Kepler Project. -+Copyright © 2003-2010 Kepler Project. -+Copyright © 2010-2022 The LuaFileSystem authors. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation -diff --git a/extra/luafilesystem/Makefile b/extra/luafilesystem/Makefile -index e50d5a0..a7312a4 100644 ---- a/extra/luafilesystem/Makefile -+++ b/extra/luafilesystem/Makefile -@@ -12,14 +12,14 @@ OBJS= src/$T.o - lib: src/lfs.so - - src/lfs.so: $(OBJS) -- MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; $(CC) $(LIB_OPTION) -o src/lfs.so $(OBJS) -+ MACOSX_DEPLOYMENT_TARGET=$(MACOSX_DEPLOYMENT_TARGET); export MACOSX_DEPLOYMENT_TARGET; $(CC) $(LIB_OPTION) -o src/lfs.so $(OBJS) - - test: lib - LUA_CPATH=./src/?.so lua tests/test.lua - - install: -- mkdir -p $(LUA_LIBDIR) -- cp src/lfs.so $(LUA_LIBDIR) -+ mkdir -p $(DESTDIR)$(LUA_LIBDIR) -+ cp src/lfs.so $(DESTDIR)$(LUA_LIBDIR) - - clean: - rm -f src/lfs.so $(OBJS) -diff --git a/extra/luafilesystem/README.md b/extra/luafilesystem/README.md -index e2a806a..7e008b8 100644 ---- a/extra/luafilesystem/README.md -+++ b/extra/luafilesystem/README.md -@@ -1,12 +1,11 @@ --[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENCE.txt) --[![Build Status](https://travis-ci.org/keplerproject/luafilesystem.svg?branch=master)](https://travis-ci.org/keplerproject/luafilesystem) -+[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) -+[![Build Status](https://github.com/lunarmodules/luafilesystem/actions/workflows/ci.yml/badge.svg)](https://github.com/lunarmodules/luafilesystem/actions) - [![Build status](https://ci.appveyor.com/api/projects/status/y04s4ms7u16trw8e?svg=true)](https://ci.appveyor.com/project/ignacio/luafilesystem) --[![Coverage Status](https://coveralls.io/repos/keplerproject/luafilesystem/badge.png)](https://coveralls.io/r/keplerproject/luafilesystem) -+[![Coverage Status](https://coveralls.io/repos/lunarmodules/luafilesystem/badge.png)](https://coveralls.io/r/lunarmodules/luafilesystem) - - # LuaFileSystem - File System Library for Lua --Copyright 2003-2016 Kepler Project - --http://keplerproject.github.io/luafilesystem -+https://lunarmodules.github.io/luafilesystem - - # Description - -@@ -14,7 +13,7 @@ LuaFileSystem is a Lua library developed to complement the set of functions - related to file systems offered by the standard Lua distribution. - - LuaFileSystem offers a portable way to access the underlying directory structure and file attributes. --LuaFileSystem is free software and uses the same license as Lua 5.1 -+LuaFileSystem is free software and uses the same license as Lua 5.x (MIT). - - # LuaRocks Installation - -@@ -24,4 +23,4 @@ luarocks install luafilesystem - - # Documentation - --Please check the documentation at doc/us/ for more information. -+Please check the at `docs/` for more information, also available at the [project website](https://lunarmodules.github.io/luafilesystem). -diff --git a/extra/luafilesystem/appveyor.yml b/extra/luafilesystem/appveyor.yml -index 29e6442..9717ebe 100644 ---- a/extra/luafilesystem/appveyor.yml -+++ b/extra/luafilesystem/appveyor.yml -@@ -1,18 +1,25 @@ - version: 0.0.1.{build}-test - --# Use default image unless needed --#os: --#- Windows Server 2012 R2 -- - shallow_clone: true - -+matrix: -+ fast_finish: true -+ - environment: - matrix: -- - LUA: "lua 5.1" -- - LUA: "lua 5.2 --compat none" -- - LUA: "lua 5.3 --compat none" -- - LUA: "luajit 2.0" -- - LUA: "luajit 2.1" -+ - LUAT: "lua" -+ LUAV: "5.1" -+ - LUAT: "lua" -+ LUAV: "5.2" -+ HEREROCKS_FLAGS: "--compat none" -+ - LUAT: "lua" -+ LUAV: "5.3" -+ HEREROCKS_FLAGS: "--compat none" -+ - LUAT: "lua" -+ LUAV: "5.4" -+ HEREROCKS_FLAGS: "--compat none" -+ - LUAT: "luajit" -+ LUAV: "2.1" - - # Abuse this section so we can have a matrix with different Compiler versions - configuration: -@@ -20,17 +27,17 @@ configuration: - - vs_32 - - vs_64 - --install: -- - set PATH=%CD%\env\bin;C:\Python27\Scripts;C:\MinGW\bin;%PATH% -- - pip install hererocks -- - hererocks env --%LUA% --target %configuration% --luarocks latest -- - before_build: --# @todo --- echo "Installing external deps" -+ - set PATH=C:\MinGW\bin;%PATH% -+ - set PATH=C:\Python37;C:\Python37\Scripts;%PATH% # Add directory containing 'pip' to PATH -+ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install --upgrade certifi ) -+ - FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "import certifi;print(certifi.where())"`) DO ( SET SSL_CERT_FILE=%%F ) -+ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install hererocks ) -+ - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( hererocks lua_install-%LUAV% --%LUAT% %LUAV% %HEREROCKS_FLAGS% --luarocks latest --target=%configuration% ) -+ - call lua_install-%LUAV%\bin\activate - - build_script: --- luarocks make rockspecs/luafilesystem-cvs-3.rockspec -+- luarocks make - - before_test: - -@@ -38,5 +45,9 @@ test_script: - - echo "Testing..." - - lua tests/test.lua - --after_test: --# @todo -+cache: -+ - lua_install-5.4 -+ - lua_install-5.3 -+ - lua_install-5.2 -+ - lua_install-5.1 -+ - lua_install-2.1 -diff --git a/extra/luafilesystem/config b/extra/luafilesystem/config -index cfd4c6a..33fe514 100644 ---- a/extra/luafilesystem/config -+++ b/extra/luafilesystem/config -@@ -3,21 +3,25 @@ - # Default installation prefix - PREFIX=/usr/local - -+LUA_VERSION = 5.1 -+ - # System's libraries directory (where binary libraries are installed) --LUA_LIBDIR= $(PREFIX)/lib/lua/5.1 -+LUA_LIBDIR= $(PREFIX)/lib/lua/$(LUA_VERSION) - - # Lua includes directory --LUA_INC= $(PREFIX)/include -+LUA_INC += -I$(PREFIX)/include -+LUA_INC += -I/usr/include/lua$(LUA_VERSION) -I/usr/include/lua/$(LUA_VERSION) - - # OS dependent - LIB_OPTION= -shared #for Linux - #LIB_OPTION= -bundle -undefined dynamic_lookup #for MacOS X - --LIBNAME= $T.so.$V -+# Minimum runtime OS version on macOS -+MACOSX_DEPLOYMENT_TARGET= 10.5 - - # Compilation directives - WARN= -O2 -Wall -fPIC -W -Waggregate-return -Wcast-align -Wmissing-prototypes -Wnested-externs -Wshadow -Wwrite-strings -pedantic --INCS= -I$(LUA_INC) -+INCS= $(LUA_INC) - CFLAGS= $(WARN) $(INCS) - CC= gcc - -diff --git a/extra/luafilesystem/config.win b/extra/luafilesystem/config.win -index 50e81f6..6aeaa60 100644 ---- a/extra/luafilesystem/config.win -+++ b/extra/luafilesystem/config.win -@@ -1,14 +1,14 @@ -+LUA_VERSION= 5.1 -+ - # Installation directories - # System's libraries directory (where binary libraries are installed) --LUA_LIBDIR= "c:\lua5.1" -+LUA_LIBDIR= "c:\lua$(LUA_VERSION)" - - # Lua includes directory --LUA_INC= "c:\lua5.1\include" -+LUA_INC= "c:\lua$(LUA_VERSION)\include" - - # Lua library --LUA_LIB= "c:\lua5.1\lua5.1.lib" -- --LIBNAME= $T.dll -+LUA_LIB= "c:\lua$(LUA_VERSION)\lua$(LUA_VERSION).lib" - - # Compilation directives - WARN= /O2 -diff --git a/extra/luafilesystem/doc/us/doc.css b/extra/luafilesystem/doc/us/doc.css -deleted file mode 100644 -index e816a7e..0000000 ---- a/extra/luafilesystem/doc/us/doc.css -+++ /dev/null -@@ -1,212 +0,0 @@ --body { -- margin-left: 1em; -- margin-right: 1em; -- font-family: arial, helvetica, geneva, sans-serif; -- background-color:#ffffff; margin:0px; --} -- --code { -- font-family: "Andale Mono", monospace; --} -- --tt { -- font-family: "Andale Mono", monospace; --} -- --body, td, th { font-size: 11pt; } -- --h1, h2, h3, h4 { margin-left: 0em; } -- --textarea, pre, tt { font-size:10pt; } --body, td, th { color:#000000; } --small { font-size:0.85em; } --h1 { font-size:1.5em; } --h2 { font-size:1.25em; } --h3 { font-size:1.15em; } --h4 { font-size:1.06em; } -- --a:link { font-weight:bold; color: #004080; text-decoration: none; } --a:visited { font-weight:bold; color: #006699; text-decoration: none; } --a:link:hover { text-decoration:underline; } --hr { color:#cccccc } --img { border-width: 0px; } -- --h3 { padding-top: 1em; } -- --p { margin-left: 1em; } -- --p.name { -- font-family: "Andale Mono", monospace; -- padding-top: 1em; -- margin-left: 0em; --} -- --blockquote { margin-left: 3em; } -- --.example { -- background-color: rgb(245, 245, 245); -- border-top-width: 1px; -- border-right-width: 1px; -- border-bottom-width: 1px; -- border-left-width: 1px; -- border-top-style: solid; -- border-right-style: solid; -- border-bottom-style: solid; -- border-left-style: solid; -- border-top-color: silver; -- border-right-color: silver; -- border-bottom-color: silver; -- border-left-color: silver; -- padding: 1em; -- margin-left: 1em; -- margin-right: 1em; -- font-family: "Andale Mono", monospace; -- font-size: smaller; --} -- --hr { -- margin-left: 0em; -- background: #00007f; -- border: 0px; -- height: 1px; --} -- --ul { list-style-type: disc; } -- --table.index { border: 1px #00007f; } --table.index td { text-align: left; vertical-align: top; } --table.index ul { padding-top: 0em; margin-top: 0em; } -- --table { -- border: 1px solid black; -- border-collapse: collapse; -- margin-left: auto; -- margin-right: auto; --} -- --th { -- border: 1px solid black; -- padding: 0.5em; --} -- --td { -- border: 1px solid black; -- padding: 0.5em; --} --div.header, div.footer { margin-left: 0em; } -- --#container { -- margin-left: 1em; -- margin-right: 1em; -- background-color: #f0f0f0; --} -- --#product { -- text-align: center; -- border-bottom: 1px solid #cccccc; -- background-color: #ffffff; --} -- --#product big { -- font-size: 2em; --} -- --#product_logo { --} -- --#product_name { --} -- --#product_description { --} -- --#main { -- background-color: #f0f0f0; -- border-left: 2px solid #cccccc; --} -- --#navigation { -- float: left; -- width: 12em; -- margin: 0; -- vertical-align: top; -- background-color: #f0f0f0; -- overflow:visible; --} -- --#navigation h1 { -- background-color:#e7e7e7; -- font-size:1.1em; -- color:#000000; -- text-align:left; -- margin:0px; -- padding:0.2em; -- border-top:1px solid #dddddd; -- border-bottom:1px solid #dddddd; --} -- --#navigation ul { -- font-size:1em; -- list-style-type: none; -- padding: 0; -- margin: 1px; --} -- --#navigation li { -- text-indent: -1em; -- margin: 0em 0em 0em 0.5em; -- display: block; -- padding: 3px 0px 0px 12px; --} -- --#navigation li li a { -- padding: 0px 3px 0px -1em; --} -- --#content { -- margin-left: 12em; -- padding: 1em; -- border-left: 2px solid #cccccc; -- border-right: 2px solid #cccccc; -- background-color: #ffffff; --} -- --#about { -- clear: both; -- margin: 0; -- padding: 5px; -- border-top: 2px solid #cccccc; -- background-color: #ffffff; --} -- --@media print { -- body { -- font: 10pt "Times New Roman", "TimeNR", Times, serif; -- } -- a { -- font-weight:bold; color: #004080; text-decoration: underline; -- } -- #main { -- background-color: #ffffff; border-left: 0px; -- } -- #container { -- margin-left: 2%; margin-right: 2%; background-color: #ffffff; -- } -- #content { -- margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; -- } -- #navigation { -- display: none; -- } -- #product_logo { -- display: none; -- } -- #about img { -- display: none; -- } -- .example { -- font-family: "Andale Mono", monospace; -- font-size: 8pt; -- page-break-inside: avoid; -- } --} -diff --git a/extra/luafilesystem/docs/doc.css b/extra/luafilesystem/docs/doc.css -new file mode 100644 -index 0000000..f233ce4 ---- /dev/null -+++ b/extra/luafilesystem/docs/doc.css -@@ -0,0 +1,209 @@ -+body { -+ color: #47555c; -+ font-size: 16px; -+ font-family: "Open Sans", sans-serif; -+ margin: 0; -+ padding: 0; -+ background: #eff4ff; -+} -+ -+a:link { color: #008fee; } -+a:visited { color: #008fee; } -+a:hover { color: #22a7ff; } -+ -+h1 { font-size:26px; } -+h2 { font-size:24px; } -+h3 { font-size:18px; } -+h4 { font-size:16px; } -+ -+hr { -+ height: 1px; -+ background: #c1cce4; -+ border: 0px; -+ margin: 20px 0; -+} -+ -+code { -+ font-family: "Open Sans Mono", "Andale Mono", monospace; -+} -+ -+tt { -+ font-family: "Open Sans Mono", "Andale Mono", monospace; -+} -+ -+body, td, th { -+} -+ -+textarea, pre, tt { -+ font-family: "Open Sans Mono", "Andale Mono", monospace; -+} -+ -+img { -+ border-width: 0px; -+} -+ -+.example { -+ background-color: #323744; -+ color: white; -+ font-size: 16px; -+ padding: 16px 24px; -+ border-radius: 2px; -+} -+ -+div.header, div.footer { -+} -+ -+#container { -+} -+ -+#product { -+ background-color: white; -+ padding: 10px; -+ height: 130px; -+ border-bottom: solid #d3dbec 1px; -+} -+ -+#product big { -+ font-size: 42px; -+} -+#product strong { -+ font-weight: normal; -+} -+ -+#product_logo { -+ float: right; -+} -+ -+#product_name { -+ padding-top: 15px; -+ padding-left: 30px; -+ font-size: 42px; -+ font-weight: normal; -+} -+ -+#product_description { -+ padding-left: 30px; -+ color: #757779; -+} -+ -+#main { -+ background: #eff4ff; -+ margin: 0; -+} -+ -+#navigation { -+ width: 100%; -+ background-color: rgb(44,62,103); -+ padding: 10px; -+ margin: 0; -+} -+ -+#navigation h1 { -+ display: none; -+} -+ -+#navigation a:hover { -+ text-decoration: underline; -+} -+ -+#navigation ul li a { -+ color: rgb(136, 208, 255); -+ font-weight: bold; -+ text-decoration: none; -+} -+ -+#navigation ul li li a { -+ color: rgb(136, 208, 255); -+ font-weight: normal; -+ text-decoration: none; -+} -+ -+#navigation ul { -+ display: inline; -+ color: white; -+ padding: 0px; -+ padding-top: 10px; -+ padding-bottom: 10px; -+} -+ -+#navigation li { -+ display: inline; -+ list-style-type: none; -+ padding-left: 5px; -+ padding-right: 5px; -+} -+ -+#navigation li { -+ padding: 10px; -+ padding: 10px; -+} -+ -+#navigation li li { -+} -+ -+#navigation li:hover a { -+ color: rgb(166, 238, 255); -+} -+ -+#content { -+ padding: 20px; -+ width: 800px; -+ margin-left: auto; -+ margin-right: auto; -+} -+ -+#about { -+ display: none; -+} -+ -+dl.reference { -+ background-color: white; -+ padding: 20px; -+ border: solid #d3dbec 1px; -+} -+ -+dl.reference dt { -+ padding: 5px; -+ padding-top: 25px; -+ color: #637bbc; -+} -+ -+dl.reference dl dt { -+ padding-top: 5px; -+ color: #637383; -+} -+ -+dl.reference dd { -+} -+ -+@media print { -+ body { -+ font: 10pt "Times New Roman", "TimeNR", Times, serif; -+ } -+ a { -+ font-weight:bold; color: #004080; text-decoration: underline; -+ } -+ #main { -+ background-color: #ffffff; border-left: 0px; -+ } -+ #container { -+ margin-left: 2%; margin-right: 2%; background-color: #ffffff; -+ } -+ #content { -+ margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; -+ } -+ #navigation { -+ display: none; -+ } -+ #product_logo { -+ display: none; -+ } -+ #about img { -+ display: none; -+ } -+ .example { -+ font-family: "Andale Mono", monospace; -+ font-size: 8pt; -+ page-break-inside: avoid; -+ } -+} -diff --git a/extra/luafilesystem/doc/us/examples.html b/extra/luafilesystem/docs/examples.html -similarity index 88% -rename from doc/us/examples.html -rename to docs/examples.html -index 68756c8..a41bd2a 100644 ---- a/extra/luafilesystem/doc/us/examples.html -+++ b/extra/luafilesystem/docs/examples.html -@@ -13,7 +13,7 @@ - -
    - -@@ -44,10 +44,10 @@ - - -
  • Examples
  • --
  • Project -+
  • Project - -
  • -
  • License
  • -diff --git a/extra/luafilesystem/doc/us/index.html b/extra/luafilesystem/docs/index.html -similarity index 73% -rename from doc/us/index.html -rename to docs/index.html -index 3ed43f2..34e0730 100644 ---- a/extra/luafilesystem/doc/us/index.html -+++ b/extra/luafilesystem/docs/index.html -@@ -13,7 +13,7 @@ - -
    - -@@ -44,10 +44,10 @@ - - -
  • Examples
  • --
  • Project -+
  • Project - -
  • -
  • License
  • -@@ -70,17 +70,39 @@ the underlying directory structure and file attributes.

    - -

    Status

    - --

    Current version is 1.6.3. It works with Lua 5.1, 5.2 and 5.3.

    -+

    Current version is 1.8.0. It works with Lua 5.1, 5.2, 5.3 and 5.4, and it runs on various -+flavors of Unix (including Linux, BSDs, macOS) and Windows.

    - -

    Download

    - --

    LuaFileSystem source can be downloaded from its --Github --page.

    -+

    LuaFileSystem can be installed using LuaRocks: -+ -+

    -+$ luarocks install luafilesystem
    -+
    -+ -+

    Its source can be found at its GitHub page.

    - -

    History

    - -
    -+
    Version 1.8.0 [22/Apr/2020]
    -+
      -+
    • Lua 5.4 support
    • -+
    • lfs.link and lfs.symlinkattributes now work on Windows
    • -+
    • MACOSX_DEPLOYMENT_TARGET is configurable in the Makefile
    • -+
    • Fallback to _POSIX_PATH_MAX when MAXPATHLEN is not avaliable
    • -+
    • Fixed memory leak in case of realloc failure
    • -+
    -+ -+
    Version 1.7.0 [15/Sep/2017]
    -+
      -+
    • symlinkattributes function now provides 'target' field, containing name of the file that the symlink points to.
    • -+
    • attributes, symlinkattributes, touch, mkdir, and rmdir functions now return system-dependent error code as the third value on error.
    • -+
    • Fixed detection of closed files for Lua 5.2+ in setmode, lock, and unlock functions.
    • -+
    • Fixed various compiler warnings.
    • -+
    -+ -
    Version 1.6.3 [15/Jan/2015]
    -
      -
    • Lua 5.3 support.
    • -@@ -188,10 +210,11 @@ page.

      - -

      Credits

      - --

      LuaFileSystem was designed by Roberto Ierusalimschy, --André Carregal and Tomás Guisasola as part of the --Kepler Project, --which holds its copyright. LuaFileSystem is currently maintained by Fábio Mascarenhas.

      -+

      The LuaFileSystem library was originally designed and -+implemented by Roberto Ierusalimschy, André Carregal and -+Tomás Guisasola. It was then maintained by Fábio -+Mascarenhas for several years and has since been maintained -+by many contributors -- see the Git history for detailed credits.

      - -
    - -diff --git a/extra/luafilesystem/doc/us/license.html b/extra/luafilesystem/docs/license.html -similarity index 74% -rename from doc/us/license.html -rename to docs/license.html -index 5048c11..78704b4 100644 ---- a/extra/luafilesystem/doc/us/license.html -+++ b/extra/luafilesystem/docs/license.html -@@ -13,7 +13,7 @@ - -
    - -@@ -44,10 +44,10 @@ - - -
  • Examples
  • --
  • Project -+
  • Project - -
  • -
  • License
  • -@@ -58,33 +58,21 @@ - -

    License

    - --

    --LuaFileSystem is free software: it can be used for both academic --and commercial purposes at absolutely no cost. There are no --royalties or GNU-like "copyleft" restrictions. LuaFileSystem --qualifies as --Open Source --software. --Its licenses are compatible with --GPL. --LuaFileSystem is not in the public domain and the --Kepler Project --keep its copyright. --The legal details are below. --

    -- -

    The spirit of the license is that you are free to use - LuaFileSystem for any purpose at no cost without having to ask us. - The only requirement is that if you do use LuaFileSystem, then you - should give us credit by including the appropriate copyright notice - somewhere in your product or its documentation.

    - --

    The LuaFileSystem library is designed and implemented by Roberto --Ierusalimschy, André Carregal and Tomás Guisasola. --The implementation is not derived from licensed software.

    -+

    The LuaFileSystem library was originally designed and -+implemented by Roberto Ierusalimschy, André Carregal and -+Tomás Guisasola, and has since been maintained over the years -+by many people -- see the Git history for detailed credits. -+The implementation is not derived from any other licensed software.

    - -
    --

    Copyright © 2003 Kepler Project.

    -+

    Copyright © 2003 - 2010 Kepler Project.

    -+

    Copyright © 2010 - 2022 The LuaFileSystem authors.

    - -

    Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation -diff --git a/extra/luafilesystem/doc/us/luafilesystem.png b/extra/luafilesystem/docs/luafilesystem.png -similarity index 100% -rename from doc/us/luafilesystem.png -rename to docs/luafilesystem.png -diff --git a/extra/luafilesystem/doc/us/manual.html b/extra/luafilesystem/docs/manual.html -similarity index 90% -rename from doc/us/manual.html -rename to docs/manual.html -index 0ecb625..0f84797 100644 ---- a/extra/luafilesystem/doc/us/manual.html -+++ b/extra/luafilesystem/docs/manual.html -@@ -13,7 +13,7 @@ - -

    - -@@ -44,10 +44,10 @@ - - -
  • Examples
  • --
  • Project -+
  • Project - -
  • -
  • License
  • -@@ -102,15 +102,15 @@ LuaFileSystem offers the following functions: -

    - -
    --
    lfs.attributes (filepath [, aname | atable])
    -+
    lfs.attributes (filepath [, request_name | result_table])
    -
    Returns a table with the file attributes corresponding to -- filepath (or nil followed by an error message -+ filepath (or nil followed by an error message and a system-dependent error code - in case of error). - If the second optional argument is given and is a string, then only the value of the - named attribute is returned (this use is equivalent to -- lfs.attributes(filepath)[aname], but the table is not created -+ lfs.attributes(filepath)[request_name], but the table is not created - and only one attribute is retrieved from the O.S.). -- if a table is passed as the second argument, it is filled with attributes and returned instead of a new table. -+ if a table is passed as the second argument, it (result_table) is filled with attributes and returned instead of a new table. - The attributes are described as follows; - attribute mode is a string, all the others are numbers, - and the time related attributes use the same time reference of -@@ -222,14 +222,14 @@ LuaFileSystem offers the following functions: -
    lfs.mkdir (dirname)
    -
    Creates a new directory. The argument is the name of the new - directory.
    -- Returns true if the operation was successful; -- in case of error, it returns nil plus an error string. -+ Returns true in case of success or nil, an error message and -+ a system-dependent error code in case of error. -
    - -
    lfs.rmdir (dirname)
    -
    Removes an existing directory. The argument is the name of the directory.
    -- Returns true if the operation was successful; -- in case of error, it returns nil plus an error string.
    -+ Returns true in case of success or nil, an error message and -+ a system-dependent error code in case of error. - -
    lfs.setmode (file, mode)
    -
    Sets the writing mode for a file. The mode string can be either "binary" or "text". -@@ -239,7 +239,7 @@ LuaFileSystem offers the following functions: - setting the mode has no effect, and the mode is always returned as binary. -
    - --
    lfs.symlinkattributes (filepath [, aname])
    -+
    lfs.symlinkattributes (filepath [, request_name])
    -
    Identical to lfs.attributes except that - it obtains information about the link itself (not the file it refers to). - It also adds a target field, containing -@@ -257,8 +257,8 @@ LuaFileSystem offers the following functions: - Lua standard function os.time). - If the modification time is omitted, the access time provided is used; - if both times are omitted, the current time is used.
    -- Returns true if the operation was successful; -- in case of error, it returns nil plus an error string. -+ Returns true in case of success or nil, an error message and -+ a system-dependent error code in case of error. -
    - -
    lfs.unlock (filehandle[, start[, length]])
    -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.3-1.rockspec b/extra/luafilesystem/luafilesystem-scm-1.rockspec -similarity index 76% -rename from rockspecs/luafilesystem-1.6.3-1.rockspec -rename to luafilesystem-scm-1.rockspec -index 89b25d4..19b6225 100644 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.3-1.rockspec -+++ b/extra/luafilesystem/luafilesystem-scm-1.rockspec -@@ -1,8 +1,7 @@ --package = "LuaFileSystem" --version = "1.6.3-1" -+package = "luafilesystem" -+version = "scm-1" - source = { -- url = "git://github.com/keplerproject/luafilesystem", -- tag = "v_1_6_3", -+ url = "git+https://github.com/lunarmodules/luafilesystem" - } - description = { - summary = "File System Library for the Lua Programming Language", -@@ -12,7 +11,7 @@ description = { - distribution. LuaFileSystem offers a portable way to access the - underlying directory structure and file attributes. - ]], -- license = "MIT/X11", -+ license = "MIT/X11" - } - dependencies = { - "lua >= 5.1" -@@ -23,6 +22,7 @@ build = { - lfs = "src/lfs.c" - }, - copy_directories = { -- "doc", "tests" -+ "docs", -+ "tests" - } - } -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec -deleted file mode 100644 -index d4d484f..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.3.0-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" --version = "1.3.0-1" --source = { -- url = "http://luaforge.net/frs/download.php/2679/luafilesystem-1.3.0.tar.gz" --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- type = "make", -- build_variables = { -- LUA_INC = "$(LUA_INCDIR)", -- LIB_OPTION = "$(LIBFLAG)" -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec -deleted file mode 100644 -index b693618..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" --version = "1.4.0-1" --source = { -- url = "http://luaforge.net/frs/download.php/3158/luafilesystem-1.4.0.tar.gz" --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- type = "make", -- build_variables = { -- LUA_INC = "$(LUA_INCDIR)", -- LIB_OPTION = "$(LIBFLAG)" -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec -deleted file mode 100644 -index f7ed871..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.0-2.rockspec -+++ /dev/null -@@ -1,43 +0,0 @@ --package = "LuaFileSystem" --version = "1.4.0-2" --source = { -- url = "http://luaforge.net/frs/download.php/3158/luafilesystem-1.4.0.tar.gz" --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- platforms = { -- unix = { -- type = "make", -- build_variables = { -- LIB_OPTION = "$(LIBFLAG)", -- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } -- }, -- win32 = { -- type = "make", -- build_variables = { -- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", -- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)", -- LUA_DIR = "$(LUADIR)", -- BIN_DIR = "$(BINDIR)" -- } -- } -- } --} -\ No newline at end of file -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec -deleted file mode 100644 -index db3a3eb..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1-1.rockspec -+++ /dev/null -@@ -1,43 +0,0 @@ --package = "LuaFileSystem" --version = "1.4.1-1" --source = { -- url = "http://luaforge.net/frs/download.php/3345/luafilesystem-1.4.1.tar.gz", --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- platforms = { -- unix = { -- type = "make", -- build_variables = { -- LIB_OPTION = "$(LIBFLAG)", -- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR) $(STAT64)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } -- }, -- win32 = { -- type = "make", -- build_variables = { -- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", -- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)", -- LUA_DIR = "$(LUADIR)", -- BIN_DIR = "$(BINDIR)" -- } -- } -- } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec -deleted file mode 100644 -index 1194711..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.1rc1-1.rockspec -+++ /dev/null -@@ -1,43 +0,0 @@ --package = "LuaFileSystem" --version = "1.4.1rc1-1" --source = { -- url = "http://luafilesystem.luaforge.net/luafilesystem-1.4.1rc1.tar.gz", --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- platforms = { -- unix = { -- type = "make", -- build_variables = { -- LIB_OPTION = "$(LIBFLAG)", -- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR) $(STAT64)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } -- }, -- win32 = { -- type = "make", -- build_variables = { -- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", -- CFLAGS = "/MD $(CFLAGS) /I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)", -- LUA_DIR = "$(LUADIR)", -- BIN_DIR = "$(BINDIR)" -- } -- } -- } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec -deleted file mode 100644 -index 7cfe92b..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.4.2-1.rockspec -+++ /dev/null -@@ -1,26 +0,0 @@ --package = "LuaFileSystem" -- --version = "1.4.2-1" -- --source = { -- url = "http://luaforge.net/frs/download.php/3931/luafilesystem-1.4.2.tar.gz", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "module", -- modules = { lfs = "src/lfs.c" } --} -\ No newline at end of file -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec -deleted file mode 100644 -index 1170ad2..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.5.0-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" -- --version = "1.5.0-1" -- --source = { -- url = "http://cloud.github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.5.0.tar.gz", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "module", -- modules = { lfs = "src/lfs.c" }, -- copy_directories = { "doc", "tests" } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec -deleted file mode 100644 -index 82d349c..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.0-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" -- --version = "1.6.0-1" -- --source = { -- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.0.tar.gz", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "builtin", -- modules = { lfs = "src/lfs.c" }, -- copy_directories = { "doc", "tests" } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec -deleted file mode 100644 -index 7f45e33..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.1-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" -- --version = "1.6.1-1" -- --source = { -- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.1.tar.gz", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "builtin", -- modules = { lfs = "src/lfs.c" }, -- copy_directories = { "doc", "tests" } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec -deleted file mode 100644 -index 1c11efc..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-1.6.2-1.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" -- --version = "1.6.2-1" -- --source = { -- url = "https://github.com/downloads/keplerproject/luafilesystem/luafilesystem-1.6.2.tar.gz", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "builtin", -- modules = { lfs = "src/lfs.c" }, -- copy_directories = { "doc", "tests" } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec -deleted file mode 100644 -index a02d4f1..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-1.rockspec -+++ /dev/null -@@ -1,44 +0,0 @@ --package = "LuaFileSystem" --version = "cvs-1" --source = { -- url = "cvs://:pserver:anonymous:@cvs.luaforge.net:/cvsroot/luafilesystem", -- cvs_tag = "HEAD" --} --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} --dependencies = { -- "lua >= 5.1" --} --build = { -- platforms = { -- unix = { -- type = "make", -- build_variables = { -- LIB_OPTION = "$(LIBFLAG)", -- CFLAGS = "$(CFLAGS) -I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)" -- } -- }, -- win32 = { -- type = "make", -- build_variables = { -- LUA_LIB = "$(LUA_LIBDIR)\\lua5.1.lib", -- CFLAGS = "$(CFLAGS) /I$(LUA_INCDIR)", -- }, -- install_variables = { -- LUA_LIBDIR = "$(LIBDIR)", -- LUA_DIR = "$(LUADIR)", -- BIN_DIR = "$(BINDIR)" -- } -- } -- } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec -deleted file mode 100644 -index 651c7cf..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-2.rockspec -+++ /dev/null -@@ -1,26 +0,0 @@ --package = "LuaFileSystem" -- --version = "cvs-2" -- --source = { -- url = "git://github.com/keplerproject/luafilesystem.git", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1" --} -- --build = { -- type = "module", -- modules = { lfs = "src/lfs.c" } --} -diff --git a/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec b/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec -deleted file mode 100644 -index a4388cd..0000000 ---- a/extra/luafilesystem/rockspecs/luafilesystem-cvs-3.rockspec -+++ /dev/null -@@ -1,27 +0,0 @@ --package = "LuaFileSystem" -- --version = "cvs-3" -- --source = { -- url = "git://github.com/keplerproject/luafilesystem.git", --} -- --description = { -- summary = "File System Library for the Lua Programming Language", -- detailed = [[ -- LuaFileSystem is a Lua library developed to complement the set of -- functions related to file systems offered by the standard Lua -- distribution. LuaFileSystem offers a portable way to access the -- underlying directory structure and file attributes. -- ]] --} -- --dependencies = { -- "lua >= 5.1, < 5.4" --} -- --build = { -- type = "builtin", -- modules = { lfs = "src/lfs.c" }, -- copy_directories = { "doc", "tests" } --} -diff --git a/extra/luafilesystem/src/lfs.c b/extra/luafilesystem/src/lfs.c -index 25122a3..b51470c 100644 ---- a/extra/luafilesystem/src/lfs.c -+++ b/extra/luafilesystem/src/lfs.c -@@ -1,34 +1,26 @@ - /* - ** LuaFileSystem --** Copyright Kepler Project 2003 - 2016 (http://keplerproject.github.io/luafilesystem) -+** File system manipulation library - ** --** File system manipulation library. --** This library offers these functions: --** lfs.attributes (filepath [, attributename | attributetable]) --** lfs.chdir (path) --** lfs.currentdir () --** lfs.dir (path) --** lfs.link (old, new[, symlink]) --** lfs.lock (fh, mode) --** lfs.lock_dir (path) --** lfs.mkdir (path) --** lfs.rmdir (path) --** lfs.setmode (filepath, mode) --** lfs.symlinkattributes (filepath [, attributename]) --** lfs.touch (filepath [, atime [, mtime]]) --** lfs.unlock (fh) -+** Copyright (C) 2003-2010 Kepler Project. -+** Copyright (C) 2010-2022 The LuaFileSystem authors. -+** (http://lunarmodules.github.io/luafilesystem) - */ - - #ifndef LFS_DO_NOT_USE_LARGE_FILE - #ifndef _WIN32 - #ifndef _AIX --#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ -+#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ - #else --#define _LARGE_FILES 1 /* AIX */ -+#define _LARGE_FILES 1 /* AIX */ - #endif - #endif - #endif - -+#ifdef _WIN32 -+#define _WIN32_WINNT 0x600 -+#endif -+ - #ifndef LFS_DO_NOT_USE_LARGE_FILE - #define _LARGEFILE64_SOURCE - #endif -@@ -41,26 +33,39 @@ - #include - - #ifdef _WIN32 -- #include -- #include -- #include -- #include -- #ifdef __BORLANDC__ -- #include -- #else -- #include -- #endif -- #include -- /* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ -- #define LFS_MAXPATHLEN MAX_PATH -+ -+#include -+#include -+#include -+#include -+ -+#ifdef __BORLANDC__ -+#include -+#else -+#include -+#endif -+ -+#include -+ -+/* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ -+#define LFS_MAXPATHLEN MAX_PATH -+ -+#else -+ -+#include -+#include -+#include -+#include -+#include -+#include /* for MAXPATHLEN */ -+ -+#ifdef MAXPATHLEN -+#define LFS_MAXPATHLEN MAXPATHLEN - #else -- #include -- #include -- #include -- #include -- #include -- #include /* for MAXPATHLEN */ -- #define LFS_MAXPATHLEN MAXPATHLEN -+#include /* for _POSIX_PATH_MAX */ -+#define LFS_MAXPATHLEN _POSIX_PATH_MAX -+#endif -+ - #endif - - #include -@@ -69,10 +74,10 @@ - - #include "lfs.h" - --#define LFS_VERSION "1.6.3" -+#define LFS_VERSION "1.8.0" - #define LFS_LIBNAME "lfs" - --#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ -+#if LUA_VERSION_NUM >= 503 /* Lua 5.3+ */ - - #ifndef luaL_optlong - #define luaL_optlong luaL_optinteger -@@ -80,8 +85,10 @@ - - #endif - --#if LUA_VERSION_NUM < 502 --# define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l)) -+#if LUA_VERSION_NUM >= 502 -+#define new_lib(L, l) (luaL_newlib(L, l)) -+#else -+#define new_lib(L, l) (lua_newtable(L), luaL_register(L, NULL, l)) - #endif - - /* Define 'strerror' for systems that do not implement it */ -@@ -91,73 +98,163 @@ - - #define DIR_METATABLE "directory metatable" - typedef struct dir_data { -- int closed; -+ int closed; - #ifdef _WIN32 -- intptr_t hFile; -- char pattern[MAX_PATH+1]; -+ intptr_t hFile; -+ char pattern[MAX_PATH + 1]; - #else -- DIR *dir; -+ DIR *dir; - #endif - } dir_data; - - #define LOCK_METATABLE "lock metatable" - - #ifdef _WIN32 -- #ifdef __BORLANDC__ -- #define lfs_setmode(file, m) (setmode(_fileno(file), m)) -- #define STAT_STRUCT struct stati64 -- #else -- #define lfs_setmode(file, m) (_setmode(_fileno(file), m)) -- #define STAT_STRUCT struct _stati64 -- #endif -+ -+#ifdef __BORLANDC__ -+#define lfs_setmode(file, m) (setmode(_fileno(file), m)) -+#define STAT_STRUCT struct stati64 -+#else -+#define lfs_setmode(file, m) (_setmode(_fileno(file), m)) -+#define STAT_STRUCT struct _stati64 -+#endif -+ -+#ifndef _S_IFLNK -+#define _S_IFLNK 0x400 -+#endif -+ -+#ifndef S_ISDIR -+#define S_ISDIR(mode) (mode&_S_IFDIR) -+#endif -+#ifndef S_ISREG -+#define S_ISREG(mode) (mode&_S_IFREG) -+#endif -+#ifndef S_ISLNK -+#define S_ISLNK(mode) (mode&_S_IFLNK) -+#endif -+#ifndef S_ISSOCK -+#define S_ISSOCK(mode) (0) -+#endif -+#ifndef S_ISFIFO -+#define S_ISFIFO(mode) (0) -+#endif -+#ifndef S_ISCHR -+#define S_ISCHR(mode) (mode&_S_IFCHR) -+#endif -+#ifndef S_ISBLK -+#define S_ISBLK(mode) (0) -+#endif -+ - #define STAT_FUNC _stati64 --#define LSTAT_FUNC STAT_FUNC -+#define LSTAT_FUNC lfs_win32_lstat -+ - #else -+ - #define _O_TEXT 0 - #define _O_BINARY 0 - #define lfs_setmode(file, m) ((void)file, (void)m, 0) - #define STAT_STRUCT struct stat - #define STAT_FUNC stat - #define LSTAT_FUNC lstat -+ -+#endif -+ -+#ifdef _WIN32 -+#define lfs_mkdir _mkdir -+#else -+#define lfs_mkdir(path) (mkdir((path), \ -+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH)) -+#endif -+ -+#ifdef _WIN32 -+ -+static int lfs_win32_pusherror(lua_State * L) -+{ -+ int en = GetLastError(); -+ lua_pushnil(L); -+ if (en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) -+ lua_pushstring(L, "File exists"); -+ else -+ lua_pushstring(L, strerror(en)); -+ return 2; -+} -+ -+#define TICKS_PER_SECOND 10000000 -+#define EPOCH_DIFFERENCE 11644473600LL -+static time_t windowsToUnixTime(FILETIME ft) -+{ -+ ULARGE_INTEGER uli; -+ uli.LowPart = ft.dwLowDateTime; -+ uli.HighPart = ft.dwHighDateTime; -+ return (time_t) (uli.QuadPart / TICKS_PER_SECOND - EPOCH_DIFFERENCE); -+} -+ -+static int lfs_win32_lstat(const char *path, STAT_STRUCT * buffer) -+{ -+ WIN32_FILE_ATTRIBUTE_DATA win32buffer; -+ if (GetFileAttributesEx(path, GetFileExInfoStandard, &win32buffer)) { -+ if (!(win32buffer.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { -+ return STAT_FUNC(path, buffer); -+ } -+ buffer->st_mode = _S_IFLNK; -+ buffer->st_dev = 0; -+ buffer->st_ino = 0; -+ buffer->st_nlink = 0; -+ buffer->st_uid = 0; -+ buffer->st_gid = 0; -+ buffer->st_rdev = 0; -+ buffer->st_atime = windowsToUnixTime(win32buffer.ftLastAccessTime); -+ buffer->st_mtime = windowsToUnixTime(win32buffer.ftLastWriteTime); -+ buffer->st_ctime = windowsToUnixTime(win32buffer.ftCreationTime); -+ buffer->st_size = 0; -+ return 0; -+ } else { -+ return 1; -+ } -+} -+ - #endif - - /* - ** Utility functions - */ --static int pusherror(lua_State *L, const char *info) -+static int pusherror(lua_State * L, const char *info) - { -- lua_pushnil(L); -- if (info==NULL) -- lua_pushstring(L, strerror(errno)); -- else -- lua_pushfstring(L, "%s: %s", info, strerror(errno)); -- lua_pushinteger(L, errno); -- return 3; -+ lua_pushnil(L); -+ if (info == NULL) -+ lua_pushstring(L, strerror(errno)); -+ else -+ lua_pushfstring(L, "%s: %s", info, strerror(errno)); -+ lua_pushinteger(L, errno); -+ return 3; - } - --static int pushresult(lua_State *L, int i, const char *info) -+static int pushresult(lua_State * L, int res, const char *info) - { -- if (i==-1) -- return pusherror(L, info); -- lua_pushinteger(L, i); -- return 1; -+ if (res == -1) { -+ return pusherror(L, info); -+ } else { -+ lua_pushboolean(L, 1); -+ return 1; -+ } - } - - - /* - ** This function changes the working (current) directory - */ --static int change_dir (lua_State *L) { -- const char *path = luaL_checkstring(L, 1); -- if (chdir(path)) { -- lua_pushnil (L); -- lua_pushfstring (L,"Unable to change working directory to '%s'\n%s\n", -- path, chdir_error); -- return 2; -- } else { -- lua_pushboolean (L, 1); -- return 1; -- } -+static int change_dir(lua_State * L) -+{ -+ const char *path = luaL_checkstring(L, 1); -+ if (chdir(path)) { -+ lua_pushnil(L); -+ lua_pushfstring(L, "Unable to change working directory to '%s'\n%s\n", -+ path, chdir_error); -+ return 2; -+ } else { -+ lua_pushboolean(L, 1); -+ return 1; -+ } - } - - /* -@@ -165,56 +262,62 @@ static int change_dir (lua_State *L) { - ** If unable to get the current directory, it returns nil - ** and a string describing the error - */ --static int get_dir (lua_State *L) { -+static int get_dir(lua_State * L) -+{ - #ifdef NO_GETCWD -- lua_pushnil(L); -- lua_pushstring(L, "Function 'getcwd' not provided by system"); -- return 2; -+ lua_pushnil(L); -+ lua_pushstring(L, "Function 'getcwd' not provided by system"); -+ return 2; - #else -- char *path = NULL; -- /* Passing (NULL, 0) is not guaranteed to work. Use a temp buffer and size instead. */ -- size_t size = LFS_MAXPATHLEN; /* initial buffer size */ -- int result; -- while (1) { -- path = realloc(path, size); -- if (!path) /* failed to allocate */ -- return pusherror(L, "get_dir realloc() failed"); -- if (getcwd(path, size) != NULL) { -- /* success, push the path to the Lua stack */ -- lua_pushstring(L, path); -- result = 1; -- break; -- } -- if (errno != ERANGE) { /* unexpected error */ -- result = pusherror(L, "get_dir getcwd() failed"); -- break; -- } -- /* ERANGE = insufficient buffer capacity, double size and retry */ -- size *= 2; -+ char *path = NULL; -+ /* Passing (NULL, 0) is not guaranteed to work. -+ Use a temp buffer and size instead. */ -+ size_t size = LFS_MAXPATHLEN; /* initial buffer size */ -+ int result; -+ while (1) { -+ char *path2 = (char *)realloc(path, size); -+ if (!path2) { /* failed to allocate */ -+ result = pusherror(L, "get_dir realloc() failed"); -+ break; - } -- free(path); -- return result; -+ path = path2; -+ if (getcwd(path, (int)size) != NULL) { -+ /* success, push the path to the Lua stack */ -+ lua_pushstring(L, path); -+ result = 1; -+ break; -+ } -+ if (errno != ERANGE) { /* unexpected error */ -+ result = pusherror(L, "get_dir getcwd() failed"); -+ break; -+ } -+ /* ERANGE = insufficient buffer capacity, double size and retry */ -+ size *= 2; -+ } -+ free(path); -+ return result; - #endif - } - - /* - ** Check if the given element on the stack is a file and returns it. - */ --static FILE *check_file (lua_State *L, int idx, const char *funcname) { -+static FILE *check_file(lua_State * L, int idx, const char *funcname) -+{ - #if LUA_VERSION_NUM == 501 -- FILE **fh = (FILE **)luaL_checkudata (L, idx, "FILE*"); -- if (*fh == NULL) { -- luaL_error (L, "%s: closed file", funcname); -- return 0; -- } else -- return *fh; --#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 503 -- luaL_Stream *fh = (luaL_Stream *)luaL_checkudata (L, idx, "FILE*"); -- if (fh->closef == 0 || fh->f == NULL) { -- luaL_error (L, "%s: closed file", funcname); -- return 0; -- } else -- return fh->f; -+ FILE **fh = (FILE **) luaL_checkudata(L, idx, "FILE*"); -+ if (*fh == NULL) { -+ luaL_error(L, "%s: closed file", funcname); -+ return 0; -+ } else -+ return *fh; -+#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504 -+ luaL_Stream *fh = (luaL_Stream *) luaL_checkudata(L, idx, "FILE*"); -+ if (fh->closef == 0 || fh->f == NULL) { -+ luaL_error(L, "%s: closed file", funcname); -+ return 0; -+ } else -+ return fh->f; - #else - #error unsupported Lua version - #endif -@@ -224,90 +327,114 @@ static FILE *check_file (lua_State *L, int idx, const char *funcname) { - /* - ** - */ --static int _file_lock (lua_State *L, FILE *fh, const char *mode, const long start, long len, const char *funcname) { -- int code; -+static int _file_lock(lua_State * L, FILE * fh, const char *mode, -+ const long start, long len, const char *funcname) -+{ -+ int code; - #ifdef _WIN32 -- /* lkmode valid values are: -- LK_LOCK Locks the specified bytes. If the bytes cannot be locked, the program immediately tries again after 1 second. If, after 10 attempts, the bytes cannot be locked, the constant returns an error. -- LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, the constant returns an error. -- LK_NBRLCK Same as _LK_NBLCK. -- LK_RLCK Same as _LK_LOCK. -- LK_UNLCK Unlocks the specified bytes, which must have been previously locked. -- -- Regions should be locked only briefly and should be unlocked before closing a file or exiting the program. -- -- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp -- */ -- int lkmode; -- switch (*mode) { -- case 'r': lkmode = LK_NBLCK; break; -- case 'w': lkmode = LK_NBLCK; break; -- case 'u': lkmode = LK_UNLCK; break; -- default : return luaL_error (L, "%s: invalid mode", funcname); -- } -- if (!len) { -- fseek (fh, 0L, SEEK_END); -- len = ftell (fh); -- } -- fseek (fh, start, SEEK_SET); -+ /* lkmode valid values are: -+ LK_LOCK Locks the specified bytes. If the bytes cannot be locked, -+ the program immediately tries again after 1 second. -+ If, after 10 attempts, the bytes cannot be locked, -+ the constant returns an error. -+ LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, -+ the constant returns an error. -+ LK_NBRLCK Same as _LK_NBLCK. -+ LK_RLCK Same as _LK_LOCK. -+ LK_UNLCK Unlocks the specified bytes, which must have been -+ previously locked. -+ -+ Regions should be locked only briefly and should be unlocked -+ before closing a file or exiting the program. -+ -+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp -+ */ -+ int lkmode; -+ switch (*mode) { -+ case 'r': -+ lkmode = LK_NBLCK; -+ break; -+ case 'w': -+ lkmode = LK_NBLCK; -+ break; -+ case 'u': -+ lkmode = LK_UNLCK; -+ break; -+ default: -+ return luaL_error(L, "%s: invalid mode", funcname); -+ } -+ if (!len) { -+ fseek(fh, 0L, SEEK_END); -+ len = ftell(fh); -+ } -+ fseek(fh, start, SEEK_SET); - #ifdef __BORLANDC__ -- code = locking (fileno(fh), lkmode, len); -+ code = locking(fileno(fh), lkmode, len); - #else -- code = _locking (fileno(fh), lkmode, len); -+ code = _locking(fileno(fh), lkmode, len); - #endif - #else -- struct flock f; -- switch (*mode) { -- case 'w': f.l_type = F_WRLCK; break; -- case 'r': f.l_type = F_RDLCK; break; -- case 'u': f.l_type = F_UNLCK; break; -- default : return luaL_error (L, "%s: invalid mode", funcname); -- } -- f.l_whence = SEEK_SET; -- f.l_start = (off_t)start; -- f.l_len = (off_t)len; -- code = fcntl (fileno(fh), F_SETLK, &f); -+ struct flock f; -+ switch (*mode) { -+ case 'w': -+ f.l_type = F_WRLCK; -+ break; -+ case 'r': -+ f.l_type = F_RDLCK; -+ break; -+ case 'u': -+ f.l_type = F_UNLCK; -+ break; -+ default: -+ return luaL_error(L, "%s: invalid mode", funcname); -+ } -+ f.l_whence = SEEK_SET; -+ f.l_start = (off_t) start; -+ f.l_len = (off_t) len; -+ code = fcntl(fileno(fh), F_SETLK, &f); - #endif -- return (code != -1); -+ return (code != -1); - } - - #ifdef _WIN32 - typedef struct lfs_Lock { - HANDLE fd; - } lfs_Lock; --static int lfs_lock_dir(lua_State *L) { -- size_t pathl; HANDLE fd; -+static int lfs_lock_dir(lua_State * L) -+{ -+ size_t pathl; -+ HANDLE fd; - lfs_Lock *lock; - char *ln; - const char *lockfile = "/lockfile.lfs"; - const char *path = luaL_checklstring(L, 1, &pathl); -- ln = (char*)malloc(pathl + strlen(lockfile) + 1); -- if(!ln) { -- lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; -- } -- strcpy(ln, path); strcat(ln, lockfile); -- if((fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_NEW, -- FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) { -- int en = GetLastError(); -- free(ln); lua_pushnil(L); -- if(en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) -- lua_pushstring(L, "File exists"); -- else -- lua_pushstring(L, strerror(en)); -- return 2; -+ ln = (char *) malloc(pathl + strlen(lockfile) + 1); -+ if (!ln) { -+ lua_pushnil(L); -+ lua_pushstring(L, strerror(errno)); -+ return 2; - } -+ strcpy(ln, path); -+ strcat(ln, lockfile); -+ fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, -+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); - free(ln); -- lock = (lfs_Lock*)lua_newuserdata(L, sizeof(lfs_Lock)); -+ if (fd == INVALID_HANDLE_VALUE) { -+ return lfs_win32_pusherror(L); -+ } -+ lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); - lock->fd = fd; -- luaL_getmetatable (L, LOCK_METATABLE); -- lua_setmetatable (L, -2); -+ luaL_getmetatable(L, LOCK_METATABLE); -+ lua_setmetatable(L, -2); - return 1; - } --static int lfs_unlock_dir(lua_State *L) { -- lfs_Lock *lock = (lfs_Lock *)luaL_checkudata(L, 1, LOCK_METATABLE); -- if(lock->fd != INVALID_HANDLE_VALUE) { -+ -+static int lfs_unlock_dir(lua_State * L) -+{ -+ lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); -+ if (lock->fd != INVALID_HANDLE_VALUE) { - CloseHandle(lock->fd); -- lock->fd=INVALID_HANDLE_VALUE; -+ lock->fd = INVALID_HANDLE_VALUE; - } - return 0; - } -@@ -315,30 +442,38 @@ static int lfs_unlock_dir(lua_State *L) { - typedef struct lfs_Lock { - char *ln; - } lfs_Lock; --static int lfs_lock_dir(lua_State *L) { -+static int lfs_lock_dir(lua_State * L) -+{ - lfs_Lock *lock; - size_t pathl; - char *ln; - const char *lockfile = "/lockfile.lfs"; - const char *path = luaL_checklstring(L, 1, &pathl); -- lock = (lfs_Lock*)lua_newuserdata(L, sizeof(lfs_Lock)); -- ln = (char*)malloc(pathl + strlen(lockfile) + 1); -- if(!ln) { -- lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; -+ lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); -+ ln = (char *) malloc(pathl + strlen(lockfile) + 1); -+ if (!ln) { -+ lua_pushnil(L); -+ lua_pushstring(L, strerror(errno)); -+ return 2; - } -- strcpy(ln, path); strcat(ln, lockfile); -- if(symlink("lock", ln) == -1) { -- free(ln); lua_pushnil(L); -- lua_pushstring(L, strerror(errno)); return 2; -+ strcpy(ln, path); -+ strcat(ln, lockfile); -+ if (symlink("lock", ln) == -1) { -+ free(ln); -+ lua_pushnil(L); -+ lua_pushstring(L, strerror(errno)); -+ return 2; - } - lock->ln = ln; -- luaL_getmetatable (L, LOCK_METATABLE); -- lua_setmetatable (L, -2); -+ luaL_getmetatable(L, LOCK_METATABLE); -+ lua_setmetatable(L, -2); - return 1; - } --static int lfs_unlock_dir(lua_State *L) { -- lfs_Lock *lock = (lfs_Lock *)luaL_checkudata(L, 1, LOCK_METATABLE); -- if(lock->ln) { -+ -+static int lfs_unlock_dir(lua_State * L) -+{ -+ lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); -+ if (lock->ln) { - unlink(lock->ln); - free(lock->ln); - lock->ln = NULL; -@@ -347,9 +482,10 @@ static int lfs_unlock_dir(lua_State *L) { - } - #endif - --static int lfs_g_setmode (lua_State *L, FILE *f, int arg) { -- static const int mode[] = {_O_BINARY, _O_TEXT}; -- static const char *const modenames[] = {"binary", "text", NULL}; -+static int lfs_g_setmode(lua_State * L, FILE * f, int arg) -+{ -+ static const int mode[] = { _O_BINARY, _O_TEXT }; -+ static const char *const modenames[] = { "binary", "text", NULL }; - int op = luaL_checkoption(L, arg, NULL, modenames); - int res = lfs_setmode(f, mode[op]); - if (res != -1) { -@@ -368,7 +504,8 @@ static int lfs_g_setmode (lua_State *L, FILE *f, int arg) { - } - } - --static int lfs_f_setmode(lua_State *L) { -+static int lfs_f_setmode(lua_State * L) -+{ - return lfs_g_setmode(L, check_file(L, 1, "setmode"), 2); - } - -@@ -379,19 +516,20 @@ static int lfs_f_setmode(lua_State *L) { - ** @param #3 Number with start position (optional). - ** @param #4 Number with length (optional). - */ --static int file_lock (lua_State *L) { -- FILE *fh = check_file (L, 1, "lock"); -- const char *mode = luaL_checkstring (L, 2); -- const long start = (long) luaL_optinteger (L, 3, 0); -- long len = (long) luaL_optinteger (L, 4, 0); -- if (_file_lock (L, fh, mode, start, len, "lock")) { -- lua_pushboolean (L, 1); -- return 1; -- } else { -- lua_pushnil (L); -- lua_pushfstring (L, "%s", strerror(errno)); -- return 2; -- } -+static int file_lock(lua_State * L) -+{ -+ FILE *fh = check_file(L, 1, "lock"); -+ const char *mode = luaL_checkstring(L, 2); -+ const long start = (long) luaL_optinteger(L, 3, 0); -+ long len = (long) luaL_optinteger(L, 4, 0); -+ if (_file_lock(L, fh, mode, start, len, "lock")) { -+ lua_pushboolean(L, 1); -+ return 1; -+ } else { -+ lua_pushnil(L); -+ lua_pushfstring(L, "%s", strerror(errno)); -+ return 2; -+ } - } - - -@@ -401,18 +539,19 @@ static int file_lock (lua_State *L) { - ** @param #2 Number with start position (optional). - ** @param #3 Number with length (optional). - */ --static int file_unlock (lua_State *L) { -- FILE *fh = check_file (L, 1, "unlock"); -- const long start = (long) luaL_optinteger (L, 2, 0); -- long len = (long) luaL_optinteger (L, 3, 0); -- if (_file_lock (L, fh, "u", start, len, "unlock")) { -- lua_pushboolean (L, 1); -- return 1; -- } else { -- lua_pushnil (L); -- lua_pushfstring (L, "%s", strerror(errno)); -- return 2; -- } -+static int file_unlock(lua_State * L) -+{ -+ FILE *fh = check_file(L, 1, "unlock"); -+ const long start = (long) luaL_optinteger(L, 2, 0); -+ long len = (long) luaL_optinteger(L, 3, 0); -+ if (_file_lock(L, fh, "u", start, len, "unlock")) { -+ lua_pushboolean(L, 1); -+ return 1; -+ } else { -+ lua_pushnil(L); -+ lua_pushfstring(L, "%s", strerror(errno)); -+ return 2; -+ } - } - - -@@ -422,16 +561,40 @@ static int file_unlock (lua_State *L) { - ** @param #2 Name of link. - ** @param #3 True if link is symbolic (optional). - */ --static int make_link(lua_State *L) -+static int make_link(lua_State * L) - { -+ const char *oldpath = luaL_checkstring(L, 1); -+ const char *newpath = luaL_checkstring(L, 2); - #ifndef _WIN32 -- const char *oldpath = luaL_checkstring(L, 1); -- const char *newpath = luaL_checkstring(L, 2); -- return pushresult(L, -- (lua_toboolean(L,3) ? symlink : link)(oldpath, newpath), NULL); -+ return pushresult(L, -+ (lua_toboolean(L, 3) ? symlink : link) (oldpath, -+ newpath), -+ NULL); - #else -- errno = ENOSYS; /* = "Function not implemented" */ -- return pushresult(L, -1, "make_link is not supported on Windows"); -+ int symbolic = lua_toboolean(L, 3); -+ STAT_STRUCT oldpathinfo; -+ int is_dir = 0; -+ if (STAT_FUNC(oldpath, &oldpathinfo) == 0) { -+ is_dir = S_ISDIR(oldpathinfo.st_mode) != 0; -+ } -+ if (!symbolic && is_dir) { -+ lua_pushnil(L); -+ lua_pushstring(L, -+ "hard links to directories are not supported on Windows"); -+ return 2; -+ } -+ -+ int result = symbolic ? CreateSymbolicLink(newpath, oldpath, is_dir) -+ : CreateHardLink(newpath, oldpath, NULL); -+ -+ if (result) { -+ return pushresult(L, result, NULL); -+ } else { -+ lua_pushnil(L); -+ lua_pushstring(L, symbolic ? "make_link CreateSymbolicLink() failed" -+ : "make_link CreateHardLink() failed"); -+ return 2; -+ } - #endif - } - -@@ -440,22 +603,10 @@ static int make_link(lua_State *L) - ** Creates a directory. - ** @param #1 Directory path. - */ --static int make_dir (lua_State *L) { -- const char *path = luaL_checkstring (L, 1); -- int fail; --#ifdef _WIN32 -- fail = _mkdir (path); --#else -- fail = mkdir (path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | -- S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH ); --#endif -- if (fail) { -- lua_pushnil (L); -- lua_pushfstring (L, "%s", strerror(errno)); -- return 2; -- } -- lua_pushboolean (L, 1); -- return 1; -+static int make_dir(lua_State * L) -+{ -+ const char *path = luaL_checkstring(L, 1); -+ return pushresult(L, lfs_mkdir(path), NULL); - } - - -@@ -463,65 +614,57 @@ static int make_dir (lua_State *L) { - ** Removes a directory. - ** @param #1 Directory path. - */ --static int remove_dir (lua_State *L) { -- const char *path = luaL_checkstring (L, 1); -- int fail; -- -- fail = rmdir (path); -- -- if (fail) { -- lua_pushnil (L); -- lua_pushfstring (L, "%s", strerror(errno)); -- return 2; -- } -- lua_pushboolean (L, 1); -- return 1; -+static int remove_dir(lua_State * L) -+{ -+ const char *path = luaL_checkstring(L, 1); -+ return pushresult(L, rmdir(path), NULL); - } - - - /* - ** Directory iterator - */ --static int dir_iter (lua_State *L) { -+static int dir_iter(lua_State * L) -+{ - #ifdef _WIN32 -- struct _finddata_t c_file; -+ struct _finddata_t c_file; - #else -- struct dirent *entry; -+ struct dirent *entry; - #endif -- dir_data *d = (dir_data *)luaL_checkudata (L, 1, DIR_METATABLE); -- luaL_argcheck (L, d->closed == 0, 1, "closed directory"); -+ dir_data *d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE); -+ luaL_argcheck(L, d->closed == 0, 1, "closed directory"); - #ifdef _WIN32 -- if (d->hFile == 0L) { /* first entry */ -- if ((d->hFile = _findfirst (d->pattern, &c_file)) == -1L) { -- lua_pushnil (L); -- lua_pushstring (L, strerror (errno)); -- d->closed = 1; -- return 2; -- } else { -- lua_pushstring (L, c_file.name); -- return 1; -- } -- } else { /* next entry */ -- if (_findnext (d->hFile, &c_file) == -1L) { -- /* no more entries => close directory */ -- _findclose (d->hFile); -- d->closed = 1; -- return 0; -- } else { -- lua_pushstring (L, c_file.name); -- return 1; -- } -- } -+ if (d->hFile == 0L) { /* first entry */ -+ if ((d->hFile = _findfirst(d->pattern, &c_file)) == -1L) { -+ lua_pushnil(L); -+ lua_pushstring(L, strerror(errno)); -+ d->closed = 1; -+ return 2; -+ } else { -+ lua_pushstring(L, c_file.name); -+ return 1; -+ } -+ } else { /* next entry */ -+ if (_findnext(d->hFile, &c_file) == -1L) { -+ /* no more entries => close directory */ -+ _findclose(d->hFile); -+ d->closed = 1; -+ return 0; -+ } else { -+ lua_pushstring(L, c_file.name); -+ return 1; -+ } -+ } - #else -- if ((entry = readdir (d->dir)) != NULL) { -- lua_pushstring (L, entry->d_name); -- return 1; -- } else { -- /* no more entries => close directory */ -- closedir (d->dir); -- d->closed = 1; -- return 0; -- } -+ if ((entry = readdir(d->dir)) != NULL) { -+ lua_pushstring(L, entry->d_name); -+ return 1; -+ } else { -+ /* no more entries => close directory */ -+ closedir(d->dir); -+ d->closed = 1; -+ return 0; -+ } - #endif - } - -@@ -529,329 +672,375 @@ static int dir_iter (lua_State *L) { - /* - ** Closes directory iterators - */ --static int dir_close (lua_State *L) { -- dir_data *d = (dir_data *)lua_touserdata (L, 1); -+static int dir_close(lua_State * L) -+{ -+ dir_data *d = (dir_data *) lua_touserdata(L, 1); - #ifdef _WIN32 -- if (!d->closed && d->hFile) { -- _findclose (d->hFile); -- } -+ if (!d->closed && d->hFile) { -+ _findclose(d->hFile); -+ } - #else -- if (!d->closed && d->dir) { -- closedir (d->dir); -- } -+ if (!d->closed && d->dir) { -+ closedir(d->dir); -+ } - #endif -- d->closed = 1; -- return 0; -+ d->closed = 1; -+ return 0; - } - - - /* - ** Factory of directory iterators - */ --static int dir_iter_factory (lua_State *L) { -- const char *path = luaL_checkstring (L, 1); -- dir_data *d; -- lua_pushcfunction (L, dir_iter); -- d = (dir_data *) lua_newuserdata (L, sizeof(dir_data)); -- luaL_getmetatable (L, DIR_METATABLE); -- lua_setmetatable (L, -2); -- d->closed = 0; -+static int dir_iter_factory(lua_State * L) -+{ -+ const char *path = luaL_checkstring(L, 1); -+ dir_data *d; -+ lua_pushcfunction(L, dir_iter); -+ d = (dir_data *) lua_newuserdata(L, sizeof(dir_data)); -+ luaL_getmetatable(L, DIR_METATABLE); -+ lua_setmetatable(L, -2); -+ d->closed = 0; - #ifdef _WIN32 -- d->hFile = 0L; -- if (strlen(path) > MAX_PATH-2) -- luaL_error (L, "path too long: %s", path); -- else -- sprintf (d->pattern, "%s/*", path); -+ d->hFile = 0L; -+ if (strlen(path) > MAX_PATH - 2) -+ luaL_error(L, "path too long: %s", path); -+ else -+ sprintf(d->pattern, "%s/*", path); - #else -- d->dir = opendir (path); -- if (d->dir == NULL) -- luaL_error (L, "cannot open %s: %s", path, strerror (errno)); -+ d->dir = opendir(path); -+ if (d->dir == NULL) -+ luaL_error(L, "cannot open %s: %s", path, strerror(errno)); -+#endif -+#if LUA_VERSION_NUM >= 504 -+ lua_pushnil(L); -+ lua_pushvalue(L, -2); -+ return 4; -+#else -+ return 2; - #endif -- return 2; - } - - - /* - ** Creates directory metatable. - */ --static int dir_create_meta (lua_State *L) { -- luaL_newmetatable (L, DIR_METATABLE); -- -- /* Method table */ -- lua_newtable(L); -- lua_pushcfunction (L, dir_iter); -- lua_setfield(L, -2, "next"); -- lua_pushcfunction (L, dir_close); -- lua_setfield(L, -2, "close"); -- -- /* Metamethods */ -- lua_setfield(L, -2, "__index"); -- lua_pushcfunction (L, dir_close); -- lua_setfield (L, -2, "__gc"); -- return 1; -+static int dir_create_meta(lua_State * L) -+{ -+ luaL_newmetatable(L, DIR_METATABLE); -+ -+ /* Method table */ -+ lua_newtable(L); -+ lua_pushcfunction(L, dir_iter); -+ lua_setfield(L, -2, "next"); -+ lua_pushcfunction(L, dir_close); -+ lua_setfield(L, -2, "close"); -+ -+ /* Metamethods */ -+ lua_setfield(L, -2, "__index"); -+ lua_pushcfunction(L, dir_close); -+ lua_setfield(L, -2, "__gc"); -+ -+#if LUA_VERSION_NUM >= 504 -+ lua_pushcfunction(L, dir_close); -+ lua_setfield(L, -2, "__close"); -+#endif -+ return 1; - } - - - /* - ** Creates lock metatable. - */ --static int lock_create_meta (lua_State *L) { -- luaL_newmetatable (L, LOCK_METATABLE); -- -- /* Method table */ -- lua_newtable(L); -- lua_pushcfunction(L, lfs_unlock_dir); -- lua_setfield(L, -2, "free"); -- -- /* Metamethods */ -- lua_setfield(L, -2, "__index"); -- lua_pushcfunction(L, lfs_unlock_dir); -- lua_setfield(L, -2, "__gc"); -- return 1; -+static int lock_create_meta(lua_State * L) -+{ -+ luaL_newmetatable(L, LOCK_METATABLE); -+ -+ /* Method table */ -+ lua_newtable(L); -+ lua_pushcfunction(L, lfs_unlock_dir); -+ lua_setfield(L, -2, "free"); -+ -+ /* Metamethods */ -+ lua_setfield(L, -2, "__index"); -+ lua_pushcfunction(L, lfs_unlock_dir); -+ lua_setfield(L, -2, "__gc"); -+ return 1; - } - - --#ifdef _WIN32 -- #ifndef S_ISDIR -- #define S_ISDIR(mode) (mode&_S_IFDIR) -- #endif -- #ifndef S_ISREG -- #define S_ISREG(mode) (mode&_S_IFREG) -- #endif -- #ifndef S_ISLNK -- #define S_ISLNK(mode) (0) -- #endif -- #ifndef S_ISSOCK -- #define S_ISSOCK(mode) (0) -- #endif -- #ifndef S_ISFIFO -- #define S_ISFIFO(mode) (0) -- #endif -- #ifndef S_ISCHR -- #define S_ISCHR(mode) (mode&_S_IFCHR) -- #endif -- #ifndef S_ISBLK -- #define S_ISBLK(mode) (0) -- #endif --#endif - /* - ** Convert the inode protection mode to a string. - */ - #ifdef _WIN32 --static const char *mode2string (unsigned short mode) { -+static const char *mode2string(unsigned short mode) -+{ - #else --static const char *mode2string (mode_t mode) { -+static const char *mode2string(mode_t mode) -+{ - #endif -- if ( S_ISREG(mode) ) -+ if (S_ISREG(mode)) - return "file"; -- else if ( S_ISDIR(mode) ) -+ else if (S_ISDIR(mode)) - return "directory"; -- else if ( S_ISLNK(mode) ) -- return "link"; -- else if ( S_ISSOCK(mode) ) -+ else if (S_ISLNK(mode)) -+ return "link"; -+ else if (S_ISSOCK(mode)) - return "socket"; -- else if ( S_ISFIFO(mode) ) -- return "named pipe"; -- else if ( S_ISCHR(mode) ) -- return "char device"; -- else if ( S_ISBLK(mode) ) -- return "block device"; -+ else if (S_ISFIFO(mode)) -+ return "named pipe"; -+ else if (S_ISCHR(mode)) -+ return "char device"; -+ else if (S_ISBLK(mode)) -+ return "block device"; - else -- return "other"; -+ return "other"; - } - - - /* --** Set access time and modification values for file -+** Set access time and modification values for a file. -+** @param #1 File path. -+** @param #2 Access time in seconds, current time is used if missing. -+** @param #3 Modification time in seconds, access time is used if missing. - */ --static int file_utime (lua_State *L) { -- const char *file = luaL_checkstring (L, 1); -- struct utimbuf utb, *buf; -- -- if (lua_gettop (L) == 1) /* set to current date/time */ -- buf = NULL; -- else { -- utb.actime = (time_t)luaL_optnumber (L, 2, 0); -- utb.modtime = (time_t) luaL_optinteger (L, 3, utb.actime); -- buf = &utb; -- } -- if (utime (file, buf)) { -- lua_pushnil (L); -- lua_pushfstring (L, "%s", strerror (errno)); -- return 2; -- } -- lua_pushboolean (L, 1); -- return 1; -+static int file_utime(lua_State * L) -+{ -+ const char *file = luaL_checkstring(L, 1); -+ struct utimbuf utb, *buf; -+ -+ if (lua_gettop(L) == 1) /* set to current date/time */ -+ buf = NULL; -+ else { -+ utb.actime = (time_t) luaL_optnumber(L, 2, 0); -+ utb.modtime = (time_t) luaL_optinteger(L, 3, utb.actime); -+ buf = &utb; -+ } -+ -+ return pushresult(L, utime(file, buf), NULL); - } - - - /* inode protection mode */ --static void push_st_mode (lua_State *L, STAT_STRUCT *info) { -- lua_pushstring (L, mode2string (info->st_mode)); -+static void push_st_mode(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushstring(L, mode2string(info->st_mode)); - } -+ - /* device inode resides on */ --static void push_st_dev (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_dev); -+static void push_st_dev(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_dev); - } -+ - /* inode's number */ --static void push_st_ino (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_ino); -+static void push_st_ino(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_ino); - } -+ - /* number of hard links to the file */ --static void push_st_nlink (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_nlink); -+static void push_st_nlink(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_nlink); - } -+ - /* user-id of owner */ --static void push_st_uid (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_uid); -+static void push_st_uid(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_uid); - } -+ - /* group-id of owner */ --static void push_st_gid (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_gid); -+static void push_st_gid(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_gid); - } -+ - /* device type, for special file inode */ --static void push_st_rdev (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_rdev); -+static void push_st_rdev(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_rdev); - } -+ - /* time of last access */ --static void push_st_atime (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_atime); -+static void push_st_atime(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_atime); - } -+ - /* time of last data modification */ --static void push_st_mtime (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_mtime); -+static void push_st_mtime(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_mtime); - } -+ - /* time of last file status change */ --static void push_st_ctime (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer) info->st_ctime); -+static void push_st_ctime(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_ctime); - } -+ - /* file size, in bytes */ --static void push_st_size (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_size); -+static void push_st_size(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_size); - } -+ - #ifndef _WIN32 - /* blocks allocated for file */ --static void push_st_blocks (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_blocks); -+static void push_st_blocks(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_blocks); - } -+ - /* optimal file system I/O blocksize */ --static void push_st_blksize (lua_State *L, STAT_STRUCT *info) { -- lua_pushinteger (L, (lua_Integer)info->st_blksize); -+static void push_st_blksize(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushinteger(L, (lua_Integer) info->st_blksize); - } - #endif - - /* --** Convert the inode protection mode to a permission list. --*/ -+ ** Convert the inode protection mode to a permission list. -+ */ - - #ifdef _WIN32 --static const char *perm2string (unsigned short mode) { -+static const char *perm2string(unsigned short mode) -+{ - static char perms[10] = "---------"; - int i; -- for (i=0;i<9;i++) perms[i]='-'; -- if (mode & _S_IREAD) -- { perms[0] = 'r'; perms[3] = 'r'; perms[6] = 'r'; } -- if (mode & _S_IWRITE) -- { perms[1] = 'w'; perms[4] = 'w'; perms[7] = 'w'; } -- if (mode & _S_IEXEC) -- { perms[2] = 'x'; perms[5] = 'x'; perms[8] = 'x'; } -+ for (i = 0; i < 9; i++) -+ perms[i] = '-'; -+ if (mode & _S_IREAD) { -+ perms[0] = 'r'; -+ perms[3] = 'r'; -+ perms[6] = 'r'; -+ } -+ if (mode & _S_IWRITE) { -+ perms[1] = 'w'; -+ perms[4] = 'w'; -+ perms[7] = 'w'; -+ } -+ if (mode & _S_IEXEC) { -+ perms[2] = 'x'; -+ perms[5] = 'x'; -+ perms[8] = 'x'; -+ } - return perms; - } - #else --static const char *perm2string (mode_t mode) { -+static const char *perm2string(mode_t mode) -+{ - static char perms[10] = "---------"; - int i; -- for (i=0;i<9;i++) perms[i]='-'; -- if (mode & S_IRUSR) perms[0] = 'r'; -- if (mode & S_IWUSR) perms[1] = 'w'; -- if (mode & S_IXUSR) perms[2] = 'x'; -- if (mode & S_IRGRP) perms[3] = 'r'; -- if (mode & S_IWGRP) perms[4] = 'w'; -- if (mode & S_IXGRP) perms[5] = 'x'; -- if (mode & S_IROTH) perms[6] = 'r'; -- if (mode & S_IWOTH) perms[7] = 'w'; -- if (mode & S_IXOTH) perms[8] = 'x'; -+ for (i = 0; i < 9; i++) -+ perms[i] = '-'; -+ if (mode & S_IRUSR) -+ perms[0] = 'r'; -+ if (mode & S_IWUSR) -+ perms[1] = 'w'; -+ if (mode & S_IXUSR) -+ perms[2] = 'x'; -+ if (mode & S_IRGRP) -+ perms[3] = 'r'; -+ if (mode & S_IWGRP) -+ perms[4] = 'w'; -+ if (mode & S_IXGRP) -+ perms[5] = 'x'; -+ if (mode & S_IROTH) -+ perms[6] = 'r'; -+ if (mode & S_IWOTH) -+ perms[7] = 'w'; -+ if (mode & S_IXOTH) -+ perms[8] = 'x'; - return perms; - } - #endif - - /* permssions string */ --static void push_st_perm (lua_State *L, STAT_STRUCT *info) { -- lua_pushstring (L, perm2string (info->st_mode)); -+static void push_st_perm(lua_State * L, STAT_STRUCT * info) -+{ -+ lua_pushstring(L, perm2string(info->st_mode)); - } - --typedef void (*_push_function) (lua_State *L, STAT_STRUCT *info); -+typedef void (*_push_function)(lua_State * L, STAT_STRUCT * info); - - struct _stat_members { -- const char *name; -- _push_function push; -+ const char *name; -+ _push_function push; - }; - - struct _stat_members members[] = { -- { "mode", push_st_mode }, -- { "dev", push_st_dev }, -- { "ino", push_st_ino }, -- { "nlink", push_st_nlink }, -- { "uid", push_st_uid }, -- { "gid", push_st_gid }, -- { "rdev", push_st_rdev }, -- { "access", push_st_atime }, -- { "modification", push_st_mtime }, -- { "change", push_st_ctime }, -- { "size", push_st_size }, -- { "permissions", push_st_perm }, -+ { "mode", push_st_mode }, -+ { "dev", push_st_dev }, -+ { "ino", push_st_ino }, -+ { "nlink", push_st_nlink }, -+ { "uid", push_st_uid }, -+ { "gid", push_st_gid }, -+ { "rdev", push_st_rdev }, -+ { "access", push_st_atime }, -+ { "modification", push_st_mtime }, -+ { "change", push_st_ctime }, -+ { "size", push_st_size }, -+ { "permissions", push_st_perm }, - #ifndef _WIN32 -- { "blocks", push_st_blocks }, -- { "blksize", push_st_blksize }, -+ { "blocks", push_st_blocks }, -+ { "blksize", push_st_blksize }, - #endif -- { NULL, NULL } -+ { NULL, NULL } - }; - - /* - ** Get file or symbolic link information - */ --static int _file_info_ (lua_State *L, int (*st)(const char*, STAT_STRUCT*)) { -- STAT_STRUCT info; -- const char *file = luaL_checkstring (L, 1); -- int i; -- -- if (st(file, &info)) { -- lua_pushnil(L); -- lua_pushfstring(L, "cannot obtain information from file '%s': %s", file, strerror(errno)); -- return 2; -- } -- if (lua_isstring (L, 2)) { -- const char *member = lua_tostring (L, 2); -- for (i = 0; members[i].name; i++) { -- if (strcmp(members[i].name, member) == 0) { -- /* push member value and return */ -- members[i].push (L, &info); -- return 1; -- } -- } -- /* member not found */ -- return luaL_error(L, "invalid attribute name '%s'", member); -- } -- /* creates a table if none is given, removes extra arguments */ -- lua_settop(L, 2); -- if (!lua_istable (L, 2)) { -- lua_newtable (L); -- } -- /* stores all members in table on top of the stack */ -- for (i = 0; members[i].name; i++) { -- lua_pushstring (L, members[i].name); -- members[i].push (L, &info); -- lua_rawset (L, -3); -- } -+static int _file_info_(lua_State * L, -+ int (*st)(const char *, STAT_STRUCT *)) -+{ -+ STAT_STRUCT info; -+ const char *file = luaL_checkstring(L, 1); -+ int i; -+ -+ if (st(file, &info)) { -+ lua_pushnil(L); -+ lua_pushfstring(L, "cannot obtain information from file '%s': %s", -+ file, strerror(errno)); -+ lua_pushinteger(L, errno); -+ return 3; -+ } -+ if (lua_isstring(L, 2)) { -+ const char *member = lua_tostring(L, 2); -+ for (i = 0; members[i].name; i++) { -+ if (strcmp(members[i].name, member) == 0) { -+ /* push member value and return */ -+ members[i].push(L, &info); - return 1; -+ } -+ } -+ /* member not found */ -+ return luaL_error(L, "invalid attribute name '%s'", member); -+ } -+ /* creates a table if none is given, removes extra arguments */ -+ lua_settop(L, 2); -+ if (!lua_istable(L, 2)) { -+ lua_newtable(L); -+ } -+ /* stores all members in table on top of the stack */ -+ for (i = 0; members[i].name; i++) { -+ lua_pushstring(L, members[i].name); -+ members[i].push(L, &info); -+ lua_rawset(L, -3); -+ } -+ return 1; - } - - - /* - ** Get file information using stat. - */ --static int file_info (lua_State *L) { -- return _file_info_ (L, STAT_FUNC); -+static int file_info(lua_State * L) -+{ -+ return _file_info_(L, STAT_FUNC); - } - - -@@ -861,62 +1050,88 @@ static int file_info (lua_State *L) { - ** Returns 1 if successful (with the target on top of the stack), - ** 0 on failure (with stack unchanged, and errno set). - */ --static int push_link_target(lua_State *L) { -+static int push_link_target(lua_State * L) -+{ -+ const char *file = luaL_checkstring(L, 1); -+#ifdef _WIN32 -+ HANDLE h = CreateFile(file, GENERIC_READ, -+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, -+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); -+ if (h == INVALID_HANDLE_VALUE) { -+ return lfs_win32_pusherror(L); -+ } -+#endif -+ char *target = NULL; -+ int tsize = 0, size = 256; /* size = initial buffer capacity */ -+ int ok = 0; -+ while (!ok) { -+ char *target2 = (char *)realloc(target, size); -+ if (!target2) { /* failed to allocate */ -+ break; -+ } -+ target = target2; - #ifdef _WIN32 -- errno = ENOSYS; -- return 0; -+ tsize = (int)GetFinalPathNameByHandle(h, target, size, FILE_NAME_OPENED); - #else -- const char *file = luaL_checkstring(L, 1); -- char *target = NULL; -- int tsize, size = 256; /* size = initial buffer capacity */ -- while (1) { -- target = realloc(target, size); -- if (!target) /* failed to allocate */ -- return 0; -- tsize = readlink(file, target, size); -- if (tsize < 0) { /* a readlink() error occurred */ -- free(target); -- return 0; -- } -- if (tsize < size) -- break; -- /* possibly truncated readlink() result, double size and retry */ -- size *= 2; -- } -- target[tsize] = '\0'; -- lua_pushlstring(L, target, tsize); -- free(target); -- return 1; -+ tsize = (int)readlink(file, target, size); -+#endif -+ if (tsize < 0) { /* a readlink() error occurred */ -+ break; -+ } -+ if (tsize < size) { -+#ifdef _WIN32 -+ if (tsize > 4 && strncmp(target, "\\\\?\\", 4) == 0) { -+ memmove(target, target + 4, tsize - 3); -+ tsize -= 4; -+ } - #endif -+ ok = 1; -+ break; -+ } -+ /* possibly truncated readlink() result, double size and retry */ -+ size *= 2; -+ } -+ if (ok) { -+ target[tsize] = '\0'; -+ lua_pushlstring(L, target, tsize); -+ } -+#ifdef _WIN32 -+ CloseHandle(h); -+#endif -+ free(target); -+ return ok; - } - - /* - ** Get symbolic link information using lstat. - */ --static int link_info (lua_State *L) { -- int ret; -- if (lua_isstring (L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { -- int ok = push_link_target(L); -- return ok ? 1 : pusherror(L, "could not obtain link target"); -- } -- ret = _file_info_ (L, LSTAT_FUNC); -- if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { -- int ok = push_link_target(L); -- if (ok) { -- lua_setfield(L, -2, "target"); -- } -- } -- return ret; -+static int link_info(lua_State * L) -+{ -+ int ret; -+ if (lua_isstring(L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { -+ int ok = push_link_target(L); -+ return ok ? 1 : pusherror(L, "could not obtain link target"); -+ } -+ ret = _file_info_(L, LSTAT_FUNC); -+ if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { -+ int ok = push_link_target(L); -+ if (ok) { -+ lua_setfield(L, -2, "target"); -+ } -+ } -+ return ret; - } - - - /* - ** Assumes the table is on top of the stack. - */ --static void set_info (lua_State *L) { -- lua_pushliteral(L, "Copyright (C) 2003-2016 Kepler Project"); -- lua_setfield(L, -2, "_COPYRIGHT"); -- lua_pushliteral(L, "LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution"); -+static void set_info(lua_State * L) -+{ -+ lua_pushliteral(L, -+ "LuaFileSystem is a Lua library developed to complement " -+ "the set of functions related to file systems offered by " -+ "the standard Lua distribution"); - lua_setfield(L, -2, "_DESCRIPTION"); - lua_pushliteral(L, "LuaFileSystem " LFS_VERSION); - lua_setfield(L, -2, "_VERSION"); -@@ -924,28 +1139,29 @@ static void set_info (lua_State *L) { - - - static const struct luaL_Reg fslib[] = { -- {"attributes", file_info}, -- {"chdir", change_dir}, -- {"currentdir", get_dir}, -- {"dir", dir_iter_factory}, -- {"link", make_link}, -- {"lock", file_lock}, -- {"mkdir", make_dir}, -- {"rmdir", remove_dir}, -- {"symlinkattributes", link_info}, -- {"setmode", lfs_f_setmode}, -- {"touch", file_utime}, -- {"unlock", file_unlock}, -- {"lock_dir", lfs_lock_dir}, -- {NULL, NULL}, -+ { "attributes", file_info }, -+ { "chdir", change_dir }, -+ { "currentdir", get_dir }, -+ { "dir", dir_iter_factory }, -+ { "link", make_link }, -+ { "lock", file_lock }, -+ { "mkdir", make_dir }, -+ { "rmdir", remove_dir }, -+ { "symlinkattributes", link_info }, -+ { "setmode", lfs_f_setmode }, -+ { "touch", file_utime }, -+ { "unlock", file_unlock }, -+ { "lock_dir", lfs_lock_dir }, -+ { NULL, NULL }, - }; - --LFS_EXPORT int luaopen_lfs (lua_State *L) { -- dir_create_meta (L); -- lock_create_meta (L); -- luaL_newlib (L, fslib); -- lua_pushvalue(L, -1); -- lua_setglobal(L, LFS_LIBNAME); -- set_info (L); -- return 1; -+LFS_EXPORT int luaopen_lfs(lua_State * L) -+{ -+ dir_create_meta(L); -+ lock_create_meta(L); -+ new_lib(L, fslib); -+ lua_pushvalue(L, -1); -+ lua_setglobal(L, LFS_LIBNAME); -+ set_info(L); -+ return 1; - } -diff --git a/extra/luafilesystem/src/lfs.def b/extra/luafilesystem/src/lfs.def -index 8a36d41..bd8c847 100644 ---- a/extra/luafilesystem/src/lfs.def -+++ b/extra/luafilesystem/src/lfs.def -@@ -1,4 +1,4 @@ - LIBRARY lfs.dll --VERSION 1.6 -+VERSION 1.8 - EXPORTS - luaopen_lfs -diff --git a/extra/luafilesystem/src/lfs.h b/extra/luafilesystem/src/lfs.h -index 7f7d2ab..116b892 100644 ---- a/extra/luafilesystem/src/lfs.h -+++ b/extra/luafilesystem/src/lfs.h -@@ -1,33 +1,37 @@ - /* - ** LuaFileSystem --** Copyright Kepler Project 2003 - 2016 (http://keplerproject.github.io/luafilesystem) -+** File system manipulation library -+** -+** Copyright (C) 2003-2010 Kepler Project. -+** Copyright (C) 2010-2022 The LuaFileSystem authors. -+** (http://lunarmodules.github.io/luafilesystem) - */ - - /* Define 'chdir' for systems that do not implement it */ - #ifdef NO_CHDIR -- #define chdir(p) (-1) -- #define chdir_error "Function 'chdir' not provided by system" -+#define chdir(p) (-1) -+#define chdir_error "Function 'chdir' not provided by system" - #else -- #define chdir_error strerror(errno) -+#define chdir_error strerror(errno) - #endif - - #ifdef _WIN32 -- #define chdir(p) (_chdir(p)) -- #define getcwd(d, s) (_getcwd(d, s)) -- #define rmdir(p) (_rmdir(p)) -- #define LFS_EXPORT __declspec (dllexport) -- #ifndef fileno -- #define fileno(f) (_fileno(f)) -- #endif -+#define chdir(p) (_chdir(p)) -+#define getcwd(d, s) (_getcwd(d, s)) -+#define rmdir(p) (_rmdir(p)) -+#define LFS_EXPORT __declspec (dllexport) -+#ifndef fileno -+#define fileno(f) (_fileno(f)) -+#endif - #else -- #define LFS_EXPORT -+#define LFS_EXPORT - #endif - - #ifdef __cplusplus - extern "C" { - #endif - --LFS_EXPORT int luaopen_lfs (lua_State *L); -+ LFS_EXPORT int luaopen_lfs(lua_State * L); - - #ifdef __cplusplus - } -diff --git a/extra/luafilesystem/tests/test.lua b/extra/luafilesystem/tests/test.lua -index 10810fe..ed154c0 100644 ---- a/extra/luafilesystem/tests/test.lua -+++ b/extra/luafilesystem/tests/test.lua -@@ -4,6 +4,8 @@ local tmp = "/tmp" - local sep = string.match (package.config, "[^\n]+") - local upper = ".." - -+local is_unix = package.config:sub(1,1) == "/" -+ - local lfs = require"lfs" - print (lfs._VERSION) - -@@ -60,6 +62,8 @@ if not attrib then - error ("could not get attributes of file `"..tmpdir.."':\n"..errmsg) - end - local f = io.open(tmpfile, "w") -+local data = "hello, file!" -+f:write(data) - f:close() - - io.write(".") -@@ -87,14 +91,42 @@ assert (new_att.modification == testdate1, "could not set modification time") - io.write(".") - io.flush() - ---- Checking link (does not work on Windows) - if lfs.link (tmpfile, "_a_link_for_test_", true) then - assert (lfs.attributes"_a_link_for_test_".mode == "file") - assert (lfs.symlinkattributes"_a_link_for_test_".mode == "link") - assert (lfs.symlinkattributes"_a_link_for_test_".target == tmpfile) - assert (lfs.symlinkattributes("_a_link_for_test_", "target") == tmpfile) -+ -+ assert (lfs.symlinkattributes(tmpfile).mode == "file") -+ - assert (lfs.link (tmpfile, "_a_hard_link_for_test_")) -- assert (lfs.attributes (tmpfile, "nlink") == 2) -+ assert (lfs.symlinkattributes"_a_hard_link_for_test_".mode == "file") -+ -+ local fd = io.open(tmpfile) -+ assert(fd:read("*a") == data) -+ fd:close() -+ -+ fd = io.open("_a_link_for_test_") -+ assert(fd:read("*a") == data) -+ fd:close() -+ -+ fd = io.open("_a_hard_link_for_test_") -+ assert(fd:read("*a") == data) -+ fd:close() -+ -+ fd = io.open("_a_hard_link_for_test_", "w+") -+ local data2 = "write in hard link" -+ fd:write(data2) -+ fd:close() -+ -+ fd = io.open(tmpfile) -+ assert(fd:read("*a") == data2) -+ fd:close() -+ -+ if is_unix then -+ assert (lfs.attributes (tmpfile, "nlink") == 2) -+ end -+ - assert (os.remove"_a_link_for_test_") - assert (os.remove"_a_hard_link_for_test_") - end -@@ -152,7 +184,10 @@ io.write(".") - io.flush() - - -- Trying to get attributes of a non-existent file --assert (lfs.attributes ("this couldn't be an actual file") == nil, "could get attributes of a non-existent file") -+local attr_ok, err, errno = lfs.attributes("this couldn't be an actual file") -+assert(attr_ok == nil, "could get attributes of a non-existent file") -+assert(type(err) == "string", "failed lfs.attributes did not return an error message") -+assert(type(errno) == "number", "failed lfs.attributes did not return error code") - assert (type(lfs.attributes (upper)) == "table", "couldn't get attributes of upper directory") - - io.write(".") -diff --git a/extra/luafilesystem/vc6/lfs.def b/extra/luafilesystem/vc6/lfs.def -deleted file mode 100644 -index 55ec688..0000000 ---- a/extra/luafilesystem/vc6/lfs.def -+++ /dev/null -@@ -1,5 +0,0 @@ --LIBRARY lfs.dll --DESCRIPTION "LuaFileSystem" --VERSION 1.2 --EXPORTS --luaopen_lfs -diff --git a/extra/luafilesystem/vc6/luafilesystem.dsw b/extra/luafilesystem/vc6/luafilesystem.dsw -deleted file mode 100644 -index b4bb4b3..0000000 ---- a/extra/luafilesystem/vc6/luafilesystem.dsw -+++ /dev/null -@@ -1,33 +0,0 @@ --Microsoft Developer Studio Workspace File, Format Version 6.00 --# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! -- --############################################################################### -- --Project: "luafilesystem_dll"=.\luafilesystem_dll.dsp - Package Owner=<4> -- --Package=<5> --{{{ -- begin source code control -- luafilesystem -- .. -- end source code control --}}} -- --Package=<4> --{{{ --}}} -- --############################################################################### -- --Global: -- --Package=<5> --{{{ --}}} -- --Package=<3> --{{{ --}}} -- --############################################################################### -- -diff --git a/extra/luafilesystem/vc6/luafilesystem_dll.dsp b/extra/luafilesystem/vc6/luafilesystem_dll.dsp -deleted file mode 100644 -index efe6c72..0000000 ---- a/extra/luafilesystem/vc6/luafilesystem_dll.dsp -+++ /dev/null -@@ -1,127 +0,0 @@ --# Microsoft Developer Studio Project File - Name="luafilesystem_dll" - Package Owner=<4> --# Microsoft Developer Studio Generated Build File, Format Version 6.00 --# ** DO NOT EDIT ** -- --# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 -- --CFG=luafilesystem_dll - Win32 Debug --!MESSAGE This is not a valid makefile. To build this project using NMAKE, --!MESSAGE use the Export Makefile command and run --!MESSAGE --!MESSAGE NMAKE /f "luafilesystem_dll.mak". --!MESSAGE --!MESSAGE You can specify a configuration when running NMAKE --!MESSAGE by defining the macro CFG on the command line. For example: --!MESSAGE --!MESSAGE NMAKE /f "luafilesystem_dll.mak" CFG="luafilesystem_dll - Win32 Debug" --!MESSAGE --!MESSAGE Possible choices for configuration are: --!MESSAGE --!MESSAGE "luafilesystem_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") --!MESSAGE "luafilesystem_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") --!MESSAGE -- --# Begin Project --# PROP AllowPerConfigDependencies 0 --# PROP Scc_ProjName "luafilesystem_dll" --# PROP Scc_LocalPath ".." --CPP=cl.exe --MTL=midl.exe --RSC=rc.exe -- --!IF "$(CFG)" == "luafilesystem_dll - Win32 Release" -- --# PROP BASE Use_MFC 0 --# PROP BASE Use_Debug_Libraries 0 --# PROP BASE Output_Dir "Release" --# PROP BASE Intermediate_Dir "Release" --# PROP BASE Target_Dir "" --# PROP Use_MFC 0 --# PROP Use_Debug_Libraries 0 --# PROP Output_Dir "../lib/vc6" --# PROP Intermediate_Dir "luafilesystem_dll/Release" --# PROP Ignore_Export_Lib 0 --# PROP Target_Dir "" --# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /c --# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../external-src/lua50/include" /I "../../compat/src" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /c --# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 --# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 --# ADD BASE RSC /l 0x416 /d "NDEBUG" --# ADD RSC /l 0x416 /d "NDEBUG" --BSC32=bscmake.exe --# ADD BASE BSC32 /nologo --# ADD BSC32 /nologo --LINK32=link.exe --# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 --# ADD LINK32 lua50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"../bin/vc6/lfs.dll" /libpath:"../../external-src/lua50/lib/dll" --# Begin Special Build Tool --SOURCE="$(InputPath)" --PostBuild_Cmds=cd ../bin/vc6 zip.exe luafilesystem-1.2-win32.zip lfs.dll --# End Special Build Tool -- --!ELSEIF "$(CFG)" == "luafilesystem_dll - Win32 Debug" -- --# PROP BASE Use_MFC 0 --# PROP BASE Use_Debug_Libraries 1 --# PROP BASE Output_Dir "Debug" --# PROP BASE Intermediate_Dir "Debug" --# PROP BASE Target_Dir "" --# PROP Use_MFC 0 --# PROP Use_Debug_Libraries 1 --# PROP Output_Dir "../lib/vc6" --# PROP Intermediate_Dir "luafilesystem_dll/Debug" --# PROP Ignore_Export_Lib 0 --# PROP Target_Dir "" --# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /GZ /c --# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../external-src/lua50/include" /I "../../compat/src" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LUAFILESYSTEM_EXPORTS" /YX /FD /GZ /c --# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 --# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 --# ADD BASE RSC /l 0x416 /d "_DEBUG" --# ADD RSC /l 0x416 /d "_DEBUG" --BSC32=bscmake.exe --# ADD BASE BSC32 /nologo --# ADD BSC32 /nologo --LINK32=link.exe --# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept --# ADD LINK32 lua50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"../bin/vc6/lfsd.dll" /pdbtype:sept /libpath:"../../external-src/lua50/lib/dll" -- --!ENDIF -- --# Begin Target -- --# Name "luafilesystem_dll - Win32 Release" --# Name "luafilesystem_dll - Win32 Debug" --# Begin Group "Source Files" -- --# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" --# Begin Source File -- --SOURCE="..\..\compat\src\compat-5.1.c" --# End Source File --# Begin Source File -- --SOURCE=..\src\lfs.c --# End Source File --# Begin Source File -- --SOURCE=.\lfs.def --# End Source File --# End Group --# Begin Group "Header Files" -- --# PROP Default_Filter "h;hpp;hxx;hm;inl" --# Begin Source File -- --SOURCE="..\..\compat\src\compat-5.1.h" --# End Source File --# Begin Source File -- --SOURCE=..\src\lfs.h --# End Source File --# End Group --# Begin Group "Resource Files" -- --# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" --# End Group --# End Target --# End Project -Submodule extra/moses ded0a9b..6fe8d76: -diff --git a/extra/moses/.gitignore b/extra/moses/.gitignore -index 6f7a8b1..a92455e 100644 ---- a/extra/moses/.gitignore -+++ b/extra/moses/.gitignore -@@ -1,10 +1,10 @@ - *.bat - tsc* - ldoc* --telescope* --LuaMin* -+LuaMinify* - *.ld - luacov* - *.exe - *.dll --test* -\ No newline at end of file -+test* -+bust* -\ No newline at end of file -diff --git a/extra/moses/.luacov b/extra/moses/.luacov -new file mode 100644 -index 0000000..1d4f5bb ---- /dev/null -+++ b/extra/moses/.luacov -@@ -0,0 +1,52 @@ -+--- Global configuration file. Copy, customize and store in your -+-- project folder as '.luacov' for project specific configuration -+-- @class module -+-- @name luacov.defaults -+return { -+ -+ -- default filename to load for config options if not provided -+ -- only has effect in 'luacov.defaults.lua' -+ ['configfile'] = '.luacov', -+ -+ -- filename to store stats collected -+ ['statsfile'] = 'luacov.stats.out', -+ -+ -- filename to store report -+ ['reportfile'] = 'luacov.report.out', -+ -+ -- Run reporter on completion? (won't work for ticks) -+ runreport = true, -+ -+ -- Delete stats file after reporting? -+ deletestats = 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) -+ ['include'] = { -+ 'moses' -+ }, -+ -+ -- Patterns for files to exclude when reporting -+ -- all will be included if nothing is listed -+ -- (exclude overrules include, do not include -+ -- the .lua extension) -+ ['exclude'] = { -+ 'luacov$', -+ 'luacov.reporter$', -+ 'luacov.defaults$', -+ 'luacov.runner$', -+ 'luacov.stats$', -+ 'luacov.tick$', -+ 'mediator.*$', -+ 'busted$', -+ 'busted.*$', -+ 'luassert.*$', -+ 'pl.*$', -+ 'say.*$', -+ 'spec.*$' -+ }, -+ -+ -+} -diff --git a/extra/moses/.travis.yml b/extra/moses/.travis.yml -index d884837..90bd434 100644 ---- a/extra/moses/.travis.yml -+++ b/extra/moses/.travis.yml -@@ -1,33 +1,32 @@ --language: c -- -+language: python - sudo: false - - env: -- global: -- - LUAROCKS=2.3.0 -- matrix: -- - LUA=lua5.1 -- - LUA=lua5.2 -- - LUA=lua5.3 -- - LUA=luajit # latest stable version (2.0.4) -- - LUA=luajit2.0 # current head of 2.0 branch -- - LUA=luajit2.1 # current head of 2.1 branc -- --branches: -- only: -- - master -+ - LUA="lua=5.1" -+ - LUA="lua=5.2" -+ - LUA="lua=5.3" -+ - LUA="luajit=2.0" -+ - LUA="luajit=2.1" - - before_install: -- - source .travis/setenv_lua.sh -- - pip install --user cpp-coveralls -- - luarocks install Lua-cURL --server=https://luarocks.org/dev -- - luarocks install luacov-coveralls --server=https://luarocks.org/dev -- - luarocks install telescope 0.6.0 --server=http://rocks.moonscript.org -+ - pip install hererocks -+ - hererocks lua_install -r^ --$LUA -+ - export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH - --script: "tsc -f spec/*" -+install: -+ - luarocks install busted -+ - luarocks install luacov -+ - luarocks install luacov-coveralls -+ -+script: -+ - busted --verbose --coverage - - after_success: -- - luacov-coveralls -c specs/.luacov -+ - luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install -+ -+branches: -+ except: -+ - gh-pages - - notifications: - email: -diff --git a/extra/moses/.travis/platform.sh b/extra/moses/.travis/platform.sh -deleted file mode 100644 -index 7259a7d..0000000 ---- a/extra/moses/.travis/platform.sh -+++ /dev/null -@@ -1,15 +0,0 @@ --if [ -z "${PLATFORM:-}" ]; then -- PLATFORM=$TRAVIS_OS_NAME; --fi -- --if [ "$PLATFORM" == "osx" ]; then -- PLATFORM="macosx"; --fi -- --if [ -z "$PLATFORM" ]; then -- if [ "$(uname)" == "Linux" ]; then -- PLATFORM="linux"; -- else -- PLATFORM="macosx"; -- fi; --fi -diff --git a/extra/moses/.travis/setenv_lua.sh b/extra/moses/.travis/setenv_lua.sh -deleted file mode 100644 -index 8d8c825..0000000 ---- a/extra/moses/.travis/setenv_lua.sh -+++ /dev/null -@@ -1,3 +0,0 @@ --export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin --bash .travis/setup_lua.sh --eval `$HOME/.lua/luarocks path` -diff --git a/extra/moses/.travis/setup_lua.sh b/extra/moses/.travis/setup_lua.sh -deleted file mode 100644 -index 6dcc0c6..0000000 ---- a/extra/moses/.travis/setup_lua.sh -+++ /dev/null -@@ -1,122 +0,0 @@ --#! /bin/bash -- --# A script for setting up environment for travis-ci testing. --# Sets up Lua and Luarocks. --# LUA must be "lua5.1", "lua5.2" or "luajit". --# luajit2.0 - master v2.0 --# luajit2.1 - master v2.1 -- --set -eufo pipefail -- --LUAJIT_VERSION="2.0.4" --LUAJIT_BASE="LuaJIT-$LUAJIT_VERSION" -- --source .travis/platform.sh -- --LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua -- --LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks -- --mkdir $HOME/.lua -- --LUAJIT="no" -- --if [ "$PLATFORM" == "macosx" ]; then -- if [ "$LUA" == "luajit" ]; then -- LUAJIT="yes"; -- fi -- if [ "$LUA" == "luajit2.0" ]; then -- LUAJIT="yes"; -- fi -- if [ "$LUA" == "luajit2.1" ]; then -- LUAJIT="yes"; -- fi; --elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then -- LUAJIT="yes"; --fi -- --mkdir -p "$LUA_HOME_DIR" -- --if [ "$LUAJIT" == "yes" ]; then -- -- if [ "$LUA" == "luajit" ]; then -- curl --location https://github.com/LuaJIT/LuaJIT/archive/v$LUAJIT_VERSION.tar.gz | tar xz; -- else -- git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_BASE; -- fi -- -- cd $LUAJIT_BASE -- -- if [ "$LUA" == "luajit2.1" ]; then -- git checkout v2.1; -- # force the INSTALL_TNAME to be luajit -- perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile -- fi -- -- make && make install PREFIX="$LUA_HOME_DIR" -- -- ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit -- ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua; -- --else -- -- if [ "$LUA" == "lua5.1" ]; then -- curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz -- cd lua-5.1.5; -- elif [ "$LUA" == "lua5.2" ]; then -- curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz -- cd lua-5.2.4; -- elif [ "$LUA" == "lua5.3" ]; then -- curl http://www.lua.org/ftp/lua-5.3.2.tar.gz | tar xz -- cd lua-5.3.2; -- fi -- -- # Build Lua without backwards compatibility for testing -- perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile -- make $PLATFORM -- make INSTALL_TOP="$LUA_HOME_DIR" install; -- -- ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua -- ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac; -- --fi -- --cd $TRAVIS_BUILD_DIR -- --lua -v -- --LUAROCKS_BASE=luarocks-$LUAROCKS -- --curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz -- --cd $LUAROCKS_BASE -- --if [ "$LUA" == "luajit" ]; then -- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; --elif [ "$LUA" == "luajit2.0" ]; then -- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; --elif [ "$LUA" == "luajit2.1" ]; then -- ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR"; --else -- ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR" --fi -- --make build && make install -- --ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks -- --cd $TRAVIS_BUILD_DIR -- --luarocks --version -- --rm -rf $LUAROCKS_BASE -- --if [ "$LUAJIT" == "yes" ]; then -- rm -rf $LUAJIT_BASE; --elif [ "$LUA" == "lua5.1" ]; then -- rm -rf lua-5.1.5; --elif [ "$LUA" == "lua5.2" ]; then -- rm -rf lua-5.2.4; --elif [ "$LUA" == "lua5.3" ]; then -- rm -rf lua-5.3.2; --fi -diff --git a/extra/moses/CHANGELOG.md b/extra/moses/CHANGELOG.md -index 63d31e9..2596b64 100644 ---- a/extra/moses/CHANGELOG.md -+++ b/extra/moses/CHANGELOG.md -@@ -1,5 +1,147 @@ - # Version history - -+## Unreleased -+ -+### Additions -+ -+* Added `mapi (t, f)` in table functions. -+ -+### Fixes and improvements -+ -+* Fixed `chunk` to avoid generating chunks at index 0. -+* Made argument `f` to `chunk` optional. Defaults to `identity` -+* Fixed alias to `uniqueId` -+* Fixed `M.powerset` -+* Prevents straight calls to io and os modules for Redis compatibility -+ -+## 2.1.0 (09/12/2018) -+ -+### Breaking changes -+ -+* Renamed `toArray` to `pack` -+* Renamed `reduceby` to `reduceBy` -+* Removed `skip` as alias to `last` -+* Changed prototype : `each(t, f, ...)` is now `each(t, f)` -+* Changed prototype : `eachi(t, f, ...)` is now `eachi(t, f)` -+* Changed prototype : `adjust(t, key, f, ...)` is now `adjust(t, key, f)` -+* Changed prototype : `countf(t, f, ...)` is now `countf(t, f)` -+* Changed prototype : `map(t, f, ...)` is now `map(t, f)` -+* Changed prototype : `reduceBy(t, f, pred, state, ...)` is now `reduceBy(t, f, pred, state)` -+* Changed prototype : `select(t, f, ...)` is now `select(t, f)` -+* Changed prototype : `reject(t, f, ...)` is now `reject(t, f)` -+* Changed prototype : `all(t, f, ...)` is now `all(t, f)` -+* Changed prototype : `invoke(t, method, ...)` is now `invoke(t, method)` -+* Changed prototype : `min(t, transform, ...)` is now `min(t, transform)` -+* Changed prototype : `max(t, transform, ...)` is now `max(t, transform)` -+* Changed prototype : `countBy(t, iter, ...)` is now `countBy(t, iter)` -+* Changed prototype : `groupBy(t, iter, ...)` is now `groupBy(t, iter)` -+* Changed prototype : `selectWhile(array, f, ...)` is now `selectWhile(array, f)` -+* Changed prototype : `dropWhile(array, f, ...)` is now `dropWhile(array, f)` -+* Changed prototype : `findIndex(array, pred, ...)` is now `findIndex(array, pred)` -+* Changed prototype : `findLastIndex(array, pred, ...)` is now `findLastIndex(array, pred)` -+* Changed prototype : `chunk(array, f, ...)` is now `chunk(array, f)` -+* Changed prototype : `times(iter, n, ...)` is now `times(iter, n)` -+* Changed prototype : `template(id, ...)` is now `template(id)` -+* Changed prototype : `tap(obj, f, ...)` is now `tap(obj, f)` -+* Changed prototype : `result(obj, method, ...)` is now `result(obj, method)` -+* Changed prototype : `intersection(array, ...)` is now `intersection(array)` -+ -+### Other changes -+ -+* Renamed `array` to `tabulate`, with no alias -+* Moved `invert` to object functions -+ -+### Additions -+ -+* Added `best` in table functions -+* Added `allEqual` in table functions -+* Added `sortedk` iterator in array functions -+* Added `sortedv` iterator in array functions -+* Added `disjoint` in array functions -+* Added `nsorted` in array functions -+* Added `duplicates` in array functions -+* Added `xpairs` in array functions -+* Added `xpairsRight` in array functions -+* Added `call` in utility functions -+* Added `type` in object functions -+* Added `spreadPath` in object functions -+* Added `flattenPath` in object functions -+* Added `thread` in utility functions -+* Added `threadRight` in utility functions -+* Added `iterlen` in utility functions -+* Added `skip` in utility functions -+* Added `both` in utility functions -+* Added `either` in utility functions -+* Added `neither` in utility functions -+* Added `dispatch` in utility functions -+* Added `noarg` in utility functions -+ -+## 2.0.0 (08/23/2018) -+### Breaking changes -+* library functions now accept iterators prototyped as `f(v, k, ...)` instead of `f(k, v, ...)`. -+It improves the benefits of chaning and helps writting a clear functional-style code. -+Library functions affected with this breaking change are : -+`each`, `eachi`,`countf`, `map`, `reduceby`, `select`, `reject`, `all`, `groupBy`, `countBy`, -+`selectWhile`, `dropWhile`, `findIndex`, `findLastIndex`, `chunk`. -+* `reduceby` is now prototyped as `reduceby(t, f, pred, state)` instead of `reduceby(t, f, state, pred)`. -+* `times` is now prototyped as `times(iter, n, ...)` instead of `times(iter, n, ...)`. -+* `bindAll` was renamed to `bindall` -+* `functions` no longer accept optional `sort` third arguments -+* `sliding` was renamed to `overlapping` -+* Improved `range` to handle negative progressions and start the count from 1. -+* `memoize` no longer takes a `hash` function. -+ -+### Other changes -+* Made `shift` a default library function, and `pop` its alias. -+* Moved `shuffle` from table function to array functions -+* Made `iterator` to accept an extra optional arg `n` -+ -+### Additions -+ -+#### Added support for operators -+* Arithmetic operators : `add`, `sub`, `mul`, `div`, `mod`, `exp`, `pow` (alias to `exp`), `unm`, `neg` (alias to `unm`), `floordiv`, `intdiv` -+* Relational operators : `eq`, `neq`, `lt`, `gt`, `le`, `ge` -+* Logical operators : `land`, `lor`, `lnot` -+* Concatenation operator : `concat` -+* Length operator : `length`, `len` (alias to `length`) -+ -+#### Added functions -+* Added `adjust` in table functions -+* Added `xprod` in array functions -+* Added `prepend` in array functions -+* Added `zeros` in array functions -+* Added `ones` in array functions -+* Added `vector` in array functions -+* Added `aperture` in array functions -+* Added `sum` in array functions -+* Added `product` in array functions -+* Added `mean` in array functions -+* Added `median` in array functions -+* Added `powerset` in array functions -+* Added `zipWith` in array functions -+* Added `pairwise` in array functions -+* Added `applySpec` in utility functions -+* Added `nthArg` in utility functions -+* Added `cond` in utility functions -+* Added `castArray` in utility functions -+* Added `unary` in utility functions -+* Added `ary` in utility functions -+* Added `rearg` in utility functions -+* Added `unfold` in utility functions -+* Added `converge` in utility functions -+* Added `path` in object functions -+ -+#### Added function aliases -+* Added `update` as alias to `adjust` -+* Added `always` as alias to `constant` -+* Added `intersperse` as alias to `interpose` -+* Added `sliding` as alias to `aperture` -+* Added `tabulate` as alias to `array` -+* Added `matches` as alias to `isEqual` -+* Added `average` as alias to `mean` -+* Added `nAry` as alias to `ary` -+* Added `transposeWith` as alias to `zipWith` -+ - ## 1.6.1 (04/27/17) - - * Added `_.array` -diff --git a/extra/moses/LICENSE b/extra/moses/LICENSE -index f06dce3..d756883 100644 ---- a/extra/moses/LICENSE -+++ b/extra/moses/LICENSE -@@ -1,4 +1,4 @@ --Copyright (c) 2012-2014 Roland Yonaba -+Copyright (c) 2012-2018 Roland Yonaba - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the -diff --git a/extra/moses/README.md b/extra/moses/README.md -index 0bf99e5..9ac401a 100644 ---- a/extra/moses/README.md -+++ b/extra/moses/README.md -@@ -1,15 +1,55 @@ - [![Build Status](https://travis-ci.org/Yonaba/Moses.png)](https://travis-ci.org/Yonaba/Moses) -+[![Latest Stable](https://img.shields.io/badge/Latest_Stable-2.1.0-blue.svg)](https://github.com/Yonaba/Moses/releases/tag/Moses-2.1.0-1) - [![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) - [![Lua](https://img.shields.io/badge/Lua-5.1%2C%205.2%2C%205.3%2C%20JIT-blue.svg)]() --[![git-brag-stats](https://labs.turbo.run/git-brag?user=Yonaba&repo=Moses)](https://github.com/turbo/git-brag) - - A Lua utility-belt library for [functional programming](http://en.wikipedia.org/wiki/Functional_programming).
    - -+## Examples -+ -+How can I get the sum of all integers between 1 and 100 ? -+ -+```lua -+local sum = M.sum(M.range(100)) -+print(sum) -- 5050 -+```` -+Say I am looking for the length of the longest word in some array ? -+ -+```lua -+local words = {'some','words','of','different','lengths'} -+print(M.max(words, M.op.length)) -- 9 letters -+```` -+ -+What is the sum of all fibonacci numbers for n below or equal 25 ? -+ -+```lua -+local function fib(n) return n < 2 and n or fib(n - 1) + fib(n - 2) end -+local fibsum = M.sum(M.map(M.range(25), fib)) -+print(fibsum) -- 196417 -+```` -+ -+Or let us do the same, object-oriented style with chaining : -+ -+```lua -+local function fib(n) return n < 2 and n or fib(n - 1) + fib(n - 2) end -+local fibsum = M.chain(M.range(25)):map(fib):sum():value() -+print(fibsum) -- 196417 -+```` -+ -+Or even shorter : -+ -+```lua -+local fibsum = M(M.range(25)):map(fib):sum():value() -+print(fibsum) -- 196417 -+```` -+ -+Feel free to download and try it on your own! -+ - ## Download - - ### Archive --* __zip__: [1.6.1 (stable)](http://github.com/Yonaba/Moses/archive/Moses-1.6.1-1.zip) | [dev](http://github.com/Yonaba/Moses/archive/master.zip) | [all releases](http://github.com/Yonaba/Moses/tags) --* __tarball__: [1.6.1 (stable)](http://github.com/Yonaba/Moses/archive/Moses-1.6.1-1.tar.gz) | [dev](http://github.com/Yonaba/Moses/archive/master.tar.gz) | [all releases](http://github.com/Yonaba/Moses/tags) -+* __2.1.0__ *(latest stable)*: [zip](http://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.zip) | [tar.gz](http://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.tar.gz) -+* __Previous versions__ : [tags](http://github.com/Yonaba/Moses/tags) - - ### Bash - -@@ -18,23 +58,24 @@ git clone git://github.com/Yonaba/Moses.git - ```` - - ### LuaRocks --``` -+ -+```` - luarocks install moses - ```` - - ### MoonRocks - --```bash -+```` - moonrocks install moses - ```` - - ## Usage - - ```lua --local _ = require "moses" -+local M = require "moses" - ```` - --*Note:* the full source [moses.lua](https://github.com/Yonaba/Moses/blob/master/moses.lua) is quite heavy (~70 kiB). You can alternatively use the [minified version](https://github.com/Yonaba/Moses/blob/master/moses_min.lua) (~22 kiB). -+*Note:* the full source [moses.lua](https://github.com/Yonaba/Moses/blob/master/moses.lua) is quite heavy (~92 kiB, 3115 LOC). You can alternatively use the [minified version](https://github.com/Yonaba/Moses/blob/master/moses_min.lua) (~35 kiB, 561 LOC). - - ## Tutorial - -@@ -42,28 +83,28 @@ Find a complete set of code examples in [tutorial.md](https://github.com/Yonaba/ - - ## Documentation - --* See *doc* folder : [doc](https://github.com/Yonaba/Moses/blob/master/doc) --* Or browse it online : see [online doc](http://yonaba.github.io/Moses/doc). -+* Read it [online](http://yonaba.github.io/Moses/doc). - - ## Credits and Acknowledgement - - * [Jeremy Ashkenas](https://github.com/jashkenas), for the amazing [Underscore.js](http://documentcloud.github.com/underscore/) --* [Marcus Irven](http://mirven.github.com/underscore.lua/)'s and [JTArchie](https://github.com/jtarchie/underscore-lua)'s 1-to-1 ports that also inspired this -+* [Marcus Irven](http://mirven.github.com/underscore.lua/)'s and [JT Archie](https://github.com/jtarchie/underscore-lua)'s 1-to-1 ports that also inspired this - * [Matthew Rocklin](https://github.com/mrocklin)'s [Toolz](https://github.com/pytoolz/toolz/) from which I borrowed some ideas --* [LDoc](https://github.com/stevedonovan/ldoc/) used to generate the current HTML documentation. -+* [Steve Donovan](https://github.com/stevedonovan)'s [LDoc](https://github.com/stevedonovan/ldoc/), used to generate the current HTML documentation. -+* [Mark Langen](https://github.com/stravant)'s [LuaMinify](https://github.com/stravant/LuaMinify/), used to generate a minified version of this library. - - ## Specification - --Run [spec tests](https://github.com/Yonaba/Moses/blob/master/spec) using [Telescope](https://github.com/norman/telescope) with the following command from the root folder: -+Run [spec tests](https://github.com/Yonaba/Moses/blob/master/spec) from Lua using [busted](https://github.com/Olivine-Labs/busted/) with the following command from the root folder: - - ```` --tsc -f spec/* -+busted - ```` - - ## License - - This work is under [MIT-LICENSE](http://www.opensource.org/licenses/mit-license.php)
    --Copyright (c) 2012-2017 Roland Yonaba.
    -+Copyright (c) 2012-2018 Roland Yonaba.
    - See [LICENSE](LICENSE). - - -diff --git a/extra/moses/doc/index.html b/extra/moses/doc/index.html -index bace80a..d950955 100644 ---- a/extra/moses/doc/index.html -+++ b/extra/moses/doc/index.html -@@ -4,7 +4,7 @@ - - - Moses documentation -- -+ - - - -@@ -29,6 +29,7 @@ - -

    Contents

    -
    -@@ -52,425 +53,641 @@ -

    Module moses

    -

    Utility-belt library for functional programming in Lua (source)

    -

    -+ -

    -

    Info:

    -
      --
    • Copyright: 2012-2017
    • --
    • Release: 1.6.1
    • -+
    • Copyright: 2012-2018
    • -+
    • Release: 2.1.0
    • -
    • License: MIT
    • -
    • Author: Roland Yonaba
    • -
    - - --

    Table functions

    -+

    Operator functions

    - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ -+ -+ -+ -+ -+ -+
    clear (t)Clears a table.operator.add (a, b)Returns a + b.
    each (t, f[, ...])Iterates on key-value pairs, calling f (k, v) at every step.operator.concat (a, b)Returns concatenation of a and b.
    eachi (t, f[, ...])Iterates on integer key-value pairs, calling f(k, v) every step.operator.div (a, b)Returns a / b.
    at (t, ...)Collects values at given keys and return them wrapped in an array.operator.eq (a, b)Checks if a equals b.
    count (t[, value])Counts occurrences of a given value in a table.operator.exp (a, b)Returns a ^ b.
    countf (t, f[, ...])Counts occurrences validating a predicate.operator.floordiv (a, b)Performs floor division (//) between a and b.
    cycle (t, n)Loops n times through a table.operator.ge (a, b)Checks if a is greater or equal to b.
    map (t, f[, ...])Maps f (k, v) on key-value pairs, collects and returns the results.operator.gt (a, b)Checks if a is strictly greater than b.
    reduce (t, f[, state])Reduces a table, left-to-right.operator.intdiv (a, b)Performs integer division between a and b.
    reduceby (t, f, state, pred[, ...])Reduces values in a table passing a given predicate.operator.land (a, b)Returns logical a and b.
    reduceRight (t, f[, state])Reduces a table, right-to-left.operator.le (a, b)Checks if a is less or equal to b.
    mapReduce (t, f[, state])Reduces a table while saving intermediate states.operator.length (a)Returns the length of a.
    mapReduceRight (t, f[, state])Reduces a table while saving intermediate states.operator.lnot (a)Returns logical not a.
    include (t, value)Performs a linear search for a value in a table.operator.lor (a, b)Returns logical a or b.
    detect (t, value)Performs a linear search for a value in a table.operator.lt (a, b)Checks if a is strictly less than b.
    where (t, props)Returns all values having specified keys props.operator.mod (a, b)Returns a % b.
    findWhere (t, props)Returns the first value having specified keys props.operator.mul (a, b)Returns a * b.
    select (t, f[, ...])Selects and returns values passing an iterator test.operator.neq (a, b)Checks if a not equals b.
    reject (t, f[, ...])Clones a table while dropping values passing an iterator test.operator.sub (a, b)Returns a - b.
    operator.unm (a)Returns -a.
    -+

    Table functions

    -+ -+ -+ -+ - - -- -+ - - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - --
    adjust (t, key, f)Adjusts the value at a given key using a function or a value.
    all (t, f[, ...])all (t, f)Checks if all values in a table are passing an iterator test.
    invoke (t, method[, ...])Invokes a method on each value in a table.allEqual (t[, comp])Checks if all values in a collection are equal.
    pluck (t, key)Extracts values in a table having a given key.at (t, ...)Collects values at given keys and return them wrapped in an array.
    max (t[, transform[, ...]])Returns the max value in a collection.best (t, f)Returns the best value passing a selector function.
    min (t[, transform[, ...]])Returns the min value in a collection.clear (t)Clears a table.
    shuffle (t[, seed])Returns a shuffled copy of a given collection.containsKeys (t, other)Checks if all the keys of other table exists in table t.
    same (a, b)Checks if two tables are the same.count (t[, val])Counts occurrences of a given value in a table.
    sort (t[, comp])Sorts a table, in-place.countBy (t, iter)Groups values in a collection and counts them.
    sortBy (t[, transform[, comp]])Sorts a table in-place using a transform.countf (t, f)Counts the number of values passing a predicate test.
    groupBy (t, iter[, ...])Splits a table into subsets groups.cycle (t[, n])Loops n times through a table.
    countBy (t, iter[, ...])Groups values in a collection and counts them.detect (t, value)Performs a linear search for a value in a table.
    size ([...])Counts the number of values in a collection.each (t, f)Iterates on key-value pairs, calling f (v, k) at every step.
    containsKeys (t, other)Checks if all the keys of other table exists in table t.eachi (t, f)Iterates on integer key-value pairs, calling f(v, k) every step.
    sameKeys (tA, tB)Checks if both given tables have the same keys.findWhere (t, props)Returns the first value having specified keys props.
    --

    Array functions

    -- - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - -+
    sample (array[, n[, seed]])Samples n random values from an array.groupBy (t, iter)Splits a table into subsets groups.
    sampleProb (array, prob[, seed])Return elements from a sequence with a given probability.include (t, value)Performs a linear search for a value in a table.
    toArray (...)Converts a list of arguments to an array.invoke (t, method)Invokes a method on each value in a table.
    find (array, value[, from])Looks for the first occurrence of a given value in an array.map (t, f)Maps f (v, k) on value-key pairs, collects and returns the results.
    reverse (array)Returns an array where values are in reverse order.mapReduce (t, f[, state])Reduces a table while saving intermediate states.
    fill (array, value[, i[, j]])Replaces elements in a given array with a given value.mapReduceRight (t, f[, state])Reduces a table while saving intermediate states.
    selectWhile (array, f[, ...])Collects values from a given array.mapi (t, f)Maps f (v, k) on value-key pairs, collects and returns the results.
    dropWhile (array, f[, ...])Collects values from a given array.max (t[, transform])Returns the max value in a collection.
    sortedIndex (array, the[, comp[, sort]])Returns the index at which a value should be inserted.min (t[, transform])Returns the min value in a collection.
    indexOf (array, value)Returns the index of the first occurence of value in an array.pluck (t, key)Extracts values in a table having a given key.
    lastIndexOf (array, value)Returns the index of the last occurrence of value in an array.reduce (t, f[, state])Reduces a table, left-to-right.
    findIndex (array, predicate[, ...])Returns the first index at which a predicate returns true.reduceBy (t, f, pred[, state[, ...]])Reduces values in a table passing a given predicate.
    findLastIndex (array, predicate[, ...])Returns the last index at which a predicate returns true.reduceRight (t, f[, state])Reduces a table, right-to-left.
    addTop (array, ...)Adds all passed-in values at the top of an array.reject (t, f)Clones a table while dropping values passing an iterator test.
    push (array, ...)Pushes all passed-in values at the end of an array.same (a, b)Checks if two tables are the same.
    pop (array[, n])Removes and returns the values at the top of a given array.sameKeys (tA, tB)Checks if both given tables have the same keys.
    unshift (array[, n])Removes and returns the values at the end of a given array.select (t, f)Selects and returns values passing an iterator test.
    pull (array, ...)Removes all provided values in a given array.size ([...])Counts the number of values in a collection.
    removeRange (array[, start[, finish]])Removes values at index within the range [start, finish].sort (t[, comp])Sorts a table, in-place.
    chunk (array, f[, ...])Chunks together consecutive values.sortBy (t[, transform[, comp]])Sorts a table in-place using a transform.
    slice (array[, start[, finish]])Slices values indexed within [start, finish] range.sortedk (t[, comp])Iterates on values with respect to key order.
    first (array[, n])Returns the first N values in an array.sortedv (t[, comp])Iterates on values with respect to values order.
    initial (array[, n])Returns all values in an array excluding the last N values.where (t, props)Returns all values having specified keys props.
    -+

    Array functions

    -+ - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ -+ -+ -+ -+ - - - -- -+ - - -- -- -+ -+ - - - - - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - --
    last (array[, n])Returns the last N values in an array.addTop (array, ...)Adds all passed-in values at the top of an array.
    rest (array[, index])Removes all values before index.aperture (array[, n])Iterator returning sliding partitions of an array.
    nth (array, index)Returns the value at a given index.append (array, other)Clones array and appends values from another array.
    chunk (array, f)Chunks together consecutive values.
    compact (array)Removes all falsy (false and nil) values.Returns all truthy values (removes falses and nils).
    flatten (array[, shallow])Flattens a nested array.concat (array[, sep[, i[, j]]])Concatenates values in a given array.
    difference (array, another)Returns values from an array not present in all passed-in args.
    union (...)Returns the duplicate-free union of all passed in arrays.disjoint (...)Checks if all passed in arrays are disjunct.
    intersection (array, ...)Returns the intersection of all passed-in arrays.dropWhile (array, f)Collects values from a given array.
    symmetricDifference (array, array2)Performs a symmetric difference.duplicates (array)Returns an array list of all duplicates in array.
    unique (array)Produces a duplicate-free version of a given array.fill (array, value[, i[, j]])Replaces elements in a given array with a given value.
    isunique (array)Checks if a given array contains distinct values.find (array, value[, from])Looks for the first occurrence of a given value in an array.
    zip (...)Merges values of each of the passed-in arrays in subsets.findIndex (array, pred)Returns the first index at which a predicate returns true.
    append (array, other)Clones array and appends other values.findLastIndex (array, pred)Returns the last index at which a predicate returns true.
    interleave (...)Interleaves arrays.first (array[, n])Returns the first N values in an array.
    interpose (value, array)Interposes value in-between consecutive pair of values in array.flatten (array[, shallow])Flattens a nested array.
    range ([from[, to[, step]]])Produces a flexible list of numbers.indexOf (array, value)Returns the index of the first occurrence of value in an array.
    rep (value, n)Creates an array list of n values, repeated.initial (array[, n])Returns all values in an array excluding the last N values.
    partition (array[, n[, pad]])Iterator returning partitions of an array.interleave (...)Interleaves arrays.
    sliding. (array[, n[, pad]])Iterator returning sliding partitions of an array.interpose (array, value)Interposes value in-between consecutive pair of values in array.
    permutation (array)Iterator returning the permutations of an array.intersection (...)Returns the intersection of all passed-in arrays.
    invert (array)Swaps keys with values.isunique (array)Checks if a given array contains distinct values.
    concat (array[, sep[, i[, j]]])Concatenates values in a given array.last (array[, n])Returns the last N values in an array.
    --

    Utility functions

    -- - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    noop ()The no-operation function.lastIndexOf (array, value)Returns the index of the last occurrence of value in an array.
    identity (value)Returns the passed-in value.mean (array)Returns the mean of an array of numbers.
    constant (value)Creates a constant function which returns the same output on every call.median (array)Returns the median of an array of numbers.
    memoize (f[, hash])Memoizes a given function by caching the computed result.nsorted (array[, n[, comp]])Returns the n-top values satisfying a predicate.
    once (f)Returns a version of f that runs only once.nth (array, index)Returns the value at a given index.
    before (f, count)Returns a version of f that will run no more than count times.ones (n)Returns an array of n 1's.
    after (f, count)Returns a version of f that runs on the count-th call.overlapping (array[, n[, pads]])Iterator returning overlapping partitions of an array.
    compose (...)Composes functions.pack (...)Converts a list of arguments to an array.
    pipe (value, ...)Pipes a value through a series of functions.pairwise (array)Iterator returning sliding pairs of an array.
    complement (f)Returns the logical complement of a given function.partition (array[, n[, pads]])Iterator returning partitions of an array.
    juxtapose (value, ...)Calls a sequence of passed-in functions with the same argument.permutation (array)Iterator returning the permutations of an array.
    wrap (f, wrapper)Wraps f inside of the wrapper function.powerset (array)Returns the powerset of array values.
    times (n, iter, ...)Runs iter function n times.prepend (array, ...)Adds all passed-in values at the top of an array.
    bind (f, v)Binds v to be the first argument to f.product (array)Returns the product of array values.
    bind2 (f, v)Binds v to be the second argument to f.pull (array, ...)Removes all provided values in a given array.
    bindn (f, ...)Binds to be the N-first arguments to function f.push (array, ...)Pushes all passed-in values at the end of an array.
    bindAll (obj, ...)Binds methods to object.range ([from[, to[, step]]])Produces a flexible list of numbers.
    uniqueId ([template[, ...]])Generates an unique ID for the current session.removeRange (array[, start[, finish]])Removes values at an index within the range [start, finish].
    iterator (f, x)Produces an iterator which repeatedly apply a function f onto an input.rep (value, n)Creates an array list of n values, repeated.
    array (...)Iterates an iterator and returns its values in an array.rest (array[, index])Returns all values after index.
    flip (f)reverse (array)Returns an array where values are in reverse order.
    sample (array[, n[, seed]])Samples n random values from an array.
    sampleProb (array, prob[, seed])Return elements from a sequence with a given probability.
    selectWhile (array, f)Collects values from a given array.
    shift (array[, n])Removes and returns the values at the top of a given array.
    shuffle (array[, seed])Returns a shuffled copy of a given array.
    slice (array[, start[, finish]])Slices values indexed within [start, finish] range.
    sortedIndex (array, the[, comp[, sort]])Returns the index at which a value should be inserted.
    sum (array)Returns the sum of array values.
    symmetricDifference (array, array2)Performs a symmetric difference.
    union (...)Returns the duplicate-free union of all passed in arrays.
    unique (array)Produces a duplicate-free version of a given array.
    unshift (array[, n])Removes and returns the values at the end of a given array.
    vector (value, n)Returns an array of n times a given value.
    xpairs (valua, array)Creates pairs from value and array.
    xpairsRight (valua, array)Creates pairs from value and array.
    xprod (array, array2)Returns all possible pairs built from given arrays.
    zeros (n)Returns an array of n zeros.
    zip (...)Merges values of each of the passed-in arrays in subsets.
    zipWith (f, ...)Merges values using a given function.
    -+

    Utility functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - - -+ -+ -+ -+ - - - -@@ -479,10 +696,6 @@ - - - -- -- -- -- - - - -@@ -491,147 +704,207 @@ - - - -- -- -- -- -- -- -+ -+ - --
    after (f, count)Returns a version of f that runs on the count-th call.
    applySpec (specs)Returns a function which applies specs on args.
    ary (f[, n])Returns a function which accepts up to n args.
    before (f, count)Returns a version of f that will run no more than count times.
    bind (f, v)Binds v to be the first argument to f.
    bind2 (f, v)Binds v to be the second argument to f.
    bindall (obj, ...)Binds methods to object.
    bindn (f, ...)Binds ... to be the N-first arguments to function f.
    both (...)Returns a validation function.
    call (f[, ...])Calls f with the supplied arguments.
    castArray (value)Casts value as an array if it is not one.
    complement (f)Returns the logical complement of a given function.
    compose (...)Composes functions.
    cond (conds)Returns a function which iterate over a set of conditions.
    constant (value)Creates a constant function which returns the same output on every call.
    converge (f, g, h)Converges two functions into one.
    curry (f[, n_args])Curries a function.
    dispatch (...)Returns a dispatching function.
    either (...)Returns a validation function.
    flip (f)Creates a function of f with arguments flipped in reverse order.
    identity (value)Returns the passed-in value.
    iterator (f, value[, n])Produces an iterator which repeatedly apply a function f onto an input.
    iterlen (...)Returns the length of an iterator.
    juxtapose (value, ...)Calls a sequence of passed-in functions with the same argument.
    memoize (f)Memoizes a given function by caching the computed result.
    neither (...)Returns a validation function.
    noarg (f)Returns a function with an arity of 0.
    noop ()The no operation function.
    nthArg (n)Returns a function that gets the nth argument.
    once (f)Returns a version of f that runs only once.
    over (...)Creates a function that runs transforms on all arguments it receives.
    overArgs (f, ...)Creates a function that invokes f with its arguments transformed.
    overEvery (...)Creates a validation function.
    Creates a validation function.
    overArgs (f, ...)Creates a function that invokes f with its arguments transformed.
    partial (f, ...)Partially apply a function by filling in any number of its arguments.
    Similar to partial, but from the right.
    curry (f[, n_args])Curries a function.
    time (f[, ...])Returns the execution time of f (…) and its returned values.pipe (value, ...)Pipes a value through a series of functions.
    --

    Object functions

    -- - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - -+
    keys (obj)Returns the keys of the object properties.rearg (f, indexes)Returns a function which runs with arguments rearranged.
    values (obj)Returns the values of the object properties.skip (iter[, n])Consumes the first n values of a iterator then returns it.
    kvpairs (obj)Converts keys and values a an array-list of [k, v].tabulate (...)Iterates over an iterator and returns its values in an array.
    toObj (kvpairs)Converts an array list of kvpairs to an object.thread (value, ...)Threads value through a series of functions.
    property (key)Returns a function that will return the key property of any passed-in object.threadRight (value, ...)Threads value through a series of functions.
    propertyOf (obj)Returns a function which will return the value of an object property.time (f[, ...])Returns the execution time of f (...) and its returned values.
    toBoolean (value)Converts any given value to a booleantimes (iter[, n])Runs iter function n times.
    extend (destObj, ...)Extends an object properties.unary (f)Returns a function which accepts up to one arg.
    functions ([obj])Returns a sorted list of all methods names found in an object.unfold (f, seed)Builds a list from a seed value.
    clone (obj[, shallow])Clones a given object properties.uniqueId ([template])Generates an unique ID for the current session.
    tap (obj, f[, ...])Invokes interceptor with the object, and then returns object.wrap (f, wrapper)Wraps f inside of the wrapper function.
    -+

    Object functions

    -+ - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - - - - - -- -- -+ -+ -+ -+ -+ -+ - - - - - - -- -- -+ -+ -+ -+ -+ -+ - - - - - - -- -- -+ -+ - - -- -- -+ -+ - - - - - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ - - -- -- -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - - - - -- -- -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - -
    has (obj, key)Checks if a given object implements a property.chain (value)Returns a wrapped object.
    pick (obj, ...)Returns an object copy having white-listed properties.clone (obj[, shallow])Clones a given object properties.
    omit (obj, ...)Returns an object copy without black-listed properties.extend (destObj, ...)Extends an object properties.
    template (obj[, template])Applies a template to an object, preserving non-nil properties.flattenPath (obj, ...)Flattens object under property path onto provided object.
    isEqual (objA, objB[, useMt])Performs a deep comparison test between two objects.functions ([obj])Returns a sorted list of all methods names found in an object.
    result (obj, method[, ...])Invokes an object method.has (obj, key)Checks if a given object implements a property.
    isTable (t)Checks if the given arg is a table.import ([context[, noConflict]])Imports all library functions into a context.
    isCallable (obj)Checks if the given argument is callable.invert (obj)Swaps keys with values.
    isArray (obj)Checks if the given argument is an array.
    isIterable (obj)Checks if the given object is iterable with pairs (or ipairs).isBoolean (obj)Checks if the given argument is a boolean.
    isCallable (obj)Checks if the given argument is callable.
    isEmpty ([obj])Checks if the given pbject is empty.
    isString (obj)Checks if the given argument is a string.isEqual (objA, objB[, useMt])Performs a deep comparison test between two objects.
    isFinite (obj)Checks if the given argument is a finite number.
    isFunction (obj)Checks if the given argument is a function.
    isNil (obj)Checks if the given argument is nil.isInteger (obj)Checks if the given argument is an integer.
    isNumber (obj)Checks if the given argument is a number.isIterable (obj)Checks if the given object is iterable with pairs (or ipairs).
    isNaN (obj)Checks if the given argument is NaN (see Not-A-Number).
    isFinite (obj)Checks if the given argument is a finite number.isNil (obj)Checks if the given argument is nil.
    isBoolean (obj)Checks if the given argument is a boolean.isNumber (obj)Checks if the given argument is a number.
    isInteger (obj)Checks if the given argument is an integer.isString (obj)Checks if the given argument is a string.
    chain (value)Returns a wrapped object.isTable (t)Checks if the given arg is a table.
    keys (obj)Returns the keys of the object properties.
    kvpairs (obj)Converts key-value pairs to an array-list of [k, v] pairs.
    obj:value ()Extracts the value of a wrapped object.
    import ([context[, noConflict]])Imports all library functions into a context.omit (obj, ...)Returns an object copy without black-listed properties.
    path (obj, ...)Returns the value at a given path in an object.
    pick (obj, ...)Returns an object copy having white-listed properties.
    property (key)Returns a function that will return the key property of any passed-in object.
    propertyOf (obj)Returns a function which will return the value of an object property.
    result (obj, method)Invokes an object method.
    spreadPath (obj, ...)Spreads object under property path onto provided object.
    tap (obj, f)Invokes interceptor with the object, and then returns object.
    template (obj[, template])Applies a template to an object, preserving non-nil properties.
    toBoolean (value)Converts any given value to a boolean
    toObj (kvpairs)Converts an array list of [k,v] pairs to an object.
    type (obj)Extends Lua's type function.
    values (obj)Returns the values of the object properties.
    - -@@ -639,28 +912,2190 @@ -
    - - --

    Table functions

    -+

    Operator functions

    - -
    -
    -- -- clear (t) -+ -+ operator.add (a, b) -+
    -+
    -+ Returns a + b. Aliased as op.add. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a + b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.concat (a, b) -+
    -+
    -+ Returns concatenation of a and b. Aliased as op.concat. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a .. b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.div (a, b) -+
    -+
    -+ Returns a / b. Aliased as op.div. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a / b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.eq (a, b) -+
    -+
    -+ Checks if a equals b. Aliased as op.eq. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a == b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.exp (a, b) -+
    -+
    -+ Returns a ^ b. Aliased as op.exp, op.pow. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a ^ b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.floordiv (a, b) -+
    -+
    -+ Performs floor division (//) between a and b. It rounds the quotient towards minus infinity. -+ Aliased as op.floordiv. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a // b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.ge (a, b) -+
    -+
    -+ Checks if a is greater or equal to b. Aliased as op.ge. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a >= b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.gt (a, b) -+
    -+
    -+ Checks if a is strictly greater than b. Aliased as op.gt. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a > b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.intdiv (a, b) -+
    -+
    -+ Performs integer division between a and b. Aliased as op.intdiv. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a / b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.land (a, b) -+
    -+
    -+ Returns logical a and b. Aliased as op.land. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a and b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.le (a, b) -+
    -+
    -+ Checks if a is less or equal to b. Aliased as op.le. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a <= b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.length (a) -+
    -+
    -+ Returns the length of a. Aliased as op.len. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ -+

      a

      -+ -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.lnot (a) -+
    -+
    -+ Returns logical not a. Aliased as op.lnot. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ not a -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.lor (a, b) -+
    -+
    -+ Returns logical a or b. Aliased as op.lor. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a or b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.lt (a, b) -+
    -+
    -+ Checks if a is strictly less than b. Aliased as op.lt. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a < b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.mod (a, b) -+
    -+
    -+ Returns a % b. Aliased as op.mod. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a % b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.mul (a, b) -+
    -+
    -+ Returns a * b. Aliased as op.mul. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a * b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.neq (a, b) -+
    -+
    -+ Checks if a not equals b. Aliased as op.neq. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a ~= b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.sub (a, b) -+
    -+
    -+ Returns a - b. Aliased as op.sub. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    • b -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a - b -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ operator.unm (a) -+
    -+
    -+ Returns -a. Aliased as op.unm, op.neg. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ -a -+
    -+ -+ -+ -+ -+
    -+
    -+

    Table functions

    -+ -+
    -+
    -+ -+ adjust (t, key, f) -+
    -+
    -+ Adjusts the value at a given key using a function or a value. In case f is a function, -+ it should be prototyped f(v). It does not mutate the given table, but rather -+ returns a new array. In case the given key does not exist in t, it throws an error. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • key -+ a key -+
    • -+
    • f -+ a function, prototyped as f(v) or a value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ all (t, f) -+
    -+
    -+ Checks if all values in a table are passing an iterator test. -+
    Aliased as every -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if all values passes the predicate, false otherwise -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ allEqual (t[, comp]) -+
    -+
    -+ Checks if all values in a collection are equal. Uses an optional comp function which is used -+ to compare values and defaults to isEqual when not given. -+
    Aliased as alleq. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • comp -+ a comparison function. Defaults to isEqual -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true when all values in t are equal, false otherwise. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ at (t, ...) -+
    -+
    -+ Collects values at given keys and return them wrapped in an array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • ... -+ A variable number of keys to collect values -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array-list of values -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ best (t, f) -+
    -+
    -+ Returns the best value passing a selector function. Acts as a special case of -+ reduce, using the first value in t as an initial state. It thens folds the given table, -+ testing each of its values v and selecting the value passing the call f(state,v) every time. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the final state of reduction -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ clear (t) -+
    -+
    -+ Clears a table. All its values become nil. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the given table, cleared. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ containsKeys (t, other) -+
    -+
    -+ Checks if all the keys of other table exists in table t. It does not -+ compares values. The test is not commutative, i.e table t may contains keys -+ not existing in other. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • other -+ another table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ count (t[, val]) -+
    -+
    -+ Counts occurrences of a given value in a table. Uses isEqual to compare values. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • val -+ a value to be searched in the table. If not given, the size of the table will be returned -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the count of occurrences of the given value -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ countBy (t, iter) -+
    -+
    -+ Groups values in a collection and counts them. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • iter -+ an iterator function, prototyped as iter (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of subsets groups names paired with their count -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ countf (t, f) -+
    -+
    -+ Counts the number of values passing a predicate test. Same as count, but uses an iterator. -+ Returns the count for values passing the test f (v, k) -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the count of values validating the predicate -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ cycle (t[, n]) -+
    -+
    -+ Loops n times through a table. In case n is omitted, it will loop forever. -+ In case n is lower or equal to 0, it returns an empty function. -+
    Aliased as loop. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • n -+ the number of loops -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator function yielding value-key pairs from the passed-in table. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ detect (t, value) -+
    -+
    -+ Performs a linear search for a value in a table. Returns the key of the value if found. -+ The given value can be a function prototyped as f (v, value) which should return true when -+ any v in the table equals the value being searched. This function is similar to find, -+ which is mostly meant to work with array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • value -+ a value to search for -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the key of the value when found or nil -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ each (t, f) -+
    -+
    -+ Iterates on key-value pairs, calling f (v, k) at every step. -+
    Aliased as forEach. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ a function, prototyped as f (v, k) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ eachi (t, f) -+
    -+
    -+ Iterates on integer key-value pairs, calling f(v, k) every step.
    -+ Only applies to values located at integer keys. The table can be a sparse array. -+ Iteration will start from the lowest integer key found to the highest one. -+
    Aliased as forEachi. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ a function, prototyped as f (v, k) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ findWhere (t, props) -+
    -+
    -+ Returns the first value having specified keys props. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • props -+ a set of keys -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a value from the passed-in table -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ groupBy (t, iter) -+
    -+
    -+ Splits a table into subsets groups. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • iter -+ an iterator function, prototyped as iter (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of subsets groups -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ include (t, value) -+
    -+
    -+ Performs a linear search for a value in a table. It does not work for nested tables. -+ The given value can be a function prototyped as f (v, value) which should return true when -+ any v in the table equals the value being searched. -+
    Aliased as any, some, contains -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • value -+ a value to search for -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a boolean : true when found, false otherwise -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ invoke (t, method) -+
    -+
    -+ Invokes a method on each value in a table. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • method -+ a function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the result of the call f (v, k) -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ map (t, f) -+
    -+
    -+ Maps f (v, k) on value-key pairs, collects and returns the results.
    -+ Uses pairs to iterate over elements in t. -+
    Aliased as collect. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of results -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ mapReduce (t, f[, state]) -+
    -+
    -+ Reduces a table while saving intermediate states. Folds the table left-to-right -+ using a given iterator and an initial state. The iterator takes a state and a value, -+ and returns a new state. The result is an array of intermediate states. -+
    Aliased as mapr -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    • state -+ an initial state of reduction. Defaults to the first value in the table. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array of states -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ mapReduceRight (t, f[, state]) -+
    -+
    -+ Reduces a table while saving intermediate states. Folds the table right-to-left -+ using a given iterator and an initial state. The iterator takes a state and a value, -+ and returns a new state. The result is an array of intermediate states. -+
    Aliased as maprr -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    • state -+ an initial state of reduction. Defaults to the last value in the table. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array of states -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ mapi (t, f) -+
    -+
    -+ Maps f (v, k) on value-key pairs, collects and returns the results.
    -+ Uses ipairs to iterate over elements in t. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of results -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ max (t[, transform]) -+
    -+
    -+ Returns the max value in a collection. If a transform function is passed, it will -+ be used to evaluate values by which all objects will be sorted. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • transform -+ a transformation function, prototyped as transform (v, k), defaults to identity -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the max value found -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ min (t[, transform]) -+
    -+
    -+ Returns the min value in a collection. If a transform function is passed, it will -+ be used to evaluate values by which all objects will be sorted. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • transform -+ a transformation function, prototyped as transform (v, k), defaults to identity -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the min value found -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ pluck (t, key) -+
    -+
    -+ Extracts values in a table having a given key. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • key -+ a key, will be used to index in each value: value[key] -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array of values having the given key -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ reduce (t, f[, state]) -+
    -+
    -+ Reduces a table, left-to-right. Folds the table from the first element to the last element -+ to a single value, using a given iterator and an initial state. -+ The iterator takes a state and a value and returns a new state. -+
    Aliased as inject, foldl. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    • state -+ an initial state of reduction. Defaults to the first value in the table. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the final state of reduction -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ reduceBy (t, f, pred[, state[, ...]]) -+
    -+
    -+ Reduces values in a table passing a given predicate. Folds the table left-to-right, considering -+ only values validating a given predicate. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    • pred -+ a predicate function pred (v, k) to select values to be considered for reduction -+
    • -+
    • state -+ an initial state of reduction. Defaults to the first value in the table of selected values. -+ (optional) -+
    • -+
    • ... -+ optional args to be passed to pred -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the final state of reduction -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ reduceRight (t, f[, state]) -+
    -+
    -+ Reduces a table, right-to-left. Folds the table from the last element to the first element -+ to single value, using a given iterator and an initial state. -+ The iterator takes a state and a value, and returns a new state. -+
    Aliased as injectr, foldr. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (state, value) -+
    • -+
    • state -+ an initial state of reduction. Defaults to the last value in the table. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the final state of reduction -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ reject (t, f) -+
    -+
    -+ Clones a table while dropping values passing an iterator test. -+
    Aliased as discard -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the remaining values -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ same (a, b) -+
    -+
    -+ Checks if two tables are the same. It compares if both tables features the same values, -+ but not necessarily at the same keys. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a table -+
    • -+
    • b -+ another table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ sameKeys (tA, tB) -+
    -+
    -+ Checks if both given tables have the same keys. It does not compares values. -+ -+ -+

    Parameters:

    -+
      -+
    • tA -+ a table -+
    • -+
    • tB -+ another table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ select (t, f) -+
    -+
    -+ Selects and returns values passing an iterator test. -+
    Aliased as filter. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • f -+ an iterator function, prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the selected values -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ size ([...]) -+
    -+
    -+ Counts the number of values in a collection. If being passed more than one argument -+ it will return the count of all passed-in arguments. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ Optional variable number of arguments -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a count -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ sort (t[, comp]) -+
    -+
    -+ Sorts a table, in-place. If a comparison function is given, it will be used to sort values. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • comp -+ a comparison function prototyped as comp (a, b), defaults to < operator. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the given table, sorted. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ sortBy (t[, transform[, comp]]) -+
    -+
    -+ Sorts a table in-place using a transform. Values are ranked in a custom order of the results of -+ running transform (v) on all values. transform may also be a string name property sort by. -+ comp is a comparison function. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • transform -+ a transform function to sort elements prototyped as transform (v). Defaults to identity -+ (optional) -+
    • -+
    • comp -+ a comparison function, defaults to the < operator -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new array of sorted values -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ sortedk (t[, comp]) -+
    -+
    -+ Iterates on values with respect to key order. Keys are sorted using comp function -+ which defaults to math.min. It returns upon each call a key, value pair. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • comp -+ a comparison function. Defaults to < operator -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator function -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ sortedv (t[, comp]) -+
    -+
    -+ Iterates on values with respect to values order. Values are sorted using comp function -+ which defaults to math.min. It returns upon each call a key, value pair. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • comp -+ a comparison function. Defaults to < operator -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator function -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ where (t, props) -+
    -+
    -+ Returns all values having specified keys props. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table -+
    • -+
    • props -+ a set of keys -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array of values from the passed-in table -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+

    Array functions

    -+ -+
    -+
    -+ -+ addTop (array, ...) -+
    -+
    -+ Adds all passed-in values at the top of an array. The last elements will bubble to the -+ top of the given array. -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • ... -+ a variable number of arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the passed-in array with new values added -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ aperture (array[, n]) -+
    -+
    -+ Iterator returning sliding partitions of an array. -+
    Aliased as sliding -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • n -+ the size of partitions. Defaults to 2 (and then behaves like pairwise) -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator function -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ append (array, other) -+
    -+
    -+ Clones array and appends values from another array. -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • other -+ an array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ chunk (array, f) -+
    -+
    -+ Chunks together consecutive values. Values are chunked on the basis of the return -+ value of a provided predicate f (v, k). Consecutive elements which return -+ the same value are chunked together. Leaves the first argument untouched if it is not an array. -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • f -+ an iterator function prototyped as f (v, k). Defaults to identity. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of chunks (arrays) -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ compact (array) -+
    -+
    -+ Returns all truthy values (removes falses and nils). -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ concat (array[, sep[, i[, j]]]) -+
    -+
    -+ Concatenates values in a given array. Handles booleans as well. If sep string is -+ passed, it will be used as a separator. Passing i and j will result in concatenating -+ only values within [i, j] range. -+
    Aliased as join -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ a given array -+
    • -+
    • sep -+ a separator string, defaults to the empty string ''. -+ (optional) -+
    • -+
    • i -+ the starting index, defaults to 1. -+ (optional) -+
    • -+
    • j -+ the final index, defaults to the array length. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ difference (array, another) -+
    -+
    -+ Returns values from an array not present in all passed-in args. -+
    Aliased as without and diff -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • another -+ array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new array -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ disjoint (...) -+
    -+
    -+ Checks if all passed in arrays are disjunct. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ a variable number of arrays -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the intersection of all arrays is not empty, false otherwise. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ dropWhile (array, f) -+
    -+
    -+ Collects values from a given array. The passed-in array should not be sparse. -+ This function collects values as long as they do not satisfy a given predicate and returns on the first truthy test. -+
    Aliased as rejectWhile -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    • f -+ an iterator function prototyped as f (v, k) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new table containing all values collected -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ duplicates (array) -+
    -+
    -+ Returns an array list of all duplicates in array. -+ -+ -+

    Parameters:

    -+
      -+
    • array -+ an array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array-list of duplicates -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ fill (array, value[, i[, j]]) -
    -
    -- Clears a table. All its values become nil. -+ Replaces elements in a given array with a given value. In case i and j are given -+ it will only replaces values at indexes between [i,j]. In case j is greater than the array -+ size, it will append new values, increasing the array size. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -+
    • -+
    • value -+ a value -+
    • -+
    • i -+ the index from which to start replacing values. Defaults to 1. -+ (optional) -+
    • -+
    • j -+ the index where to stop replacing values. Defaults to the array size. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- the given table, cleared. -+ the original array with values changed -
    - - -@@ -668,114 +3103,122 @@ - -
    -
    -- -- each (t, f[, ...]) -+ -+ find (array, value[, from]) -
    -
    -- Iterates on key-value pairs, calling f (k, v) at every step. --
    Aliased as forEach. -+ Looks for the first occurrence of a given value in an array. Returns the value index if found. -+ Uses isEqual to compare values. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array of values -
    • --
    • f -- a function, prototyped as f (k, v, …) -+
    • value -+ a value to lookup for -
    • --
    • ... -- Optional args to be passed to f -+
    • from -+ the index from where the search will start. Defaults to 1. - (optional) -
    • -
    - -+

    Returns:

    -+
      -+ -+ the index of the value if found in the array, nil otherwise. -+
    - - -

    See also:

    - - - -
    -
    -- -- eachi (t, f[, ...]) -+ -+ findIndex (array, pred) -
    -
    -- Iterates on integer key-value pairs, calling f(k, v) every step.
    -- Only applies to values located at integer keys. The table can be a sparse array. -- Iteration will start from the lowest integer key found to the highest one. --
    Aliased as forEachi. -+ Returns the first index at which a predicate returns true. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- a function, prototyped as f (k, v, …) -+
    • array -+ an array -
    • --
    • ... -- Optional args to be passed to f -- (optional) -+
    • pred -+ a predicate function prototyped as pred (v, k) -
    • -
    - -+

    Returns:

    -+
      -+ -+ the index found or nil -+
    - - -

    See also:

    - - - -
    -
    -- -- at (t, ...) -+ -+ findLastIndex (array, pred) -
    -
    -- Collects values at given keys and return them wrapped in an array. -+ Returns the last index at which a predicate returns true. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • ... -- A variable number of keys to collect values -+
    • pred -+ a predicate function prototyped as pred (k, v) -
    • -
    - -

    Returns:

    -
      - -- an array-list of values -+ the index found or nil -
    - - -+

    See also:

    -+ - - -
    -
    -- -- count (t[, value]) -+ -+ first (array[, n]) -
    -
    -- Counts occurrences of a given value in a table. Uses isEqual to compare values. -+ Returns the first N values in an array. -+
    Aliased as head, take - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • value -- a value to be searched in the table. If not given, the size of the table will be returned -+
    • n -+ the number of values to be collected, defaults to 1. - (optional) -
    • -
    -@@ -783,37 +3226,34 @@ -

    Returns:

    -
      - -- the count of occurrences of the given value -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- countf (t, f[, ...]) -+ -+ flatten (array[, shallow]) -
    -
    -- Counts occurrences validating a predicate. Same as count, but uses an iterator. -- Returns the count for values passing the test f (k, v, …) -+ Flattens a nested array. Passing shallow will only flatten at the first level. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (k, v, …) -+
    • array -+ an array -
    • --
    • ... -- Optional args to be passed to f -+
    • shallow -+ specifies the flattening depth. Defaults to false.` - (optional) -
    • -
    -@@ -821,67 +3261,60 @@ -

    Returns:

    -
      - -- the count of values validating the predicate -+ a flattened array -
    - - --

    See also:

    -- - - -
    -
    -- -- cycle (t, n) -+ -+ indexOf (array, value) -
    -
    -- Loops n times through a table. In case n is omitted, it will loop forever. -- In case n is lower or equal to 0, it returns an empty function. --
    Aliased as loop. -+ Returns the index of the first occurrence of value in an array. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • n -- the number of loops -+
    • value -+ the value to search for -
    • -
    - -

    Returns:

    -
      - -- an iterator function yielding key-value pairs from the passed-in table. -+ the index of the passed-in value -
    - - -+

    See also:

    -+ - - -
    -
    -- -- map (t, f[, ...]) -+ -+ initial (array[, n]) -
    -
    -- Maps f (k, v) on key-value pairs, collects and returns the results. --
    Aliased as collect. -+ Returns all values in an array excluding the last N values. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (k, v, …) -+
    • array -+ an array -
    • --
    • ... -- Optional args to be passed to f -+
    • n -+ the number of values to be left, defaults to the array length. - (optional) -
    • -
    -@@ -889,195 +3322,161 @@ -

    Returns:

    -
      - -- a table of results -+ a new array -
    - - -+

    See also:

    -+ - - -
    -
    -- -- reduce (t, f[, state]) -+ -+ interleave (...) -
    -
    -- Reduces a table, left-to-right. Folds the table from the first element to the last element -- to a single value, using a given iterator and an initial state. -- The iterator takes a state and a value and returns a new state. --
    Aliased as inject, foldl. -+ Interleaves arrays. It returns a single array made of values from all -+ passed in arrays in their given order, interleaved. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (state, value) --
    • --
    • state -- an initial state of reduction. Defaults to the first value in the table. -- (optional) -+
    • ... -+ a variable list of arrays -
    • -
    - -

    Returns:

    -
      - -- the final state of reduction -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- reduceby (t, f, state, pred[, ...]) -+ -+ interpose (array, value) -
    -
    -- Reduces values in a table passing a given predicate. Folds the table left-to-right, considering -- only values validating a given predicate. -+ Interposes value in-between consecutive pair of values in array. -+
    Aliased as intersperse - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (state, value) --
    • --
    • state -- an initial state of reduction. --
    • --
    • pred -- a predicate function pred (k, v, …) to select values to be considered for reduction -+
    • array -+ an array -
    • --
    • ... -- optional args to be passed to pred -- (optional) -+
    • value -+ a value -
    • -
    - -

    Returns:

    -
      - -- the final state of reduction -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- reduceRight (t, f[, state]) -+ -+ intersection (...) -
    -
    -- Reduces a table, right-to-left. Folds the table from the last element to the first element -- to single value, using a given iterator and an initial state. -- The iterator takes a state and a value, and returns a new state. --
    Aliased as injectr, foldr. -+ Returns the intersection of all passed-in arrays. -+ Each value in the result is present in each of the passed-in arrays. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (state, value) --
    • --
    • state -- an initial state of reduction. Defaults to the last value in the table. -- (optional) -+
    • ... -+ a variable number of array arguments -
    • -
    - -

    Returns:

    -
      - -- the final state of reduction -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- mapReduce (t, f[, state]) -+ -+ isunique (array) -
    -
    -- Reduces a table while saving intermediate states. Folds the table left-to-right -- using a given iterator and an initial state. The iterator takes a state and a value, -- and returns a new state. The result is an array of intermediate states. --
    Aliased as mapr -+ Checks if a given array contains distinct values. Such an array is made of distinct elements, -+ which only occur once in this array. -+
    Aliased as isuniq - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (state, value) --
    • --
    • state -- an initial state of reduction. Defaults to the first value in the table. -- (optional) -+
    • array -+ an array -
    • -
    - -

    Returns:

    -
      - -- an array of states -+ true if the given array is unique, false otherwise. -
    - - -

    See also:

    - - - -
    -
    -- -- mapReduceRight (t, f[, state]) -+ -+ last (array[, n]) -
    -
    -- Reduces a table while saving intermediate states. Folds the table right-to-left -- using a given iterator and an initial state. The iterator takes a state and a value, -- and returns a new state. The result is an array of intermediate states. --
    Aliased as maprr -+ Returns the last N values in an array. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (state, value) -+
    • array -+ an array -
    • --
    • state -- an initial state of reduction. Defaults to the last value in the table. -+
    • n -+ the number of values to be collected, defaults to the array length. - (optional) -
    • -
    -@@ -1085,243 +3484,227 @@ -

    Returns:

    -
      - -- an array of states -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- include (t, value) -+ -+ lastIndexOf (array, value) -
    -
    -- Performs a linear search for a value in a table. It does not work for nested tables. -- The given value can be a function prototyped as f (v, value) which should return true when -- any v in the table equals the value being searched. --
    Aliased as any, some, contains -+ Returns the index of the last occurrence of value in an array. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • -
    • value -- a value to search for -+ the value to search for -
    • -
    - -

    Returns:

    -
      - -- a boolean : true when found, false otherwise -+ the index of the last occurrence of the passed-in value or nil -
    - - -

    See also:

    - - - -
    -
    -- -- detect (t, value) -+ -+ mean (array) -
    -
    -- Performs a linear search for a value in a table. Returns the key of the value if found. -- The given value can be a function prototyped as f (v, value) which should return true when -- any v in the table equals the value being searched. -+ Returns the mean of an array of numbers. -+
    Aliased as average - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • value -- a value to search for -+
    • array -+ an array of numbers -
    • -
    - -

    Returns:

    -
      - -- the key of the value when found or nil -+ a number -
    - - -

    See also:

    - - - -
    -
    -- -- where (t, props) -+ -+ median (array) -
    -
    -- Returns all values having specified keys props. -+ Returns the median of an array of numbers. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • props -- a set of keys -+
    • array -+ an array of numbers -
    • -
    - -

    Returns:

    -
      - -- an array of values from the passed-in table -+ a number -
    - - -

    See also:

    - - - -
    -
    -- -- findWhere (t, props) -+ -+ nsorted (array[, n[, comp]]) -
    -
    -- Returns the first value having specified keys props. -+ Returns the n-top values satisfying a predicate. It takes a comparison function -+ comp used to sort array values, and then picks the top n-values. It leaves the original array untouched. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • props -- a set of keys -+
    • n -+ a number of values to retrieve. Defaults to 1. -+ (optional) -+
    • -+
    • comp -+ a comparison function. Defaults to < operator. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a value from the passed-in table -+ an array of top n values -
    - - --

    See also:

    -- - - -
    -
    -- -- select (t, f[, ...]) -+ -+ nth (array, index) -
    -
    -- Selects and returns values passing an iterator test. --
    Aliased as filter. -+ Returns the value at a given index. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (k, v, …) -+
    • array -+ an array -
    • --
    • ... -- Optional args to be passed to f -- (optional) -+
    • index -+ an index -
    • -
    - -

    Returns:

    -
      - -- the selected values -+ the value at the given index -
    - - --

    See also:

    -- - - -
    -
    -- -- reject (t, f[, ...]) -+ -+ ones (n) -
    -
    -- Clones a table while dropping values passing an iterator test. --
    Aliased as discard -+ Returns an array of n 1's. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • f -- an iterator function, prototyped as f (k, v, …) --
    • --
    • ... -- Optional args to be passed to f -- (optional) -+
    • n -+ a number -
    • -
    - -

    Returns:

    -
      - -- the remaining values -+ an array -
    - - -

    See also:

    - - - -
    -
    -- -- all (t, f[, ...]) -+ -+ overlapping (array[, n[, pads]]) -
    -
    -- Checks if all values in a table are passing an iterator test. --
    Aliased as every -+ Iterator returning overlapping partitions of an array.
    -+ If the last subsequence has lower elements than n and pad is -+ supplied, it will be adjusted to n elements with pad value. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • f -- an iterator function, prototyped as f (k, v, …) -+
    • n -+ the size of partitions. Defaults to 2. -+ (optional) -
    • --
    • ... -- Optional args to be passed to f -+
    • pads -+ a value to adjust the last subsequence to the n elements - (optional) -
    • -
    -@@ -1329,97 +3712,93 @@ -

    Returns:

    -
      - -- true if all values passes the predicate, false otherwise -+ an iterator function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- invoke (t, method[, ...]) -+ -+ pack (...) -
    -
    -- Invokes a method on each value in a table. -+ Converts a list of arguments to an array. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • method -- a function, prototyped as f (v, …) --
    • -
    • ... -- Optional args to be passed to method -- (optional) -+ a list of arguments -
    • -
    - -

    Returns:

    -
      - -- the result of the call f (v, …) -+ an array of all passed-in args -
    - - --

    See also:

    -- - - -
    -
    -- -- pluck (t, key) -+ -+ pairwise (array) -
    -
    -- Extracts values in a table having a given key. -+ Iterator returning sliding pairs of an array. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • key -- a key, will be used to index in each value: value[key] -+
    • array -+ an array -
    • -
    - -

    Returns:

    -
      - -- an array of values having the given key -+ an iterator function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- max (t[, transform[, ...]]) -+ -+ partition (array[, n[, pads]]) -
    -
    -- Returns the max value in a collection. If an transformation function is passed, it will -- be used to evaluate values by which all objects will be sorted. -+ Iterator returning partitions of an array. It returns arrays of length n -+ made of values from the given array. If the last partition has lower elements than n and -+ pad is supplied, it will be adjusted to n of elements with pad value. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • transform -- a transformation function, prototyped as transform (v, …), defaults to identity -+
    • n -+ the size of partitions. Defaults to 1. - (optional) -
    • --
    • ... -- Optional args to be passed to transform -+
    • pads -+ a value to adjust the last subsequence to the n elements - (optional) -
    • -
    -@@ -1427,108 +3806,123 @@ -

    Returns:

    -
      - -- the max value found -+ an iterator function -
    - - -

    See also:

    - - - -
    -
    -- -- min (t[, transform[, ...]]) -+ -+ permutation (array) -
    -
    -- Returns the min value in a collection. If an transformation function is passed, it will -- be used to evaluate values by which all objects will be sorted. -+ Iterator returning the permutations of an array. It returns arrays made of all values -+ from the passed-in array, with values permuted. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • transform -- a transformation function, prototyped as transform (v, …), defaults to identity -- (optional) --
    • --
    • ... -- Optional args to be passed to transform -- (optional) -+
    • array -+ an array -
    • -
    - -

    Returns:

    -
      - -- the min value found -+ an iterator function -
    - - --

    See also:

    -+ -+ -+
    -+
    -+ -+ powerset (array) -+
    -+
    -+ Returns the powerset of array values. For instance, when given the set {1,2,3}, -+ returns {{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}}. -+ -+ -+

    Parameters:

    -
      -- max -+
    • array -+ an array -+
    • -
    - -+

    Returns:

    -+
      -+ -+ an array -+
    -+ -+ -+ - -
    -
    -- -- shuffle (t[, seed]) -+ -+ prepend (array, ...) -
    -
    -- Returns a shuffled copy of a given collection. If a seed is provided, it will -- be used to init the pseudo random number generator (using math.randomseed). -+ Adds all passed-in values at the top of an array. As opposed to addTop, it preserves the order -+ of the passed-in elements. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • seed -- a seed -- (optional) -+
    • ... -+ a variable number of arguments -
    • -
    - -

    Returns:

    -
      - -- a shuffled copy of the given table -+ the passed-in array with new values added -
    - - -+

    See also:

    -+ - - -
    -
    -- -- same (a, b) -+ -+ product (array) -
    -
    -- Checks if two tables are the same. It compares if both tables features the same values, -- but not necessarily at the same keys. -+ Returns the product of array values. - - -

    Parameters:

    -
      --
    • a -- a table --
    • --
    • b -- another table -+
    • array -+ a given array -
    • -
    - -

    Returns:

    -
      - -- true or false -+ the product of array values -
    - - -@@ -1536,95 +3930,89 @@ - -
    -
    -- -- sort (t[, comp]) -+ -+ pull (array, ...) -
    -
    -- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. -+ Removes all provided values in a given array. -+
    Aliased as remove - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • comp -- a comparison function prototyped as comp (a, b), defaults to < operator. -- (optional) -+
    • ... -+ a variable number of values to be removed from the array -
    • -
    - -

    Returns:

    -
      - -- the initial table, sorted. -+ the passed-in array with values removed -
    - - --

    See also:

    -- - - -
    -
    -- -- sortBy (t[, transform[, comp]]) -+ -+ push (array, ...) -
    -
    -- Sorts a table in-place using a transform. Values are ranked in a custom order of the results of -- running transform (v) on all values. transform may also be a string name property sort by. -- comp is a comparison function. -+ Pushes all passed-in values at the end of an array. - - -

    Parameters:

    -
      --
    • t -- a table --
    • --
    • transform -- a transform function to sort elements prototyped as transform (v). Defaults to identity -- (optional) -+
    • array -+ an array -
    • --
    • comp -- a comparision function, defaults to the < operator -- (optional) -+
    • ... -+ a variable number of arguments -
    • -
    - -

    Returns:

    -
      - -- a new array of sorted values -+ the passed-in array with new added values -
    - - -

    See also:

    - - - -
    -
    -- -- groupBy (t, iter[, ...]) -+ -+ range ([from[, to[, step]]]) -
    -
    -- Splits a table into subsets groups. -+ Produces a flexible list of numbers. If one value is passed, will count from 1 to that value, -+ with a default step of 1 (or -1). If two values are passed, will count from the first one to the second one, -+ using a default step of 1 (or -1). A third value passed will be considered a step value. - - -

    Parameters:

    -
      --
    • t -- a table -+
    • from -+ the initial value of the range -+ (optional) -
    • --
    • iter -- an iterator function, prototyped as iter (k, v, …) -+
    • to -+ the final value of the range -+ (optional) -
    • --
    • ... -- Optional args to be passed to iter -+
    • step -+ the step of count. Defaults to 1 or -1. - (optional) -
    • -
    -@@ -1632,7 +4020,7 @@ -

    Returns:

    -
      - -- a table of subsets groups -+ a new array of numbers -
    - - -@@ -1640,23 +4028,25 @@ - -
    -
    -- -- countBy (t, iter[, ...]) -+ -+ removeRange (array[, start[, finish]]) -
    -
    -- Groups values in a collection and counts them. -+ Removes values at an index within the range [start, finish]. -+
    Aliased as rmRange, chop - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • iter -- an iterator function, prototyped as iter (k, v, …) -+
    • start -+ the lower bound index, defaults to the first index in the array. -+ (optional) -
    • --
    • ... -- Optional args to be passed to iter -+
    • finish -+ the upper bound index, defaults to the array length. - (optional) -
    • -
    -@@ -1664,7 +4054,7 @@ -

    Returns:

    -
      - -- a table of subsets groups names paired with their count -+ the passed-in array with values removed -
    - - -@@ -1672,107 +4062,94 @@ - -
    -
    -- -- size ([...]) -+ -+ rep (value, n) -
    -
    -- Counts the number of values in a collection. If being passed more than one argument -- it will return the count of all passed-in arguments. -+ Creates an array list of n values, repeated. - - -

    Parameters:

    -
      --
    • ... -- Optional variable number of arguments -- (optional) -+
    • value -+ a value to be repeated -+
    • -+
    • n -+ the number of repetitions of value. -
    • -
    - -

    Returns:

    -
      - -- a count -+ a new array of n values -
    - - --

    See also:

    -- - - -
    -
    -- -- containsKeys (t, other) -+ -+ rest (array[, index]) -
    -
    -- Checks if all the keys of other table exists in table t. It does not -- compares values. The test is not commutative, i.e table t may contains keys -- not existing in other. -+ Returns all values after index. -+
    Aliased as tail - - -

    Parameters:

    -
      --
    • t -- a table -+
    • array -+ an array -
    • --
    • other -- another table -+
    • index -+ an index, defaults to 1 -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- true or false -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- sameKeys (tA, tB) -+ -+ reverse (array) -
    -
    -- Checks if both given tables have the same keys. It does not compares values. -+ Returns an array where values are in reverse order. The passed-in array should not be sparse. - - -

    Parameters:

    -
      --
    • tA -- a table --
    • --
    • tB -- another table -+
    • array -+ an array -
    • -
    - -

    Returns:

    -
      - -- true or false -+ a reversed array -
    - - --

    See also:

    -- - - -
    --
    --

    Array functions

    -- --
    -
    - - sample (array[, n[, seed]]) -@@ -1801,7 +4178,7 @@ -

    Returns:

    -
      - -- an array of selected values or a single value when n == 1 -+ an array of selected values -
    - - -@@ -1851,96 +4228,46 @@ - - -
    -- -- toArray (...) --
    --
    -- Converts a list of arguments to an array. -- -- --

    Parameters:

    --
      --
    • ... -- a list of arguments --
    • --
    -- --

    Returns:

    --
      -- -- an array of all passed-in args --
    -- -- -- -- --
    --
    -- -- find (array, value[, from]) -+ -+ selectWhile (array, f) -
    -
    -- Looks for the first occurrence of a given value in an array. Returns the value index if found. -- Uses isEqual to compare values. -+ Collects values from a given array. The passed-in array should not be sparse. -+ This function collects values as long as they satisfy a given predicate and returns on the first falsy test. -+
    Aliased as takeWhile - - -

    Parameters:

    -
      -
    • array -- an array of values --
    • --
    • value -- a value to lookup for -+ an array -
    • --
    • from -- the index from where the search will start. Defaults to 1. -- (optional) -+
    • f -+ an iterator function prototyped as f (v, k) -
    • -
    - -

    Returns:

    -
      - -- the index of the value if found in the array, nil otherwise. -+ a new table containing all values collected -
    - - -- -- --
    --
    -- -- reverse (array) --
    --
    -- Returns an array where values are in reverse order. The passed-in array should not be sparse. -- -- --

    Parameters:

    -+

    See also:

    - - --

    Returns:

    --
      -- -- a reversed array --
    -- -- -- - -
    -
    -- -- fill (array, value[, i[, j]]) -+ -+ shift (array[, n]) -
    -
    -- Replaces elements in a given array with a given value. In case i and j are given -- it will only replaces values at indexes between [i,j]. In case j is greather than the array -- size, it will append new values, increasing the array. -+ Removes and returns the values at the top of a given array. -+
    Aliased as pop - - -

    Parameters:

    -@@ -1948,15 +4275,8 @@ -
  • array - an array -
  • --
  • value -- a value --
  • --
  • i -- the index from which to start replacing values. Defaults to 1. -- (optional) --
  • --
  • j -- the index where to stop replacing values. Defaults to the array size. -+
  • n -+ the number of values to be popped. Defaults to 1. - (optional) -
  • - -@@ -1964,21 +4284,24 @@ -

    Returns:

    -
      - -- the original array with values changed -+ the popped values -
    - - -+

    See also:

    -+ - - -
    -
    -- -- selectWhile (array, f[, ...]) -+ -+ shuffle (array[, seed]) -
    -
    -- Collects values from a given array. The passed-in array should not be sparse. -- This function collects values as long as they satisfy a given predicate and returns on the first falsy test. --
    Aliased as takeWhile -+ Returns a shuffled copy of a given array. If a seed is provided, it will -+ be used to init the built-in pseudo random number generator (using math.randomseed). - - -

    Parameters:

    -@@ -1986,11 +4309,8 @@ -
  • array - an array -
  • --
  • f -- an iterator function prototyped as f (k, v, …) --
  • --
  • ... -- Optional args to be passed to f -+
  • seed -+ a seed - (optional) -
  • - -@@ -1998,25 +4318,20 @@ -

    Returns:

    -
      - -- a new table containing all values collected -+ a shuffled copy of the given array -
    - - --

    See also:

    -- - - -
    -
    -- -- dropWhile (array, f[, ...]) -+ -+ slice (array[, start[, finish]]) -
    -
    -- Collects values from a given array. The passed-in array should not be sparse. -- This function collects values as long as they do not satisfy a given predicate and returns on the first truthy test. --
    Aliased as rejectWhile -+ Slices values indexed within [start, finish] range. -+
    Aliased as M.sub - - -

    Parameters:

    -@@ -2024,11 +4339,12 @@ -
  • array - an array -
  • --
  • f -- an iterator function prototyped as f (k,v, …) -+
  • start -+ the lower bound index, defaults to the first index in the array. -+ (optional) -
  • --
  • ... -- Optional args to be passed to f -+
  • finish -+ the upper bound index, defaults to the array length. - (optional) -
  • - -@@ -2036,7 +4352,7 @@ -

    Returns:

    -
      - -- a new table containing all values collected -+ a new array of sliced values -
    - - -@@ -2082,75 +4398,38 @@ - -
    -
    -- -- indexOf (array, value) --
    --
    -- Returns the index of the first occurence of value in an array. -- -- --

    Parameters:

    --
      --
    • array -- an array --
    • --
    • value -- the value to search for --
    • --
    -- --

    Returns:

    --
      -- -- the index of the passed-in value --
    -- -- --

    See also:

    -- -- -- --
    --
    -- -- lastIndexOf (array, value) -+ -+ sum (array) -
    -
    -- Returns the index of the last occurrence of value in an array. -+ Returns the sum of array values. - - -

    Parameters:

    -
      -
    • array -- an array --
    • --
    • value -- the value to search for -+ a given array -
    • -
    - -

    Returns:

    -
      - -- the index of the last occurrence of the passed-in value or nil -+ the sum of array values -
    - - --

    See also:

    -- - - -
    -
    -- -- findIndex (array, predicate[, ...]) -+ -+ symmetricDifference (array, array2) -
    -
    -- Returns the first index at which a predicate returns true. -+ Performs a symmetric difference. Returns values from array not present in array2 and also values -+ from array2 not present in array. -+
    Aliased as symdiff - - -

    Parameters:

    -@@ -2158,72 +4437,65 @@ -
  • array - an array -
  • --
  • predicate -- a predicate function prototyped as predicate (k, v, …) --
  • --
  • ... -- optional arguments to pred -- (optional) -+
  • array2 -+ another array -
  • - - -

    Returns:

    -
      - -- the index found or nil -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- findLastIndex (array, predicate[, ...]) -+ -+ union (...) -
    -
    -- Returns the last index at which a predicate returns true. -+ Returns the duplicate-free union of all passed in arrays. - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • predicate -- a predicate function prototyped as predicate (k, v, …) --
    • -
    • ... -- optional arguments to pred -- (optional) -+ a variable number of arrays arguments -
    • -
    - -

    Returns:

    -
      - -- the index found or nil -+ a new array -
    - - -

    See also:

    - - - -
    -
    -- -- addTop (array, ...) -+ -+ unique (array) -
    -
    -- Adds all passed-in values at the top of an array. The last elements will bubble to the -- top of the given array. -+ Produces a duplicate-free version of a given array. -+
    Aliased as uniq - - -

    Parameters:

    -@@ -2231,31 +4503,29 @@ -
  • array - an array -
  • --
  • ... -- a variable number of arguments --
  • - - -

    Returns:

    -
      - -- the passed-in array with new values added -+ a new array, duplicate-free -
    - - -

    See also:

    - - - -
    -
    -- -- push (array, ...) -+ -+ unshift (array[, n]) -
    -
    -- Pushes all passed-in values at the end of an array. -+ Removes and returns the values at the end of a given array. - - -

    Parameters:

    -@@ -2263,115 +4533,109 @@ -
  • array - an array -
  • --
  • ... -- a variable number of arguments -+
  • n -+ the number of values to be unshifted. Defaults to 1. -+ (optional) -
  • - - -

    Returns:

    -
      - -- the passed-in array with new added values -+ the values -
    - - -

    See also:

    - - - -
    -
    -- -- pop (array[, n]) -+ -+ vector (value, n) -
    -
    -- Removes and returns the values at the top of a given array. --
    Aliased as shift -+ Returns an array of n times a given value. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • value -+ a value -
    • -
    • n -- the number of values to be popped. Defaults to 1. -- (optional) -+ a number -
    • -
    - -

    Returns:

    -
      - -- the popped values -+ an array -
    - - -

    See also:

    - - - -
    -
    -- -- unshift (array[, n]) -+ -+ xpairs (valua, array) -
    -
    -- Removes and returns the values at the end of a given array. -+ Creates pairs from value and array. Value is always prepended to the pair. - - -

    Parameters:

    -
      -+
    • valua -+ a value -+
    • -
    • array - an array -
    • --
    • n -- the number of values to be unshifted. Defaults to 1. -- (optional) --
    • -
    - -

    Returns:

    -
      - -- the values -+ an array list of all pairs -
    - - --

    See also:

    -- - - -
    -
    -- -- pull (array, ...) -+ -+ xpairsRight (valua, array) -
    -
    -- Removes all provided values in a given array. --
    Aliased as remove -+ Creates pairs from value and array. Value is always appended as the last item to the pair. - - -

    Parameters:

    -
      -+
    • valua -+ a value -+
    • -
    • array - an array -
    • --
    • ... -- a variable number of values to be removed from the array --
    • -
    - -

    Returns:

    -
      - -- the passed-in array with values removed -+ an array list of all pairs -
    - - -@@ -2379,33 +4643,27 @@ - -
    -
    -- -- removeRange (array[, start[, finish]]) -+ -+ xprod (array, array2) -
    -
    -- Removes values at index within the range [start, finish]. --
    Aliased as rmRange, chop -+ Returns all possible pairs built from given arrays. - - -

    Parameters:

    -
      -
    • array -- an array --
    • --
    • start -- the lower bound index, defaults to the first index in the array. -- (optional) -+ a first array -
    • --
    • finish -- the upper bound index, defaults to the array length. -- (optional) -+
    • array2 -+ a second array -
    • -
    - -

    Returns:

    -
      - -- the passed-in array with values removed -+ an array list of all pairs -
    - - -@@ -2413,199 +4671,182 @@ - -
    -
    -- -- chunk (array, f[, ...]) -+ -+ zeros (n) -
    -
    -- Chunks together consecutive values. Values are chunked on the basis of the return -- value of a provided predicate f (k, v, …). Consecutive elements which return -- the same value are chunked together. Leaves the first argument untouched if it is not an array. -+ Returns an array of n zeros. - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • f -- an iterator function prototyped as f (k, v, …) --
    • --
    • ... -- Optional args to be passed to f -- (optional) -+
    • n -+ a number -
    • -
    - -

    Returns:

    -
      - -- a table of chunks (arrays) -+ an array -
    - - -

    See also:

    - - - -
    -
    -- -- slice (array[, start[, finish]]) -+ -+ zip (...) -
    -
    -- Slices values indexed within [start, finish] range. --
    Aliased as _.sub -+ Merges values of each of the passed-in arrays in subsets. -+ Only values indexed with the same key in the given arrays are merged in the same subset. -+
    Aliased as transpose - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • start -- the lower bound index, defaults to the first index in the array. -- (optional) --
    • --
    • finish -- the upper bound index, defaults to the array length. -- (optional) -+
    • ... -+ a variable number of array arguments -
    • -
    - -

    Returns:

    -
      - -- a new array of sliced values -+ a new array -
    - - -+

    See also:

    -+ - - -
    -
    -- -- first (array[, n]) -+ -+ zipWith (f, ...) -
    -
    -- Returns the first N values in an array. --
    Aliased as head, take -+ Merges values using a given function. -+ Only values indexed with the same key in the given arrays are merged in the same subset. -+ Function f is used to combine values. -+
    Aliased as transposeWith - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • n -- the number of values to be collected, defaults to 1. -- (optional) -+
    • ... -+ a variable number of array arguments -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a flat array of results -
    - - -

    See also:

    - - - -
    -+
    -+

    Utility functions

    -+ -+
    -
    -- -- initial (array[, n]) -+ -+ after (f, count) -
    -
    -- Returns all values in an array excluding the last N values. -+ Returns a version of f that runs on the count-th call. -+ Useful when dealing with asynchronous tasks. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • n -- the number of values to be left, defaults to the array length. -- (optional) -+
    • count -+ the number of calls before f will start running. -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a new function -
    - - -

    See also:

    - - - -
    -
    -- -- last (array[, n]) -+ -+ applySpec (specs) -
    -
    -- Returns the last N values in an array. -+ Returns a function which applies specs on args. This function produces an object having -+ the same structure than specs by mapping each property to the result of calling its -+ associated function with the supplied arguments - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • n -- the number of values to be collected, defaults to the array length. -- (optional) -+
    • specs -+ a table -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a function -
    - - --

    See also:

    -- - - -
    -
    -- -- rest (array[, index]) -+ -+ ary (f[, n]) -
    -
    -- Removes all values before index. --
    Aliased as tail -+ Returns a function which accepts up to n args. It ignores any additional arguments. -+
    Aliased as nAry. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • index -- an index, defaults to 1 -+
    • n -+ a number. Defaults to 1. - (optional) -
    • -
    -@@ -2613,348 +4854,347 @@ -

    Returns:

    -
      - -- a new array -+ a function -
    - - -

    See also:

    - - - -
    -
    -- -- nth (array, index) -+ -+ before (f, count) -
    -
    -- Returns the value at a given index. -+ Returns a version of f that will run no more than count times. Next calls will -+ keep yielding the results of the count-th call. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • index -- an index -+
    • count -+ a count -
    • -
    - -

    Returns:

    -
      - -- the value at the given index -+ a new function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- compact (array) -+ -+ bind (f, v) -
    -
    -- Removes all falsy (false and nil) values. -+ Binds v to be the first argument to f. Calling f (...) will result to f (v, ...). - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -+
    • -+
    • v -+ a value -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- flatten (array[, shallow]) -+ -+ bind2 (f, v) -
    -
    -- Flattens a nested array. Passing shallow will only flatten at the first level. -+ Binds v to be the second argument to f. Calling f (a, ...) will result to f (a, v, ...). - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • shallow -- specifies the flattening depth -- (optional) -+
    • v -+ a value -
    • -
    - -

    Returns:

    -
      - -- a new array, flattened -+ a function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- difference (array, another) -+ -+ bindall (obj, ...) -
    -
    -- Returns values from an array not present in all passed-in args. --
    Aliased as without and diff -+ Binds methods to object. As such, whenever any of these methods is invoked, it -+ always receives the object as its first argument. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • obj -+ an abject -
    • --
    • another -- array -+
    • ... -+ a variable number of method names -
    • -
    - -

    Returns:

    -
      - -- a new array -+ the passed-in object with all methods bound to the object itself. -
    - - -

    See also:

    - - - -
    -
    -- -- union (...) -+ -+ bindn (f, ...) -
    -
    -- Returns the duplicate-free union of all passed in arrays. -+ Binds ... to be the N-first arguments to function f.
    -+ Calling f (a1, a2, ..., aN) will result to f (..., a1, a2, ...,aN). - - -

    Parameters:

    -
      -+
    • f -+ a function -+
    • -
    • ... -- a variable number of arrays arguments -+ a variable number of arguments -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a function -
    - - -

    See also:

    - - - -
    -
    -- -- intersection (array, ...) -+ -+ both (...) -
    -
    -- Returns the intersection of all passed-in arrays. -- Each value in the result is present in each of the passed-in arrays. -+ Returns a validation function. Given a set of functions, the validation function evaluates -+ to true only when all its funcs returns true. - - -

    Parameters:

    -
      --
    • array -- an array --
    • -
    • ... -- a variable number of array arguments -+ an array list of functions -
    • -
    - -

    Returns:

    -
      - -- a new array -+ true when all given funcs returns true with input, false otherwise -
    - - --

    See also:

    -- - - -
    -
    -- -- symmetricDifference (array, array2) -+ -+ call (f[, ...]) -
    -
    -- Performs a symmetric difference. Returns values from array not present in array2 and also values -- from array2 not present in array. --
    Aliased as symdiff -+ Calls f with the supplied arguments. Returns the results of f(...). - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • --
    • array2 -- another array -+
    • ... -+ a vararg list of args to f -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a new array -+ the result of f(...) call. -
    - - --

    See also:

    -- - - -
    -
    -- -- unique (array) -+ -+ castArray (value) -
    -
    -- Produces a duplicate-free version of a given array. --
    Aliased as uniq -+ Casts value as an array if it is not one. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • value -+ a value -
    • -
    - -

    Returns:

    -
      - -- a new array, duplicate-free -+ an array containing the given value -
    - - --

    See also:

    -- - - -
    -
    -- -- isunique (array) -+ -+ complement (f) -
    -
    -- Checks if a given array contains distinct values. Such an array is made of distinct elements, -- which only occur once in this array. --
    Aliased as isuniq -+ Returns the logical complement of a given function. For a given input, the returned -+ function will output false if the original function would have returned true, -+ and vice-versa. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • f -+ a function -
    • -
    - -

    Returns:

    -
      - -- true if the given array is unique, false otherwise. -+ the logical complement of the given function f. -
    - - --

    See also:

    -- - - -
    -
    -- -- zip (...) -+ -+ compose (...) -
    -
    -- Merges values of each of the passed-in arrays in subsets. -- Only values indexed with the same key in the given arrays are merged in the same subset. --
    Aliased as transpose -+ Composes functions. Each passed-in function consumes the return value of the function that follows. -+ In math terms, composing the functions f, g, and h produces the function f(g(h(...))). - - -

    Parameters:

    -
      -
    • ... -- a variable number of array arguments -+ a variable number of functions -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a new function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- append (array, other) -+ -+ cond (conds) -
    -
    -- Clones array and appends other values. -+ Returns a function which iterate over a set of conditions. It invokes each predicate, -+ passing it given values. It returns the value of the corresponding function of the first -+ predicate to return a non-nil value. - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • other -- an array -+
    • conds -+ an array list of predicate-function pairs -
    • -
    - -

    Returns:

    -
      - -- a new array -+ the result of invoking f(...) of the first predicate to return a non-nil value -
    - - -@@ -2962,89 +5202,79 @@ - -
    -
    -- -- interleave (...) -+ -+ constant (value) -
    -
    -- Interleaves arrays. It returns a single array made of values from all -- passed in arrays in their given order, interleaved. -+ Creates a constant function which returns the same output on every call. -+
    Aliased as always - - -

    Parameters:

    -
      --
    • ... -- a variable list of arrays -+
    • value -+ a constant value -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a constant function -
    - - --

    See also:

    -- - - -
    -
    -- -- interpose (value, array) -+ -+ converge (f, g, h) -
    -
    -- Interposes value in-between consecutive pair of values in array. -+ Converges two functions into one. - - -

    Parameters:

    -
      --
    • value -- a value -+
    • f -+ a function -
    • --
    • array -- an array -+
    • g -+ a function -+
    • -+
    • h -+ a function -
    • -
    - -

    Returns:

    -
      - -- a new array -+ a new version of function f -
    - - --

    See also:

    -- - - -
    -
    -- -- range ([from[, to[, step]]]) -+ -+ curry (f[, n_args]) -
    -
    -- Produces a flexible list of numbers. If one positive value is passed, will count from 0 to that value, -- with a default step of 1. If two values are passed, will count from the first one to the second one, with the -- same default step of 1. A third value passed will be considered a step value. -+ Curries a function. If the given function f takes multiple arguments, it returns another version of -+ f that takes a single argument (the first of the arguments to the original function) and returns a new -+ function that takes the remainder of the arguments and returns the result. - - -

    Parameters:

    -
      --
    • from -- the initial value of the range -- (optional) --
    • --
    • to -- the final value of the range -- (optional) -+
    • f -+ a function -
    • --
    • step -- the step of count -+
    • n_args -+ the number of arguments expected for f. Defaults to 2. - (optional) -
    • -
    -@@ -3052,35 +5282,38 @@ -

    Returns:

    -
      - -- a new array of numbers -+ a curried version of f -
    - - -+

    See also:

    -+ - - -
    -
    -- -- rep (value, n) -+ -+ dispatch (...) -
    -
    -- Creates an array list of n values, repeated. -+ Returns a dispatching function. When called with arguments, this function invokes each of its functions -+ in the passed-in order and returns the results of the first non-nil evaluation. - - -

    Parameters:

    -
      --
    • value -- a value to be repeated --
    • --
    • n -- the number of repetitions of value. -+
    • ... -+ a vararg list of functions -
    • -
    - -

    Returns:

    -
      - -- a new array of n values -+ a dispatch function -
    - - -@@ -3088,34 +5321,25 @@ - -
    -
    -- -- partition (array[, n[, pad]]) -+ -+ either (...) -
    -
    -- Iterator returning partitions of an array. It returns arrays of length n -- made of values from the given array. If the last partition has lower elements than n and -- pad is supplied, it will be adjusted to n of elements with pad value. -+ Returns a validation function. Given a set of functions, the validation function evaluates -+ to true when at least one of its funcs returns true. - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • n -- the size of partitions. Should be greater than 0. Defaults to 1. -- (optional) --
    • --
    • pad -- a value to adjust the last subsequence to the n elements -- (optional) -+
    • ... -+ an array list of functions -
    • -
    - -

    Returns:

    -
      - -- an iterator function -+ true when one of the given funcs returns true with input, false otherwise -
    - - -@@ -3123,34 +5347,24 @@ - -
    -
    -- -- sliding. (array[, n[, pad]]) -+ -+ flip (f) -
    -
    -- Iterator returning sliding partitions of an array. It returns overlapping subsequences -- of length n. If the last subsequence has lower elements than n and pad is -- supplied, it will be adjusted to n elements with pad value. -+ Creates a function of f with arguments flipped in reverse order. - - -

    Parameters:

    -
      --
    • array -- an array --
    • --
    • n -- the size of partitions. Should be greater than 1. Defaults to 2. -- (optional) --
    • --
    • pad -- a value to adjust the last subsequence to the n elements -- (optional) -+
    • f -+ a function -
    • -
    - -

    Returns:

    -
      - -- an iterator function -+ a function -
    - - -@@ -3158,25 +5372,25 @@ - -
    -
    -- -- permutation (array) -+ -+ identity (value) -
    -
    -- Iterator returning the permutations of an array. It returns arrays made of all values -- from the passed-in array, with values permuted. -+ Returns the passed-in value. This function is used internally -+ as a default iterator. - - -

    Parameters:

    -
      --
    • array -- an array -+
    • value -+ a value -
    • -
    - -

    Returns:

    -
      - -- an iterator function -+ the passed-in value -
    - - -@@ -3184,26 +5398,33 @@ - -
    -
    -- -- invert (array) -+ -+ iterator (f, value[, n]) -
    -
    -- Swaps keys with values. Produces a new array where previous keys are now values, -- while previous values are now keys. --
    Aliased as mirror -+ Produces an iterator which repeatedly apply a function f onto an input.
    -+ Yields value, then f(value), then f(f(value)), continuously. -+
    Aliased as iter. - - -

    Parameters:

    -
      --
    • array -- a given array -+
    • f -+ a function -+
    • -+
    • value -+ an initial input to f -+
    • -+
    • n -+ the number of times the iterator should run -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a new array -+ an iterator function -
    - - -@@ -3211,62 +5432,54 @@ - -
    -
    -- -- concat (array[, sep[, i[, j]]]) -+ -+ iterlen (...) -
    -
    -- Concatenates values in a given array. Handles booleans as well. If sep string is -- passed, it will be used as a separator. Passing i and j will result in concatenating -- only values within [i, j] range. --
    Aliased as join -+ Returns the length of an iterator. It consumes the iterator itself. - - -

    Parameters:

    -
      --
    • array -- a given array --
    • --
    • sep -- a separator string, defaults to the empty string ''. -- (optional) --
    • --
    • i -- the starting index, defaults to 1. -- (optional) --
    • --
    • j -- the final index, defaults to the array length. -- (optional) -+
    • ... -+ an iterator function (returning a generator, a state and a value) -
    • -
    - -

    Returns:

    -
      - -- a string -+ the iterator length -
    - - - - -
    --
    --

    Utility functions

    -- --
    -
    -- -- noop () -+ -+ juxtapose (value, ...) -
    -
    -- The no-operation function. -+ Calls a sequence of passed-in functions with the same argument. -+ Returns a sequence of results. -+
    Aliased as juxt - - -+

    Parameters:

    -+
      -+
    • value -+ a value -+
    • -+
    • ... -+ a variable number of functions -+
    • -+
    - -

    Returns:

    -
      - -- nothing -+ a list of results -
    - - -@@ -3274,25 +5487,26 @@ - -
    -
    -- -- identity (value) -+ -+ memoize (f) -
    -
    -- Returns the passed-in value. This function is used internally -- as a default iterator. -+ Memoizes a given function by caching the computed result. -+ Useful for speeding-up slow-running functions. -+
    Aliased as cache - - -

    Parameters:

    -
      --
    • value -- a value -+
    • f -+ a function -
    • -
    - -

    Returns:

    -
      - -- the passed-in value -+ a new function -
    - - -@@ -3300,24 +5514,25 @@ - -
    -
    -- -- constant (value) -+ -+ neither (...) -
    -
    -- Creates a constant function which returns the same output on every call. -+ Returns a validation function. Given a set of functions, the validation function evaluates -+ to true when neither of its func return true. - - -

    Parameters:

    -
      --
    • value -- a constant value -+
    • ... -+ an array list of functions -
    • -
    - -

    Returns:

    -
      - -- a constant function -+ true when neither of the given funcs returns true with input, false otherwise -
    - - -@@ -3325,14 +5540,11 @@ - -
    -
    -- -- memoize (f[, hash]) -+ -+ noarg (f) -
    -
    -- Memoizes a given function by caching the computed result. -- Useful for speeding-up slow-running functions. If a hash function is passed, -- it will be used to compute hash keys for a set of input values for caching. --
    Aliased as cache -+ Returns a function with an arity of 0. The new function ignores any arguments passed to it. - - -

    Parameters:

    -@@ -3340,10 +5552,6 @@ -
  • f - a function -
  • --
  • hash -- a hash function, defaults to identity -- (optional) --
  • - - -

    Returns:

    -@@ -3357,78 +5565,58 @@ - -
    -
    -- -- once (f) -+ -+ noop () -
    -
    -- Returns a version of f that runs only once. Successive calls to f -- will keep yielding the same output, no matter what the passed-in arguments are. -- It can be used to initialize variables. -+ The no operation function. - - --

    Parameters:

    --
      --
    • f -- a function --
    • --
    - -

    Returns:

    -
      - -- a new function -+ nothing -
    - - --

    See also:

    -- - - -
    -
    -- -- before (f, count) -+ -+ nthArg (n) -
    -
    -- Returns a version of f that will run no more than count times. Next calls will -- keep yielding the results of the count-th call. -+ Returns a function that gets the nth argument.
    -+ If n is negative, the nth argument from the end is returned. - - -

    Parameters:

    -
      --
    • f -- a function --
    • --
    • count -- a count -+
    • n -+ a number -
    • -
    - -

    Returns:

    -
      - -- a new function -+ a function -
    - - --

    See also:

    -- - - -
    -
    -- -- after (f, count) -+ -+ once (f) -
    -
    -- Returns a version of f that runs on the count-th call. -- Useful when dealing with asynchronous tasks. -+ Returns a version of f that runs only once. Successive calls to f -+ will keep yielding the same output, no matter what the passed-in arguments are. -+ It can be used to initialize variables. - - -

    Parameters:

    -@@ -3436,9 +5624,6 @@ -
  • f - a function -
  • --
  • count -- the number of calls before f will start running. --
  • - - -

    Returns:

    -@@ -3450,233 +5635,250 @@ - -

    See also:

    - - - -
    -
    -- -- compose (...) -+ -+ over (...) -
    -
    -- Composes functions. Each passed-in function consumes the return value of the function that follows. -- In math terms, composing the functions f, g, and h produces the function f(g(h(…))). -+ Creates a function that runs transforms on all arguments it receives. - - -

    Parameters:

    -
      -
    • ... -- a variable number of functions -+ a set of functions which will receive all arguments to the returned function -
    • -
    - -

    Returns:

    -
      - -- a new function -+ a function -
    - - -

    See also:

    - - - -
    -
    -- -- pipe (value, ...) -+ -+ overArgs (f, ...) -
    -
    -- Pipes a value through a series of functions. In math terms, -- given some functions f, g, and h in that order, it returns f(g(h(value))). -+ Creates a function that invokes f with its arguments transformed. 1rst arguments will be passed to -+ the 1rst transform, 2nd arg to the 2nd transform, etc. Remaining arguments will not be transformed. - - -

    Parameters:

    -
      --
    • value -- a value -+
    • f -+ a function -
    • -
    • ... -- a variable number of functions -+ a list of transforms funcs prototyped as f (v) -
    • -
    - -

    Returns:

    -
      - -- the result of the composition of function calls. -+ the result of running f with its transformed arguments -
    - - -

    See also:

    - - - -
    -
    -- -- complement (f) -+ -+ overEvery (...) -
    -
    -- Returns the logical complement of a given function. For a given input, the returned -- function will output false if the original function would have returned true, -- and vice-versa. -+ Creates a validation function. The returned function checks if all of the given predicates return -+ truthy when invoked with the arguments it receives. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • ... -+ a list of predicate functions -
    • -
    - -

    Returns:

    -
      - -- the logical complement of the given function f. -+ a new function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- juxtapose (value, ...) -+ -+ overSome (...) -
    -
    -- Calls a sequence of passed-in functions with the same argument. -- Returns a sequence of results. --
    Aliased as juxt -+ Creates a validation function. The return function checks if any of a given predicates return -+ truthy when invoked with the arguments it receives. - - -

    Parameters:

    -
      --
    • value -- a value --
    • -
    • ... -- a variable number of functions -+ a list of predicate functions -
    • -
    - -

    Returns:

    -
      - -- a list of results -+ a new function -
    - - -+

    See also:

    -+ - - -
    -
    -- -- wrap (f, wrapper) -+ -+ partial (f, ...) -
    -
    -- Wraps f inside of the wrapper function. It passes f as the first argument to wrapper. -- This allows the wrapper to execute code before and after f runs, -- adjust the arguments, and execute it conditionally. -+ Partially apply a function by filling in any number of its arguments.
    -+ One may pass a string 'M' as a placeholder in the list of arguments to specify an argument -+ that should not be pre-filled, but left open to be supplied at call-time. - - -

    Parameters:

    -
      -
    • f -- a function to be wrapped, prototyped as f (…) -+ a function -
    • --
    • wrapper -- a wrapper function, prototyped as wrapper (f, …) -+
    • ... -+ a list of partial arguments to f -
    • -
    - -

    Returns:

    -
      - -- the results -+ a new version of function f having some of it original arguments filled -
    - - -+

    See also:

    -+ - - -
    -
    -- -- times (n, iter, ...) -+ -+ partialRight (f, ...) -
    -
    -- Runs iter function n times. Collects the results of each run and returns them in an array. -+ Similar to partial, but from the right. - - -

    Parameters:

    -
      --
    • n -- the number of times iter should be called --
    • --
    • iter -- an iterator function, prototyped as iter (i, …) -+
    • f -+ a function -
    • -
    • ... -- args to be passed to iter function -+ a list of partial arguments to f -
    • -
    - -

    Returns:

    -
      - -- table an array of results -+ a new version of function f having some of it original arguments filled -
    - - -+

    See also:

    -+ - - -
    -
    -- -- bind (f, v) -+ -+ pipe (value, ...) -
    -
    -- Binds v to be the first argument to f. Calling f (…) will result to f (v, …). -+ Pipes a value through a series of functions. In math terms, -+ given some functions f, g, and h in that order, it returns f(g(h(value))). - - -

    Parameters:

    -
      --
    • f -- a function --
    • --
    • v -+
    • value - a value -
    • -+
    • ... -+ a variable number of functions -+
    • -
    - -

    Returns:

    -
      - -- a function -+ the result of the composition of function calls. -
    - - -

    See also:

    - - - -
    -
    -- -- bind2 (f, v) -+ -+ rearg (f, indexes) -
    -
    -- Binds v to be the second argument to f. Calling f (a, …) will result to f (a, v, …). -+ Returns a function which runs with arguments rearranged. Arguments are passed to the -+ returned function in the order of supplied indexes at call-time. - - -

    Parameters:

    -@@ -3684,8 +5886,8 @@ -
  • f - a function -
  • --
  • v -- a value -+
  • indexes -+ an array list of indexes -
  • - - -@@ -3696,167 +5898,154 @@ - - - --

    See also:

    -- - - -
    -
    -- -- bindn (f, ...) -+ -+ skip (iter[, n]) -
    -
    -- Binds to be the N-first arguments to function f.
    -- Calling f (a1, a2, …, aN) will result to f (…, a1, a2, …,aN). -+ Consumes the first n values of a iterator then returns it. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • iter -+ an iterator function -
    • --
    • ... -- a variable number of arguments -+
    • n -+ a number. Defaults to 1. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a function -+ the given iterator -
    - - --

    See also:

    -- - - -
    -
    -- -- bindAll (obj, ...) -+ -+ tabulate (...) -
    -
    -- Binds methods to object. As such, whenever any of these methods is invoked, it -- always receives the object as its first argument. -+ Iterates over an iterator and returns its values in an array. - - -

    Parameters:

    -
      --
    • obj -- an abject --
    • -
    • ... -- a variable number of method names -+ an iterator function (returning a generator, a state and a value) -
    • -
    - -

    Returns:

    -
      - -- the passed-in object with all methods bound to the object itself. -+ an array of results -
    - - --

    See also:

    -- - - -
    -
    -- -- uniqueId ([template[, ...]]) -+ -+ thread (value, ...) -
    -
    -- Generates an unique ID for the current session. If given a string template, it -- will use this template for output formatting. Otherwise, if template is a function, it -- will evaluate template (id, …). --
    Aliased as uid. -+ Threads value through a series of functions. If a function expects more than one args, -+ it can be specified using an array list, where the first item is the function and the following -+ are the remaining args neeeded. The value is used as the first input. - - -

    Parameters:

    -
      --
    • template -- either a string or a function template to format the ID -- (optional) -+
    • value -+ a value -
    • -
    • ... -- a variable number of arguments to be passed to template, in case it is a function. -- (optional) -+ a vararg list of functions or arrays -
    • -
    - -

    Returns:

    -
      - -- value an ID -+ a value -
    - - -+

    See also:

    -+ - - -
    -
    -- -- iterator (f, x) -+ -+ threadRight (value, ...) -
    -
    -- Produces an iterator which repeatedly apply a function f onto an input.
    -- Yields x, then f(x), then f(f(x)), continuously. -+ Threads value through a series of functions. If a function expects more than one args, -+ it can be specified using an array list, where the first item is the function and the following -+ are the remaining args neeeded. The value is used as the last input. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • value -+ a value -
    • --
    • x -- an initial input to f -+
    • ... -+ a vararg list of functions or arrays -
    • -
    - -

    Returns:

    -
      - -- an iterator fnction --
      Aliased as iter. -+ a value -
    - - -+

    See also:

    -+ - - -
    -
    -- -- array (...) -+ -+ time (f[, ...]) -
    -
    -- Iterates an iterator and returns its values in an array. -+ Returns the execution time of f (...) and its returned values. - - -

    Parameters:

    -
      -+
    • f -+ a function -+
    • -
    • ... -- an iterator (a function, a table and a value) -+ optional args to f -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- an array of results -+ the execution time and the results of f (...) -
    - - -@@ -3864,24 +6053,28 @@ - -
    -
    -- -- flip (f) -+ -+ times (iter[, n]) -
    -
    -- Creates a function of f with arguments flipped in reverse order. -+ Runs iter function n times. Collects the results of each run and returns them in an array. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • iter -+ an iterator function, prototyped as iter (i) -+
    • -+
    • n -+ the number of times iter should be called. Defaults to 1. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a function -+ table an array of results -
    - - -@@ -3889,17 +6082,17 @@ - -
    -
    -- -- over (...) -+ -+ unary (f) -
    -
    -- Creates a function that runs transforms on all arguments it receives. -+ Returns a function which accepts up to one arg. It ignores any additional arguments. - - -

    Parameters:

    -
      --
    • ... -- a set of functions which will receive all arguments to the returned function -+
    • f -+ a function -
    • -
    - -@@ -3912,268 +6105,245 @@ - -

    See also:

    - - - -
    -
    -- -- overEvery (...) -+ -+ unfold (f, seed) -
    -
    -- Creates a validation function. The returned function checks if all of the given predicates return -- truthy when invoked with the arguments it receives. -+ Builds a list from a seed value. Accepts an iterator function, which -+ returns either nil to stop iteration or two values : the value to add to the list -+ of results and the seed to be used in the next call to the iterator function. - - -

    Parameters:

    -
      --
    • ... -- a list of predicate functions -+
    • f -+ an iterator function -+
    • -+
    • seed -+ a seed value -
    • -
    - -

    Returns:

    -
      - -- a new function -+ an array of values -
    - - --

    See also:

    -- - - -
    -
    -- -- overSome (...) -+ -+ uniqueId ([template]) -
    -
    -- Creates a validation function. The return function checks if any of a given predicates return -- truthy when invoked with the arguments it receives. -+ Generates an unique ID for the current session. If given a string template, it -+ will use this template for output formatting. Otherwise, if template is a function, it -+ will evaluate template (id). -+
    Aliased as uid. - - -

    Parameters:

    -
      --
    • ... -- a list of predicate functions -+
    • template -+ either a string or a function template to format the ID -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a new function -+ value an ID -
    - - --

    See also:

    -- - - -
    -
    -- -- overArgs (f, ...) -+ -+ wrap (f, wrapper) -
    -
    -- Creates a function that invokes f with its arguments transformed. 1rst arguments will be passed to -- the 1rst transform, 2nd arg to the 2nd transform, etc. Remaining arguments will not be transformed. -+ Wraps f inside of the wrapper function. It passes f as the first argument to wrapper. -+ This allows the wrapper to execute code before and after f runs, -+ adjust the arguments, and execute it conditionally. - - -

    Parameters:

    -
      -
    • f -- a function -+ a function to be wrapped, prototyped as f (...) -
    • --
    • ... -- a list of transforms funcs prototyped as f (v) -+
    • wrapper -+ a wrapper function, prototyped as wrapper (f, ...) -
    • -
    - -

    Returns:

    -
      - -- the result of running f with its transformed arguments -+ the results -
    - - --

    See also:

    -- - - -
    -+
    -+

    Object functions

    -+ -+
    -
    -- -- partial (f, ...) -+ -+ chain (value) -
    -
    -- Partially apply a function by filling in any number of its arguments.
    -- One may pass a string '_' as a placeholder in the list of arguments to specify an argument -- that should not be pre-filled, but left open to be supplied at call-time. -+ Returns a wrapped object. Calling library functions as methods on this object -+ will continue to return wrapped objects until obj:value is used. Can be aliased as M(value). - - -

    Parameters:

    -
      --
    • f -- a function --
    • --
    • ... -- a list of partial arguments to f -+
    • value -+ a value to be wrapped -
    • -
    - -

    Returns:

    -
      - -- a new version of function f having some of it original arguments filled --
    -- -- --

    See also:

    -- -+ a wrapped object -+ -+ -+ - - -
    -
    -- -- partialRight (f, ...) -+ -+ clone (obj[, shallow]) -
    -
    -- Similar to partial, but from the right. -+ Clones a given object properties. If shallow is passed will also clone nested array properties. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • obj -+ an object -
    • --
    • ... -- a list of partial arguments to f -+
    • shallow -+ whether or not nested array-properties should be cloned, defaults to false. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- a new version of function f having some of it original arguments filled -+ a copy of the passed-in object -
    - - --

    See also:

    -- - - -
    -
    -- -- curry (f[, n_args]) -+ -+ extend (destObj, ...) -
    -
    -- Curries a function. If the given function f takes multiple arguments, it returns another version of -- f that takes a single argument (the first of the arguments to the original function) and returns a new -- function that takes the remainder of the arguments and returns the result. -+ Extends an object properties. It copies the properties of extra passed-in objects -+ into the destination object, and returns the destination object. The last objects -+ will override properties of the same name. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • destObj -+ a destination object -
    • --
    • n_args -- the number of arguments expected for f. Defaults to 2. -- (optional) -+
    • ... -+ a list of objects -
    • -
    - -

    Returns:

    -
      - -- a curried version of f -+ the destination object extended -
    - - --

    See also:

    -- - - -
    -
    -- -- time (f[, ...]) -+ -+ flattenPath (obj, ...) -
    -
    -- Returns the execution time of f (…) and its returned values. -+ Flattens object under property path onto provided object.
    -+ It is similar to spreadPath, but preserves object under the property path. - - -

    Parameters:

    -
      --
    • f -- a function -+
    • obj -+ an object -
    • -
    • ... -- optional args to f -- (optional) -+ a property path given as a vararg list -
    • -
    - -

    Returns:

    -
      - -- the execution time and the results of f (…) -+ the passed-in object with changes -
    - - -+

    See also:

    -+ - - -
    --
    --

    Object functions

    -- --
    -
    -- -- keys (obj) -+ -+ functions ([obj]) -
    -
    -- Returns the keys of the object properties. -+ Returns a sorted list of all methods names found in an object. If the given object -+ has a metatable implementing an __index field pointing to another table, will also recurse on this -+ table if recurseMt is provided. If obj is omitted, it defaults to the library functions. -+
    Aliased as methods. - - -

    Parameters:

    -
      -
    • obj -- an object -+ an object. Defaults to Moses library functions. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- an array -+ an array-list of methods names -
    - - -@@ -4181,11 +6351,11 @@ - -
    -
    -- -- values (obj) -+ -+ has (obj, key) -
    -
    -- Returns the values of the object properties. -+ Checks if a given object implements a property. - - -

    Parameters:

    -@@ -4193,12 +6363,15 @@ -
  • obj - an object -
  • -+
  • key -+ a key property to be checked -+
  • - - -

    Returns:

    -
      - -- an array -+ true or false -
    - - -@@ -4206,100 +6379,94 @@ - -
    -
    -- -- kvpairs (obj) -+ -+ import ([context[, noConflict]]) -
    -
    -- Converts keys and values a an array-list of [k, v]. -+ Imports all library functions into a context. - - -

    Parameters:

    -
      --
    • obj -- an object -+
    • context -+ a context. Defaults to _ENV or _G`` (current environment). -+ (optional) -+
    • -+
    • noConflict -+ if supplied, will not import conflicting functions in the destination context. -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- an array list of key-values pairs -+ the passed-in context -
    - - --

    See also:

    -- - - -
    -
    -- -- toObj (kvpairs) -+ -+ invert (obj) -
    -
    -- Converts an array list of kvpairs to an object. Keys are taken -- from the 1rst column in the kvpairs sequence, associated with values in the 2nd -- column -+ Swaps keys with values. Produces a new object where previous keys are now values, -+ while previous values are now keys. -+
    Aliased as mirror - - -

    Parameters:

    -
      --
    • kvpairs -- an array-list of kvpairs -+
    • obj -+ a given object -
    • -
    - -

    Returns:

    -
      - -- an object -+ a new object -
    - - --

    See also:

    -- - - -
    -
    -- -- property (key) -+ -+ isArray (obj) -
    -
    -- Returns a function that will return the key property of any passed-in object. -+ Checks if the given argument is an array. Assumes obj is an array -+ if is a table with consecutive integer keys starting at 1. - - -

    Parameters:

    -
      --
    • key -- a key property name -+
    • obj -+ an object -
    • -
    - -

    Returns:

    -
      - -- a function which should accept an object as argument -+ true or false -
    - - --

    See also:

    -- - - -
    -
    -- -- propertyOf (obj) -+ -+ isBoolean (obj) -
    -
    -- Returns a function which will return the value of an object property. -+ Checks if the given argument is a boolean. - - -

    Parameters:

    -@@ -4312,36 +6479,33 @@ -

    Returns:

    -
      - -- a function which should accept a key property argument -+ true or false -
    - - --

    See also:

    -- - - -
    -
    -- -- toBoolean (value) -+ -+ isCallable (obj) -
    -
    -- Converts any given value to a boolean -+ Checks if the given argument is callable. Assumes obj is callable if -+ it is either a function or a table having a metatable implementing __call metamethod. - - -

    Parameters:

    -
      --
    • value -- a value. Can be of any type -+
    • obj -+ an object -
    • -
    - -

    Returns:

    -
      - -- true if value is true, false otherwise (false or nil). -+ true or false -
    - - -@@ -4349,29 +6513,27 @@ - -
    -
    -- -- extend (destObj, ...) -+ -+ isEmpty ([obj]) -
    -
    -- Extends an object properties. It copies the properties of extra passed-in objects -- into the destination object, and returns the destination object. The last objects -- will override properties of the same name. -+ Checks if the given pbject is empty. If obj is a string, will return true -+ if #obj == 0. Otherwise, if obj is a table, will return whether or not this table -+ is empty. If obj is nil, it will return true. - - -

    Parameters:

    -
      --
    • destObj -- a destination object --
    • --
    • ... -- a list of objects -+
    • obj -+ an object -+ (optional) -
    • -
    - -

    Returns:

    -
      - -- the destination object extended -+ true or false -
    - - -@@ -4379,20 +6541,27 @@ - -
    -
    -- -- functions ([obj]) -+ -+ isEqual (objA, objB[, useMt]) -
    -
    -- Returns a sorted list of all methods names found in an object. If the given object -- has a metatable implementing an __index field pointing to another table, will also recurse on this -- table if recurseMt is provided. If obj is omitted, it defaults to the library functions. --
    Aliased as methods. -+ Performs a deep comparison test between two objects. Can compare strings, functions -+ (by reference), nil, booleans. Compares tables by reference or by values. If useMt -+ is passed, the equality operator == will be used if one of the given objects has a -+ metatable implementing __eq. -+
    Aliased as M.compare, M.matches - - -

    Parameters:

    -
      --
    • obj -- an object. Defaults to Moses library functions. -+
    • objA -+ an object -+
    • -+
    • objB -+ another object -+
    • -+
    • useMt -+ whether or not __eq should be used, defaults to false. - (optional) -
    • -
    -@@ -4400,19 +6569,23 @@ -

    Returns:

    -
      - -- an array-list of methods names -+ true or false -
    - - -+

    See also:

    -+ - - -
    -
    -- -- clone (obj[, shallow]) -+ -+ isFinite (obj) -
    -
    -- Clones a given object properties. If shallow is passed will also clone nested array properties. -+ Checks if the given argument is a finite number. - - -

    Parameters:

    -@@ -4420,16 +6593,12 @@ -
  • obj - an object -
  • --
  • shallow -- whether or not nested array-properties should be cloned, defaults to false. -- (optional) --
  • - - -

    Returns:

    -
      - -- a copy of the passed-in object -+ true or false -
    - - -@@ -4437,13 +6606,11 @@ - -
    -
    -- -- tap (obj, f[, ...]) -+ -+ isFunction (obj) -
    -
    -- Invokes interceptor with the object, and then returns object. -- The primary purpose of this method is to “tap into” a method chain, in order to perform operations -- on intermediate results within the chain. -+ Checks if the given argument is a function. - - -

    Parameters:

    -@@ -4451,19 +6618,12 @@ -
  • obj - an object -
  • --
  • f -- an interceptor function, should be prototyped as f (obj, …) --
  • --
  • ... -- args to be passed to f -- (optional) --
  • - - -

    Returns:

    -
      - -- the passed-in object -+ true or false -
    - - -@@ -4471,11 +6631,11 @@ - -
    -
    -- -- has (obj, key) -+ -+ isInteger (obj) -
    -
    -- Checks if a given object implements a property. -+ Checks if the given argument is an integer. - - -

    Parameters:

    -@@ -4483,9 +6643,6 @@ -
  • obj - an object -
  • --
  • key -- a key property to be checked --
  • - - -

    Returns:

    -@@ -4499,12 +6656,11 @@ - -
    -
    -- -- pick (obj, ...) -+ -+ isIterable (obj) -
    -
    -- Returns an object copy having white-listed properties. --
    Aliased as choose. -+ Checks if the given object is iterable with pairs (or ipairs). - - -

    Parameters:

    -@@ -4512,15 +6668,12 @@ -
  • obj - an object -
  • --
  • ... -- a variable number of string keys --
  • - - -

    Returns:

    -
      - -- the filtered object -+ true if the object can be iterated with pairs (or ipairs), false otherwise -
    - - -@@ -4528,12 +6681,11 @@ - -
    -
    -- -- omit (obj, ...) -+ -+ isNaN (obj) -
    -
    -- Returns an object copy without black-listed properties. --
    Aliased as drop. -+ Checks if the given argument is NaN (see Not-A-Number). - - -

    Parameters:

    -@@ -4541,28 +6693,28 @@ -
  • obj - an object -
  • --
  • ... -- a variable number of string keys --
  • - - -

    Returns:

    -
      - -- the filtered object -+ true or false -
    - - -+

    See also:

    -+ - - -
    -
    -- -- template (obj[, template]) -+ -+ isNil (obj) -
    -
    -- Applies a template to an object, preserving non-nil properties. --
    Aliased as defaults. -+ Checks if the given argument is nil. - - -

    Parameters:

    -@@ -4570,16 +6722,12 @@ -
  • obj - an object -
  • --
  • template -- a template object. Defaults to an empty table {}. -- (optional) --
  • - - -

    Returns:

    -
      - -- the passed-in object filled -+ true or false -
    - - -@@ -4587,29 +6735,18 @@ - -
    -
    -- -- isEqual (objA, objB[, useMt]) -+ -+ isNumber (obj) -
    -
    -- Performs a deep comparison test between two objects. Can compare strings, functions -- (by reference), nil, booleans. Compares tables by reference or by values. If useMt -- is passed, the equality operator == will be used if one of the given objects has a -- metatable implementing __eq. --
    Aliased as _.compare -+ Checks if the given argument is a number. - - -

    Parameters:

    -
      --
    • objA -+
    • obj - an object -
    • --
    • objB -- another object --
    • --
    • useMt -- whether or not __eq should be used, defaults to false. -- (optional) --
    • -
    - -

    Returns:

    -@@ -4619,16 +6756,19 @@ - - - -+

    See also:

    -+ - - -
    -
    -- -- result (obj, method[, ...]) -+ -+ isString (obj) -
    -
    -- Invokes an object method. It passes the object itself as the first argument. if method is not -- callable, will return obj[method]. -+ Checks if the given argument is a string. - - -

    Parameters:

    -@@ -4636,19 +6776,12 @@ -
  • obj - an object -
  • --
  • method -- a string key to index in object obj. --
  • --
  • ... -- Optional args to be passed to method -- (optional) --
  • - - -

    Returns:

    -
      - -- the returned value of method (obj, …) call -+ true or false -
    - - -@@ -4681,12 +6814,11 @@ - -
    -
    -- -- isCallable (obj) -+ -+ keys (obj) -
    -
    -- Checks if the given argument is callable. Assumes obj is callable if -- it is either a function or a table having a metatable implementing __call metamethod. -+ Returns the keys of the object properties. - - -

    Parameters:

    -@@ -4699,7 +6831,7 @@ -

    Returns:

    -
      - -- true or false -+ an array -
    - - -@@ -4707,12 +6839,11 @@ - -
    -
    -- -- isArray (obj) -+ -+ kvpairs (obj) -
    -
    -- Checks if the given argument is an array. Assumes obj is an array -- if is a table with consecutive integer keys starting at 1. -+ Converts key-value pairs to an array-list of [k, v] pairs. - - -

    Parameters:

    -@@ -4725,19 +6856,43 @@ -

    Returns:

    -
      - -- true or false -+ an array list of key-value pairs -
    - - -+

    See also:

    -+ - - -
    -
    -- -- isIterable (obj) -+ -+ obj:value () -
    -
    -- Checks if the given object is iterable with pairs (or ipairs). -+ Extracts the value of a wrapped object. Must be called on an chained object (see chain). -+ -+ -+ -+

    Returns:

    -+
      -+ -+ the value previously wrapped -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ omit (obj, ...) -+
    -+
    -+ Returns an object copy without black-listed properties. -+
    Aliased as drop. - - -

    Parameters:

    -@@ -4745,12 +6900,15 @@ -
  • obj - an object -
  • -+
  • ... -+ a variable number of string keys -+
  • - - -

    Returns:

    -
      - -- true if the object can be iterated with pairs (or ipairs), false otherwise -+ the filtered object -
    - - -@@ -4758,27 +6916,28 @@ - -
    -
    -- -- isEmpty ([obj]) -+ -+ path (obj, ...) -
    -
    -- Checks if the given pbject is empty. If obj is a string, will return true -- if #obj == 0. Otherwise, if obj is a table, will return whether or not this table -- is empty. If obj is nil, it will return true. -+ Returns the value at a given path in an object.
    -+ Path is given as a vararg list of keys. - - -

    Parameters:

    -
      -
    • obj - an object -- (optional) -+
    • -+
    • ... -+ a vararg list of keys -
    • -
    - -

    Returns:

    -
      - -- true or false -+ a value or nil -
    - - -@@ -4786,11 +6945,12 @@ - -
    -
    -- -- isString (obj) -+ -+ pick (obj, ...) -
    -
    -- Checks if the given argument is a string. -+ Returns an object copy having white-listed properties. -+
    Aliased as choose. - - -

    Parameters:

    -@@ -4798,12 +6958,15 @@ -
  • obj - an object -
  • -+
  • ... -+ a variable number of string keys -+
  • - - -

    Returns:

    -
      - -- true or false -+ the filtered object -
    - - -@@ -4811,36 +6974,40 @@ - -
    -
    -- -- isFunction (obj) -+ -+ property (key) -
    -
    -- Checks if the given argument is a function. -+ Returns a function that will return the key property of any passed-in object. - - -

    Parameters:

    -
      --
    • obj -- an object -+
    • key -+ a key property name -
    • -
    - -

    Returns:

    -
      - -- true or false -+ a function which should accept an object as argument -
    - - -+

    See also:

    -+ - - -
    -
    -- -- isNil (obj) -+ -+ propertyOf (obj) -
    -
    -- Checks if the given argument is nil. -+ Returns a function which will return the value of an object property. - - -

    Parameters:

    -@@ -4853,19 +7020,24 @@ -

    Returns:

    -
      - -- true or false -+ a function which should accept a key property argument -
    - - -+

    See also:

    -+ - - -
    -
    -- -- isNumber (obj) -+ -+ result (obj, method) -
    -
    -- Checks if the given argument is a number. -+ Invokes an object method. It passes the object itself as the first argument. if method is not -+ callable, will return obj[method]. - - -

    Parameters:

    -@@ -4873,28 +7045,28 @@ -
  • obj - an object -
  • -+
  • method -+ a string key to index in object obj. -+
  • - - -

    Returns:

    -
      - -- true or false -+ the returned value of method (obj) call -
    - - --

    See also:

    -- - - -
    -
    -- -- isNaN (obj) -+ -+ spreadPath (obj, ...) -
    -
    -- Checks if the given argument is NaN (see Not-A-Number). -+ Spreads object under property path onto provided object.
    -+ It is similar to flattenPath, but removes object under the property path. - - -

    Parameters:

    -@@ -4902,28 +7074,33 @@ -
  • obj - an object -
  • -+
  • ... -+ a property path given as a vararg list -+
  • - - -

    Returns:

    -
      - -- true or false -+ the passed-in object with changes -
    - - -

    See also:

    - - - -
    -
    -- -- isFinite (obj) -+ -+ tap (obj, f) -
    -
    -- Checks if the given argument is a finite number. -+ Invokes interceptor with the object, and then returns object. -+ The primary purpose of this method is to "tap into" a method chain, in order to perform operations -+ on intermediate results within the chain. - - -

    Parameters:

    -@@ -4931,12 +7108,15 @@ -
  • obj - an object -
  • -+
  • f -+ an interceptor function, should be prototyped as f (obj) -+
  • - - -

    Returns:

    -
      - -- true or false -+ the passed-in object -
    - - -@@ -4944,11 +7124,12 @@ - -
    -
    -- -- isBoolean (obj) -+ -+ template (obj[, template]) -
    -
    -- Checks if the given argument is a boolean. -+ Applies a template to an object, preserving non-nil properties. -+
    Aliased as defaults. - - -

    Parameters:

    -@@ -4956,12 +7137,16 @@ -
  • obj - an object -
  • -+
  • template -+ a template object. If nil, leaves obj untouched. -+ (optional) -+
  • - - -

    Returns:

    -
      - -- true or false -+ the passed-in object filled -
    - - -@@ -4969,24 +7154,24 @@ - -
    -
    -- -- isInteger (obj) -+ -+ toBoolean (value) -
    -
    -- Checks if the given argument is an integer. -+ Converts any given value to a boolean - - -

    Parameters:

    -
      --
    • obj -- an object -+
    • value -+ a value. Can be of any type -
    • -
    - -

    Returns:

    -
      - -- true or false -+ true if value is true, false otherwise (false or nil). -
    - - -@@ -4994,44 +7179,56 @@ - -
    -
    -- -- chain (value) -+ -+ toObj (kvpairs) -
    -
    -- Returns a wrapped object. Calling library functions as methods on this object -- will continue to return wrapped objects until obj:value is used. Can be aliased as _(value). -+ Converts an array list of [k,v] pairs to an object. Keys are taken -+ from the 1rst column in the [k,v] pairs sequence, associated with values in the 2nd -+ column. - - -

    Parameters:

    -
      --
    • value -- a value to be wrapped -+
    • kvpairs -+ an array-list of [k,v] pairs -
    • -
    - -

    Returns:

    -
      - -- a wrapped object -+ an object -
    - - -+

    See also:

    -+ - - -
    -
    -- -- obj:value () -+ -+ type (obj) -
    -
    -- Extracts the value of a wrapped object. Must be called on an chained object (see chain). -+ Extends Lua's type function. It returns the type of the given object and also recognises -+ file userdata - - -+

    Parameters:

    -+
      -+
    • obj -+ an object -+
    • -+
    - -

    Returns:

    -
      - -- the value previously wrapped -+ the given object type -
    - - -@@ -5039,29 +7236,24 @@ - -
    -
    -- -- import ([context[, noConflict]]) -+ -+ values (obj) -
    -
    -- Imports all library functions into a context. -+ Returns the values of the object properties. - - -

    Parameters:

    -
      --
    • context -- a context. Defaults to _G (global environment) when not given. -- (optional) --
    • --
    • noConflict -- if supplied, will not import functions having a key existing in the destination context. -- (optional) -+
    • obj -+ an object -
    • -
    - -

    Returns:

    -
      - -- the passed-in context -+ an array of values -
    - - -@@ -5075,7 +7267,7 @@ -
    -
    - generated by LDoc 1.4.6 --Last updated 2017-04-27 15:26:55 -+Last updated 2019-04-01 23:55:17 -
    -
    - -diff --git a/extra/moses/doc/ldoc.css b/extra/moses/doc/ldoc.css -deleted file mode 100644 -index 52c4ad2..0000000 ---- a/extra/moses/doc/ldoc.css -+++ /dev/null -@@ -1,303 +0,0 @@ --/* BEGIN RESET -- --Copyright (c) 2010, Yahoo! Inc. All rights reserved. --Code licensed under the BSD License: --http://developer.yahoo.com/yui/license.html --version: 2.8.2r1 --*/ --html { -- color: #000; -- background: #FFF; --} --body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { -- margin: 0; -- padding: 0; --} --table { -- border-collapse: collapse; -- border-spacing: 0; --} --fieldset,img { -- border: 0; --} --address,caption,cite,code,dfn,em,strong,th,var,optgroup { -- font-style: inherit; -- font-weight: inherit; --} --del,ins { -- text-decoration: none; --} --li { -- margin-left: 20px; --} --caption,th { -- text-align: left; --} --h1,h2,h3,h4,h5,h6 { -- font-size: 100%; -- font-weight: bold; --} --q:before,q:after { -- content: ''; --} --abbr,acronym { -- border: 0; -- font-variant: normal; --} --sup { -- vertical-align: baseline; --} --sub { -- vertical-align: baseline; --} --legend { -- color: #000; --} --input,button,textarea,select,optgroup,option { -- font-family: inherit; -- font-size: inherit; -- font-style: inherit; -- font-weight: inherit; --} --input,button,textarea,select {*font-size:100%; --} --/* END RESET */ -- --body { -- margin-left: 1em; -- margin-right: 1em; -- font-family: arial, helvetica, geneva, sans-serif; -- background-color: #ffffff; margin: 0px; --} -- --code, tt { font-family: monospace; font-size: 1.1em; } --span.parameter { font-family:monospace; } --span.parameter:after { content:":"; } --span.types:before { content:"("; } --span.types:after { content:")"; } --.type { font-weight: bold; font-style:italic } -- --body, p, td, th { font-size: .95em; line-height: 1.2em;} -- --p, ul { margin: 10px 0 0 0px;} -- --strong { font-weight: bold;} -- --em { font-style: italic;} -- --h1 { -- font-size: 1.5em; -- margin: 20px 0 20px 0; --} --h2, h3, h4 { margin: 15px 0 10px 0; } --h2 { font-size: 1.25em; } --h3 { font-size: 1.15em; } --h4 { font-size: 1.06em; } -- --a:link { font-weight: bold; color: #004080; text-decoration: none; } --a:visited { font-weight: bold; color: #006699; text-decoration: none; } --a:link:hover { text-decoration: underline; } -- --hr { -- color:#cccccc; -- background: #00007f; -- height: 1px; --} -- --blockquote { margin-left: 3em; } -- --ul { list-style-type: disc; } -- --p.name { -- font-family: "Andale Mono", monospace; -- padding-top: 1em; --} -- --pre { -- background-color: rgb(245, 245, 245); -- border: 1px solid #C0C0C0; /* silver */ -- padding: 10px; -- margin: 10px 0 10px 0; -- overflow: auto; -- font-family: "Andale Mono", monospace; --} -- --pre.example { -- font-size: .85em; --} -- --table.index { border: 1px #00007f; } --table.index td { text-align: left; vertical-align: top; } -- --#container { -- margin-left: 1em; -- margin-right: 1em; -- background-color: #f0f0f0; --} -- --#product { -- text-align: center; -- border-bottom: 1px solid #cccccc; -- background-color: #ffffff; --} -- --#product big { -- font-size: 2em; --} -- --#main { -- background-color: #f0f0f0; -- border-left: 2px solid #cccccc; --} -- --#navigation { -- float: left; -- width: 14em; -- vertical-align: top; -- background-color: #f0f0f0; -- overflow: visible; --} -- --#navigation h2 { -- background-color:#e7e7e7; -- font-size:1.1em; -- color:#000000; -- text-align: left; -- padding:0.2em; -- border-top:1px solid #dddddd; -- border-bottom:1px solid #dddddd; --} -- --#navigation ul --{ -- font-size:1em; -- list-style-type: none; -- margin: 1px 1px 10px 1px; --} -- --#navigation li { -- text-indent: -1em; -- display: block; -- margin: 3px 0px 0px 22px; --} -- --#navigation li li a { -- margin: 0px 3px 0px -1em; --} -- --#content { -- margin-left: 14em; -- padding: 1em; -- width: 700px; -- border-left: 2px solid #cccccc; -- border-right: 2px solid #cccccc; -- background-color: #ffffff; --} -- --#about { -- clear: both; -- padding: 5px; -- border-top: 2px solid #cccccc; -- background-color: #ffffff; --} -- --@media print { -- body { -- font: 12pt "Times New Roman", "TimeNR", Times, serif; -- } -- a { font-weight: bold; color: #004080; text-decoration: underline; } -- -- #main { -- background-color: #ffffff; -- border-left: 0px; -- } -- -- #container { -- margin-left: 2%; -- margin-right: 2%; -- background-color: #ffffff; -- } -- -- #content { -- padding: 1em; -- background-color: #ffffff; -- } -- -- #navigation { -- display: none; -- } -- pre.example { -- font-family: "Andale Mono", monospace; -- font-size: 10pt; -- page-break-inside: avoid; -- } --} -- --table.module_list { -- border-width: 1px; -- border-style: solid; -- border-color: #cccccc; -- border-collapse: collapse; --} --table.module_list td { -- border-width: 1px; -- padding: 3px; -- border-style: solid; -- border-color: #cccccc; --} --table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } --table.module_list td.summary { width: 100%; } -- -- --table.function_list { -- border-width: 1px; -- border-style: solid; -- border-color: #cccccc; -- border-collapse: collapse; --} --table.function_list td { -- border-width: 1px; -- padding: 3px; -- border-style: solid; -- border-color: #cccccc; --} --table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } --table.function_list td.summary { width: 100%; } -- --ul.nowrap { -- overflow:auto; -- white-space:nowrap; --} -- --dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} --dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} --dl.table h3, dl.function h3 {font-size: .95em;} -- --/* stop sublists from having initial vertical space */ --ul ul { margin-top: 0px; } --ol ul { margin-top: 0px; } --ol ol { margin-top: 0px; } --ul ol { margin-top: 0px; } -- --/* make the target distinct; helps when we're navigating to a function */ --a:target + * { -- background-color: #FF9; --} -- -- --/* styles for prettification of source */ --pre .comment { color: #558817; } --pre .constant { color: #a8660d; } --pre .escape { color: #844631; } --pre .keyword { color: #aa5050; font-weight: bold; } --pre .library { color: #0e7c6b; } --pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } --pre .string { color: #8080ff; } --pre .number { color: #f8660d; } --pre .operator { color: #2239a8; font-weight: bold; } --pre .preprocessor, pre .prepro { color: #a33243; } --pre .global { color: #800080; } --pre .user-keyword { color: #800080; } --pre .prompt { color: #558817; } --pre .url { color: #272fc2; text-decoration: underline; } -- -diff --git a/extra/moses/doc/manual/tutorial.md.html b/extra/moses/doc/manual/tutorial.md.html -new file mode 100644 -index 0000000..0314339 ---- /dev/null -+++ b/extra/moses/doc/manual/tutorial.md.html -@@ -0,0 +1,3295 @@ -+ -+ -+ -+ -+ Moses documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ Moses: a utility-belt library for functional programming in Lua

    -+ -+

    Moses is a Lua utility library which provides support for functional programming. -+It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more. -+
    -+

    -+ -+

    Sections

    -+ -+ -+ -+ -+

    Drop the file moses.lua into your project and add it to your code with the require function:

    -+ -+ -+
    -+local M = require ("moses")
    -+
    -+ -+ -+

    Moses provides a large set of functions that can be classified into four categories:

    -+ -+ -+ -+

    [⬆]

    -+ -+

    -+

    Table functions

    -+ -+

    clear (t)

    -+ -+

    Clears a table. All its values becomes nil. Returns the passed-in table.

    -+ -+ -+
    -+M.clear({1,2,'hello',true}) -- => {}
    -+
    -+ -+ -+

    each (t, f)

    -+

    *Aliases: forEach*.

    -+ -+

    Iterates over each value-key pair in the passed-in table.

    -+ -+ -+
    -+M.each({4,2,1},print)
    -+
    -+-- => 4 1
    -+-- => 2 2
    -+-- => 1 3
    -+
    -+ -+ -+

    The table can be map-like (both array part and hash part).

    -+ -+ -+
    -+M.each({one = 1, two = 2, three = 3},print)
    -+
    -+-- => 1 one
    -+-- => 2 two
    -+-- => 3 three
    -+
    -+ -+ -+

    Can index and assign in an outer table or in the passed-in table:

    -+ -+ -+
    -+t = {'a','b','c'}
    -+M.each(t,function(v,i)
    -+  t[i] = v:rep(2)
    -+  print(t[i])
    -+end)
    -+
    -+-- => aa
    -+-- => bb
    -+-- => cc
    -+
    -+ -+ -+

    eachi (t, f)

    -+

    *Aliases: forEachi*.

    -+ -+

    Iterates only on integer keys in an array table. It returns value-key pairs.

    -+ -+ -+
    -+M.eachi({4,2,1},print)
    -+
    -+-- => 4 1
    -+-- => 2 2
    -+-- => 1 3
    -+
    -+ -+ -+

    The given array can be sparse, or even have a hash-like part.

    -+ -+ -+
    -+local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5}
    -+M.eachi(t,print)
    -+
    -+-- => 6 -1
    -+-- => 1 0
    -+-- => 3 1
    -+-- => 5 2
    -+
    -+ -+ -+

    at (t, ...)

    -+ -+

    Collects values at given keys and returns them in an array.

    -+ -+ -+
    -+local t = {4,5,6}
    -+M.at(t,1,3) -- => "{4,6}"
    -+
    -+local t = {a = 4, bb = true, ccc = false}
    -+M.at(t,'a', 'ccc') -- => "{4, false}"
    -+
    -+ -+ -+

    adjust (t, key, f)

    -+ -+

    Adjusts the value at a given key using a function or a value. In case f is a function, it should be prototyped f(v). -+It does not mutate the given table, but rather returns a new array.

    -+ -+ -+
    -+local t = {1,2,3}
    -+M.adjust(t, 2, math.sin) -- => {1, 0.90929, 3}
    -+
    -+local v = {x = 1}
    -+ M.adjust(t, 'x', 4) -- => {x = 4}
    -+
    -+ -+ -+

    In case the given key does not exist in t, it throws an error.

    -+ -+

    count (t [, val])

    -+ -+

    Counts the number of occurences of a given value in a table.

    -+ -+ -+
    -+M.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2
    -+M.count({1,1,2,3,3,3,2,4,3,2},2) -- => 3
    -+M.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4
    -+M.count({false, false, true},false) -- => 2
    -+M.count({false, false, true},true) -- => 1
    -+
    -+ -+ -+

    Returns the size of the list in case no value was provided.

    -+ -+ -+
    -+M.count({1,1,2,3,3}) -- => 5
    -+
    -+ -+ -+

    countf (t, f)

    -+ -+

    Counts the number of values passing an iterator test.

    -+ -+ -+
    -+M.countf({1,2,3,4,5,6}, function(v)
    -+  return v%2==0
    -+end) -- => 3
    -+
    -+M.countf({print, pairs, os, assert, ipairs}, function(v)
    -+  return type(v)=='function'
    -+end) -- => 4
    -+
    -+ -+ -+

    allEqual (t [, comp])

    -+

    *Aliases: alleq*.

    -+ -+

    Checks if all values in a collection are equal. Uses M.isEqual by default to compare values.

    -+ -+ -+
    -+M.allEqual({1,1,1,1,1}, comp) -- => true
    -+M.allEqual({1,1,2,1,1}, comp) -- => false
    -+
    -+local t1 = {1, 2, {3}}
    -+local t2 = {1, 2, {3}}
    -+M.allEqual({t1, t2}) -- => true
    -+
    -+ -+ -+

    Can take an optional comp function which will be used to compare values.

    -+ -+ -+
    -+local t1 = {x = 1, y = 0}
    -+local t2 = {x = 1, y = 0}
    -+local t3 = {x = 1, y = 2}
    -+local t4 = {x = 1, y = 2}
    -+local function compx(a, b) return a.x == b.x end
    -+local function compy(a, b) return a.y == b.y end
    -+
    -+M.allEqual({t1, t2}, compx) -- => true
    -+M.allEqual({t1, t2}, compy) -- => true
    -+M.allEqual({t3, t4}, compx) -- => true
    -+M.allEqual({t3, t4}, compy) -- => true
    -+M.allEqual({t1, t2, t3, t4}, compx) -- => true
    -+M.allEqual({t1, t2, t3, t4}, compy) -- => false
    -+
    -+ -+ -+

    cycle (t [, n = 1])

    -+

    *Aliases: loop*.

    -+ -+

    Returns a function which iterates on each value-key pair in a given table (similarly to M.each), except that it restarts iterating again n times. -+If n is not provided, it defaults to 1.

    -+ -+ -+
    -+local t = {'a','b','c'}
    -+for v in M.cycle(t, 2) do
    -+  print(v)
    -+end
    -+
    -+-- => 'a'
    -+-- => 'b'
    -+-- => 'c'
    -+-- => 'a'
    -+-- => 'b'
    -+-- => 'c'
    -+
    -+ -+ -+

    Supports array-like tables and map-like tables.

    -+ -+ -+
    -+local t = {x = 1, y = 2, z = 3}
    -+for v in M.cycle(t) do
    -+  print(v)
    -+end
    -+
    -+-- => 2
    -+-- => 1
    -+-- => 3
    -+
    -+ -+ -+

    map (t, f)

    -+

    *Aliases: collect*.

    -+ -+

    Executes a function on each value in a given array.

    -+ -+ -+
    -+M.map({1,2,3},function(v)
    -+  return v+10
    -+end) -- => "{11,12,13}"
    -+
    -+ -+ -+ -+
    -+M.map({a = 1, b = 2},function(v, k)
    -+  return k..v
    -+end) -- => "{a = 'a1', b = 'b2'}"
    -+
    -+ -+ -+

    It also maps both keys and values.

    -+ -+ -+
    -+M.map({a = 1, b = 2},function(v, k)
    -+  return k..k, v*2
    -+end) -- => "{aa = 2, bb = 4}"
    -+
    -+ -+ -+

    mapi (t, f)

    -+ -+

    Executes a function on each value in a given array.

    -+ -+ -+
    -+M.mapi({1,2,3},function(v)
    -+  return v+10
    -+end) -- => "{11,12,13}"
    -+
    -+ -+ -+

    It only works for the array-part of the given table.

    -+ -+ -+
    -+M.map({a = 1, 2, 3, 4, 5},function(v, k)
    -+  return k..v
    -+end) -- => "{'12','23','34','45'}"
    -+
    -+ -+ -+

    reduce (t, f [, state = next(t)])

    -+

    *Aliases: inject, foldl*.

    -+ -+

    Can sum all values in a table. In case state is not provided, it defaults to the first value in the given table t.

    -+ -+ -+
    -+local function add(a,b) return a+b end
    -+M.reduce({1,2,3,4},add) -- => 10
    -+
    -+ -+ -+

    Or concatenates all values.

    -+ -+ -+
    -+local function concat(a,b) return a..b end
    -+M.reduce({'a','b','c','d'},concat) -- => abcd   
    -+
    -+ -+ -+

    best (t, f)

    -+ -+

    Returns the best value passing a selector function. Acts as a special case of reduce, using the first value in t as -+an initial state. It thens folds the given table, testing each of its values v and selecting the value passing the -+call f(state,v) every time.

    -+ -+ -+
    -+local words = {'Lua', 'Programming', 'Language'}
    -+M.best(words, function(a,b) return #a > #b end) -- => 'Programming'
    -+M.best(words, function(a,b) return #a < #b end) -- => 'Lua'
    -+
    -+ -+ -+

    reduceBy (t, f, pred [, state = next(t)])

    -+ -+

    Reduces a table considering only values matching a predicate. -+For example,let us define a set of values.

    -+ -+ -+
    -+local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9}
    -+
    -+ -+ -+

    And a reduction function which will add up values.

    -+ -+ -+
    -+local function add(a,b) return a+b end
    -+
    -+ -+ -+

    We can also define some predicate functions.

    -+ -+ -+
    -+-- predicate for negative values
    -+local function neg(v) return v<=0 end
    -+
    -+-- predicate for positive values
    -+local function pos(v) return v>=0 end
    -+
    -+ -+ -+

    Then we can perform reduction considering only negative or positive values :

    -+ -+ -+
    -+M.reduceBy(val, add, neg) -- => -17
    -+M.reduceBy(val, add, pos) -- => 19
    -+
    -+ -+ -+

    An initial state can be passed in.

    -+ -+ -+
    -+M.reduceBy(val, add, neg, 17) -- => 0
    -+M.reduceBy(val, add, pos, -19) -- => 0
    -+
    -+ -+ -+

    reduceRight (t, f [, state = next(t)])

    -+

    *Aliases: injectr, foldr*.

    -+ -+

    Similar to M.reduce, but performs from right to left.

    -+ -+ -+
    -+local initial_state = 256
    -+local function div(a,b) return a/b end
    -+M.reduceRight({1,2,4,16},div,initial_state) -- => 2
    -+
    -+ -+ -+

    mapReduce (t, f [, state = next(t)])

    -+

    *Aliases: mapr*.

    -+ -+

    Reduces while saving intermediate states.

    -+ -+ -+
    -+local function concat(a,b) return a..b end
    -+M.mapReduce({'a','b','c'},concat) -- => "{'a', 'ab', 'abc'}"
    -+
    -+ -+ -+

    mapReduceRight (t, f [, state = next(t)])

    -+

    *Aliases: maprr*.

    -+ -+

    Reduces from right to left, while saving intermediate states.

    -+ -+ -+
    -+local function concat(a,b) return a..b end
    -+M.mapReduceRight({'a','b','c'},concat) -- => "{'c', 'cb', 'cba'}"
    -+
    -+ -+ -+

    include (t, value)

    -+

    *Aliases: any, some, contains*.

    -+ -+

    Looks for a value in a table.

    -+ -+ -+
    -+M.include({6,8,10,16,29},16) -- => true
    -+M.include({6,8,10,16,29},1) -- => false
    -+
    -+local complex_table = {18,{2,{3}}}
    -+local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    -+M.include(collection, complex_table) -- => true
    -+
    -+ -+ -+

    Handles iterator functions.

    -+ -+ -+
    -+local function isUpper(v) return v:upper()== v end
    -+M.include({'a','B','c'},isUpper) -- => true
    -+
    -+ -+ -+

    detect (t, value)

    -+ -+

    Returns the index of a value in a table.

    -+ -+ -+
    -+M.detect({6,8,10,16},8) -- => 2
    -+M.detect({nil,true,0,true,true},false) -- => nil
    -+
    -+local complex_table = {18,{2,6}}
    -+local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    -+M.detect(collection, complex_table) -- => 2
    -+
    -+ -+ -+

    Handles iterator functions.

    -+ -+ -+
    -+local function isUpper(v)
    -+  return v:upper()==v
    -+end
    -+M.detect({'a','B','c'},isUpper) -- => 2
    -+
    -+ -+ -+

    where (t, props)

    -+ -+

    Looks through a table and returns all the values that matches all of the key-value pairs listed in props.

    -+ -+ -+
    -+local items = {
    -+  {height = 10, weight = 8, price = 500},
    -+  {height = 10, weight = 15, price = 700},
    -+  {height = 15, weight = 15, price = 3000},
    -+  {height = 10, weight = 8, price = 3000},
    -+}
    -+M.where(items, {height = 10}) -- => {items[1], items[2], items[4]}
    -+M.where(items, {weight = 15}) -- => {items[2], items[3]}
    -+M.where(items, {prince = 3000}) -- => {items[3], items[4]}
    -+M.where(items, {height = 10, weight = 15, prince = 700}) -- => {items[2]}
    -+
    -+ -+ -+

    findWhere (t, props)

    -+ -+

    Looks through a table and returns the first value found that matches all of the key-value pairs listed in props.

    -+ -+ -+
    -+local a = {a = 1, b = 2, c = 3}
    -+local b = {a = 2, b = 3, d = 4}
    -+local c = {a = 3, b = 4, e = 5}
    -+M.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true
    -+
    -+ -+ -+

    select (t, f)

    -+

    *Aliases: filter*.

    -+ -+

    Collects values passing a validation test.

    -+ -+ -+
    -+local function isEven(v) return v%2==0 end
    -+local function isOdd(v) return v%2~=0 end
    -+
    -+M.select({1,2,3,4,5,6,7}, isEven) -- => "{2,4,6}"
    -+M.select({1,2,3,4,5,6,7}, isOdd) -- => "{1,3,5,7}"
    -+
    -+ -+ -+

    reject (t, f)

    -+

    *Aliases: reject*.

    -+ -+

    Removes all values failing (returning false or nil) a validation test:

    -+ -+ -+
    -+local function isEven(v) return v%2==0 end
    -+local function isOdd(v) return v%2~=0 end
    -+
    -+M.reject({1,2,3,4,5,6,7}, isEven) -- => "{1,3,5,7}"
    -+M.reject({1,2,3,4,5,6,7}, isOdd) -- => "{2,4,6}"
    -+
    -+ -+ -+

    all (t, f)

    -+

    *Aliases: every*.

    -+ -+

    Checks whether or not all elements pass a validation test.

    -+ -+ -+
    -+local function isEven(v) return v%2==0 end
    -+M.all({2,4,6}, isEven) -- => true
    -+
    -+ -+ -+

    invoke (t, method)

    -+ -+

    Invokes a given function on each value in a table.

    -+ -+ -+
    -+M.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}"
    -+
    -+ -+ -+

    Can reference the method of the same name in each value.

    -+ -+ -+
    -+local a, b, c, d = {id = 'a'}, {id = 'b'}, {id = 'c'}, {id = 'd'}
    -+local function call(self) return self.id end
    -+M.invoke({a,b,c,d},call) -- => "{'a','b','c','d'}"
    -+
    -+ -+ -+

    pluck (t, property)

    -+ -+

    Fetches all values indexed with specific key in a table of objects.

    -+ -+ -+
    -+local peoples = {
    -+  {name = 'John', age = 23},{name = 'Peter', age = 17},
    -+  {name = 'Steve', age = 15},{age = 33}}
    -+
    -+M.pluck(peoples,'age') -- => "{23,17,15,33}"
    -+M.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}"
    -+
    -+ -+ -+

    max (t [, transform])

    -+ -+

    Returns the maximum value in a collection.

    -+ -+ -+
    -+M.max {1,2,3} -- => 3
    -+M.max {'a','b','c'} -- => 'c'
    -+
    -+ -+ -+

    Can take an iterator function to extract a specific property.

    -+ -+ -+
    -+local peoples = {
    -+  {name = 'John', age = 23},{name = 'Peter', age = 17},
    -+  {name = 'Steve', age = 15},{age = 33}}
    -+M.max(peoples,function(people) return people.age end) -- => 33
    -+
    -+ -+ -+

    min (t [, transform])

    -+ -+

    Returns the minimum value in a collection.

    -+ -+ -+
    -+M.min {1,2,3} -- => 1
    -+M.min {'a','b','c'} -- => 'a'
    -+
    -+ -+ -+

    Can take an iterator function to extract a specific property.

    -+ -+ -+
    -+local peoples = {
    -+  {name = 'John', age = 23},{name = 'Peter', age = 17},
    -+  {name = 'Steve', age = 15},{age = 33}}
    -+M.min(peoples,function(people) return people.age end) -- => 15
    -+
    -+ -+ -+

    same (a, b)

    -+ -+

    Tests whether or not all values in each of the passed-in tables exists in both tables.

    -+ -+ -+
    -+local a = {'a','b','c','d'}
    -+local b = {'b','a','d','c'}
    -+M.same(a,b) -- => true
    -+
    -+b[#b+1] = 'e'
    -+M.same(a,b) -- => false
    -+
    -+ -+ -+

    sort (t [, comp = math.min])

    -+ -+

    Sorts a collection.

    -+ -+ -+
    -+M.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}"
    -+
    -+ -+ -+

    Handles custom comparison functions.

    -+ -+ -+
    -+M.sort({'b','a','d','c'}, function(a,b)
    -+  return a:byte() > b:byte()
    -+end) -- => "{'d','c','b','a'}"
    -+
    -+ -+ -+

    sortedk (t [, comp])

    -+ -+

    Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. -+It returns upon each call a key, value pair.

    -+ -+ -+
    -+local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
    -+for k, v in M.sortedk(tbl) do print(k, v) end
    -+
    -+-- => 1    12
    -+-- => 2 6
    -+-- => 3 5
    -+-- => 4 10
    -+-- => 5 8
    -+
    -+local function comp(a,b) return a > b end
    -+for k, v in M.sortedk(tbl, comp) do print(k, v) end
    -+
    -+-- => 5    8
    -+-- => 4 10
    -+-- => 3 5
    -+-- => 2 6
    -+-- => 1 12
    -+
    -+ -+ -+

    sortedv (t [, comp])

    -+ -+

    Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. -+It returns upon each call a key, value pair.

    -+ -+ -+
    -+local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
    -+for k, v in M.sortedv(tbl) do print(k, v) end
    -+
    -+-- => 3    5
    -+-- => 2 6
    -+-- => 5 8
    -+-- => 4 10
    -+-- => 1 12
    -+
    -+local function comp(a,b) return a > b end
    -+for k, v in M.sortedv(tbl, comp) do print(k, v) end
    -+
    -+-- => 1    12
    -+-- => 4 10
    -+-- => 5 8
    -+-- => 2 6
    -+-- => 3 5
    -+
    -+ -+ -+

    sortBy (t [, transform [, comp = math.min]])

    -+ -+

    Sorts items in a collection based on the result of running a transform function through every item in the collection.

    -+ -+ -+
    -+local r = M.sortBy({1,2,3,4,5}, math.sin)
    -+print(table.concat(r,','))
    -+
    -+-- => {5,4,3,1,2}
    -+
    -+ -+ -+

    The transform function can also be a string name property.

    -+ -+ -+
    -+local people = {
    -+    {name = 'albert', age = 40},
    -+    {name = 'louis', age = 55},
    -+    {name = 'steve', age = 35},
    -+    {name = 'henry', age = 19},
    -+}
    -+local r = M.sortBy(people, 'age')
    -+M.each(r, function(v) print(v.age, v.name) end)
    -+
    -+-- => 19   henry
    -+-- => 35    steve
    -+-- => 40    albert
    -+-- => 55    louis
    -+
    -+ -+ -+

    As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort the list of people by decreasing age order :

    -+ -+ -+
    -+local people = {
    -+    {name = 'albert', age = 40},
    -+    {name = 'louis', age = 55},
    -+    {name = 'steve', age = 35},
    -+    {name = 'henry', age = 19},
    -+}
    -+local r = M.sortBy(people, 'age', function(a,b) return a > b end)
    -+M.each(r, function(v) print(v.age, v.name) end)
    -+
    -+-- => 55   louis
    -+-- => 40    albert
    -+-- => 35    steve
    -+-- => 19    henry
    -+
    -+ -+ -+

    The transform function defaults to M.indentity and in that case, M.sortBy behaves like M.sort.

    -+ -+ -+
    -+local r = M.sortBy({1,2,3,4,5})
    -+print(table.concat(r,','))
    -+
    -+-- => {1,2,3,4,5}
    -+
    -+ -+ -+

    groupBy (t, iter)

    -+ -+

    Groups values in a collection depending on their return value when passed to a predicate test.

    -+ -+ -+
    -+M.groupBy({0,1,2,3,4,5,6},function(v)
    -+  return v%2==0 and 'even' or 'odd'
    -+end)
    -+-- => "{odd = {1,3,5}, even = {0,2,4,6}}"
    -+
    -+M.groupBy({0,'a',true, false,nil,b,0.5},type)
    -+-- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}"        
    -+
    -+ -+ -+

    countBy (t, iter)

    -+ -+

    Splits a table in subsets and provide the count for each subset.

    -+ -+ -+
    -+M.countBy({0,1,2,3,4,5,6},function(v)
    -+  return v%2==0 and 'even' or 'odd'
    -+end) -- => "{odd = 3, even = 4}"
    -+
    -+ -+ -+

    size (...)

    -+ -+

    When given a table, provides the count for the very number of values in that table.

    -+ -+ -+
    -+M.size {1,2,3} -- => 3
    -+M.size {one = 1, two = 2} -- => 2
    -+
    -+ -+ -+

    When given a vararg list of arguments, returns the count of these arguments.

    -+ -+ -+
    -+M.size(1,2,3) -- => 3
    -+M.size('a','b',{}, function() end) -- => 4
    -+
    -+ -+ -+

    containsKeys (t, other)

    -+ -+

    Checks whether a table has all the keys existing in another table.

    -+ -+ -+
    -+M.contains({1,2,3,4},{1,2,3}) -- => true
    -+M.contains({1,2,'d','b'},{1,2,3,5}) -- => true
    -+M.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true
    -+
    -+ -+ -+

    sameKeys (tA, tB)

    -+ -+

    Checks whether both tables features the same keys:

    -+ -+ -+
    -+M.sameKeys({1,2,3,4},{1,2,3}) -- => false
    -+M.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true
    -+M.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false
    -+
    -+ -+ -+

    [⬆]

    -+ -+

    -+

    Array functions

    -+ -+

    sample (array [, n = 1 [, seed]])

    -+ -+

    Samples n values from array.

    -+ -+ -+
    -+local array = M.range(1,20)
    -+local sample = M.sample(array, 3)
    -+print(table.concat(sample,','))
    -+
    -+-- => {12,11,15}
    -+
    -+ -+ -+

    n defaults to 1. In that case, a single value will be returned.

    -+ -+ -+
    -+local array = M.range(1,20)
    -+local sample = M.sample(array)
    -+print(sample)
    -+
    -+-- => 12
    -+
    -+ -+ -+

    An optional 3rd argument seed can be passed for deterministic random sampling.

    -+ -+

    sampleProb (array, prob [, seed])

    -+ -+

    Returns an array of values randomly selected from a given array. -+In case seed is provided, it is used for deterministic sampling.

    -+ -+ -+
    -+local array = M.range(1,20)
    -+local sample = M.sampleProb(array, 0.2)
    -+print(table.concat(sample,','))
    -+
    -+-- => 5,11,12,15
    -+
    -+sample = M.sampleProb(array, 0.2, os.time())
    -+print(table.concat(sample,','))
    -+
    -+-- => 1,6,10,12,15,20 (or similar)
    -+
    -+ -+ -+

    nsorted (array [, n = 1[, comp]])

    -+ -+

    Returns the n-top values satisfying a predicate. It takes a comparison function comp used to sort array values, -+and then picks the top n-values. It leaves the original array untouched.

    -+ -+ -+
    -+local function comp(a,b) return a > b end
    -+M.nsorted(array,5, comp) -- => {5,4,3,2,1}
    -+
    -+ -+ -+

    n defaults to 1 and comp defaults to the < operator.

    -+ -+ -+
    -+local array = M.range(1,20)
    -+M.nsorted(array) -- => {1}
    -+
    -+ -+ -+

    shuffle (array [, seed])

    -+ -+

    Shuffles a given array.

    -+ -+ -+
    -+local list = M.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}"
    -+M.each(list,print)
    -+
    -+ -+ -+

    pack (...)

    -+ -+

    Converts a vararg list of arguments to an array.

    -+ -+ -+
    -+M.pack(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}"
    -+
    -+ -+ -+

    find (array, value [, from = 1])

    -+ -+

    Looks for a value in a given array and returns the position of the first occurence.

    -+ -+ -+
    -+local value = {3}
    -+M.find({{4},{3},{2},{1}},value) -- => 2
    -+
    -+ -+ -+

    It can also start the search at a specific position in the array:

    -+ -+ -+
    -+-- search value 4 starting from index 3
    -+M.find({1,4,2,3,4,5},4,3) -- => 5
    -+
    -+ -+ -+

    reverse (array)

    -+ -+

    Reverses an array.

    -+ -+ -+
    -+M.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}"
    -+
    -+ -+ -+

    fill (array, value [, i = 1 [, j = #array]])

    -+ -+

    Replaces all elements in a given array with a given value.

    -+ -+ -+
    -+local array = M.range(1,5)
    -+M.fill(array, 0) -- => {0,0,0,0,0}
    -+
    -+ -+ -+

    It can start replacing value at a specific index.

    -+ -+ -+
    -+local array = M.range(1,5)
    -+M.fill(array,0,3) -- => {1,2,0,0,0}
    -+
    -+ -+ -+

    It can replace only values within a specific range.

    -+ -+ -+
    -+local array = M.range(1,5)
    -+M.fill(array,0,2,4) -- => {1,0,0,0,5}
    -+
    -+ -+ -+

    In case the upper bound index i greather than the array size, it will enlarge the array.

    -+ -+ -+
    -+local array = M.range(1,5)
    -+M.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0}
    -+
    -+ -+ -+

    zeros (n)

    -+ -+

    Returns an array of n zeros.

    -+ -+ -+
    -+M.zeros(4) -- => {0,0,0,0}
    -+
    -+ -+ -+

    ones (n)

    -+ -+

    Returns an array of n 1's.

    -+ -+ -+
    -+M.ones(3) -- => {1,1,1}
    -+
    -+ -+ -+

    vector (value, n)

    -+ -+

    Returns an array of n times a given value.

    -+ -+ -+
    -+M.vector(10, 4) -- => {10,10,10,10}
    -+
    -+ -+ -+

    selectWhile (array, f [, ...])

    -+

    *Aliases: takeWhile*.

    -+ -+

    Collects values as long as they pass a given test. Stops on the first non-passing test.

    -+ -+ -+
    -+M.selectWhile({2,4,5,8}, function(v)
    -+  return v%2==0
    -+end) -- => "{2,4}"
    -+
    -+ -+ -+

    dropWhile (array, f [, ...])

    -+

    *Aliases: rejectWhile*.

    -+ -+

    Removes values as long as they pass a given test. Stops on the first non-passing test.

    -+ -+ -+
    -+M.dropWhile({2,4,5,8}, function(v)
    -+  return v%2==0
    -+end) -- => "{5,8}"
    -+
    -+ -+ -+

    sortedIndex (array, value [, comp = math.min [, sort = nil]])

    -+ -+

    Returns the index at which a value should be inserted to preserve order.

    -+ -+ -+
    -+M.sortedIndex({1,2,3},4) -- => 4
    -+
    -+ -+ -+

    Can take a custom comparison functions.

    -+ -+ -+
    -+local comp = function(a,b) return a<b end
    -+M.sortedIndex({-5,0,4,4},3,comp) -- => 3
    -+
    -+ -+ -+

    indexOf (array, value)

    -+ -+

    Returns the index of a value in an array.

    -+ -+ -+
    -+M.indexOf({1,2,3},2) -- => 2
    -+
    -+ -+ -+

    lastIndexOf (array, value)

    -+ -+

    Returns the index of the last occurence of a given value in an array.

    -+ -+ -+
    -+M.lastIndexOf({1,2,2,3},2) -- => 3
    -+
    -+ -+ -+

    findIndex (array, pred)

    -+ -+

    Returns the first index at which a predicate passes a truth test.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6}
    -+local function multipleOf3(v) return v%3==0 end
    -+M.findIndex(array, multipleOf3) -- => 3
    -+
    -+ -+ -+

    findLastIndex (array, pred)

    -+ -+

    Returns the last index at which a predicate passes a truthy test.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6}
    -+local function multipleOf3(v) return v%3==0 end
    -+M.findLastIndex(array, multipleOf3) -- => 6
    -+
    -+ -+ -+

    addTop (array, ...)

    -+ -+

    Adds given values at the top of an array. The latter values bubbles at the top.

    -+ -+ -+
    -+local array = {1}
    -+M.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}"
    -+
    -+ -+ -+

    prepend (array, ...)

    -+ -+

    Adds given values at the top of an array, preserving the order at which elements are passed-in.

    -+ -+ -+
    -+local array = {'old_val'}
    -+M.prepend(array,1,2,3,4) -- => "{1,2,3,4,'old_val'}"
    -+
    -+ -+ -+

    push (array, ...)

    -+ -+

    Adds given values at the end of an array.

    -+ -+ -+
    -+local array = {1}
    -+M.push(array,1,2,3,4) -- => "{1,1,2,3,4}"
    -+
    -+ -+ -+

    shift (array [, n = 1])

    -+

    *Aliases: pop*.

    -+ -+

    Removes and returns the first value in an array.

    -+ -+ -+
    -+local array = {1,2,3}
    -+local shift = M.shift(array) -- => "shift = 1", "array = {2,3}"
    -+
    -+ -+

    If n is supplied, returns n values.

    -+ -+ -+
    -+local array = {1,2,3,4,5}
    -+local a, b = M.shift(array, 2) -- => "a = 1, b = 2", "array = {3,4,5}"
    -+
    -+ -+ -+

    unshift (array [, n = 1])

    -+ -+

    Removes and returns the last value in an array.

    -+ -+ -+
    -+local array = {1,2,3}
    -+local value = M.unshift(array) -- => "value = 3", "array = {1,2}"
    -+
    -+ -+ -+

    pull (array, ...)

    -+

    *Aliases: remove*.

    -+ -+

    Removes all provided values from a given array.

    -+ -+ -+
    -+M.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}"
    -+
    -+ -+ -+

    removeRange (array [, start = 1 [, finish = #array]])

    -+

    *Aliases: rmRange, M.chop*.

    -+ -+

    Trims out all values index within a range.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.removeRange(array, 3,8) -- => "{1,2,9}"
    -+
    -+ -+ -+

    chunk (array [, f])

    -+ -+

    Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return value of f(v, k, ...). Consecutive elements which return the same value are chunked together.

    -+ -+ -+
    -+local t = {1,5,2,4,3,3,4}
    -+M.chunk(t, function(v) return v%2==0 end) -- => "{{1,5},{2,4},{3,3},{4}}"
    -+
    -+ -+ -+

    If not given, f defaults to identity.

    -+ -+ -+
    -+local t = {1,5,2,4,3,3,4}
    -+M.chunk(t) -- => "{{1},{5},{2},{4},{3,3},{4}}"
    -+
    -+ -+ -+

    slice (array [, start = 1 [, finish = #array]])

    -+

    *Aliases: sub*.

    -+ -+

    Slices and returns a part of an array.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.slice(array, 3,6) -- => "{3,4,5,6}"
    -+
    -+ -+ -+

    first (array [, n = 1])

    -+

    *Aliases: head, M.take*.

    -+ -+

    Returns the first N elements in an array.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.first(array,3) -- => "{1,2,3}"
    -+
    -+ -+ -+

    initial (array [, n = #array])

    -+ -+

    Excludes the last N elements in an array.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.initial(array,5) -- => "{1,2,3,4}"
    -+
    -+ -+ -+

    last (array [, n = #array])

    -+ -+

    Returns the last N elements in an array.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.last(array,3) -- => "{7,8,9}"
    -+
    -+ -+ -+

    rest (array [, index = 1])

    -+

    *Aliases: tail*.

    -+ -+

    Returns all values after index, including the given index itself.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6,7,8,9}
    -+M.rest(array,6) -- => "{6,7,8,9}"
    -+
    -+ -+ -+

    nth (array, index)

    -+ -+

    Returns the value at index.

    -+ -+ -+
    -+local array = {1,2,3,4,5,6}
    -+M.nth(array,3) -- => "3"
    -+
    -+ -+ -+

    compact (array)

    -+ -+

    Trims out all falsy values.

    -+ -+ -+
    -+M.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}"
    -+
    -+ -+ -+

    flatten (array [, shallow = false])

    -+ -+

    Flattens a nested array.

    -+ -+ -+
    -+M.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}"
    -+
    -+ -+ -+

    When given arg shallow, flatten only at the first level.

    -+ -+ -+
    -+M.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}"
    -+
    -+ -+ -+

    difference (array, array2)

    -+

    *Aliases: without, diff*.

    -+ -+

    Returns values in the given array not present in a second array.

    -+ -+ -+
    -+local array = {1,2,'a',4,5}
    -+M.difference(array,{1,'a'}) -- => "{2,4,5}"
    -+
    -+ -+ -+

    union (...)

    -+ -+

    Produces a duplicate-free union of all passed-in arrays.

    -+ -+ -+
    -+local A = {'a'}
    -+local B = {'a',1,2,3}
    -+local C = {2,10}
    -+M.union(A,B,C) -- => "{'a',1,2,3,10}"
    -+
    -+ -+ -+

    intersection (...)

    -+ -+

    Returns the intersection (common-part) of all passed-in arrays:

    -+ -+ -+
    -+local A = {'a'}
    -+local B = {'a',1,2,3}
    -+local C = {2,10,1,'a'}
    -+M.intersection(A,B,C) -- => "{'a'}"
    -+
    -+ -+ -+

    disjoint (...)

    -+ -+

    Checks if all passed in arrays are disjoint.

    -+ -+ -+
    -+local A = {'a'}
    -+local B = {'a',1,3}
    -+local C = {3,10,2}
    -+
    -+M.disjoint(A,B) -- => false
    -+M.disjoint(A,C) -- => true
    -+M.disjoint(B,C) -- => false
    -+
    -+ -+ -+

    symmetricDifference (array, array2)

    -+

    *Aliases: symdiff,xor*.

    -+ -+

    Returns values in the first array not present in the second and also values in the second array not present in the first one.

    -+ -+ -+
    -+local array = {1,2,3}
    -+local array2 = {1,4,5}
    -+M.symmetricDifference(array, array2) -- => "{2,3,4,5}"
    -+
    -+ -+ -+

    unique (array)

    -+

    *Aliases: uniq*.

    -+ -+

    Makes an array duplicate-free.

    -+ -+ -+
    -+M.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}"
    -+
    -+ -+ -+

    isunique (array)

    -+

    *Aliases: isuniq*.

    -+ -+

    Checks if a given array contains no duplicate value.

    -+ -+ -+
    -+M.isunique({1,2,3,4,5}) -- => true
    -+M.isunique({1,2,3,4,4}) -- => false
    -+
    -+ -+ -+

    duplicates (array)

    -+ -+

    Returns an array list of all duplicates in array.

    -+ -+ -+
    -+M.duplicates({1,2,3,3,8,8,3,2,4}) -- => {2,3,8}
    -+
    -+ -+ -+

    zip (...)

    -+

    *Aliases: transpose*.

    -+ -+

    Zips values from different arrays, on the basis on their common keys.

    -+ -+ -+
    -+local names = {'Bob','Alice','James'}
    -+local ages = {22, 23}
    -+M.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}"
    -+
    -+ -+ -+

    zipWith (f, ...)

    -+

    *Aliases: transposeWith*.

    -+ -+

    Merges values using a given function. Only values indexed with the same key in the given arrays are merged in the same subset. -+Function f is used to combine values.

    -+ -+ -+
    -+local names = {'Bob','Alice','James'}; local ages = {22, 23, 25}
    -+local function introduce(name, age) return 'I am '..name..' and I am '..age..' years old.' end
    -+local t = M.zipWith(introduce,names,ages)
    -+-- => {
    -+-- =>  'I am Bob and I am 22 years old.'
    -+-- =>  'I am Alice and I am 23 years old.'
    -+-- =>  'I am James and I am 25 years old.'
    -+-- => }
    -+
    -+ -+ -+

    append (array, other)

    -+ -+

    Appends two arrays.

    -+ -+ -+
    -+M.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}"
    -+
    -+ -+ -+

    interleave (...)

    -+ -+

    Interleaves values from passed-in arrays.

    -+ -+ -+
    -+t1 = {1, 2, 3}
    -+t2 = {'a', 'b', 'c'}
    -+M.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}"
    -+
    -+ -+ -+

    interpose (array, value)

    -+

    *Aliases: intersperce*.

    -+ -+

    Interposes a value between consecutive values in an arrays.

    -+ -+ -+
    -+M.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}"
    -+
    -+ -+ -+

    range ([from [, to [, step]]])

    -+ -+

    Generates an arithmetic sequence.

    -+ -+ -+
    -+M.range(1,4) -- => "{1,2,3,4}"
    -+
    -+ -+ -+

    In case a single value is provided, it generates a sequence from 1 to that value.

    -+ -+ -+
    -+M.range(3) -- => "{1,2,3}"
    -+
    -+ -+ -+

    The incremental step can also be provided as third argument.

    -+ -+ -+
    -+M.range(0,2,0.7) -- => "{0,0.7,1.4}"
    -+
    -+ -+ -+

    It also handles negative progressions.

    -+ -+ -+
    -+M.range(-5) -- => "{-1,-2,-3,-4,-5}"
    -+M.range(5,1) -- => "{5,4,3,2,1}"
    -+
    -+ -+ -+

    rep (value, n)

    -+ -+

    Generates a list of n repetitions of a value.

    -+ -+ -+
    -+M.rep(4,3) -- => "{4,4,4}"
    -+
    -+ -+ -+

    powerset (array)

    -+ -+

    Returns the powerset of an array.

    -+ -+ -+
    -+M.powerset {1,2,3} -- => "{{1},{2},{3},{1,2},{2,3},{1,2,3}}"
    -+
    -+ -+ -+

    partition (array [, n = 1 [, pad]])

    -+

    *Aliases: part*.

    -+ -+

    Returns an iterator function for partitions of a given array.

    -+ -+ -+
    -+local t = {1,2,3,4,5,6}
    -+for p in M.partition(t,2) do
    -+  print(table.concat(p, ','))
    -+end
    -+
    -+-- => 1,2
    -+-- => 3,4
    -+-- => 5,6
    -+
    -+local t = {1,2,3,4,5,6}
    -+for p in M.partition(t,4) do
    -+  print(table.concat(p, ','))
    -+end
    -+
    -+-- => 1,2,3,4
    -+-- => 5,6
    -+
    -+ -+ -+

    In case the last partition has less elements than desired, a 3rd argument can be supplied to adjust the partition size.

    -+ -+ -+
    -+local t = {1,2,3,4,5,6}
    -+for p in M.partition(t,4,0) do
    -+  print(table.concat(p, ','))
    -+end
    -+
    -+-- => 1,2,3,4
    -+-- => 5,6,0,0
    -+
    -+ -+ -+

    overlapping (array [, n = 2 [, pad]])

    -+ -+

    Returns an iterator function which provides overlapping subsequences of a given array.

    -+ -+ -+
    -+local t = {1,2,3,4,5,6,7}
    -+for p in M.overlapping(t,3) do
    -+    print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3
    -+-- => 3,4,5
    -+-- => 5,6,7
    -+
    -+for p in M.overlapping(t,4) do
    -+    print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3,4
    -+-- => 4,5,6,7
    -+
    -+for p in M.overlapping(t,5) do
    -+    print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3,4,5
    -+-- => 5,6,7
    -+
    -+ -+ -+

    In case the last subsequence wil not match the exact desired length, it can be adjusted with a 3rd argument pad.

    -+ -+ -+
    -+local t = {1,2,3,4,5,6,7}
    -+for p in M.overlapping(t,5,0) do
    -+    print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3,4,5
    -+-- => 5,6,7,0,0
    -+
    -+ -+ -+

    aperture (array [, n = 2])

    -+

    *Aliases: sliding*.

    -+ -+

    Returns an iterator function which provides sliding partitions of a given array.

    -+ -+ -+
    -+local t = {1,2,3,4,5}
    -+for p in M.aperture(t,4) do
    -+  print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3,4
    -+-- => 2,3,4,5
    -+
    -+for p in M.aperture(t,3) do
    -+  print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2,3
    -+-- => 2,3,4
    -+-- => 3,4,5
    -+
    -+ -+ -+

    pairwise (array)

    -+ -+

    Iterator returning sliding pairs of an array.

    -+ -+ -+
    -+local t = M.range(5)
    -+for p in pairwise(t) do
    -+  print(table.concat(p,','))
    -+end
    -+
    -+-- => 1,2
    -+-- => 2,3
    -+-- => 3,4
    -+-- => 4,5
    -+
    -+ -+ -+

    permutation (array)

    -+

    *Aliases: perm*.

    -+ -+

    Returns an iterator function for permutations of a given array.

    -+ -+ -+
    -+t = {'a','b','c'}
    -+for p in M.permutation(t) do
    -+  print(table.concat(p))
    -+end
    -+
    -+-- => 'bca'
    -+-- => 'cba'
    -+-- => 'cab'
    -+-- => 'acb'
    -+-- => 'bac'
    -+-- => 'abc'
    -+
    -+ -+ -+

    concat (array [, sep = '' [, i = 1 [, j = #array]]])

    -+

    *Aliases: join*.

    -+ -+

    Concatenates a given array values:

    -+ -+ -+
    -+M.concat({'a',1,0,1,'b'}) -- => 'a101b'
    -+
    -+ -+ -+

    xprod (array, array2)

    -+ -+

    Returns all possible pairs built from given arrays.

    -+ -+ -+
    -+local t = M.xprod({1,2},{'a','b'})
    -+-- => {{1,'a'},{1,'b'},{2,'a'},{2,'b'}}
    -+
    -+ -+ -+

    xpairs (value, array)

    -+ -+

    Creates pairs from value and array. Value is always prepended to the pair.

    -+ -+ -+
    -+local t = M.xpairs(1, {1, 2, 3})
    -+-- => {{1,1},{1,2},{1,3}}
    -+
    -+ -+ -+

    xpairsRight (value, array)

    -+ -+

    Creates pairs from value and array. Value is always appended as the last item to the pair.

    -+ -+ -+
    -+local t = M.xpairsRight(1, {1, 2, 3})
    -+-- => {{1,1},{2,1},{3,1}}
    -+
    -+ -+ -+

    sum (array)

    -+ -+

    Returns the sum of array values.

    -+ -+ -+
    -+M.sum({1,2,3,4,5}) -- => 15
    -+
    -+ -+ -+

    product (array)

    -+ -+

    Returns the product of array values.

    -+ -+ -+
    -+M.product({1,2,3,4,5}) -- => 120
    -+
    -+ -+ -+

    mean (array)

    -+ -+

    Returns the mean of array values.

    -+ -+ -+
    -+M.mean({1,2,3,4,5}) -- => 3
    -+
    -+ -+ -+

    median (array)

    -+ -+

    Returns the median of array values.

    -+ -+ -+
    -+M.median({1,2,3,4,5}) -- => 3
    -+M.median({1,2,3,4}) -- => 2.5
    -+
    -+ -+ -+

    [⬆]

    -+ -+

    -+

    Utility functions

    -+ -+

    noop ()

    -+ -+

    The no-operation function. Takes nothing, returns nothing. It is being used internally.

    -+ -+ -+
    -+M.noop() -- => nil
    -+
    -+ -+ -+

    identity (value)

    -+ -+

    Returns the passed-in value.
    -+This function is internally used as a default transformation function.

    -+ -+ -+
    -+M.identity(1)-- => 1
    -+M.identity(false) -- => false
    -+M.identity('hello!') -- => 'hello!'
    -+
    -+ -+ -+

    call (f [, ...])

    -+ -+

    Calls f with the supplied arguments. Returns the results of f(...).

    -+ -+ -+
    -+M.call(math.pow, 2, 3) -- => 8
    -+M.call(string.len, 'hello' ) -- => 5
    -+M.call(table.concat, {1,2,3,4,5}, ',', 2, 4) -- => {2,3,4}
    -+
    -+ -+ -+

    constant (value)

    -+ -+

    Creates a constant function. This function will continuously yield the same output.

    -+ -+ -+
    -+local pi = M.constant(math.pi)
    -+pi(1) -- => 3.1415926535898
    -+pi(2) -- => 3.1415926535898
    -+pi(math.pi) -- => 3.1415926535898
    -+
    -+ -+ -+

    applySpec (specs)

    -+ -+

    Returns a function which applies specs on args. This function will produce an object having the same structure than specs -+by mapping each property to the result of calling its associated function with the supplied arguments.

    -+ -+ -+
    -+local stats = M.applySpec({
    -+  min = function(...) return math.min(...) end,
    -+  max = function(...) return math.max(...) end,
    -+})
    -+
    -+stats(5,4,10,1,8) -- => {min = 1, max = 10}
    -+
    -+ -+ -+

    thread (value [, ...])

    -+ -+

    Threads value through a series of functions.

    -+ -+ -+
    -+local function inc(x) return x + 1 end
    -+local function double(x) return 2 * x end
    -+local function square(x) return x * x end
    -+M.thread(2, inc, double, square) -- => 36
    -+M.thread(3, double, inc, square) -- => 49
    -+M.thread(4, square, double, inc) -- => 33
    -+M.thread(5, square, inc, double) -- => 52
    -+
    -+ -+ -+

    If a function expects more than one args, it can be specified using an array list, -+where the first item is the function and the following are the remaining args neeeded.

    -+ -+ -+
    -+local function inc(x) return x + 1 end
    -+local function add(x, y) return x * y end
    -+local function pow(x, y) return x ^ y end
    -+M.thread(2, inc, {add, 3}, {pow, 2}) -- => 36
    -+M.thread(2, {add, 4}, inc, {pow, 2}) -- => 49
    -+
    -+ -+ -+

    threadRight (value [, ...])

    -+ -+

    Threads value through a series of functions. If a function expects more than one args, -+it can be specified using an array list, where the first item is the function and the following are -+the remaining args neeeded. The value is used as the last input.

    -+ -+ -+
    -+local function inc(x) return x + 1 end
    -+local function add(x, y) return x * y end
    -+local function pow(x, y) return x ^ y end
    -+M.threadRight(2, inc, {add, 3}, {pow, 2}) -- => 64
    -+M.threadRight(2, {add, 4}, inc, {pow, 2}) -- => 128
    -+
    -+ -+ -+

    dispatch (...)

    -+ -+

    Returns a dispatching function. When called with arguments, this function invokes each of its functions -+in the passed-in order and returns the results of the first non-nil evaluation.

    -+ -+ -+
    -+local f = M.dispatch(
    -+  function() return nil end,
    -+  function (v) return v+1 end,
    -+  function (v) return 2*v end
    -+)
    -+f(5) -- => 6
    -+f(7) -- => 8
    -+
    -+ -+ -+

    memoize (f)

    -+

    *Aliases: cache*.

    -+ -+

    Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body.

    -+ -+ -+
    -+local function fibonacci(n)
    -+  return n < 2 and n or fibonacci(n-1)+fibonacci(n-2)
    -+end
    -+local mem_fibonacci = M.memoize(fibonacci)
    -+fibonacci(20) -- => 6765 (but takes some time)
    -+mem_fibonacci(20) -- => 6765 (takes less time)
    -+
    -+ -+ -+

    unfold (f, seed)

    -+ -+

    Builds a list from a seed value. Accepts an iterator function, which returns either nil to stop iteration or two values : the value to add to the list of results and the seed to be used in the next call to the iterator function.

    -+ -+ -+
    -+local function f(v)
    -+  if v < 100 then return v, v * 2 end
    -+end
    -+local t = M.unfold(f, 10) -- => {10,20,40,80}
    -+
    -+ -+ -+

    once (f)

    -+ -+

    Produces a function that runs only once. Successive calls to this function will still yield the same input.

    -+ -+ -+
    -+local sq = M.once(function(a) return a*a end)
    -+sq(1) -- => 1
    -+sq(2) -- => 1
    -+sq(3) -- => 1
    -+sq(4) -- => 1
    -+sq(5) -- => 1
    -+
    -+ -+ -+

    before (f, count)

    -+ -+

    Returns a version of f that will run no more than count times. Next calls will keep yielding the results of the (n-th)-1 call.

    -+ -+ -+
    -+local function greet(someone) return 'hello '..someone end
    -+local greetOnly3people = M.before(greet, 3)
    -+greetOnly3people('John') -- => 'hello John'
    -+greetOnly3people('Moe') -- => 'hello Moe'
    -+greetOnly3people('James') -- => 'hello James'
    -+greetOnly3people('Joseph') -- => 'hello James'
    -+greetOnly3people('Allan') -- => 'hello James'
    -+
    -+ -+ -+

    after (f, count)

    -+ -+

    Produces a function that will respond only after a given number of calls.

    -+ -+ -+
    -+local f = M.after(M.identity,3)
    -+f(1) -- => nil
    -+f(2) -- => nil
    -+f(3) -- => 3
    -+f(4) -- => 4
    -+
    -+ -+ -+

    compose (...)

    -+ -+

    Composes functions. Each function consumes the return value of the one that follows.

    -+ -+ -+
    -+local function f(x) return x^2 end
    -+local function g(x) return x+1 end
    -+local function h(x) return x/2 end
    -+local compositae = M.compose(f,g,h)
    -+compositae(10) -- => 36
    -+compositae(20) -- => 121
    -+
    -+ -+ -+

    pipe (value, ...)

    -+ -+

    Pipes a value through a series of functions.

    -+ -+ -+
    -+local function f(x) return x^2 end
    -+local function g(x) return x+1 end
    -+local function h(x) return x/2 end
    -+M.pipe(10,f,g,h) -- => 36
    -+M.pipe(20,f,g,h) -- => 121
    -+
    -+ -+ -+

    complement (f)

    -+ -+

    Returns a function which returns the logical complement of a given function.

    -+ -+ -+
    -+M.complement(function() return true end)() -- => false
    -+
    -+ -+ -+

    juxtapose (value, ...)

    -+

    *Aliases: juxt*.

    -+ -+

    Calls a sequence of functions with the same input.

    -+ -+ -+
    -+local function f(x) return x^2 end
    -+local function g(x) return x+1 end
    -+local function h(x) return x/2 end
    -+M.juxtapose(10, f, g, h) -- => 100, 11, 5
    -+
    -+ -+ -+

    wrap (f, wrapper)

    -+ -+

    Wraps a function inside a wrapper. Allows the wrapper to execute code before and after function run.

    -+ -+ -+
    -+local greet = function(name) return "hi: " .. name end
    -+local greet_backwards = M.wrap(greet, function(f,arg)
    -+  return f(arg) ..'\nhi: ' .. arg:reverse()
    -+end)
    -+greet_backwards('John')
    -+
    -+-- => hi: John
    -+-- => hi: nhoJ
    -+
    -+ -+ -+

    times (iter [, n])

    -+ -+

    Calls a given function n times.

    -+ -+ -+
    -+local f = ('Lua programming'):gmatch('.')
    -+M.times(f, 3) -- => {'L','u','a'}
    -+
    -+ -+ -+

    bind (f, v)

    -+ -+

    Binds a value to be the first argument to a function.

    -+ -+ -+
    -+local sqrt2 = M.bind(math.sqrt,2)
    -+sqrt2() -- => 1.4142135623731
    -+
    -+ -+ -+

    bind2 (f, v)

    -+ -+

    Binds a value to be the second argument to a function.

    -+ -+ -+
    -+local last2 = M.bind(M.last,2)
    -+last2({1,2,3,4,5,6}) -- => {5,6}
    -+
    -+ -+ -+

    bindn (f, ...)

    -+ -+

    Binds a variable number of values to be the first arguments to a function.

    -+ -+ -+
    -+local function out(...) return table.concat {...} end
    -+local out = M.bindn(out,'OutPut',':',' ')
    -+out(1,2,3) -- => OutPut: 123
    -+out('a','b','c','d') -- => OutPut: abcd
    -+
    -+ -+ -+

    bindall (obj, ...)

    -+ -+

    Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument.

    -+ -+ -+
    -+local window = {
    -+    setPos = function(w,x,y) w.x, w.y = x, y end,
    -+    setName = function(w,name) w.name = name end,
    -+    getName = function(w) return w.name end,
    -+}
    -+window = M.bindall(window, 'setPos', 'setName', 'getName')
    -+window.setPos(10,15)
    -+print(window.x, window.y) -- => 10,15
    -+
    -+window.setName('fooApp')
    -+print(window.name) -- => 'fooApp'
    -+
    -+print(window.getName()) -- => 'fooApp'
    -+
    -+ -+ -+

    cond (conds)

    -+ -+

    Returns a function which iterate over an array list of conditions. It invokes each predicate, passing it given values. It returns the value of the corresponding function of the first predicate to return a non-nil value

    -+ -+ -+
    -+local multipleOf = M.cond({
    -+  {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end},
    -+  {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end},
    -+  {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end},
    -+  {function() return true end, function(v) return 'could not find an answer for '..v end}
    -+})
    -+for i = 15, 20 do
    -+  print(multipleOf(i))
    -+end
    -+
    -+-- => 15 is multiple of 3
    -+-- => 16 is multiple of 2
    -+-- => could not find an answer for 17
    -+-- => 18 is multiple of 2
    -+-- => could not find an answer for 19
    -+-- => 20 is multiple of 2
    -+
    -+ -+ -+

    both (...)

    -+ -+

    Returns a validation function. Given a set of functions, the validation function -+evaluates to true only when all its funcs returns true.

    -+ -+ -+
    -+local f = M.both(
    -+    function(x) return x > 0 end,
    -+    function(x) return x < 10 end,
    -+    function(x) return x % 2 == 0 end
    -+)
    -+f(2) -- => true
    -+f(8) -- => true
    -+f(9) -- => false
    -+
    -+ -+ -+

    either (...)

    -+ -+

    Returns a validation function. Given a set of functions, the validation function -+evaluates to true when one of its funcs returns true.

    -+ -+ -+
    -+local f = M.either(
    -+    function(x) return x > 0 end,
    -+    function(x) return x % 2 == 0 end
    -+)
    -+f(0) -- => true
    -+f(-3) -- => false
    -+
    -+ -+ -+

    neither (...)

    -+ -+

    Returns a validation function. Given a set of functions, the validation function -+evaluates to true when neither of its funcs returns true.

    -+ -+ -+
    -+local f = M.neither(
    -+    function(x) return x > 10 end,
    -+    function(x) return x % 2 == 0 end
    -+)
    -+f(12) -- => false
    -+f(8) -- => false
    -+f(7) -- => true
    -+
    -+ -+ -+

    uniqueId ([template])

    -+

    *Aliases: uid*.

    -+ -+

    Returns an unique integer ID.

    -+ -+ -+
    -+M.uniqueId() -- => 1
    -+
    -+ -+ -+

    Can handle string templates for formatted output.

    -+ -+ -+
    -+M.uniqueId('ID%s') -- => 'ID2'
    -+
    -+ -+ -+

    Or a function, for the same purpose.

    -+ -+ -+
    -+local formatter = function(ID) return '$'..ID..'$' end
    -+M.uniqueId(formatter) -- => '$ID1$'
    -+
    -+ -+ -+

    iterator (f, value [, n])

    -+

    *Aliases: iter*.

    -+ -+

    Returns an iterator function which constinuously applies a function f onto an input value. -+For example, let us go through the powers of two using iterator.

    -+ -+ -+
    -+local function po2(x) return x*2 end
    -+local function iter_po2 = M.iterator(po2, 1)
    -+iter_po2() -- => 2
    -+iter_po2() -- => 4
    -+iter_po2() -- => 8
    -+
    -+ -+ -+

    if n is supplied, it will run at maximum n times.

    -+ -+ -+
    -+local function po2(x) return x*2 end
    -+local function iter_po2 = M.iterator(po2, 1, 3)
    -+iter_po2() -- => 2
    -+iter_po2() -- => 4
    -+iter_po2() -- => 8
    -+iter_po2() -- => nil
    -+
    -+ -+ -+

    skip (iter [, n = 1])

    -+ -+

    Consumes the first n values of a iterator then returns it.

    -+ -+ -+
    -+local w = "hello"
    -+local char = string.gmatch(w,'.')
    -+local iter = M.skip(char, 3)
    -+for w in iter do print(w) end -- => 'l', 'o'
    -+
    -+ -+ -+

    n defaults to 1 when not given.

    -+ -+ -+
    -+local w = "hello"
    -+local char = string.gmatch(w,'.')
    -+local iter = M.skip(char)
    -+for w in iter do print(w) end -- => 'e', 'l', 'l', 'o'
    -+
    -+ -+ -+

    tabulate (...)

    -+ -+

    Iterates a given iterator function and returns its values packed in an array.

    -+ -+ -+
    -+local text = 'letters'
    -+local chars = string.gmatch(text, '.')
    -+M.tabulate(chars) -- => {'l','e','t','t','e','r','s'}
    -+
    -+ -+ -+

    iterlen (...)

    -+ -+

    Returns the length of an iterator.

    -+ -+ -+
    -+local text = 'letters'
    -+local chars = string.gmatch(text, '.')
    -+M.iterlen(chars) -- => 7
    -+
    -+ -+ -+

    It consumes the iterator itself.

    -+ -+ -+
    -+local text = 'lua'
    -+local chars = string.gmatch(text, '.')
    -+M.iterlen(chars) -- => 3
    -+chars() -- => nil
    -+
    -+ -+ -+

    castArray (value)

    -+ -+

    Casts the passed-in value to an array containing the value itself.

    -+ -+ -+
    -+M.castArray(true) -- => {true}
    -+M.castArray(2) -- => {2}
    -+
    -+ -+ -+

    It leaves the given value untouched in case it is already a table.

    -+ -+ -+
    -+local t = {1}
    -+print(M.castArray(t) == t) -- => true
    -+
    -+ -+ -+

    flip (f)

    -+ -+

    Creates a function of f with arguments flipped in reverse order.

    -+ -+ -+
    -+local function f(...) return table.concat({...}) end
    -+local flipped = M.flip(f)
    -+flipped('a','b','c') -- => 'cba'
    -+
    -+ -+ -+

    nthArg (n)

    -+ -+

    Returns a function that gets the nth argument.

    -+ -+ -+
    -+local f = M.nthArg(3)
    -+f('a','b','c') -- => 'c'
    -+
    -+ -+ -+

    If n is negative, the nth argument from the end is returned.

    -+ -+ -+
    -+local f = M.nthArg(-2)
    -+f('a','b','c') -- => 'b'
    -+
    -+ -+ -+

    unary (f)

    -+ -+

    Returns a function which accepts up to one argument. It ignores any additional arguments.

    -+ -+ -+
    -+local f = M.unary(function (...) return ... end)
    -+f('a') - ==> 'a'
    -+f('a','b','c') -- => 'a'
    -+
    -+ -+ -+

    ary (f [, n = 1])

    -+

    *Aliases: nAry*.

    -+ -+

    Returns a function which accepts up to n args. It ignores any additional arguments.

    -+ -+ -+
    -+local f = M.ary(function (...) return ... end, 2)
    -+f(1,2) - ==> 1,2
    -+f(1,2,3,4) -- => 1,2
    -+
    -+ -+ -+

    If n is not given, it defaults to 1.

    -+ -+ -+
    -+local f = M.unary(function (...) return ... end)
    -+f('a','b','c') -- => 'a'
    -+
    -+ -+ -+

    noarg (f)

    -+ -+

    Returns a function with an arity of 0. The new function ignores any arguments passed to it.

    -+ -+ -+
    -+local f = M.noarg(function (x) return x or 'default' end)
    -+f(1) -- => 'default'
    -+f(function() end, 3) -- => 'default'
    -+
    -+ -+ -+

    rearg (f, indexes)

    -+ -+

    Returns a function which runs with arguments arranged according to given indexes.

    -+ -+ -+
    -+local f = M.rearg(function (...) return ... end, {5,4,3,2,1})
    -+f('a','b','c','d','e') -- => 'e','d','c','b','a'
    -+
    -+ -+ -+

    over (...)

    -+ -+

    Creates a function that invokes a set of transforms with the arguments it receives.
    -+One can use use for example to get the tuple of min and max values from a set of values

    -+ -+ -+
    -+local minmax = M.over(math.min, math.max)
    -+minmax(5,10,12,4,3) -- => {3,12}
    -+
    -+ -+ -+

    overEvery (...)

    -+ -+

    Creates a validation function. The returned function checks if all of the given predicates return truthy when invoked with the arguments it receives.

    -+ -+ -+
    -+local function alleven(...)
    -+    for i, v in ipairs({...}) do
    -+        if v%2~=0 then return false end
    -+    end
    -+    return true
    -+end
    -+
    -+local function allpositive(...)
    -+    for i, v in ipairs({...}) do
    -+        if v < 0 then return false end
    -+    end
    -+    return true
    -+end
    -+
    -+local allok = M.overEvery(alleven, allpositive)
    -+
    -+allok(2,4,-1,8) -- => false
    -+allok(10,3,2,6) -- => false
    -+allok(8,4,6,10) -- => true
    -+
    -+ -+ -+

    overSome (...)

    -+ -+

    Creates a validation function. The returned function checks if any of the given predicates return truthy when invoked with the arguments it receives.

    -+ -+ -+
    -+local function alleven(...)
    -+    for i, v in ipairs({...}) do
    -+        if v%2~=0 then return false end
    -+    end
    -+    return true
    -+end
    -+
    -+local function allpositive(...)
    -+    for i, v in ipairs({...}) do
    -+        if v < 0 then return false end
    -+    end
    -+    return true
    -+end
    -+
    -+local anyok = M.overSome(alleven,allpositive)
    -+
    -+anyok(2,4,-1,8) -- => false
    -+anyok(10,3,2,6) -- => true
    -+anyok(-1,-5,-3) -- => false
    -+
    -+ -+ -+

    overArgs (f, ...)

    -+ -+

    Creates a function that invokes f with its arguments transformed

    -+ -+ -+
    -+local function f(x, y) return x, y end
    -+local function triple(x) retun x*3 end
    -+local function square(x) retun x^2 end
    -+local new_f = M.overArgs(f, triple, square)
    -+
    -+new_f(1,2) -- => 3, 4
    -+new_f(10,10) -- => 30, 100
    -+
    -+ -+ -+

    In case the number of arguments is greater than the number of transforms, the remaining args will be left as-is.

    -+ -+ -+
    -+local function f(x, y, z) return x, y, z end
    -+local function triple(x) retun x*3 end
    -+local function square(x) retun x^2 end
    -+local new_f = M.overArgs(f, triple, square)
    -+
    -+new_f(1,2,3) -- => 3, 4, 3
    -+new_f(10,10,10) -- => 30, 100, 10
    -+
    -+ -+ -+

    converge (f, g, h)

    -+ -+

    Converges two functions into one.

    -+ -+ -+
    -+local function pow2(x) return x*x end
    -+local function pow3(x) return x*x*x end
    -+local function sum(a,b) return a+b end
    -+local poly = M.converge(sum, pow2, pow3)
    -+poly(5) -- => 150 (ie. 5*5 + 5*5*5)
    -+
    -+ -+ -+

    partial (f, ...)

    -+ -+

    Partially apply a function by filling in any number of its arguments.

    -+ -+ -+
    -+local function diff(a, b) return a - b end
    -+local diffFrom20 = M.partial(diff, 20) -- arg 'a' will be 20 by default
    -+diffFrom20(5) -- => 15
    -+
    -+ -+ -+

    The string '_' can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time.

    -+ -+ -+
    -+local function diff(a, b) return a - b end
    -+local remove5 = M.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5
    -+remove5(20) -- => 15
    -+
    -+ -+ -+

    partialRight (f, ...)

    -+ -+

    Like M.partial, it partially applies a function by filling in any number of its arguments, but from the right.

    -+ -+ -+
    -+local function concat(...) return table.concat({...},',') end
    -+local concat_right = M.partialRight(concat,'a','b','c')
    -+concat_right('d') -- => d,a,b,c
    -+
    -+concat_right = M.partialRight(concat,'a','b')
    -+concat_right('c','d') -- => c,d,a,b
    -+
    -+concat_right = M.partialRight(concat,'a')
    -+concat_right('b','c','d') -- => b,c,d,a
    -+
    -+ -+ -+

    The string '_', as always, can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time. -+In that case, the first args supplied at runtime will be used to fill the initial list of args while the remaining will be prepended.

    -+ -+ -+
    -+local function concat(...) return table.concat({...},',') end
    -+local concat_right = M.partialRight(concat,'a','_','c')
    -+concat_right('d','b') -- => b,a,d,c
    -+
    -+concat_right = M.partialRight(concat,'a','b','_')
    -+concat_right('c','d') -- => d,a,b,c
    -+
    -+concat_right = M.partialRight(concat,'_','a')
    -+concat_right('b','c','d') -- => c,d,b,a
    -+
    -+ -+ -+

    curry (f [, n_args = 2])

    -+ -+

    Curries a function. If the given function f takes multiple arguments, it returns another version of f that takes a single argument -+(the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result.

    -+ -+ -+
    -+local function sumOf3args(x,y,z) return x + y + z end
    -+local curried_sumOf3args = M.curry(sumOf3args, 3)
    -+sumOf3args(1)(2)(3)) -- => 6
    -+sumOf3args(0)(6)(9)) -- => 15
    -+
    -+ -+ -+

    n_args defaults to 2.

    -+ -+ -+
    -+local function product(x,y) return x * y end
    -+local curried_product = M.curry(product)
    -+curried_product(5)(4) -- => 20
    -+curried_product(3)(-5) -- => -15
    -+curried_product(0)(1) -- => 0
    -+
    -+ -+ -+

    time (f [, ...])

    -+ -+

    Returns the execution time of f (...) in seconds and its results.

    -+ -+ -+
    -+local function wait_count(n)
    -+    local i = 0
    -+    while i < n do i = i + 1 end
    -+    return i
    -+end
    -+
    -+local time, i = M.time(wait_count, 1e6) -- => 0.002 1000000
    -+local time, i = M.time(wait_count, 1e7) -- => 0.018 10000000
    -+
    -+ -+ -+

    [⬆]

    -+ -+

    -+

    Object functions

    -+ -+

    keys (obj)

    -+ -+

    Collects the names of an object attributes.

    -+ -+ -+
    -+M.keys({1,2,3}) -- => "{1,2,3}"
    -+M.keys({x = 0, y = 1}) -- => "{'y','x'}"
    -+
    -+ -+ -+

    values (obj)

    -+ -+

    Collects the values of an object attributes.

    -+ -+ -+
    -+M.values({1,2,3}) -- => "{1,2,3}"
    -+M.values({x = 0, y = 1}) -- => "{1,0}"
    -+
    -+ -+ -+

    path (obj, ...)

    -+ -+

    Returns the value at a given path in an object.

    -+ -+ -+
    -+local entity = {
    -+  pos = {x = 1, y = 2},
    -+  engine = {
    -+    left = {status = 'active', damage = 5},
    -+    right = {status = 'off', damage = 10}
    -+  },
    -+  boost = false
    -+}
    -+
    -+M.path(entity,'pos','x') -- => 1
    -+M.path(entity,'pos','y') -- => 2
    -+M.path(entity,'engine','left','status') -- => 'active'
    -+M.path(entity,'engine','right','damage') -- => 10
    -+M.path(entity,'boost') -- => false
    -+
    -+ -+ -+

    spreadPath (obj, ...)

    -+ -+

    Spreads object under property path onto provided object. It is similar to flattenPath, but removes object under the property path.

    -+ -+ -+
    -+local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}}
    -+M.spreadPath(obj, 'c', 'f')
    -+-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {f = {}}}
    -+
    -+ -+ -+

    flattenPath (obj, ...)

    -+ -+

    Flattens object under property path onto provided object. It is similar to spreadPath, but preserves object under the property path.

    -+ -+ -+
    -+local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}}
    -+M.spreadPath(obj, 'c', 'f')
    -+-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}}
    -+
    -+ -+ -+

    kvpairs (obj)

    -+ -+

    Converts an object to an array-list of key-value pairs.

    -+ -+ -+
    -+local obj = {x = 1, y = 2, z = 3}
    -+M.each(M.kvpairs(obj), function(v,k)
    -+    print(k, table.concat(v,','))
    -+end)
    -+
    -+-- => 1    y,2
    -+-- => 2 x,1
    -+-- => 3 z,3
    -+
    -+ -+ -+

    toObj (kvpairs)

    -+ -+

    Converts an array list of kvpairs to an object where keys are taken from the 1rst column in the kvpairs sequence, associated with values in the 2nd column.

    -+ -+ -+
    -+local list_pairs = {{'x',1},{'y',2},{'z',3}}
    -+obj = M.toObj(list_pairs)
    -+
    -+-- => {x = 1, y = 2, z = 3}
    -+
    -+ -+ -+

    invert (obj)

    -+

    *Aliases: mirror*.

    -+ -+

    Switches key-value pairs:

    -+ -+ -+
    -+M.invert {'a','b','c'} -- => "{a=1, b=2, c=3}"
    -+M.invert {x = 1, y = 2} -- => "{'x','y'}"
    -+
    -+ -+ -+

    property (key)

    -+ -+

    Returns a function that will return the key property of any passed-in object.

    -+ -+ -+
    -+local who = M.property('name')
    -+local people = {name = 'Henry'}
    -+who(people) -- => 'Henry'
    -+
    -+ -+ -+

    propertyOf (obj)

    -+ -+

    Returns a function that will return the key property of any passed-in object.

    -+ -+ -+
    -+local people = {name = 'Henry'}
    -+print(M.propertyOf(people)('name')) -- => 'Henry'
    -+
    -+ -+ -+

    toBoolean (value)

    -+ -+

    Converts a given value to a boolean.

    -+ -+ -+
    -+M.toBoolean(true) -- => true
    -+M.toBoolean(false) -- => false
    -+M.toBoolean(nil) -- => false
    -+M.toBoolean({}) -- => true
    -+M.toBoolean(1) -- => true
    -+
    -+ -+ -+

    extend (destObj, ...)

    -+ -+

    Extends a destination object with the properties of some source objects.

    -+ -+ -+
    -+M.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}"
    -+
    -+ -+ -+

    functions (obj [, recurseMt])

    -+

    *Aliases: methods*.

    -+ -+

    Returns all functions names within an object.

    -+ -+ -+
    -+M.functions(coroutine)
    -+-- => "{'yield','wrap','status','resume','running','create'}"
    -+
    -+ -+ -+

    When given recurseMt, will also include obj metatable's functions.

    -+ -+ -+
    -+local mt = {print = print}
    -+local t = {assert = assert}
    -+setmetatable(t, {__index = mt})
    -+M.functions(t, true) -- => "{'assert','print'}"
    -+
    -+ -+ -+

    clone (obj [, shallow])

    -+ -+

    Clones a given object.

    -+ -+ -+
    -+local obj = {1,2,3}
    -+local obj2 = M.clone(obj)
    -+print(obj2 == obj) -- => false
    -+print(M.isEqual(obj2, obj)) -- => true
    -+
    -+ -+ -+

    tap (obj, f)

    -+ -+

    Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. -+The passed-in interceptor should be prototyped as f(obj,...).

    -+ -+ -+
    -+local v = M.chain({1,2,3,4,5,6,7,8,9,10})
    -+  :filter(function(v) return v%2~=0 end) -- retain odd values
    -+  :tap(function(v) print('Max is', M.max(v) end) -- Tap max value
    -+  :map(function(v) return v^2 end)
    -+  :value() -- =>    Max is 89
    -+
    -+ -+ -+

    has (obj, key)

    -+ -+

    Checks if an object has a given attribute.

    -+ -+ -+
    -+M.has(_,'has') -- => true
    -+M.has(coroutine,'resume') -- => true
    -+M.has(math,'random') -- => true
    -+
    -+ -+ -+

    pick (obj, ...)

    -+

    *Aliases: choose*.

    -+ -+

    Collects whilelisted properties of a given object.

    -+ -+ -+
    -+local object = {a = 1, b = 2, c = 3}
    -+M.pick(object,'a','c') -- => "{a = 1, c = 3}"
    -+
    -+ -+ -+

    omit (obj, ...)

    -+

    *Aliases: drop*.

    -+ -+

    Omits blacklisted properties of a given object.

    -+ -+ -+
    -+local object = {a = 1, b = 2, c = 3}
    -+M.omit(object,'a','c') -- => "{b = 2}"
    -+
    -+ -+ -+

    template (obj [, template])

    -+

    *Aliases: defaults*.

    -+ -+

    Applies a template on an object, preserving existing properties.

    -+ -+ -+
    -+local obj = {a = 0}
    -+M.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}"
    -+
    -+ -+ -+

    isEqual (objA, objB [, useMt])

    -+

    *Aliases: compare, M.matches*.

    -+ -+

    Compares objects:

    -+ -+ -+
    -+M.isEqual(1,1) -- => true
    -+M.isEqual(true,false) -- => false
    -+M.isEqual(3.14,math.pi) -- => false
    -+M.isEqual({3,4,5},{3,4,{5}}) -- => false
    -+
    -+ -+ -+

    result (obj, method)

    -+ -+

    Calls an object method, passing it as a first argument the object itself.

    -+ -+ -+
    -+M.result('abc','len') -- => 3
    -+M.result({'a','b','c'},table.concat) -- => 'abc'
    -+
    -+ -+ -+

    isTable (t)

    -+ -+

    Is the given argument an object (i.e a table) ?

    -+ -+ -+
    -+M.isTable({}) -- => true
    -+M.isTable(math) -- => true
    -+M.isTable(string) -- => true
    -+
    -+ -+ -+

    isCallable (obj)

    -+ -+

    Is the given argument callable ?

    -+ -+ -+
    -+M.isCallable(print) -- => true
    -+M.isCallable(function() end) -- => true
    -+M.isCallable(setmetatable({},{__index = string}).upper) -- => true
    -+M.isCallable(setmetatable({},{__call = function() return end})) -- => true
    -+
    -+ -+ -+

    isArray (obj)

    -+ -+

    Is the given argument an array (i.e. a sequence) ?

    -+ -+ -+
    -+M.isArray({}) -- => true
    -+M.isArray({1,2,3}) -- => true
    -+M.isArray({'a','b','c'}) -- => true
    -+
    -+ -+ -+

    isIterable (obj)

    -+ -+

    Checks if the given object is iterable with pairs.

    -+ -+ -+
    -+M.isIterable({}) -- => true
    -+M.isIterable(function() end) -- => false
    -+M.isIterable(false) -- => false
    -+M.isIterable(1) -- => false
    -+
    -+ -+ -+

    type (obj)

    -+ -+

    Extends Lua's type function. It returns the type of the given object and also recognises 'file' userdata

    -+ -+ -+
    -+M.type('string') -- => 'string'
    -+M.type(table) -- => 'table'
    -+M.type(function() end) -- => 'function'
    -+M.type(io.open('f','w')) -- => 'file'
    -+
    -+ -+ -+

    isEmpty ([obj])

    -+ -+

    Is the given argument empty ?

    -+ -+ -+
    -+M.isEmpty('') -- => true
    -+M.isEmpty({})  -- => true
    -+M.isEmpty({'a','b','c'}) -- => false
    -+
    -+ -+ -+

    isString (obj)

    -+ -+

    Is the given argument a string ?

    -+ -+ -+
    -+M.isString('') -- => true
    -+M.isString('Hello') -- => false
    -+M.isString({}) -- => false
    -+
    -+ -+ -+

    isFunction (obj)

    -+ -+

    Is the given argument a function ?

    -+ -+ -+
    -+M.isFunction(print) -- => true
    -+M.isFunction(function() end) -- => true
    -+M.isFunction({}) -- => false
    -+
    -+ -+ -+

    isNil (obj)

    -+ -+

    Is the given argument nil ?

    -+ -+ -+
    -+M.isNil(nil) -- => true
    -+M.isNil() -- => true
    -+M.isNil({}) -- => false
    -+
    -+ -+ -+

    isNumber (obj)

    -+ -+

    Is the given argument a number ?

    -+ -+ -+
    -+M.isNumber(math.pi) -- => true
    -+M.isNumber(math.huge) -- => true
    -+M.isNumber(0/0) -- => true
    -+M.isNumber() -- => false
    -+
    -+ -+ -+

    isNaN (obj)

    -+ -+

    Is the given argument NaN ?

    -+ -+ -+
    -+M.isNaN(1) -- => false
    -+M.isNaN(0/0) -- => true
    -+
    -+ -+ -+

    isFinite (obj)

    -+ -+

    Is the given argument a finite number ?

    -+ -+ -+
    -+M.isFinite(99e99) -- => true
    -+M.isFinite(math.pi) -- => true
    -+M.isFinite(math.huge) -- => false
    -+M.isFinite(1/0) -- => false
    -+M.isFinite(0/0) -- => false
    -+
    -+ -+ -+

    isBoolean (obj)

    -+ -+

    Is the given argument a boolean ?

    -+ -+ -+
    -+M.isBoolean(true) -- => true
    -+M.isBoolean(false) -- => true
    -+M.isBoolean(1==1) -- => true
    -+M.isBoolean(print) -- => false
    -+
    -+ -+ -+

    isInteger (obj)

    -+ -+

    Is the given argument an integer ?

    -+ -+ -+
    -+M.isInteger(math.pi) -- => false
    -+M.isInteger(1) -- => true
    -+M.isInteger(-1) -- => true
    -+
    -+ -+ -+

    [⬆]

    -+ -+

    -+

    Chaining

    -+ -+

    Method chaining (also known as name parameter idiom), is a technique for invoking consecutively method calls in object-oriented style. -+Each method returns an object, and method calls are chained together. -+Moses offers chaining for your perusal.

    -+ -+

    Let's use chaining to get the count of evey single word in some lyrics (case won't matter here).

    -+ -+ -+ -+
    -+local lyrics = {
    -+  "I am a lumberjack and I am okay",
    -+  "I sleep all night and I work all day",
    -+  "He is a lumberjack and he is okay",
    -+  "He sleeps all night and he works all day"
    -+}
    -+
    -+-- split a text into words
    -+local function words(line)
    -+  local t = {}
    -+  for w in line:gmatch('(%w+)') do t[#t+1] = w end
    -+  return t
    -+end
    -+
    -+local stats = M.chain(lyrics)
    -+  :map(words)
    -+  :flatten()
    -+  :countBy(string.lower)
    -+  :value()
    -+
    -+-- => "{
    -+-- =>    sleep = 1, night = 2, works = 1, am = 2, is = 2,
    -+-- =>    he = 2, and = 4, I = 4, he = 2, day = 2, a = 2,
    -+-- =>    work = 1, all = 4, okay = 2
    -+-- =>  }"
    -+
    -+ -+ -+

    For convenience, you can also use M(value) to start chaining methods, instead of M.chain(value).

    -+ -+

    Note that one can use :value() to unwrap a chained object.

    -+ -+ -+
    -+local t = {1,2,3}
    -+print(_(t):value() == t) -- => true
    -+
    -+ -+ -+

    [⬆]

    -+ -+

    -+

    Import

    -+ -+

    All library functions can be imported in a context using import into a specified context.

    -+ -+ -+
    -+local context = {}
    -+M.import(context)
    -+
    -+context.each({1,2,3},print)
    -+
    -+-- => 1 1
    -+-- => 2 2
    -+-- => 3 3
    -+
    -+ -+ -+

    When no context was provided, it defaults to the current environment, _ENV or _G.

    -+ -+ -+
    -+M.import()
    -+
    -+each({1,2,3},print)
    -+
    -+-- => 1 1
    -+-- => 2 2
    -+-- => 3 3
    -+
    -+ -+ -+

    Passing noConflict argument leaves untouched conflicting keys while importing into the context.

    -+ -+ -+
    -+local context = {each = 1}
    -+M.import(context, true)
    -+
    -+print(context.each) -- => 1
    -+context.eachi({1,2,3},print)
    -+
    -+-- => 1 1
    -+-- => 2 2
    -+-- => 3 3
    -+
    -+ -+ -+

    [⬆] -+ -+

    -+
    -+
    -+generated by LDoc 1.4.6 -+Last updated 2019-04-01 23:55:17 -+
    -+
    -+ -+ -diff --git a/extra/moses/doc/topics/tutorial.md.html b/extra/moses/doc/topics/tutorial.md.html -deleted file mode 100644 -index 1acd09f..0000000 ---- a/extra/moses/doc/topics/tutorial.md.html -+++ /dev/null -@@ -1,2349 +0,0 @@ -- -- -- -- -- Moses documentation -- -- -- -- --
    -- --
    -- --
    --
    --
    -- -- --
    -- -- -- -- -- -- --
    -- -- Moses: a utility-belt library for functional programming in Lua

    -- --

    Moses is a Lua utility library which provides support for functional programming. --It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more.
    --Moses was deeply inspired by Underscore.js.

    -- --

    Table of Contents

    -- -- -- -- --

    Adding Moses to your project

    -- --

    Drop the file moses.lua into your project and add it to your code with the require function:

    -- --
    --local _ = require ("moses")
    --
    -- -- --

    Note: Lua purists tend to use “_” to design a “dummy variable”. Here, the usage of this underscore is quite idiomatic and refers to the name Underscore, the JS library from which Moses takes inspiration.

    -- --

    Moses‘ provides a large set of functions that can be classified into four categories:

    -- --
      --
    • Table functions, which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part,
    • --
    • Array functions, meant for array lists (or sequences),
    • --
    • Utility functions,
    • --
    • Object functions.
    • --
    -- -- --

    [⬆]

    -- --

    -- --

    Table functions

    -- --

    clear (t)

    -- --

    Clears a table. All its values becomes nil. It returns the passed-in table.

    -- --
    --local t = _.clear({1,2,'hello',true}) -- => {}
    --
    -- -- --

    each (t, f, …)

    -- --

    Aliases: _.forEach.

    -- --

    Iterates over each key-value pair in table.

    -- --
    --_.each({1,2,3},print)
    --
    ---- => 1 1
    ---- => 2 2
    ---- => 3 3
    --
    -- -- --

    The table can be map-like (array part and hash-part).

    -- --
    --_.each({one = 1, two = 2, three = 3},print)
    --
    ---- => one 1
    ---- => two 2
    ---- => three 3
    --
    -- -- --

    Can index and assign in an outer table or in the passed-in table:

    -- --
    --t = {'a','b','c'}
    --_.each(t,function(i,v)
    --  t[i] = v:rep(2)
    --  print(t[i])
    --end)
    --
    ---- => 1 aa
    ---- => 2 bb
    ---- => 3 cc
    --
    -- -- --

    eachi (t, f, …)

    -- --

    Aliases: _.forEachi.

    -- --

    Iterates only on integer keys in a sparse array table.

    -- --
    --_.eachi({1,2,3},print)
    --
    ---- => 1 1
    ---- => 2 2
    ---- => 3 3
    --
    -- -- --

    The given array can be sparse, or even have a hash-like part.

    -- --
    --local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5}
    --_.eachi(t,function(i,v)
    --  print(i,v)
    --end)
    --
    ---- => -1 6
    ---- => 0 1
    ---- => 1 3
    ---- => 2 5
    --
    -- -- --

    at (t, …)

    -- --

    Collects all values at some specific keys and returns them in an array.

    -- --
    --local t = {4,5,6}
    --_.at(t,1,3) -- => "{4,6}"
    --
    --local t = {a = 4, bb = true, ccc = false}
    --_.at(t,'a', 'ccc') -- => "{4, false}"
    --
    -- -- --

    count (t, value)

    -- --

    Counts the number of occurences of a given value in a table.

    -- --
    --_.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2
    --_.count({1,1,2,3,3,3,2,4,3,2},2) -- => 2
    --_.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4
    --_.count({false, false, true},false) -- => 2
    --_.count({false, false, true},true) -- => 1
    --
    -- -- --

    Returns the size of the list in case no value was provided.

    -- --
    --_.count({1,1,2,3,3}) -- => 5
    --
    -- -- --

    countf (t, f, …)

    -- --

    Count the number of occurences of all values passing an iterator test.

    -- --
    --_.countf({1,2,3,4,5,6}, function(i,v)
    --  return v%2==0
    --end) -- => 3
    --
    --_.countf({print, pairs, os, assert, ipairs}, function(i,v)
    --  return type(v)=='function'
    --end) -- => 4
    --
    -- -- --

    cycle (t, n)

    -- --

    Aliases: _.loop.

    -- --

    Returns a function which iterates on each key-value pair in a given table (similarly to _.each), except that it restarts iterating again n times. --If n is not provided, it defaults to 1.

    -- --
    --local t = {'a,'b','c'}
    --for k,v in _.cycle(t, 2) do
    --  print(k,v)
    --end
    --
    ---- => 1 'a'
    ---- => 2 'b'
    ---- => 3 'c'
    ---- => 1 'a'
    ---- => 2 'b'
    ---- => 3 'c'
    --
    -- -- --

    Supports array-like tables and map-like tables.

    -- --
    --local t = {x = 1, y = 2, z = 3}
    --for k,v in _.cycle(t) do
    --  print(k,v)
    --end
    --
    ---- => y    2
    ---- => x 1
    ---- => z 3
    --
    -- -- --

    map (t, f, …)

    -- --

    Aliases: _.collect.

    -- --

    Executes a function on each key-value pairs.

    -- --
    --_.map({1,2,3},function(i,v)
    --  return v+10
    --end) -- => "{11,12,13}"
    --
    -- -- --
    --_.map({a = 1, b = 2},function(k,v)
    --  return k..v
    --end) -- => "{a = 'a1', b = 'b2'}"
    --
    -- -- --

    It also maps key-value pairs to key-value pairs

    -- --
    --_.map({a = 1, b = 2},function(k,v)
    --  return k..k, v*2
    --end) -- => "{aa = 2, bb = 4}"
    --
    -- -- --

    reduce (t, f, state)

    -- --

    Aliases: _.inject, _.foldl.

    -- --

    Can sums all values in a table.

    -- --
    --_.reduce({1,2,3,4},function(memo,v)
    --  return memo+v
    --end) -- => 10
    --
    -- -- --

    Or concatenates all values.

    -- --
    --_.reduce({'a','b','c','d'},function(memo,v)
    --  return memo..v
    --end) -- => abcd    
    --
    -- -- --

    reduceby (t, f, state, pred, …)

    -- --

    Reduces a table considering only values matching a predicate. --For example,let us define a set of values.

    -- --
    --local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9}
    --
    -- -- --

    We can also define some predicate functions.

    -- --
    ---- predicate for negative values
    --local function neg(_, v) return v<=0 end
    --
    ---- predicate for positive values
    --local function pos(_, v) return v>=0 end
    --
    -- -- --

    Then we can perform reduction considering only negative values :

    -- --
    --_.reduceby(val, function(memo,v)
    --  return memo+v
    --end, 0, neg) -- => -17
    --
    -- -- --

    Or only positive values :

    -- --
    --_.reduceby(val, function(memo,v)
    --  return memo+v
    --end, 0, pos) -- => 19
    --
    -- -- --

    reduceRight (t, f, state)

    -- --

    Aliases: _.injectr, _.foldr.

    -- --

    Similar to _.reduce, but performs from right to left.

    -- --
    --local initial_state = 256
    --_.reduceRight({1,2,4,16},function(memo,v)
    --  return memo/v
    --end,initial_state) -- => 2
    --
    -- -- --

    mapReduce (t, f, state)

    -- --

    Aliases: _.mapr.

    -- --

    Reduces while saving intermediate states.

    -- --
    --_.mapReduce({'a','b','c'},function(memo,v)
    --  return memo..v
    --end) -- => "{'a', 'ab', 'abc'}"
    --
    -- -- --

    mapReduceRight (t, f, state)

    -- --

    Aliases: _.maprr.

    -- --

    Reduces from right to left, while saving intermediate states.

    -- --
    --_.mapReduceRight({'a','b','c'},function(memo,v)
    --  return memo..v
    --end) -- => "{'c', 'cb', 'cba'}"
    --
    -- -- --

    include (t, value)

    -- --

    Aliases: _.any, _.some, _.contains.

    -- --

    Looks for a value in a table.

    -- --
    --_.include({6,8,10,16,29},16) -- => true
    --_.include({6,8,10,16,29},1) -- => false
    --
    --local complex_table = {18,{2,{3}}}
    --local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    --_.include(collection, complex_table) -- => true
    --
    -- -- --

    Handles iterator functions.

    -- --
    --local function isUpper(v) return v:upper()== v end
    --_.include({'a','B','c'},isUpper) -- => true
    --
    -- -- --

    detect (t, value)

    -- --

    Returns the index of a value in a table.

    -- --
    --_.detect({6,8,10,16},8) -- => 2
    --_.detect({nil,true,0,true,true},false) -- => nil
    --
    --local complex_table = {18,{2,6}}
    --local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
    --_.detect(collection, complex_table) -- => 2
    --
    -- -- --

    Handles iterator functions.

    -- --
    --local function isUpper(v)
    --  return v:upper()==v
    --end
    --_.detect({'a','B','c'},isUpper) -- => 2
    --
    -- -- --

    where (t, props)

    -- --

    Looks through a table and returns all the values that matches all of the key-value pairs listed in props.

    -- --
    --local tA = {a = 1, b = 2, c = 0}
    --local tB = {a = 1, b = 4, c = 1}
    --local tC = {a = 4, b = 4, c = 3}
    --local tD = {a = 1, b = 2, c = 3}
    --local found = _.where({tA, tB, tC, tD}, {a = 1})
    --
    ---- => found = {tA, tB, tD}
    --
    --found = _.where({tA, tB, tC, tD}, {b = 4})
    --
    ---- => found = {tB, tC}
    --
    --found = _.where({tA, tB, tC, tD}, {b = 4, c = 3})
    --
    ---- => found = {tC}
    --
    -- -- --

    findWhere (t, props)

    -- --

    Looks through a table and returns the first value that matches all of the key-value pairs listed in props.

    -- --
    --local a = {a = 1, b = 2, c = 3}
    --local b = {a = 2, b = 3, d = 4}
    --local c = {a = 3, b = 4, e = 5}
    --_.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true
    --
    -- -- --

    select (t, f, …)

    -- --

    Aliases: _.filter.

    -- --

    Collects values passing a validation test.

    -- --
    ---- Even values
    --_.select({1,2,3,4,5,6,7}, function(key,value)
    --  return (value%2==0)
    --end) -- => "{2,4,6}"
    --
    ---- Odd values
    --_.select({1,2,3,4,5,6,7}, function(key,value)
    --  return (value%2~=0)
    --end) -- => "{1,3,5,7}"
    --
    -- -- --

    reject (t, f, …)

    -- --

    Aliases: _.reject.

    -- --

    Removes all values failing a validation test:

    -- --
    --_.reject({1,2,3,4,5,6,7}, function(key,value)
    --  return (value%2==0)
    --end) -- => "{1,3,5,7}"
    --
    --_.reject({1,2,3,4,5,6,7}, function(key,value)
    --  return (value%2~=0)
    --end) -- => "{2,4,6}"
    --
    -- -- --

    all (t, f, …)

    -- --

    Aliases: _.every.

    -- --

    Checks whether or not all elements pass a validation test.

    -- --
    --_.all({2,4,6}, function(key,value)
    --  return (value%2==0)
    --end) -- => true
    --
    -- -- --

    invoke (t, method, …)

    -- --

    Invokes a given function on each value in a table

    -- --
    --_.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}"
    --
    -- -- --

    Can reference the method of the same name in each value.

    -- --
    --local a = {}
    --function a:call() return 'a' end
    --local b, c, d = {}, {}, {}
    --b.call, c.call, d.call = a.call, a.call, a.call
    --
    --_.invoke({a,b,c,d},'call') -- => "{'a','a','a','a'}"
    --
    -- -- --

    pluck (t, property)

    -- --

    Fetches all values indxed with specific key in a table of objects.

    -- --
    --local peoples = {
    --  {name = 'John', age = 23},{name = 'Peter', age = 17},
    --  {name = 'Steve', age = 15},{age = 33}}
    --
    --_.pluck(peoples,'age') -- => "{23,17,15,33}"
    --_.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}"
    --
    -- -- --

    max (t, transform, …)

    -- --

    Returns the maximum value in a collection.

    -- --
    --_.max {1,2,3} -- => 3
    --_.max {'a','b','c'} -- => 'c'
    --
    -- -- --

    Can take an iterator function to extract a specific property.

    -- --
    --local peoples = {
    --  {name = 'John', age = 23},{name = 'Peter', age = 17},
    --  {name = 'Steve', age = 15},{age = 33}}
    --_.max(peoples,function(people) return people.age end) -- => 33
    --
    -- -- --

    min (t, transform, …)

    -- --

    Returns the minimum value in a collection.

    -- --
    --_.min {1,2,3} -- => 1
    --_.min {'a','b','c'} -- => 'a'
    --
    -- -- --

    Can take an iterator function to extract a specific property.

    -- --
    --local peoples = {
    --  {name = 'John', age = 23},{name = 'Peter', age = 17},
    --  {name = 'Steve', age = 15},{age = 33}}
    --_.min(peoples,function(people) return people.age end) -- => 15
    --
    -- -- --

    shuffle (t, seed)

    -- --

    Shuffles a collection.

    -- --
    --local list = _.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}"
    --_.each(list,print)
    --
    -- -- --

    same (a, b)

    -- --

    Tests whether or not all values in each of the passed-in tables exists in both tables.

    -- --
    --local a = {'a','b','c','d'}
    --local b = {'b','a','d','c'}
    --_.same(a,b) -- => true
    --
    --b[#b+1] = 'e'
    --_.same(a,b) -- => false
    --
    -- -- --

    sort (t, comp)

    -- --

    Sorts a collection.

    -- --
    --_.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}"
    --
    -- -- --

    Handles custom comparison functions.

    -- --
    --_.sort({'b','a','d','c'}, function(a,b)
    --  return a:byte() > b:byte()
    --end) -- => "{'d','c','b','a'}"
    --
    -- -- --

    sortBy (t, transform, comp)

    -- --

    Sorts items in a collection based on the result of running a transform function through every item in the collection.

    -- --
    --local r = _.sortBy({1,2,3,4,5}, math.sin)
    --print(table.concat(r,','))
    --
    ---- => {5,4,3,1,2}
    --
    -- -- --

    The transform function can also be a string name property.

    -- --
    --local people ={
    --    {name = 'albert', age = 40},
    --    {name = 'louis', age = 55},
    --    {name = 'steve', age = 35},
    --    {name = 'henry', age = 19},
    --}
    --local r = _.sortBy(people, 'age')
    --_.each(r, function(__,v) print(v.age, v.name)  end)
    --
    ---- => 19   henry
    ---- => 35    steve
    ---- => 40    albert
    ---- => 55    louis
    --
    -- -- --

    As seen above, the defaut comparison function is the ‘<’ operator. For example, let us supply a different one to sort --the list of people by decreasing age order :

    -- --
    --local people ={
    --    {name = 'albert', age = 40},
    --    {name = 'louis', age = 55},
    --    {name = 'steve', age = 35},
    --    {name = 'henry', age = 19},
    --}
    --local r = _.sortBy(people, 'age', function(a,b) return a > b end)
    --_.each(r, function(__,v) print(v.age, v.name)  end)
    --
    ---- => 55   louis
    ---- => 40    albert
    ---- => 35    steve
    ---- => 19    henry
    --
    -- -- --

    The transform function defaults to _.indentity and in that case, _.sortBy behaves like _.sort.

    -- --
    --local r = _.sortBy({1,2,3,4,5})
    --print(table.concat(r,','))
    --
    ---- => {1,2,3,4,5}
    --
    -- -- --

    groupBy (t, iter, …)

    -- --

    Groups values in a collection depending on their return value when passed to a predicate test.

    -- --
    --_.groupBy({0,1,2,3,4,5,6},function(i,value)
    --  return value%2==0 and 'even' or 'odd'
    --end) -- => "{odd = {1,3,5}, even = {0,2,4,6}}"
    --
    --_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value)
    --  return type(value)
    --end) -- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}"      
    --
    -- -- --

    countBy (t, iter, …)

    -- --

    Splits a table in subsets and provide the count for each subset.

    -- --
    --_.countBy({0,1,2,3,4,5,6},function(i,value)
    --  return value%2==0 and 'even' or 'odd'
    --end) -- => "{odd = 3, even = 4}"
    --
    -- -- --

    size (…)

    -- --

    When given a table, provides the count for the very number of values in that table.

    -- --
    --_.size {1,2,3} -- => 3
    --_.size {one = 1, two = 2} -- => 2
    --
    -- -- --

    When given a vararg list of argument, returns the count of these arguments.

    -- --
    --_.size(1,2,3) -- => 3
    --_.size('a','b',{}, function() end) -- => 4
    --
    -- -- --

    containsKeys (t, other)

    -- --

    Checks whether a table has all the keys existing in another table.

    -- --
    --_.contains({1,2,3,4},{1,2,3}) -- => true
    --_.contains({1,2,'d','b'},{1,2,3,5}) -- => true
    --_.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true
    --
    -- -- --

    sameKeys (tA, tB)

    -- --

    Checks whether both tables features the same keys:

    -- --
    --_.sameKeys({1,2,3,4},{1,2,3}) -- => false
    --_.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true
    --_.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false
    --
    -- -- --

    [⬆]

    -- --

    -- --

    Array functions

    -- --

    sample (array, n, seed)

    -- --

    Samples n values from array.

    -- --
    --local array = _.range(1,20)
    --local sample = _.sample(array, 3)
    --print(table.concat(sample,','))
    --
    ---- => {12,11,15}
    --
    -- -- --

    n defaults to 1. In that case, a single value will be returned.

    -- --
    --local array = _.range(1,20)
    --local sample = _.sample(array)
    --print(sample)
    --
    ---- => 12
    --
    -- -- --

    An optional 3rd argument seed can be passed for deterministic random sampling.

    -- --

    sampleProb (array, prob, seed)

    -- --

    Returns an array of values randomly selected from a given array. --In case seed is provided, it is used for deterministic sampling.

    -- --
    --local array = _.range(1,20)
    --local sample = _.sampleProb(array, 0.2)
    --print(table.concat(sample,','))
    --
    ---- => 5,11,12,15
    --
    --sample = _.sampleProb(array, 0.2, os.time())
    --print(table.concat(sample,','))
    --
    ---- => 1,6,10,12,15,20 (or similar)
    --
    -- -- --

    toArray (…)

    -- --

    Converts a vararg list of arguments to an array.

    -- --
    --_.toArray(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}"
    --
    -- -- --

    find (array, value, from)

    -- --

    Looks for a value in a given array and returns the position of the first occurence.

    -- --
    --_.find({{4},{3},{2},{1}},{3}) -- => 2
    --
    -- -- --

    It can also start the search at a specific position in the array:

    -- --
    ---- search value 4 starting from index 3
    --_.find({1,4,2,3,4,5},4,3) -- => 5
    --
    -- -- --

    reverse (array)

    -- --

    Reverses an array.

    -- --
    --_.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}"
    --
    -- -- --

    fill (array, value, i, j)

    -- --

    Replaces all elements in a given array with a given value.

    -- --
    --local array = _.range(1,5)
    --_.fill(array, 0) -- => {0,0,0,0,0}
    --
    -- -- --

    It can start replacing value at a specific index.

    -- --
    --local array = _.range(1,5)
    --_.fill(array,0,3) -- => {1,2,0,0,0}
    --
    -- -- --

    It can replace only values within a specific range.

    -- --
    --local array = _.range(1,5)
    --_.fill(array,0,2,4) -- => {1,0,0,0,5}
    --
    -- -- --

    In case the upper bound index i greather than the array size, it will enlarge the array.

    -- --
    --local array = _.range(1,5)
    --_.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0}
    --
    -- -- --

    selectWhile (array, f, …

    -- --

    Aliases: _.takeWhile.

    -- --

    Collects values as long as they pass a given test. Stops on the first non-passing test.

    -- --
    --_.selectWhile({2,4,5,8}, function(i,v)
    --  return v%2==0
    --end) -- => "{2,4}"
    --
    -- -- --

    dropWhile (array, f, …

    -- --

    Aliases: _.rejectWhile.

    -- --

    Removes values as long as they pass a given test. Stops on the first non-passing test.

    -- --
    --_.dropWhile({2,4,5,8}, function(i,v)
    --  return v%2==0
    --end) -- => "{5,8}"
    --
    -- -- --

    sortedIndex (array, value, comp, sort)

    -- --

    Returns the index at which a value should be inserted to preserve order.

    -- --
    --_.sortedIndex({1,2,3},4) -- => 4
    --
    -- -- --

    Can take a custom comparison functions.

    -- --
    --local comp = function(a,b) return a<b end
    --_.sortedIndex({-5,0,4,4},3,comp) -- => 3
    --
    -- -- --

    indexOf (array, value)

    -- --

    Returns the index of a value in an array.

    -- --
    --_.indexOf({1,2,3},2) -- => 2
    --
    -- -- --

    lastIndexOf (array, value)

    -- --

    Returns the index of the last occurence of a given value in an array.

    -- --
    --_.lastIndexOf({1,2,2,3},2) -- => 3
    --
    -- -- --

    findIndex (array, predicate, …)

    -- --

    Returns the first index at which a predicate passes a truth test.

    -- --
    --local array = {1,2,3,4,5,6}
    --local function multipleOf3(__,v) return v%3==0 end
    --_.findIndex(array, multipleOf3) -- => 3
    --
    -- -- --

    findLastIndex (array, predicate, …)

    -- --

    Returns the last index at which a predicate passes a truth test.

    -- --
    --local array = {1,2,3,4,5,6}
    --local function multipleOf3(__,v) return v%3==0 end
    --_.findLastIndex(array, multipleOf3) -- => 6
    --
    -- -- --

    addTop (array, …)

    -- --

    Adds given values at the top of an array. The latter values bubbles at the top.

    -- --
    --local array = {1}
    --_.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}"
    --
    -- -- --

    push (array, …)

    -- --

    Adds given values at the end of an array.

    -- --
    --local array = {1}
    --_.push(array,1,2,3,4) -- => "{1,1,2,3,4}"
    --
    -- -- --

    pop (array, n)

    -- --

    Aliases: _.shift.

    -- --

    Removes and returns the first value in an array.

    -- --
    --local array = {1,2,3}
    --local pop = _.pop(array) -- => "pop = 1", "array = {2,3}"
    --
    -- -- --

    unshift (array, n)

    -- --

    Removes and returns the last value in an array.

    -- --
    --local array = {1,2,3}
    --local value = _.unshift(array) -- => "value = 3", "array = {1,2}"
    --
    -- -- --

    pull (array, …)

    -- --

    Aliases: _.remove.

    -- --

    Removes all provided values from a given array.

    -- --
    --_.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}"
    --
    -- -- --

    removeRange (array, start, finish)

    -- --

    Aliases: _.rmRange, _.chop.

    -- --

    Trims out all values index within a range.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.removeRange(array, 3,8) -- => "{1,2,9}"
    --
    -- -- --

    chunk (array, f, …)

    -- --

    Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return --value of f(key,value,…). Consecutive elements which return the same value are aggregated together.

    -- --
    --local t = {1,1,2,3,3,4}
    --_.chunk(t, function(k,v) return v%2==0 end) -- => "{{1,1},{2},{3,3},{4}}"
    --
    -- -- --

    slice (array, start, finish)

    -- --

    Aliases: _.sub.

    -- --

    Slices and returns a part of an array.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.slice(array, 3,6) -- => "{3,4,5,6}"
    --
    -- -- --

    first (array, n)

    -- --

    Aliases: _.head, _.take.

    -- --

    Returns the first N elements in an array.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.first(array,3) -- => "{1,2,3}"
    --
    -- -- --

    initial (array, n)

    -- --

    Excludes the last N elements in an array.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.initial(array,5) -- => "{1,2,3,4}"
    --
    -- -- --

    last (array, n)

    -- --

    Aliases: _.skip.

    -- --

    Returns the last N elements in an array.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.last(array,3) -- => "{7,8,9}"
    --
    -- -- --

    rest (array, index)

    -- --

    Aliases: _.tail.

    -- --

    Trims out all values indexed before index.

    -- --
    --local array = {1,2,3,4,5,6,7,8,9}
    --_.rest(array,6) -- => "{6,7,8,9}"
    --
    -- -- --

    nth (array, index)

    -- --

    Returns the value at index.

    -- --
    --local array = {1,2,3,4,5,6}
    --_.nth(array,3) -- => "3"
    --
    -- -- --

    compact (array)

    -- --

    Trims out all falsy values.

    -- --
    --_.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}"
    --
    -- -- --

    flatten (array, shallow)

    -- --

    Flattens a nested array.

    -- --
    --_.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}"
    --
    -- -- --

    When given arg “shallow”, flatten only at the first level.

    -- --
    --_.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}"
    --
    -- -- --

    difference (array, array2)

    -- --

    Aliases: _.without, _.diff.

    -- --

    Returns values in the given array not present in a second array.

    -- --
    --local array = {1,2,'a',4,5}
    --_.difference(array,{1,'a'}) -- => "{2,4,5}"
    --
    -- -- --

    union (…)

    -- --

    Produces a duplicate-free union of all passed-in arrays.

    -- --
    --local A = {'a'}
    --local B = {'a',1,2,3}
    --local C = {2,10}
    --_.union(A,B,C) -- => "{'a',1,2,3,10}"
    --
    -- -- --

    intersection (array, …)

    -- --

    Returns the intersection (common-part) of all passed-in arrays:

    -- --
    --local A = {'a'}
    --local B = {'a',1,2,3}
    --local C = {2,10,1,'a'}
    --_.intersection(A,B,C) -- => "{'a',2,1}"
    --
    -- -- --

    symmetricDifference (array, array2)

    -- --

    Aliases: _.symdiff,_.xor.

    -- --

    Returns values in the first array not present in the second and also values in the second array not present in the first one.

    -- --
    --local array = {1,2,3}
    --local array2 = {1,4,5}
    --_.symmetricDifference(array, array2) -- => "{2,3,4,5}"
    --
    -- -- --

    unique (array)

    -- --

    Aliases: _.uniq.

    -- --

    Makes an array duplicate-free.

    -- --
    --_.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}"
    --
    -- -- --

    isunique (array)

    -- --

    Aliases: _.isuniq.

    -- --

    Checks if a given array contains no duplicate value.

    -- --
    --_.isunique({1,2,3,4,5}) -- => true
    --_.isunique({1,2,3,4,4}) -- => false
    --
    -- -- --

    zip (…)

    -- --

    Aliases: _.transpose.

    -- --

    Zips values from different arrays, on the basis on their common keys.

    -- --
    --local names = {'Bob','Alice','James'}
    --local ages = {22, 23}
    --_.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}"
    --
    -- -- --

    append (array, other)

    -- --

    Appends two arrays.

    -- --
    --_.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}"
    --
    -- -- --

    interleave (…)

    -- --

    Interleaves values from passed-in arrays.

    -- --
    --t1 = {1, 2, 3}
    --t2 = {'a', 'b', 'c'}
    --_.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}"
    --
    -- -- --

    interpose (value, array)

    -- --

    Interposes a value between consecutive values in an arrays.

    -- --
    --_.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}"
    --
    -- -- --

    range (…)

    -- --

    Generates an arithmetic sequence.

    -- --
    --_.range(1,4) -- => "{1,2,3,4}"
    --
    -- -- --

    In case a single value is provided, it generates a sequence from 0 to that value.

    -- --
    --_.range(3) -- => "{0,1,2,3}"
    --
    -- -- --

    The incremental step can also be provided as third argument.

    -- --
    --_.range(0,2,0.7) -- => "{0,0.7,1.4}"
    --
    -- -- --

    rep (value, n)

    -- --

    Generates a list of n repetitions of a value.

    -- --
    --_.rep(4,3) -- => "{4,4,4}"
    --
    -- -- --

    partition (array, n, pad)

    -- --

    Aliases: _.part.

    -- --

    Returns an iterator function for partitions of a given array.

    -- --
    --local t = {1,2,3,4,5,6}
    --for p in _.partition(t,2) do
    --  print(table.concat(p, ','))
    --end
    --
    ---- => 1,2
    ---- => 3,4
    ---- => 5,6
    --
    --local t = {1,2,3,4,5,6}
    --for p in _.partition(t,4) do
    --  print(table.concat(p, ','))
    --end
    --
    ---- => 1,2,3,4
    ---- => 5,6
    --
    -- -- --

    In case the last partition has less elements than desired, a 3rd argument can be supplied to adjust the partition size.

    -- --
    --local t = {1,2,3,4,5,6}
    --for p in _.partition(t,4,0) do
    --  print(table.concat(p, ','))
    --end
    --
    ---- => 1,2,3,4
    ---- => 5,6,0,0
    --
    -- -- --

    sliding (array, n, pad)

    -- --

    Returns an iterator function which provides overlapping subsequences of a given array.

    -- --
    --local t = {1,2,3,4,5,6,7}
    --for p in _.sliding(t,3) do
    --    print(table.concat(p,','))
    --end
    --
    ---- => 1,2,3
    ---- => 3,4,5
    ---- => 5,6,7
    --
    --for p in _.sliding(t,4) do
    --    print(table.concat(p,','))
    --end
    --
    ---- => 1,2,3,4
    ---- => 4,5,6,7
    --
    --for p in _.sliding(t,5) do
    --    print(table.concat(p,','))
    --end
    --
    ---- => 1,2,3,4,5
    ---- => 5,6,7
    --
    -- -- --

    In case the last subsequence wil not match the exact desired length, it can be adjusted with a 3rd argument pad.

    -- --
    --local t = {1,2,3,4,5,6,7}
    --for p in _.sliding(t,5,0) do
    --    print(table.concat(p,','))
    --end
    --
    ---- => 1,2,3,4,5
    ---- => 5,6,7,0,0
    --
    -- -- --

    permutation (array)

    -- --

    Aliases: _.perm.

    -- --

    Returns an iterator function for permutations of a given array.

    -- --
    --t = {'a','b','c'}
    --for p in _.permutation(t) do
    --  print(table.concat(p))
    --end
    --
    ---- => 'bca'
    ---- => 'cba'
    ---- => 'cab'
    ---- => 'acb'
    ---- => 'bac'
    ---- => 'abc'
    --
    -- -- --

    invert (array)

    -- --

    Aliases: _.mirror.

    -- --

    Switches key-value pairs:

    -- --
    --_.invert {'a','b','c'} -- => "{a=1, b=2, c=3}"
    --
    -- -- --

    concat (array, sep, i, j)

    -- --

    Aliases: _.join.

    -- --

    Concatenates a given array values:

    -- --
    --_.concat({'a',1,0,1,'b'}) -- => 'a101b'
    --
    -- -- --

    [⬆]

    -- --

    -- --

    Utility functions

    -- --

    noop ()

    -- --

    The no-operation function. Takes nothing, returns nothing. It is being used internally.

    -- --
    --_.noop() -- => nil
    --
    -- -- --

    identity (value)

    -- --

    Returns the passed-in value.
    --This function is internally used as a default transformation function.

    -- --
    --_.identity(1)-- => 1
    --_.identity(false) -- => false
    --_.identity('hello!') -- => 'hello!'
    --
    -- -- --

    constant (value)

    -- --

    Creates a constant function. This function will constinuously yield the same output.

    -- --
    --local pi = _.constant(math.pi)
    --pi(1) -- => 3.1415926535898
    --pi(2) -- => 3.1415926535898
    --pi(math.pi) -- => 3.1415926535898
    --
    -- -- --

    memoize (f, hash)

    -- --

    Aliases: _.cache.

    -- --

    Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body.

    -- --
    --local function fibonacci(n)
    --  return n < 2 and n or fibonacci(n-1)+fibonacci(n-2)
    --end
    --local mem_fibonacci = _.memoize(fibonacci)
    --fibonacci(20) -- => 6765 (but takes some time)
    --mem_fibonacci(20) -- => 6765 (takes less time)
    --
    -- -- --

    once (f)

    -- --

    Produces a function that runs only once. Successive calls to this function will still yield the same input.

    -- --
    --local sq = _.once(function(a) return a*a end)
    --sq(1) -- => 1
    --sq(2) -- => 1
    --sq(3) -- => 1
    --sq(4) -- => 1
    --sq(5) -- => 1
    --
    -- -- --

    before (f, count)

    -- --

    Returns a version of f that will run no more than count times. Next calls will keep yielding the results of the (n-th)-1 call.

    -- --
    --local function greet(someone) return 'hello '..someone end
    --local greetOnly3people = _.before(greet, 3)
    --greetOnly3people('John') -- => 'hello John'
    --greetOnly3people('Moe') -- => 'hello Moe'
    --greetOnly3people('James') -- => 'hello James'
    --greetOnly3people('Joseph') -- => 'hello James'
    --greetOnly3people('Allan') -- => 'hello James'
    --
    -- -- --

    after (f, count)

    -- --

    Produces a function that will respond only after a given number of calls.

    -- --
    --local f = _.after(_.identity,3)
    --f(1) -- => nil
    --f(2) -- => nil
    --f(3) -- => 3
    --f(4) -- => 4
    --
    -- -- --

    compose (…)

    -- --

    Composes functions. Each function consumes the return value of the one that follows.

    -- --
    --local function f(x) return x^2 end
    --local function g(x) return x+1 end
    --local function h(x) return x/2 end
    --local compositae = _.compose(f,g,h)
    --compositae(10) -- => 36
    --compositae(20) -- => 121
    --
    -- -- --

    pipe (value, …)

    -- --

    Pipes a value through a series of functions.

    -- --
    --local function f(x) return x^2 end
    --local function g(x) return x+1 end
    --local function h(x) return x/2 end
    --_.pipe(10,f,g,h) -- => 36
    --_.pipe(20,f,g,h) -- => 121
    --
    -- -- --

    complement (f)

    -- --

    Returns a function which returns the logical complement of a given function.

    -- --
    --_.complement(function() return true end)() -- => false
    --
    -- -- --

    juxtapose (value, …)

    -- --

    Aliases: _.juxt.

    -- --

    Calls a sequence of functions with the same input.

    -- --
    --local function f(x) return x^2 end
    --local function g(x) return x+1 end
    --local function h(x) return x/2 end
    --_.juxtapose(10, f, g, h) -- => 100, 11, 5
    --
    -- -- --

    wrap (f, wrapper)

    -- --

    Wraps a function inside a wrapper. Allows the wrapper to execute code before and after function run.

    -- --
    --local greet = function(name) return "hi: " .. name end
    --local greet_backwards = _.wrap(greet, function(f,arg)
    --  return f(arg) ..'\nhi: ' .. arg:reverse()
    --end)
    --greet_backwards('John')
    --
    ---- => hi: John
    ---- => hi: nhoJ
    --
    -- -- --

    times (n, iter, …)

    -- --

    Calls a given function n times.

    -- --
    --local f = ('Lua programming'):gmatch('.')
    --_.times(3,f) -- => {'L','u','a'}
    --
    -- -- --

    bind (f, v)

    -- --

    Binds a value to be the first argument to a function.

    -- --
    --local sqrt2 = _.bind(math.sqrt,2)
    --sqrt2() -- => 1.4142135623731
    --
    -- -- --

    bind2 (f, v)

    -- --

    Binds a value to be the second argument to a function.

    -- --
    --local last2 = _.bind(_.last,2)
    --last2({1,2,3,4,5,6}) -- => {5,6}
    --
    -- -- --

    bindn (f, …)

    -- --

    Binds a variable number of values to be the first arguments to a function.

    -- --
    --local function out(...) return table.concat {...} end
    --local out = _.bindn(out,'OutPut',':',' ')
    --out(1,2,3) -- => OutPut: 123
    --out('a','b','c','d') -- => OutPut: abcd
    --
    -- -- --

    bindAll (obj, …)

    -- --

    Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument.

    -- --
    --local window = {
    --    setPos = function(w,x,y) w.x, w.y = x, y end,
    --    setName = function(w,name) w.name = name end,
    --    getName = function(w) return w.name end,
    --}
    --window = _.bindAll(window, 'setPos', 'setName', 'getName')
    --window.setPos(10,15)
    --print(window.x, window.y) -- => 10,15
    --
    --window.setName('fooApp')
    --print(window.name) -- => 'fooApp'
    --
    --print(window.getName()) -- => 'fooApp'
    --
    -- -- --

    uniqueId (template, …)

    -- --

    Aliases: _.uid.

    -- --

    Returns an unique integer ID.

    -- --
    --_.uniqueId() -- => 1
    --
    -- -- --

    Can handle string templates for formatted output.

    -- --
    --_.uniqueId('ID%s') -- => 'ID2'
    --
    -- -- --

    Or a function, for the same purpose.

    -- --
    --local formatter = function(ID) return '$'..ID..'$' end
    --_.uniqueId(formatter) -- => '$ID1$'
    --
    -- -- --

    iterator(f, x)

    -- --

    Aliases: _.iter.

    -- --

    Returns an iterator function which constinuously applies a function f onto an input x. --For example, let us go through the powers of two.

    -- --
    --local function po2(x) return x*2 end
    --local function iter_po2 = _.iterator(po2, 1)
    --iter_po2() -- => 2
    --iter_po2() -- => 4
    --iter_po2() -- => 8
    --
    -- -- --

    array (…)

    -- --

    Iterates a given iterator function and returns its values packed in an array.

    -- --
    --local text = 'letters'
    --local chars = string.gmatch(text, '.')
    --local letters = _.array(chars) -- => {'l','e','t','t','e','r','s'}
    --
    -- -- --

    flip (f)

    -- --

    Creates a function of f with arguments flipped in reverse order.

    -- --
    --local function f(...) return table.concat({...}) end
    --local flipped = _.flip(f)
    --flipped('a','b','c') -- => 'cba'
    --
    -- -- --

    over (…)

    -- --

    Creates a function that invokes a set of transforms with the arguments it receives.
    --One can use use for example to get the tuple of min and max values from a set of values

    -- --
    --local minmax = _.over(math.min, math.max)
    --minmax(5,10,12,4,3) -- => {3,12}
    --
    -- -- --

    overEvery (…)

    -- --

    Creates a validation function. The returned function checks if all of the given predicates return truthy when invoked with the arguments it receives.

    -- --
    --local function alleven(...)
    --    for i, v in ipairs({...}) do
    --        if v%2~=0 then return false end
    --    end
    --    return true
    --end
    --
    --local function allpositive(...)
    --    for i, v in ipairs({...}) do
    --        if v < 0 then return false end
    --    end
    --    return true
    --end
    --
    --local allok = _.overEvery(alleven, allpositive)
    --
    --allok(2,4,-1,8) -- => false
    --allok(10,3,2,6) -- => false
    --allok(8,4,6,10) -- => true
    --
    -- -- --

    overSome (…)

    -- --

    Creates a validation function. The returned function checks if any of the given predicates return truthy when invoked with the arguments it receives.

    -- --
    --local function alleven(...)
    --    for i, v in ipairs({...}) do
    --        if v%2~=0 then return false end
    --    end
    --    return true
    --end
    --
    --local function allpositive(...)
    --    for i, v in ipairs({...}) do
    --        if v < 0 then return false end
    --    end
    --    return true
    --end
    --
    --local anyok = _.overSome(alleven,allpositive)
    --
    --anyok(2,4,-1,8) -- => false
    --anyok(10,3,2,6) -- => true
    --anyok(-1,-5,-3) -- => false
    --
    -- -- --

    overArgs (f, …)

    -- --

    Creates a function that invokes f with its arguments transformed

    -- --
    --local function f(x, y) return x, y end
    --local function triple(x) retun x*3 end
    --local function square(x) retun x^2 end
    --local new_f = _.overArgs(f, triple, square)
    --
    --new_f(1,2) -- => 3, 4
    --new_f(10,10) -- => 30, 100
    --
    -- -- --

    In case the number of arguments is greater than the number of transforms, the remaining args will be left as-is.

    -- --
    --local function f(x, y, z) return x, y, z end
    --local function triple(x) retun x*3 end
    --local function square(x) retun x^2 end
    --local new_f = _.overArgs(f, triple, square)
    --
    --new_f(1,2,3) -- => 3, 4, 3
    --new_f(10,10,10) -- => 30, 100, 10
    --
    -- -- --

    partial (f, …)

    -- --

    Partially apply a function by filling in any number of its arguments.

    -- --
    --local function diff(a, b) return a - b end
    --local diffFrom20 = _.partial(diff, 20) -- arg 'a' will be 20 by default
    --diffFrom20(5) -- => 15
    --
    -- -- --

    The string '_' can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time.

    -- --
    --local function diff(a, b) return a - b end
    --local remove5 = _.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5
    --remove5(20) -- => 15
    --
    -- -- --

    partialRight (f, …)

    -- --

    Like _.partial, it partially applies a function by filling in any number of its arguments, but from the right.

    -- --
    --local function concat(...) return table.concat({...},',') end
    --local concat_right = _.partialRight(concat,'a','b','c')
    --concat_right('d') -- => d,a,b,c
    --
    --concat_right = _.partialRight(concat,'a','b')
    --concat_right('c','d') -- => c,d,a,b
    --
    --concat_right = _.partialRight(concat,'a')
    --concat_right('b','c','d') -- => b,c,d,a
    --
    -- -- --

    The string '_', as always, can be used as a placeholder in the list of arguments to specify an argument that should not be pre-filled, but is rather left open to be supplied at call-time. --In that case, the first args supplied at runtime will be used to fill the initial list of args while the remaining will be prepended.

    -- --
    --local function concat(...) return table.concat({...},',') end
    --local concat_right = _.partialRight(concat,'a','_','c')
    --concat_right('d','b') -- => b,a,d,c
    --
    --concat_right = _.partialRight(concat,'a','b','_')
    --concat_right('c','d') -- => d,a,b,c
    --
    --concat_right = _.partialRight(concat,'_','a')
    --concat_right('b','c','d') -- => c,d,b,a
    --
    -- -- --

    curry (f, n_args)

    -- --

    Curries a function. If the given function f takes multiple arguments, it returns another version of f that takes a single argument --(the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result.

    -- --
    --local function sumOf3args(x,y,z) return x + y + z end
    --local curried_sumOf3args = _.curry(sumOf3args, 3)
    --sumOf3args(1)(2)(3)) -- => 6
    --sumOf3args(0)(6)(9)) -- => 15
    --
    -- -- --

    n_args defaults to 2.

    -- --
    --local function product(x,y) return x * y end
    --local curried_product = _.curry(product)
    --curried_product(5)(4) -- => 20
    --curried_product(3)(-5) -- => -15
    --curried_product(0)(1) -- => 0
    --
    -- -- --

    time (f, …)

    -- --

    Returns the execution time of f (…) in seconds and its results.

    -- --
    --local function wait_count(n)
    --    local i = 0
    --    while i < n do i = i + 1 end
    --    return i
    --end
    --
    --local time, i = _.time(wait_count, 1e6) -- => 0.002 1000000
    --local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000
    --
    -- -- --

    [⬆]

    -- --

    -- --

    Object functions

    -- --

    keys (obj)

    -- --

    Collects the names of an object attributes.

    -- --
    --_.keys({1,2,3}) -- => "{1,2,3}"
    --_.keys({x = 0, y = 1}) -- => "{'y','x'}"
    --
    -- -- --

    values (obj)

    -- --

    Collects the values of an object attributes.

    -- --
    --_.values({1,2,3}) -- => "{1,2,3}"
    --_.values({x = 0, y = 1}) -- => "{1,0}"
    --
    -- -- --

    kvpairs (obj)

    -- --

    Converts an object to an array-list of key-value pairs.

    -- --
    --local obj = {x = 1, y = 2, z = 3}
    --_.each(_.kvpairs(obj), function(k,v)
    --    print(k, table.concat(v,','))
    --end)
    --
    ---- => 1    y,2
    ---- => 2 x,1
    ---- => 3 z,3
    --
    -- -- --

    toObj

    -- --

    Converts an array list of kvpairs to an object where keys are taken from the 1rst column in the kvpairs sequence, associated with values in the 2nd column.

    -- --
    --local list_pairs = {{'x',1},{'y',2},{'z',3}}
    --obj = _.toObj(list_pairs)
    --
    ---- => {x = 1, y = 2, z = 3}
    --
    -- -- --

    property (key)

    -- --

    Returns a function that will return the key property of any passed-in object.

    -- --
    --local who = _.property('name')
    --local people = {name = 'Henry'}
    --who(people) -- => 'Henry'
    --
    -- -- --

    propertyOf (obj)

    -- --

    Returns a function that will return the key property of any passed-in object.

    -- --
    --local people = {name = 'Henry'}
    --print(_.propertyOf(people)('name')) -- => 'Henry'
    --
    -- -- --

    toBoolean (value)

    -- --

    Converts a given value to a boolean.

    -- --
    --_.toBoolean(true) -- => true
    --_.toBoolean(false) -- => false
    --_.toBoolean(nil) -- => false
    --_.toBoolean({}) -- => true
    --_.toBoolean(1) -- => true
    --
    -- -- --

    extend (destObj, …)

    -- --

    Extends a destination object with the properties of some source objects.

    -- --
    --_.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}"
    --
    -- -- --

    functions (obj, recurseMt)

    -- --

    Aliases: _.methods.

    -- --

    Returns all functions names within an object.

    -- --
    --_.functions(coroutine) -- => "{'create','resume','running','status','wrap','yield'}"
    --
    -- -- --

    clone (obj, shallow)

    -- --

    Clones a given object.

    -- --
    --local obj = {1,2,3}
    --local obj2 = _.clone(obj)
    --print(obj2 == obj) -- => false
    --print(_.isEqual(obj2, obj)) -- => true
    --
    -- -- --

    tap (obj, f, …)

    -- --

    Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. --The pased-interceptor is prototyped as f(obj,…).

    -- --
    --local v = _.chain({1,2,3,4,5,6,7,8,9,10)
    --  :filter(function(k,v) return v%2~=0 end) -- filters even values
    --  :tap(function(v) print('Max is', _.max(v) end) -- Tap max values
    --  :map(function(k,v) return k^2)
    --  :value() -- =>    Max is 9
    --
    -- -- --

    has (obj, key)

    -- --

    Checks if an object has a given attribute.

    -- --
    --_.has(_,'has') -- => true
    --_.has(coroutine,'resume') -- => true
    --_.has(math,'random') -- => true
    --
    -- -- --

    pick (obj, …)

    -- --

    Aliases: _.choose.

    -- --

    Collects whilelisted properties of a given object.

    -- --
    --local object = {a = 1, b = 2, c = 3}
    --_.pick(object,'a','c') -- => "{a = 1, c = 3}"
    --
    -- -- --

    omit (obj, …)

    -- --

    Aliases: _.drop.

    -- --

    Omits blacklisted properties of a given object.

    -- --
    --local object = {a = 1, b = 2, c = 3}
    --_.omit(object,'a','c') -- => "{b = 2}"
    --
    -- -- --

    template (obj, template)

    -- --

    Aliases: _.defaults.

    -- --

    Applies a template on an object, preserving existing properties.

    -- --
    --local obj = {a = 0}
    --_.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}"
    --
    -- -- --

    isEqual (objA, objB, useMt)

    -- --

    Aliases: _.compare.

    -- --

    Compares objects:

    -- --
    --_.isEqual(1,1) -- => true
    --_.isEqual(true,false) -- => false
    --_.isEqual(3.14,math.pi) -- => false
    --_.isEqual({3,4,5},{3,4,{5}}) -- => false
    --
    -- -- --

    result (obj, method, …)

    -- --

    Calls an object method, passing it as a first argument the object itself.

    -- --
    --_.result('abc','len') -- => 3
    --_.result({'a','b','c'},table.concat) -- => 'abc'
    --
    -- -- --

    isTable (t)

    -- --

    Is the given argument an object (i.e a table) ?

    -- --
    --_.isTable({}) -- => true
    --_.isTable(math) -- => true
    --_.isTable(string) -- => true
    --
    -- -- --

    isCallable (obj)

    -- --

    Is the given argument callable ?

    -- --
    --_.isCallable(print) -- => true
    --_.isCallable(function() end) -- => true
    --_.isCallable(setmetatable({},{__index = string}).upper) -- => true
    --_.isCallable(setmetatable({},{__call = function() return end})) -- => true
    --
    -- -- --

    isArray (obj)

    -- --

    Is the given argument an array (i.e. a sequence) ?

    -- --
    --_.isArray({}) -- => true
    --_.isArray({1,2,3}) -- => true
    --_.isArray({'a','b','c'}) -- => true
    --
    -- -- --

    isIterable (obj)

    -- --

    Checks if the given object is iterable with pairs.

    -- --
    --_.isIterable({}) -- => true
    --_.isIterable(function() end) -- => false
    --_.isIterable(false) -- => false
    --_.isIterable(1) -- => false
    --
    -- -- --

    isEmpty (obj)

    -- --

    Is the given argument empty ?

    -- --
    --_.isEmpty('') -- => true
    --_.isEmpty({})  -- => true
    --_.isEmpty({'a','b','c'}) -- => false
    --
    -- -- --

    isString (obj)

    -- --

    Is the given argument a string ?

    -- --
    --_.isString('') -- => true
    --_.isString('Hello') -- => false
    --_.isString({}) -- => false
    --
    -- -- --

    isFunction (obj)

    -- --

    Is the given argument a function ?

    -- --
    --_.isFunction(print) -- => true
    --_.isFunction(function() end) -- => true
    --_.isFunction({}) -- => false
    --
    -- -- --

    isNil (obj)

    -- --

    Is the given argument nil ?

    -- --
    --_.isNil(nil) -- => true
    --_.isNil() -- => true
    --_.isNil({}) -- => false
    --
    -- -- --

    isNumber (obj)

    -- --

    Is the given argument a number ?

    -- --
    --_.isNumber(math.pi) -- => true
    --_.isNumber(math.huge) -- => true
    --_.isNumber(0/0) -- => true
    --_.isNumber() -- => false
    --
    -- -- --

    isNaN (obj)

    -- --

    Is the given argument NaN ?

    -- --
    --_.isNaN(1) -- => false
    --_.isNaN(0/0) -- => true
    --
    -- -- --

    isFinite (obj)

    -- --

    Is the given argument a finite number ?

    -- --
    --_.isFinite(99e99) -- => true
    --_.isFinite(math.pi) -- => true
    --_.isFinite(math.huge) -- => false
    --_.isFinite(1/0) -- => false
    --_.isFinite(0/0) -- => false
    --
    -- -- --

    isBoolean (obj)

    -- --

    Is the given argument a boolean ?

    -- --
    --_.isBoolean(true) -- => true
    --_.isBoolean(false) -- => true
    --_.isBoolean(1==1) -- => true
    --_.isBoolean(print) -- => false
    --
    -- -- --

    isInteger (obj)

    -- --

    Is the given argument an integer ?

    -- --
    --_.isInteger(math.pi) -- => false
    --_.isInteger(1) -- => true
    --_.isInteger(-1) -- => true
    --
    -- -- --

    [⬆]

    -- --

    -- --

    Chaining

    -- --

    Method chaining (also known as name parameter idiom), is a technique for invoking consecutively method calls in object-oriented style. --Each method returns an object, and methods calls are chained together. --Moses offers chaining for your perusal.
    --Let’s use chaining to get the count of evey single word in some lyrics (case won’t matter here).

    -- --
    --local lyrics = {
    --  "I am a lumberjack and I am okay",
    --  "I sleep all night and I work all day",
    --  "He is a lumberjack and he is okay",
    --  "He sleeps all night and he works all day"
    --}
    --
    --local stats = _.chain(lyrics)
    --  :map(function(k,line)
    --    local t = {}
    --    for w in line:gmatch('(%w+)') do
    --      t[#t+1] = w
    --    end
    --    return t
    --  end)
    --  :flatten()
    --  :countBy(function(i,v) return v:lower() end)
    --  :value()
    --
    ---- => "{
    ---- =>    sleep = 1, night = 2, works = 1, am = 2, is = 2,
    ---- =>    he = 2, and = 4, I = 4, he = 2, day = 2, a = 2,
    ---- =>    work = 1, all = 4, okay = 2
    ---- =>  }"
    --
    -- -- --

    For convenience, you can also use _(value) to start chaining methods, instead of _.chain(value).

    -- --

    Note that one can use :value() to unwrap a chained object.

    -- --
    --local t = {1,2,3}
    --print(_(t):value() == t) -- => true
    --
    -- -- --

    [⬆]

    -- --

    -- --

    Import

    -- --

    All library functions can be imported in a context using import into a specified context.

    -- --
    --local context = {}
    --_.import(context)
    --
    --context.each({1,2,3},print)
    --
    ---- => 1 1
    ---- => 2 2
    ---- => 3 3
    --
    -- -- --

    When no context was provided, it defaults to the global environment _G.

    -- --
    --_.import()
    --
    --each({1,2,3},print)
    --
    ---- => 1 1
    ---- => 2 2
    ---- => 3 3
    --
    -- -- --

    Passing noConflict argument leaves untouched conflicting keys while importing into the context.

    -- --
    --local context = {each = 1}
    --_.import(context, true)
    --
    --print(context.each) -- => 1
    --context.eachi({1,2,3},print)
    --
    ---- => 1 1
    ---- => 2 2
    ---- => 3 3
    --
    -- -- --

    [⬆] -- --

    --
    --
    --generated by LDoc 1.4.6 --Last updated 2017-04-27 15:26:55 --
    --
    -- -- -diff --git a/extra/moses/doc/tutorial.md b/extra/moses/doc/tutorial.md -index feff3e9..035a77c 100644 ---- a/extra/moses/doc/tutorial.md -+++ b/extra/moses/doc/tutorial.md -@@ -1,10 +1,11 @@ - *Moses: a utility-belt library for functional programming in Lua* - - __Moses__ is a Lua utility library which provides support for functional programming. --It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more.
    --__Moses__ was deeply inspired by [Underscore.js](http://documentcloud.github.com/underscore/). -+It complements built-in Lua functions, making easier common operations on tables, arrays, lists, collections, objects, and a lot more. -+
    -+
    - --# Table of Contents -+# Sections - - - * [Adding *Moses* to your project](#adding) -@@ -14,23 +15,21 @@ __Moses__ was deeply inspired by [Underscore.js](http://documentcloud.github.com - * [Object functions](#object) - * [Chaining](#chaining) - * [Import](#import) -- -+

    - # Adding *Moses* to your project - - Drop the file [moses.lua](http://github.com/Yonaba/Moses/blob/master/moses.lua) into your project and add it to your code with the *require* function: - - ```lua --local _ = require ("moses") -+local M = require ("moses") - ```` - --*Note: Lua purists tend to use "\_" to design a "dummy variable". Here, the usage of this underscore is quite idiomatic and refers to the name [Underscore](http://documentcloud.github.com/underscore/), the JS library from which *Moses* takes inspiration*. -- --*Moses*' provides a large set of functions that can be classified into four categories: -+*Moses* provides a large set of functions that can be classified into four categories: - --* __Table functions__, which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part, --* __Array functions__, meant for array lists (or sequences), --* __Utility functions__, --* __Object functions__. -+* [__Table functions__](#table), which are mostly meant for tables, i.e Lua tables which contains both an array-part and a hash-part, -+* [__Array functions__](#array), meant for array lists (or sequences), -+* [__Utility functions__](#utility), -+* [__Object functions__](#object). - - **[[⬆]](#TOC)** - -@@ -38,198 +37,271 @@ local _ = require ("moses") - - ### clear (t) - --Clears a table. All its values becomes nil. It returns the passed-in table. -+Clears a table. All its values becomes nil. Returns the passed-in table. - - ```lua --local t = _.clear({1,2,'hello',true}) -- => {} -+M.clear({1,2,'hello',true}) -- => {} - ```` - --### each (t, f, ...) --*Aliases: `_.forEach`*. -+### each (t, f) -+*Aliases: `forEach`*. - --Iterates over each key-value pair in table. -+Iterates over each value-key pair in the passed-in table. - - ```lua --_.each({1,2,3},print) -+M.each({4,2,1},print) - ---- => 1 1 -+-- => 4 1 - -- => 2 2 ---- => 3 3 -+-- => 1 3 - ```` - --The table can be map-like (array part and hash-part). -+The table can be map-like (both array part and hash part). - - ```lua --_.each({one = 1, two = 2, three = 3},print) -+M.each({one = 1, two = 2, three = 3},print) - ---- => one 1 ---- => two 2 ---- => three 3 -+-- => 1 one -+-- => 2 two -+-- => 3 three - ```` - - Can index and assign in an outer table or in the passed-in table: - - ```lua - t = {'a','b','c'} --_.each(t,function(i,v) -+M.each(t,function(v,i) - t[i] = v:rep(2) - print(t[i]) - end) - ---- => 1 aa ---- => 2 bb ---- => 3 cc -+-- => aa -+-- => bb -+-- => cc - ```` - --### eachi (t, f, ...) --*Aliases: `_.forEachi`*. -+### eachi (t, f) -+*Aliases: `forEachi`*. - --Iterates only on integer keys in a sparse array table. -+Iterates only on integer keys in an array table. It returns value-key pairs. - - ```lua --_.eachi({1,2,3},print) -+M.eachi({4,2,1},print) - ---- => 1 1 -+-- => 4 1 - -- => 2 2 ---- => 3 3 -+-- => 1 3 - ```` - - The given array can be sparse, or even have a hash-like part. - - ```lua - local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5} --_.eachi(t,function(i,v) -- print(i,v) --end) -+M.eachi(t,print) - ---- => -1 6 ---- => 0 1 ---- => 1 3 ---- => 2 5 -+-- => 6 -1 -+-- => 1 0 -+-- => 3 1 -+-- => 5 2 - ```` - - ### at (t, ...) - --Collects all values at some specific keys and returns them in an array. -+Collects values at given keys and returns them in an array. - - ```lua - local t = {4,5,6} --_.at(t,1,3) -- => "{4,6}" -+M.at(t,1,3) -- => "{4,6}" - - local t = {a = 4, bb = true, ccc = false} --_.at(t,'a', 'ccc') -- => "{4, false}" -+M.at(t,'a', 'ccc') -- => "{4, false}" - ```` - --### count (t, value) -+### adjust (t, key, f) -+ -+Adjusts the value at a given key using a function or a value. In case `f` is a function, it should be prototyped `f(v)`. -+It does not mutate the given table, but rather returns a new array. -+ -+```lua -+local t = {1,2,3} -+M.adjust(t, 2, math.sin) -- => {1, 0.90929, 3} -+ -+local v = {x = 1} -+ M.adjust(t, 'x', 4) -- => {x = 4} -+```` -+ -+In case the given `key` does not exist in `t`, it throws an error. -+ -+### count (t [, val]) - - Counts the number of occurences of a given value in a table. - - ```lua --_.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2 --_.count({1,1,2,3,3,3,2,4,3,2},2) -- => 2 --_.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4 --_.count({false, false, true},false) -- => 2 --_.count({false, false, true},true) -- => 1 -+M.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2 -+M.count({1,1,2,3,3,3,2,4,3,2},2) -- => 3 -+M.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4 -+M.count({false, false, true},false) -- => 2 -+M.count({false, false, true},true) -- => 1 - ```` - - Returns the size of the list in case no value was provided. - - ```lua --_.count({1,1,2,3,3}) -- => 5 -+M.count({1,1,2,3,3}) -- => 5 - ```` - --### countf (t, f, ...) -+### countf (t, f) - --Count the number of occurences of all values passing an iterator test. -+Counts the number of values passing an iterator test. - - ```lua --_.countf({1,2,3,4,5,6}, function(i,v) -+M.countf({1,2,3,4,5,6}, function(v) - return v%2==0 - end) -- => 3 - --_.countf({print, pairs, os, assert, ipairs}, function(i,v) -+M.countf({print, pairs, os, assert, ipairs}, function(v) - return type(v)=='function' - end) -- => 4 - ```` - --### cycle (t, n) --*Aliases: `_.loop`*. -+### allEqual (t [, comp]) -+*Aliases: `alleq`*. -+ -+Checks if all values in a collection are equal. Uses `M.isEqual` by default to compare values. -+ -+```lua -+M.allEqual({1,1,1,1,1}, comp) -- => true -+M.allEqual({1,1,2,1,1}, comp) -- => false -+ -+local t1 = {1, 2, {3}} -+local t2 = {1, 2, {3}} -+M.allEqual({t1, t2}) -- => true -+```` -+ -+Can take an optional `comp` function which will be used to compare values. -+ -+```lua -+local t1 = {x = 1, y = 0} -+local t2 = {x = 1, y = 0} -+local t3 = {x = 1, y = 2} -+local t4 = {x = 1, y = 2} -+local function compx(a, b) return a.x == b.x end -+local function compy(a, b) return a.y == b.y end -+ -+M.allEqual({t1, t2}, compx) -- => true -+M.allEqual({t1, t2}, compy) -- => true -+M.allEqual({t3, t4}, compx) -- => true -+M.allEqual({t3, t4}, compy) -- => true -+M.allEqual({t1, t2, t3, t4}, compx) -- => true -+M.allEqual({t1, t2, t3, t4}, compy) -- => false -+```` -+ -+### cycle (t [, n = 1]) -+*Aliases: `loop`*. - --Returns a function which iterates on each key-value pair in a given table (similarly to `_.each`), except that it restarts iterating again `n` times. -+Returns a function which iterates on each value-key pair in a given table (similarly to `M.each`), except that it restarts iterating again `n` times. - If `n` is not provided, it defaults to 1. - - ```lua --local t = {'a,'b','c'} --for k,v in _.cycle(t, 2) do -- print(k,v) -+local t = {'a','b','c'} -+for v in M.cycle(t, 2) do -+ print(v) - end - ---- => 1 'a' ---- => 2 'b' ---- => 3 'c' ---- => 1 'a' ---- => 2 'b' ---- => 3 'c' -+-- => 'a' -+-- => 'b' -+-- => 'c' -+-- => 'a' -+-- => 'b' -+-- => 'c' - ```` - - Supports array-like tables and map-like tables. - - ```lua - local t = {x = 1, y = 2, z = 3} --for k,v in _.cycle(t) do -- print(k,v) -+for v in M.cycle(t) do -+ print(v) - end - ---- => y 2 ---- => x 1 ---- => z 3 -+-- => 2 -+-- => 1 -+-- => 3 - ```` - --### map (t, f, ...) --*Aliases: `_.collect`*. -+### map (t, f) -+*Aliases: `collect`*. - --Executes a function on each key-value pairs. -+Executes a function on each value in a given array. - - ```lua --_.map({1,2,3},function(i,v) -+M.map({1,2,3},function(v) - return v+10 - end) -- => "{11,12,13}" - ```` - - ```lua --_.map({a = 1, b = 2},function(k,v) -+M.map({a = 1, b = 2},function(v, k) - return k..v - end) -- => "{a = 'a1', b = 'b2'}" - ```` - --It also maps key-value pairs to key-value pairs -+It also maps both keys and values. - - ```lua --_.map({a = 1, b = 2},function(k,v) -+M.map({a = 1, b = 2},function(v, k) - return k..k, v*2 - end) -- => "{aa = 2, bb = 4}" - ```` - --### reduce (t, f, state) --*Aliases: `_.inject`, `_.foldl`*. -+### mapi (t, f) -+ -+Executes a function on each value in a given array. -+ -+```lua -+M.mapi({1,2,3},function(v) -+ return v+10 -+end) -- => "{11,12,13}" -+```` - --Can sums all values in a table. -+It only works for the array-part of the given table. - - ```lua --_.reduce({1,2,3,4},function(memo,v) -- return memo+v --end) -- => 10 -+M.map({a = 1, 2, 3, 4, 5},function(v, k) -+ return k..v -+end) -- => "{'12','23','34','45'}" -+```` -+ -+### reduce (t, f [, state = next(t)]) -+*Aliases: `inject`, `foldl`*. -+ -+Can sum all values in a table. In case `state` is not provided, it defaults to the first value in the given table `t`. -+ -+```lua -+local function add(a,b) return a+b end -+M.reduce({1,2,3,4},add) -- => 10 - ```` - - Or concatenates all values. - --```lua --_.reduce({'a','b','c','d'},function(memo,v) -- return memo..v --end) -- => abcd -+```lua -+local function concat(a,b) return a..b end -+M.reduce({'a','b','c','d'},concat) -- => abcd - ```` - --### reduceby (t, f, state, pred, ...) -+### best (t, f) -+ -+Returns the best value passing a selector function. Acts as a special case of `reduce`, using the first value in `t` as -+an initial state. It thens folds the given table, testing each of its values `v` and selecting the value passing the -+call `f(state,v)` every time. -+ -+```lua -+local words = {'Lua', 'Programming', 'Language'} -+M.best(words, function(a,b) return #a > #b end) -- => 'Programming' -+M.best(words, function(a,b) return #a < #b end) -- => 'Lua' -+```` -+ -+### reduceBy (t, f, pred [, state = next(t)]) - - Reduces a table considering only values matching a predicate. - For example,let us define a set of values. -@@ -237,85 +309,87 @@ For example,let us define a set of values. - ```lua - local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9} - ```` -+ -+And a reduction function which will add up values. -+ -+```lua -+local function add(a,b) return a+b end -+```` -+ - We can also define some predicate functions. - - ```lua - -- predicate for negative values --local function neg(_, v) return v<=0 end -+local function neg(v) return v<=0 end - - -- predicate for positive values --local function pos(_, v) return v>=0 end -+local function pos(v) return v>=0 end - ```` - --Then we can perform reduction considering only negative values : -+Then we can perform reduction considering only negative or positive values : - - ```lua --_.reduceby(val, function(memo,v) -- return memo+v --end, 0, neg) -- => -17 -+M.reduceBy(val, add, neg) -- => -17 -+M.reduceBy(val, add, pos) -- => 19 - ```` - --Or only positive values : -+An initial state can be passed in. - - ```lua --_.reduceby(val, function(memo,v) -- return memo+v --end, 0, pos) -- => 19 -+M.reduceBy(val, add, neg, 17) -- => 0 -+M.reduceBy(val, add, pos, -19) -- => 0 - ```` - --### reduceRight (t, f, state) --*Aliases: `_.injectr`, `_.foldr`*. -+### reduceRight (t, f [, state = next(t)]) -+*Aliases: `injectr`, `foldr`*. - --Similar to `_.reduce`, but performs from right to left. -+Similar to `M.reduce`, but performs from right to left. - - ```lua - local initial_state = 256 --_.reduceRight({1,2,4,16},function(memo,v) -- return memo/v --end,initial_state) -- => 2 -+local function div(a,b) return a/b end -+M.reduceRight({1,2,4,16},div,initial_state) -- => 2 - ```` - --### mapReduce (t, f, state) --*Aliases: `_.mapr`*. -+### mapReduce (t, f [, state = next(t)]) -+*Aliases: `mapr`*. - - Reduces while saving intermediate states. - - ```lua --_.mapReduce({'a','b','c'},function(memo,v) -- return memo..v --end) -- => "{'a', 'ab', 'abc'}" -+local function concat(a,b) return a..b end -+M.mapReduce({'a','b','c'},concat) -- => "{'a', 'ab', 'abc'}" - ```` - --### mapReduceRight (t, f, state) --*Aliases: `_.maprr`*. -+### mapReduceRight (t, f [, state = next(t)]) -+*Aliases: `maprr`*. - - Reduces from right to left, while saving intermediate states. - - ```lua --_.mapReduceRight({'a','b','c'},function(memo,v) -- return memo..v --end) -- => "{'c', 'cb', 'cba'}" -+local function concat(a,b) return a..b end -+M.mapReduceRight({'a','b','c'},concat) -- => "{'c', 'cb', 'cba'}" - ```` - - ### include (t, value) --*Aliases: `_.any`, `_.some`, `_.contains`*. -+*Aliases: `any`, `some`, `contains`*. - - Looks for a value in a table. - - ```lua --_.include({6,8,10,16,29},16) -- => true --_.include({6,8,10,16,29},1) -- => false -+M.include({6,8,10,16,29},16) -- => true -+M.include({6,8,10,16,29},1) -- => false - - local complex_table = {18,{2,{3}}} - local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29} --_.include(collection, complex_table) -- => true -+M.include(collection, complex_table) -- => true - ```` - - Handles iterator functions. - - ```lua - local function isUpper(v) return v:upper()== v end --_.include({'a','B','c'},isUpper) -- => true -+M.include({'a','B','c'},isUpper) -- => true - ```` - - ### detect (t, value) -@@ -323,12 +397,12 @@ _.include({'a','B','c'},isUpper) -- => true - Returns the index of a value in a table. - - ```lua --_.detect({6,8,10,16},8) -- => 2 --_.detect({nil,true,0,true,true},false) -- => nil -+M.detect({6,8,10,16},8) -- => 2 -+M.detect({nil,true,0,true,true},false) -- => nil - - local complex_table = {18,{2,6}} - local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29} --_.detect(collection, complex_table) -- => 2 -+M.detect(collection, complex_table) -- => 2 - ```` - - Handles iterator functions. -@@ -337,7 +411,7 @@ Handles iterator functions. - local function isUpper(v) - return v:upper()==v - end --_.detect({'a','B','c'},isUpper) -- => 2 -+M.detect({'a','B','c'},isUpper) -- => 2 - ```` - - ### where (t, props) -@@ -345,94 +419,79 @@ _.detect({'a','B','c'},isUpper) -- => 2 - Looks through a table and returns all the values that matches all of the key-value pairs listed in `props`. - - ```lua --local tA = {a = 1, b = 2, c = 0} --local tB = {a = 1, b = 4, c = 1} --local tC = {a = 4, b = 4, c = 3} --local tD = {a = 1, b = 2, c = 3} --local found = _.where({tA, tB, tC, tD}, {a = 1}) -- ---- => found = {tA, tB, tD} -- --found = _.where({tA, tB, tC, tD}, {b = 4}) -- ---- => found = {tB, tC} -- --found = _.where({tA, tB, tC, tD}, {b = 4, c = 3}) -- ---- => found = {tC} -+local items = { -+ {height = 10, weight = 8, price = 500}, -+ {height = 10, weight = 15, price = 700}, -+ {height = 15, weight = 15, price = 3000}, -+ {height = 10, weight = 8, price = 3000}, -+} -+M.where(items, {height = 10}) -- => {items[1], items[2], items[4]} -+M.where(items, {weight = 15}) -- => {items[2], items[3]} -+M.where(items, {prince = 3000}) -- => {items[3], items[4]} -+M.where(items, {height = 10, weight = 15, prince = 700}) -- => {items[2]} - ```` - - ### findWhere (t, props) - --Looks through a table and returns the first value that matches all of the key-value pairs listed in `props`. -+Looks through a table and returns the first value found that matches all of the key-value pairs listed in `props`. - - ```lua - local a = {a = 1, b = 2, c = 3} - local b = {a = 2, b = 3, d = 4} - local c = {a = 3, b = 4, e = 5} --_.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true -+M.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true - ```` - --### select (t, f, ...) --*Aliases: `_.filter`*. -+### select (t, f) -+*Aliases: `filter`*. - - Collects values passing a validation test. - - ```lua ---- Even values --_.select({1,2,3,4,5,6,7}, function(key,value) -- return (value%2==0) --end) -- => "{2,4,6}" -+local function isEven(v) return v%2==0 end -+local function isOdd(v) return v%2~=0 end - ---- Odd values --_.select({1,2,3,4,5,6,7}, function(key,value) -- return (value%2~=0) --end) -- => "{1,3,5,7}" -+M.select({1,2,3,4,5,6,7}, isEven) -- => "{2,4,6}" -+M.select({1,2,3,4,5,6,7}, isOdd) -- => "{1,3,5,7}" - ```` - --### reject (t, f, ...) --*Aliases: `_.reject`*. -+### reject (t, f) -+*Aliases: `reject`*. - --Removes all values failing a validation test: -+Removes all values failing (returning false or nil) a validation test: - - ```lua --_.reject({1,2,3,4,5,6,7}, function(key,value) -- return (value%2==0) --end) -- => "{1,3,5,7}" -+local function isEven(v) return v%2==0 end -+local function isOdd(v) return v%2~=0 end - --_.reject({1,2,3,4,5,6,7}, function(key,value) -- return (value%2~=0) --end) -- => "{2,4,6}" -+M.reject({1,2,3,4,5,6,7}, isEven) -- => "{1,3,5,7}" -+M.reject({1,2,3,4,5,6,7}, isOdd) -- => "{2,4,6}" - ```` - --### all (t, f, ...) --*Aliases: `_.every`*. -+### all (t, f) -+*Aliases: `every`*. - - Checks whether or not all elements pass a validation test. - - ```lua --_.all({2,4,6}, function(key,value) -- return (value%2==0) --end) -- => true -+local function isEven(v) return v%2==0 end -+M.all({2,4,6}, isEven) -- => true - ```` - --### invoke (t, method, ...) -+### invoke (t, method) - --Invokes a given function on each value in a table -+Invokes a given function on each value in a table. - - ```lua --_.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}" -+M.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}" - ```` - - Can reference the method of the same name in each value. - - ```lua --local a = {} --function a:call() return 'a' end --local b, c, d = {}, {}, {} --b.call, c.call, d.call = a.call, a.call, a.call -- --_.invoke({a,b,c,d},'call') -- => "{'a','a','a','a'}" -+local a, b, c, d = {id = 'a'}, {id = 'b'}, {id = 'c'}, {id = 'd'} -+local function call(self) return self.id end -+M.invoke({a,b,c,d},call) -- => "{'a','b','c','d'}" - ```` - - ### pluck (t, property) -@@ -444,17 +503,17 @@ local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} - --_.pluck(peoples,'age') -- => "{23,17,15,33}" --_.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}" -+M.pluck(peoples,'age') -- => "{23,17,15,33}" -+M.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}" - ```` - --### max (t, transform, ...) -+### max (t [, transform]) - - Returns the maximum value in a collection. - - ```lua --_.max {1,2,3} -- => 3 --_.max {'a','b','c'} -- => 'c' -+M.max {1,2,3} -- => 3 -+M.max {'a','b','c'} -- => 'c' - ```` - - Can take an iterator function to extract a specific property. -@@ -463,16 +522,16 @@ Can take an iterator function to extract a specific property. - local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} --_.max(peoples,function(people) return people.age end) -- => 33 -+M.max(peoples,function(people) return people.age end) -- => 33 - ```` - --### min (t, transform, ...) -+### min (t [, transform]) - - Returns the minimum value in a collection. - - ```lua --_.min {1,2,3} -- => 1 --_.min {'a','b','c'} -- => 'a' -+M.min {1,2,3} -- => 1 -+M.min {'a','b','c'} -- => 'a' - ```` - - Can take an iterator function to extract a specific property. -@@ -481,16 +540,7 @@ Can take an iterator function to extract a specific property. - local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} --_.min(peoples,function(people) return people.age end) -- => 15 --```` -- --### shuffle (t, seed) -- --Shuffles a collection. -- --```lua --local list = _.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}" --_.each(list,print) -+M.min(peoples,function(people) return people.age end) -- => 15 - ```` - - ### same (a, b) -@@ -500,34 +550,84 @@ Tests whether or not all values in each of the passed-in tables exists in both t - ```lua - local a = {'a','b','c','d'} - local b = {'b','a','d','c'} --_.same(a,b) -- => true -+M.same(a,b) -- => true - - b[#b+1] = 'e' --_.same(a,b) -- => false -+M.same(a,b) -- => false - ```` - --### sort (t, comp) -+### sort (t [, comp = math.min]) - - Sorts a collection. - - ```lua --_.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}" -+M.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}" - ```` - - Handles custom comparison functions. - - ```lua --_.sort({'b','a','d','c'}, function(a,b) -+M.sort({'b','a','d','c'}, function(a,b) - return a:byte() > b:byte() - end) -- => "{'d','c','b','a'}" - ```` - --### sortBy (t, transform, comp) -+### sortedk (t [, comp]) -+ -+Iterates on values with respect to key order. Keys are sorted using `comp` function which defaults to `math.min`. -+It returns upon each call a `key, value` pair. -+ -+```lua -+local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+for k, v in M.sortedk(tbl) do print(k, v) end -+ -+-- => 1 12 -+-- => 2 6 -+-- => 3 5 -+-- => 4 10 -+-- => 5 8 -+ -+local function comp(a,b) return a > b end -+for k, v in M.sortedk(tbl, comp) do print(k, v) end -+ -+-- => 5 8 -+-- => 4 10 -+-- => 3 5 -+-- => 2 6 -+-- => 1 12 -+```` -+ -+### sortedv (t [, comp]) -+ -+Iterates on values with respect to key order. Keys are sorted using `comp` function which defaults to `math.min`. -+It returns upon each call a `key, value` pair. -+ -+```lua -+local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+for k, v in M.sortedv(tbl) do print(k, v) end -+ -+-- => 3 5 -+-- => 2 6 -+-- => 5 8 -+-- => 4 10 -+-- => 1 12 -+ -+local function comp(a,b) return a > b end -+for k, v in M.sortedv(tbl, comp) do print(k, v) end -+ -+-- => 1 12 -+-- => 4 10 -+-- => 5 8 -+-- => 2 6 -+-- => 3 5 -+```` -+ -+### sortBy (t [, transform [, comp = math.min]]) - - Sorts items in a collection based on the result of running a transform function through every item in the collection. - - ```lua --local r = _.sortBy({1,2,3,4,5}, math.sin) -+local r = M.sortBy({1,2,3,4,5}, math.sin) - print(table.concat(r,',')) - - -- => {5,4,3,1,2} -@@ -536,14 +636,14 @@ print(table.concat(r,',')) - The transform function can also be a string name property. - - ```lua --local people ={ -+local people = { - {name = 'albert', age = 40}, - {name = 'louis', age = 55}, - {name = 'steve', age = 35}, - {name = 'henry', age = 19}, - } --local r = _.sortBy(people, 'age') --_.each(r, function(__,v) print(v.age, v.name) end) -+local r = M.sortBy(people, 'age') -+M.each(r, function(v) print(v.age, v.name) end) - - -- => 19 henry - -- => 35 steve -@@ -551,18 +651,17 @@ _.each(r, function(__,v) print(v.age, v.name) end) - -- => 55 louis - ```` - --As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort --the list of people by decreasing age order : -+As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort the list of people by decreasing age order : - - ```lua --local people ={ -+local people = { - {name = 'albert', age = 40}, - {name = 'louis', age = 55}, - {name = 'steve', age = 35}, - {name = 'henry', age = 19}, - } --local r = _.sortBy(people, 'age', function(a,b) return a > b end) --_.each(r, function(__,v) print(v.age, v.name) end) -+local r = M.sortBy(people, 'age', function(a,b) return a > b end) -+M.each(r, function(v) print(v.age, v.name) end) - - -- => 55 louis - -- => 40 albert -@@ -570,36 +669,36 @@ _.each(r, function(__,v) print(v.age, v.name) end) - -- => 19 henry - ```` - --The `transform` function defaults to `_.indentity` and in that case, `_.sortBy` behaves like `_.sort`. -+The `transform` function defaults to `M.indentity` and in that case, `M.sortBy` behaves like `M.sort`. - - ```lua --local r = _.sortBy({1,2,3,4,5}) -+local r = M.sortBy({1,2,3,4,5}) - print(table.concat(r,',')) - - -- => {1,2,3,4,5} - ```` - --### groupBy (t, iter, ...) -+### groupBy (t, iter) - - Groups values in a collection depending on their return value when passed to a predicate test. - - ```lua --_.groupBy({0,1,2,3,4,5,6},function(i,value) -- return value%2==0 and 'even' or 'odd' --end) -- => "{odd = {1,3,5}, even = {0,2,4,6}}" -+M.groupBy({0,1,2,3,4,5,6},function(v) -+ return v%2==0 and 'even' or 'odd' -+end) -+-- => "{odd = {1,3,5}, even = {0,2,4,6}}" - --_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value) -- return type(value) --end) -- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}" -+M.groupBy({0,'a',true, false,nil,b,0.5},type) -+-- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}" - ```` - --### countBy (t, iter, ...) -+### countBy (t, iter) - - Splits a table in subsets and provide the count for each subset. - - ```lua --_.countBy({0,1,2,3,4,5,6},function(i,value) -- return value%2==0 and 'even' or 'odd' -+M.countBy({0,1,2,3,4,5,6},function(v) -+ return v%2==0 and 'even' or 'odd' - end) -- => "{odd = 3, even = 4}" - ```` - -@@ -608,15 +707,15 @@ end) -- => "{odd = 3, even = 4}" - When given a table, provides the count for the very number of values in that table. - - ```lua --_.size {1,2,3} -- => 3 --_.size {one = 1, two = 2} -- => 2 -+M.size {1,2,3} -- => 3 -+M.size {one = 1, two = 2} -- => 2 - ```` - --When given a vararg list of argument, returns the count of these arguments. -+When given a vararg list of arguments, returns the count of these arguments. - - ```lua --_.size(1,2,3) -- => 3 --_.size('a','b',{}, function() end) -- => 4 -+M.size(1,2,3) -- => 3 -+M.size('a','b',{}, function() end) -- => 4 - ```` - - ### containsKeys (t, other) -@@ -624,9 +723,9 @@ _.size('a','b',{}, function() end) -- => 4 - Checks whether a table has all the keys existing in another table. - - ```lua --_.contains({1,2,3,4},{1,2,3}) -- => true --_.contains({1,2,'d','b'},{1,2,3,5}) -- => true --_.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true -+M.contains({1,2,3,4},{1,2,3}) -- => true -+M.contains({1,2,'d','b'},{1,2,3,5}) -- => true -+M.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true - ```` - - ### sameKeys (tA, tB) -@@ -634,22 +733,22 @@ _.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true - Checks whether both tables features the same keys: - - ```lua --_.sameKeys({1,2,3,4},{1,2,3}) -- => false --_.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true --_.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false -+M.sameKeys({1,2,3,4},{1,2,3}) -- => false -+M.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true -+M.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false - ```` - - **[[⬆]](#TOC)** - - ## Array functions - --### sample (array, n, seed) -+### sample (array [, n = 1 [, seed]]) - - Samples `n` values from array. - - ```lua --local array = _.range(1,20) --local sample = _.sample(array, 3) -+local array = M.range(1,20) -+local sample = M.sample(array, 3) - print(table.concat(sample,',')) - - -- => {12,11,15} -@@ -658,8 +757,8 @@ print(table.concat(sample,',')) - `n` defaults to 1. In that case, a single value will be returned. - - ```lua --local array = _.range(1,20) --local sample = _.sample(array) -+local array = M.range(1,20) -+local sample = M.sample(array) - print(sample) - - -- => 12 -@@ -667,45 +766,72 @@ print(sample) - - An optional 3rd argument `seed` can be passed for deterministic random sampling. - --### sampleProb (array, prob, seed) -+### sampleProb (array, prob [, seed]) - - Returns an array of values randomly selected from a given array. - In case `seed` is provided, it is used for deterministic sampling. - - ```lua --local array = _.range(1,20) --local sample = _.sampleProb(array, 0.2) -+local array = M.range(1,20) -+local sample = M.sampleProb(array, 0.2) - print(table.concat(sample,',')) - - -- => 5,11,12,15 - --sample = _.sampleProb(array, 0.2, os.time()) -+sample = M.sampleProb(array, 0.2, os.time()) - print(table.concat(sample,',')) - - -- => 1,6,10,12,15,20 (or similar) - ```` - --### toArray (...) -+### nsorted (array [, n = 1[, comp]]) -+ -+Returns the n-top values satisfying a predicate. It takes a comparison function `comp` used to sort array values, -+and then picks the top n-values. It leaves the original array untouched. -+ -+```lua -+local function comp(a,b) return a > b end -+M.nsorted(array,5, comp) -- => {5,4,3,2,1} -+```` -+ -+`n` defaults to 1 and `comp` defaults to the `<` operator. -+ -+```lua -+local array = M.range(1,20) -+M.nsorted(array) -- => {1} -+```` -+ -+### shuffle (array [, seed]) -+ -+Shuffles a given array. -+ -+```lua -+local list = M.shuffle {1,2,3,4,5,6} -- => "{3,2,6,4,1,5}" -+M.each(list,print) -+```` -+ -+### pack (...) - - Converts a vararg list of arguments to an array. - - ```lua --_.toArray(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}" -+M.pack(1,2,8,'d','a',0) -- => "{1,2,8,'d','a',0}" - ```` - --### find (array, value, from) -+### find (array, value [, from = 1]) - - Looks for a value in a given array and returns the position of the first occurence. - - ```lua --_.find({{4},{3},{2},{1}},{3}) -- => 2 -+local value = {3} -+M.find({{4},{3},{2},{1}},value) -- => 2 - ```` - - It can also start the search at a specific position in the array: - - ```lua - -- search value 4 starting from index 3 --_.find({1,4,2,3,4,5},4,3) -- => 5 -+M.find({1,4,2,3,4,5},4,3) -- => 5 - ```` - - ### reverse (array) -@@ -713,74 +839,98 @@ _.find({1,4,2,3,4,5},4,3) -- => 5 - Reverses an array. - - ```lua --_.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}" -+M.reverse({1,2,3,'d'}) -- => "{'d',3,2,1}" - ```` - --### fill (array, value, i, j) -+### fill (array, value [, i = 1 [, j = #array]]) - - Replaces all elements in a given array with a given value. - - ```lua --local array = _.range(1,5) --_.fill(array, 0) -- => {0,0,0,0,0} -+local array = M.range(1,5) -+M.fill(array, 0) -- => {0,0,0,0,0} - ```` - - It can start replacing value at a specific index. - - ```lua --local array = _.range(1,5) --_.fill(array,0,3) -- => {1,2,0,0,0} -+local array = M.range(1,5) -+M.fill(array,0,3) -- => {1,2,0,0,0} - ```` - - It can replace only values within a specific range. - - ```lua --local array = _.range(1,5) --_.fill(array,0,2,4) -- => {1,0,0,0,5} -+local array = M.range(1,5) -+M.fill(array,0,2,4) -- => {1,0,0,0,5} - ```` - - In case the upper bound index i greather than the array size, it will enlarge the array. - - ```lua --local array = _.range(1,5) --_.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0} -+local array = M.range(1,5) -+M.fill(array,0,5,10) -- => {1,2,3,4,0,0,0,0,0,0} - ```` - --### selectWhile (array, f, ... --*Aliases: `_.takeWhile`*. -+### zeros (n) -+ -+Returns an array of `n` zeros. -+ -+```lua -+M.zeros(4) -- => {0,0,0,0} -+```` -+ -+### ones (n) -+ -+Returns an array of `n` 1's. -+ -+```lua -+M.ones(3) -- => {1,1,1} -+```` -+ -+### vector (value, n) -+ -+Returns an array of `n` times a given value. -+ -+```lua -+M.vector(10, 4) -- => {10,10,10,10} -+```` -+ -+### selectWhile (array, f [, ...]) -+*Aliases: `takeWhile`*. - - Collects values as long as they pass a given test. Stops on the first non-passing test. - - ```lua --_.selectWhile({2,4,5,8}, function(i,v) -+M.selectWhile({2,4,5,8}, function(v) - return v%2==0 - end) -- => "{2,4}" - ```` - --### dropWhile (array, f, ... --*Aliases: `_.rejectWhile`*. -+### dropWhile (array, f [, ...]) -+*Aliases: `rejectWhile`*. - - Removes values as long as they pass a given test. Stops on the first non-passing test. - - ```lua --_.dropWhile({2,4,5,8}, function(i,v) -+M.dropWhile({2,4,5,8}, function(v) - return v%2==0 - end) -- => "{5,8}" - ```` - --### sortedIndex (array, value, comp, sort) -+### sortedIndex (array, value [, comp = math.min [, sort = nil]]) - - Returns the index at which a value should be inserted to preserve order. - - ```lua --_.sortedIndex({1,2,3},4) -- => 4 -+M.sortedIndex({1,2,3},4) -- => 4 - ```` - - Can take a custom comparison functions. - - ```lua - local comp = function(a,b) return a 3 -+M.sortedIndex({-5,0,4,4},3,comp) -- => 3 - ```` - - ### indexOf (array, value) -@@ -788,7 +938,7 @@ _.sortedIndex({-5,0,4,4},3,comp) -- => 3 - Returns the index of a value in an array. - - ```lua --_.indexOf({1,2,3},2) -- => 2 -+M.indexOf({1,2,3},2) -- => 2 - ```` - - ### lastIndexOf (array, value) -@@ -796,27 +946,27 @@ _.indexOf({1,2,3},2) -- => 2 - Returns the index of the last occurence of a given value in an array. - - ```lua --_.lastIndexOf({1,2,2,3},2) -- => 3 -+M.lastIndexOf({1,2,2,3},2) -- => 3 - ```` - --### findIndex (array, predicate, ...) -+### findIndex (array, pred) - - Returns the first index at which a predicate passes a truth test. - - ```lua - local array = {1,2,3,4,5,6} --local function multipleOf3(__,v) return v%3==0 end --_.findIndex(array, multipleOf3) -- => 3 -+local function multipleOf3(v) return v%3==0 end -+M.findIndex(array, multipleOf3) -- => 3 - ```` - --### findLastIndex (array, predicate, ...) -+### findLastIndex (array, pred) - --Returns the last index at which a predicate passes a truth test. -+Returns the last index at which a predicate passes a truthy test. - - ```lua - local array = {1,2,3,4,5,6} --local function multipleOf3(__,v) return v%3==0 end --_.findLastIndex(array, multipleOf3) -- => 6 -+local function multipleOf3(v) return v%3==0 end -+M.findLastIndex(array, multipleOf3) -- => 6 - ```` - - ### addTop (array, ...) -@@ -825,7 +975,16 @@ Adds given values at the top of an array. The latter values bubbles at the top. - - ```lua - local array = {1} --_.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}" -+M.addTop(array,1,2,3,4) -- => "{4,3,2,1,1}" -+```` -+ -+### prepend (array, ...) -+ -+Adds given values at the top of an array, preserving the order at which elements are passed-in. -+ -+```lua -+local array = {'old_val'} -+M.prepend(array,1,2,3,4) -- => "{1,2,3,4,'old_val'}" - ```` - - ### push (array, ...) -@@ -834,104 +993,115 @@ Adds given values at the end of an array. - - ```lua - local array = {1} --_.push(array,1,2,3,4) -- => "{1,1,2,3,4}" -+M.push(array,1,2,3,4) -- => "{1,1,2,3,4}" - ```` - --### pop (array, n) --*Aliases: `_.shift`*. -+### shift (array [, n = 1]) -+*Aliases: `pop`*. - - Removes and returns the first value in an array. - - ```lua - local array = {1,2,3} --local pop = _.pop(array) -- => "pop = 1", "array = {2,3}" -+local shift = M.shift(array) -- => "shift = 1", "array = {2,3}" -+```` -+If `n` is supplied, returns `n` values. -+ -+```lua -+local array = {1,2,3,4,5} -+local a, b = M.shift(array, 2) -- => "a = 1, b = 2", "array = {3,4,5}" - ```` - --### unshift (array, n) -+### unshift (array [, n = 1]) - - Removes and returns the last value in an array. - - ```lua - local array = {1,2,3} --local value = _.unshift(array) -- => "value = 3", "array = {1,2}" -+local value = M.unshift(array) -- => "value = 3", "array = {1,2}" - ```` - - ### pull (array, ...) --*Aliases: `_.remove`*. -+*Aliases: `remove`*. - - Removes all provided values from a given array. - - ```lua --_.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}" -+M.pull({1,2,1,2,3,4,3},1,2,3) -- => "{4}" - ```` - --### removeRange (array, start, finish) --*Aliases: `_.rmRange`, `_.chop`*. -+### removeRange (array [, start = 1 [, finish = #array]]) -+*Aliases: `rmRange`, `M.chop`*. - - Trims out all values index within a range. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.removeRange(array, 3,8) -- => "{1,2,9}" -+M.removeRange(array, 3,8) -- => "{1,2,9}" - ```` - --### chunk (array, f, ...) -+### chunk (array [, f]) - --Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return --value of `f(key,value,...)`. Consecutive elements which return the same value are aggregated together. -+Iterates over an array aggregating consecutive values in subsets tables, on the basis of the return value of `f(v, k, ...)`. Consecutive elements which return the same value are chunked together. - - ```lua --local t = {1,1,2,3,3,4} --_.chunk(t, function(k,v) return v%2==0 end) -- => "{{1,1},{2},{3,3},{4}}" -+local t = {1,5,2,4,3,3,4} -+M.chunk(t, function(v) return v%2==0 end) -- => "{{1,5},{2,4},{3,3},{4}}" - ```` - --### slice (array, start, finish) --*Aliases: `_.sub`*. -+If not given, `f` defaults to `identity`. -+ -+```lua -+local t = {1,5,2,4,3,3,4} -+M.chunk(t) -- => "{{1},{5},{2},{4},{3,3},{4}}" -+```` -+ -+### slice (array [, start = 1 [, finish = #array]]) -+*Aliases: `sub`*. - - Slices and returns a part of an array. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.slice(array, 3,6) -- => "{3,4,5,6}" -+M.slice(array, 3,6) -- => "{3,4,5,6}" - ```` - --### first (array, n) --*Aliases: `_.head`, `_.take`*. -+### first (array [, n = 1]) -+*Aliases: `head`, `M.take`*. - - Returns the first N elements in an array. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.first(array,3) -- => "{1,2,3}" -+M.first(array,3) -- => "{1,2,3}" - ```` - --### initial (array, n) -+### initial (array [, n = #array]) - - Excludes the last N elements in an array. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.initial(array,5) -- => "{1,2,3,4}" -+M.initial(array,5) -- => "{1,2,3,4}" - ```` - --### last (array, n) --*Aliases: `_.skip`*. -+### last (array [, n = #array]) - - Returns the last N elements in an array. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.last(array,3) -- => "{7,8,9}" -+M.last(array,3) -- => "{7,8,9}" - ```` - --### rest (array, index) --*Aliases: `_.tail`*. -+### rest (array [, index = 1]) -+*Aliases: `tail`*. - --Trims out all values indexed before *index*. -+Returns all values after *index*, including the given *index* itself. - - ```lua - local array = {1,2,3,4,5,6,7,8,9} --_.rest(array,6) -- => "{6,7,8,9}" -+M.rest(array,6) -- => "{6,7,8,9}" - ```` - - ### nth (array, index) -@@ -940,7 +1110,7 @@ Returns the value at *index*. - - ```lua - local array = {1,2,3,4,5,6} --_.nth(array,3) -- => "3" -+M.nth(array,3) -- => "3" - ```` - - ### compact (array) -@@ -948,31 +1118,31 @@ _.nth(array,3) -- => "3" - Trims out all falsy values. - - ```lua --_.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}" -+M.compact {a,'aa',false,'bb',true} -- => "{'aa','bb',true}" - ```` - --### flatten (array, shallow) -+### flatten (array [, shallow = false]) - - Flattens a nested array. - - ```lua --_.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}" -+M.flatten({1,{2,3},{4,5,{6,7}}}) -- => "{1,2,3,4,5,6,7}" - ```` - --When given arg "shallow", flatten only at the first level. -+When given arg `shallow`, flatten only at the first level. - - ```lua --_.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}" -+M.flatten({1,{2},{{3}}},true) -- => "{1,{2},{{3}}}" - ```` - - ### difference (array, array2) --*Aliases: `_.without`, `_.diff`*. -+*Aliases: `without`, `diff`*. - - Returns values in the given array not present in a second array. - - ```lua - local array = {1,2,'a',4,5} --_.difference(array,{1,'a'}) -- => "{2,4,5}" -+M.difference(array,{1,'a'}) -- => "{2,4,5}" - ```` - - ### union (...) -@@ -983,10 +1153,10 @@ Produces a duplicate-free union of all passed-in arrays. - local A = {'a'} - local B = {'a',1,2,3} - local C = {2,10} --_.union(A,B,C) -- => "{'a',1,2,3,10}" -+M.union(A,B,C) -- => "{'a',1,2,3,10}" - ```` - --### intersection (array, ...) -+### intersection (...) - - Returns the intersection (common-part) of all passed-in arrays: - -@@ -994,48 +1164,87 @@ Returns the intersection (common-part) of all passed-in arrays: - local A = {'a'} - local B = {'a',1,2,3} - local C = {2,10,1,'a'} --_.intersection(A,B,C) -- => "{'a',2,1}" -+M.intersection(A,B,C) -- => "{'a'}" -+```` -+ -+### disjoint (...) -+ -+Checks if all passed in arrays are disjoint. -+ -+```lua -+local A = {'a'} -+local B = {'a',1,3} -+local C = {3,10,2} -+ -+M.disjoint(A,B) -- => false -+M.disjoint(A,C) -- => true -+M.disjoint(B,C) -- => false - ```` - - ### symmetricDifference (array, array2) --*Aliases: `_.symdiff`,`_.xor`*. -+*Aliases: `symdiff`,`xor`*. - - Returns values in the first array not present in the second and also values in the second array not present in the first one. - - ```lua - local array = {1,2,3} - local array2 = {1,4,5} --_.symmetricDifference(array, array2) -- => "{2,3,4,5}" -+M.symmetricDifference(array, array2) -- => "{2,3,4,5}" - ```` - - ### unique (array) --*Aliases: `_.uniq`*. -+*Aliases: `uniq`*. - - Makes an array duplicate-free. - - ```lua --_.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}" -+M.unique {1,1,2,2,3,3,4,4,4,5} -- => "{1,2,3,4,5}" - ```` - - ### isunique (array) --*Aliases: `_.isuniq`*. -+*Aliases: `isuniq`*. - - Checks if a given array contains no duplicate value. - - ```lua --_.isunique({1,2,3,4,5}) -- => true --_.isunique({1,2,3,4,4}) -- => false -+M.isunique({1,2,3,4,5}) -- => true -+M.isunique({1,2,3,4,4}) -- => false -+```` -+ -+### duplicates (array) -+ -+Returns an array list of all duplicates in array. -+ -+```lua -+M.duplicates({1,2,3,3,8,8,3,2,4}) -- => {2,3,8} - ```` - - ### zip (...) --*Aliases: `_.transpose`*. -+*Aliases: `transpose`*. - - Zips values from different arrays, on the basis on their common keys. - - ```lua - local names = {'Bob','Alice','James'} - local ages = {22, 23} --_.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" -+M.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" -+```` -+ -+### zipWith (f, ...) -+*Aliases: `transposeWith`*. -+ -+Merges values using a given function. Only values indexed with the same key in the given arrays are merged in the same subset. -+Function `f` is used to combine values. -+ -+```lua -+local names = {'Bob','Alice','James'}; local ages = {22, 23, 25} -+local function introduce(name, age) return 'I am '..name..' and I am '..age..' years old.' end -+local t = M.zipWith(introduce,names,ages) -+-- => { -+-- => 'I am Bob and I am 22 years old.' -+-- => 'I am Alice and I am 23 years old.' -+-- => 'I am James and I am 25 years old.' -+-- => } - ```` - - ### append (array, other) -@@ -1043,7 +1252,7 @@ _.zip(names,ages) -- => "{{'Bob',22},{'Alice',23},{'James'}}" - Appends two arrays. - - ```lua --_.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}" -+M.append({1,2,3},{'a','b'}) -- => "{1,2,3,'a','b'}" - ```` - - ### interleave (...) -@@ -1053,35 +1262,43 @@ Interleaves values from passed-in arrays. - ```lua - t1 = {1, 2, 3} - t2 = {'a', 'b', 'c'} --_.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}" -+M.interleave(t1, t2) -- => "{1,'a',2,'b',3,'c'}" - ```` - --### interpose (value, array) -+### interpose (array, value) -+*Aliases: `intersperce`*. - - Interposes a value between consecutive values in an arrays. - - ```lua --_.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}" -+M.interleave('a', {1,2,3}) -- => "{1,'a',2,'a',3}" - ```` - --### range (...) -+### range ([from [, to [, step]]]) - - Generates an arithmetic sequence. - - ```lua --_.range(1,4) -- => "{1,2,3,4}" -+M.range(1,4) -- => "{1,2,3,4}" - ```` - --In case a single value is provided, it generates a sequence from 0 to that value. -+In case a single value is provided, it generates a sequence from 1 to that value. - - ```` --_.range(3) -- => "{0,1,2,3}" -+M.range(3) -- => "{1,2,3}" - ```` - - The incremental step can also be provided as third argument. - - ```lua --_.range(0,2,0.7) -- => "{0,0.7,1.4}" -+M.range(0,2,0.7) -- => "{0,0.7,1.4}" -+```` -+ -+It also handles negative progressions. -+ -+```lua -+M.range(-5) -- => "{-1,-2,-3,-4,-5}" -+M.range(5,1) -- => "{5,4,3,2,1}" - ```` - - ### rep (value, n) -@@ -1089,17 +1306,25 @@ _.range(0,2,0.7) -- => "{0,0.7,1.4}" - Generates a list of n repetitions of a value. - - ```lua --_.rep(4,3) -- => "{4,4,4}" -+M.rep(4,3) -- => "{4,4,4}" -+```` -+ -+### powerset (array) -+ -+Returns the powerset of an array. -+ -+```lua -+M.powerset {1,2,3} -- => "{{1},{2},{3},{1,2},{2,3},{1,2,3}}" - ```` - --### partition (array, n, pad) --*Aliases: `_.part`*. -+### partition (array [, n = 1 [, pad]]) -+*Aliases: `part`*. - - Returns an iterator function for partitions of a given array. - - ```lua - local t = {1,2,3,4,5,6} --for p in _.partition(t,2) do -+for p in M.partition(t,2) do - print(table.concat(p, ',')) - end - -@@ -1108,7 +1333,7 @@ end - -- => 5,6 - - local t = {1,2,3,4,5,6} --for p in _.partition(t,4) do -+for p in M.partition(t,4) do - print(table.concat(p, ',')) - end - -@@ -1120,7 +1345,7 @@ In case the last partition has less elements than desired, a 3rd argument can be - - ```lua - local t = {1,2,3,4,5,6} --for p in _.partition(t,4,0) do -+for p in M.partition(t,4,0) do - print(table.concat(p, ',')) - end - -@@ -1128,13 +1353,13 @@ end - -- => 5,6,0,0 - ```` - --### sliding (array, n, pad) -+### overlapping (array [, n = 2 [, pad]]) - - Returns an iterator function which provides overlapping subsequences of a given array. - - ```lua - local t = {1,2,3,4,5,6,7} --for p in _.sliding(t,3) do -+for p in M.overlapping(t,3) do - print(table.concat(p,',')) - end - -@@ -1142,14 +1367,14 @@ end - -- => 3,4,5 - -- => 5,6,7 - --for p in _.sliding(t,4) do -+for p in M.overlapping(t,4) do - print(table.concat(p,',')) - end - - -- => 1,2,3,4 - -- => 4,5,6,7 - --for p in _.sliding(t,5) do -+for p in M.overlapping(t,5) do - print(table.concat(p,',')) - end - -@@ -1161,7 +1386,7 @@ In case the last subsequence wil not match the exact desired length, it can be a - - ```lua - local t = {1,2,3,4,5,6,7} --for p in _.sliding(t,5,0) do -+for p in M.overlapping(t,5,0) do - print(table.concat(p,',')) - end - -@@ -1169,14 +1394,53 @@ end - -- => 5,6,7,0,0 - ```` - -+### aperture (array [, n = 2]) -+*Aliases: `sliding`*. -+ -+Returns an iterator function which provides sliding partitions of a given array. -+ -+```lua -+local t = {1,2,3,4,5} -+for p in M.aperture(t,4) do -+ print(table.concat(p,',')) -+end -+ -+-- => 1,2,3,4 -+-- => 2,3,4,5 -+ -+for p in M.aperture(t,3) do -+ print(table.concat(p,',')) -+end -+ -+-- => 1,2,3 -+-- => 2,3,4 -+-- => 3,4,5 -+```` -+ -+### pairwise (array) -+ -+Iterator returning sliding pairs of an array. -+ -+```lua -+local t = M.range(5) -+for p in pairwise(t) do -+ print(table.concat(p,',')) -+end -+ -+-- => 1,2 -+-- => 2,3 -+-- => 3,4 -+-- => 4,5 -+```` -+ - ### permutation (array) --*Aliases: `_.perm`*. -+*Aliases: `perm`*. - - Returns an iterator function for permutations of a given array. - - ```lua - t = {'a','b','c'} --for p in _.permutation(t) do -+for p in M.permutation(t) do - print(table.concat(p)) - end - -@@ -1188,22 +1452,73 @@ end - -- => 'abc' - ```` - --### invert (array) --*Aliases: `_.mirror`*. -+### concat (array [, sep = '' [, i = 1 [, j = #array]]]) -+*Aliases: `join`*. - --Switches key-value pairs: -+Concatenates a given array values: - - ```lua --_.invert {'a','b','c'} -- => "{a=1, b=2, c=3}" -+M.concat({'a',1,0,1,'b'}) -- => 'a101b' - ```` - --### concat (array, sep, i, j) --*Aliases: `_.join`*. -+### xprod (array, array2) - --Concatenates a given array values: -+Returns all possible pairs built from given arrays. -+ -+```lua -+local t = M.xprod({1,2},{'a','b'}) -+-- => {{1,'a'},{1,'b'},{2,'a'},{2,'b'}} -+```` -+ -+### xpairs (value, array) -+ -+Creates pairs from value and array. Value is always prepended to the pair. -+ -+```lua -+local t = M.xpairs(1, {1, 2, 3}) -+-- => {{1,1},{1,2},{1,3}} -+```` -+ -+### xpairsRight (value, array) -+ -+Creates pairs from value and array. Value is always appended as the last item to the pair. - - ```lua --_.concat({'a',1,0,1,'b'}) -- => 'a101b' -+local t = M.xpairsRight(1, {1, 2, 3}) -+-- => {{1,1},{2,1},{3,1}} -+```` -+ -+### sum (array) -+ -+Returns the sum of array values. -+ -+```lua -+M.sum({1,2,3,4,5}) -- => 15 -+```` -+ -+### product (array) -+ -+Returns the product of array values. -+ -+```lua -+M.product({1,2,3,4,5}) -- => 120 -+```` -+ -+### mean (array) -+ -+Returns the mean of array values. -+ -+```lua -+M.mean({1,2,3,4,5}) -- => 3 -+```` -+ -+### median (array) -+ -+Returns the median of array values. -+ -+```lua -+M.median({1,2,3,4,5}) -- => 3 -+M.median({1,2,3,4}) -- => 2.5 - ```` - - **[[⬆]](#TOC)** -@@ -1215,7 +1530,7 @@ _.concat({'a',1,0,1,'b'}) -- => 'a101b' - The no-operation function. Takes nothing, returns nothing. It is being used internally. - - ```lua --_.noop() -- => nil -+M.noop() -- => nil - ```` - - ### identity (value) -@@ -1224,9 +1539,19 @@ Returns the passed-in value.
    - This function is internally used as a default transformation function. - - ```lua --_.identity(1)-- => 1 --_.identity(false) -- => false --_.identity('hello!') -- => 'hello!' -+M.identity(1)-- => 1 -+M.identity(false) -- => false -+M.identity('hello!') -- => 'hello!' -+```` -+ -+### call (f [, ...]) -+ -+Calls `f` with the supplied arguments. Returns the results of `f(...)`. -+ -+```lua -+M.call(math.pow, 2, 3) -- => 8 -+M.call(string.len, 'hello' ) -- => 5 -+M.call(table.concat, {1,2,3,4,5}, ',', 2, 4) -- => {2,3,4} - ```` - - ### constant (value) -@@ -1234,14 +1559,82 @@ _.identity('hello!') -- => 'hello!' - Creates a constant function. This function will continuously yield the same output. - - ```lua --local pi = _.constant(math.pi) -+local pi = M.constant(math.pi) - pi(1) -- => 3.1415926535898 - pi(2) -- => 3.1415926535898 - pi(math.pi) -- => 3.1415926535898 - ```` - --### memoize (f, hash) --*Aliases: `_.cache`*. -+### applySpec (specs) -+ -+Returns a function which applies `specs` on args. This function will produce an object having the same structure than `specs` -+by mapping each property to the result of calling its associated function with the supplied arguments. -+ -+```lua -+local stats = M.applySpec({ -+ min = function(...) return math.min(...) end, -+ max = function(...) return math.max(...) end, -+}) -+ -+stats(5,4,10,1,8) -- => {min = 1, max = 10} -+```` -+ -+### thread (value [, ...]) -+ -+Threads `value` through a series of functions. -+ -+```lua -+local function inc(x) return x + 1 end -+local function double(x) return 2 * x end -+local function square(x) return x * x end -+M.thread(2, inc, double, square) -- => 36 -+M.thread(3, double, inc, square) -- => 49 -+M.thread(4, square, double, inc) -- => 33 -+M.thread(5, square, inc, double) -- => 52 -+```` -+ -+If a function expects more than one args, it can be specified using an array list, -+where the first item is the function and the following are the remaining args neeeded. -+ -+```lua -+local function inc(x) return x + 1 end -+local function add(x, y) return x * y end -+local function pow(x, y) return x ^ y end -+M.thread(2, inc, {add, 3}, {pow, 2}) -- => 36 -+M.thread(2, {add, 4}, inc, {pow, 2}) -- => 49 -+```` -+ -+### threadRight (value [, ...]) -+ -+Threads `value` through a series of functions. If a function expects more than one args, -+it can be specified using an array list, where the first item is the function and the following are -+the remaining args neeeded. The value is used as the last input. -+ -+```lua -+local function inc(x) return x + 1 end -+local function add(x, y) return x * y end -+local function pow(x, y) return x ^ y end -+M.threadRight(2, inc, {add, 3}, {pow, 2}) -- => 64 -+M.threadRight(2, {add, 4}, inc, {pow, 2}) -- => 128 -+```` -+ -+### dispatch (...) -+ -+Returns a dispatching function. When called with arguments, this function invokes each of its functions -+in the passed-in order and returns the results of the first non-nil evaluation. -+ -+```lua -+local f = M.dispatch( -+ function() return nil end, -+ function (v) return v+1 end, -+ function (v) return 2*v end -+) -+f(5) -- => 6 -+f(7) -- => 8 -+```` -+ -+### memoize (f) -+*Aliases: `cache`*. - - Memoizes a slow-running function. It caches the result for a specific input, so that the next time the function is called with the same input, it will lookup the result in its cache, instead of running again the function body. - -@@ -1249,17 +1642,28 @@ Memoizes a slow-running function. It caches the result for a specific input, so - local function fibonacci(n) - return n < 2 and n or fibonacci(n-1)+fibonacci(n-2) - end --local mem_fibonacci = _.memoize(fibonacci) -+local mem_fibonacci = M.memoize(fibonacci) - fibonacci(20) -- => 6765 (but takes some time) - mem_fibonacci(20) -- => 6765 (takes less time) - ```` - -+### unfold (f, seed) -+ -+Builds a list from a seed value. Accepts an iterator function, which returns either nil to stop iteration or two values : the value to add to the list of results and the seed to be used in the next call to the iterator function. -+ -+```lua -+local function f(v) -+ if v < 100 then return v, v * 2 end -+end -+local t = M.unfold(f, 10) -- => {10,20,40,80} -+```` -+ - ### once (f) - - Produces a function that runs only once. Successive calls to this function will still yield the same input. - - ```lua --local sq = _.once(function(a) return a*a end) -+local sq = M.once(function(a) return a*a end) - sq(1) -- => 1 - sq(2) -- => 1 - sq(3) -- => 1 -@@ -1273,7 +1677,7 @@ Returns a version of `f` that will run no more than `count` times. Next calls wi - - ```lua - local function greet(someone) return 'hello '..someone end --local greetOnly3people = _.before(greet, 3) -+local greetOnly3people = M.before(greet, 3) - greetOnly3people('John') -- => 'hello John' - greetOnly3people('Moe') -- => 'hello Moe' - greetOnly3people('James') -- => 'hello James' -@@ -1286,7 +1690,7 @@ greetOnly3people('Allan') -- => 'hello James' - Produces a function that will respond only after a given number of calls. - - ```lua --local f = _.after(_.identity,3) -+local f = M.after(M.identity,3) - f(1) -- => nil - f(2) -- => nil - f(3) -- => 3 -@@ -1301,7 +1705,7 @@ Composes functions. Each function consumes the return value of the one that foll - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end --local compositae = _.compose(f,g,h) -+local compositae = M.compose(f,g,h) - compositae(10) -- => 36 - compositae(20) -- => 121 - ```` -@@ -1314,8 +1718,8 @@ Pipes a value through a series of functions. - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end --_.pipe(10,f,g,h) -- => 36 --_.pipe(20,f,g,h) -- => 121 -+M.pipe(10,f,g,h) -- => 36 -+M.pipe(20,f,g,h) -- => 121 - ```` - - ### complement (f) -@@ -1323,11 +1727,11 @@ _.pipe(20,f,g,h) -- => 121 - Returns a function which returns the logical complement of a given function. - - ```lua --_.complement(function() return true end)() -- => false -+M.complement(function() return true end)() -- => false - ```` - - ### juxtapose (value, ...) --*Aliases: `_.juxt`*. -+*Aliases: `juxt`*. - - Calls a sequence of functions with the same input. - -@@ -1335,7 +1739,7 @@ Calls a sequence of functions with the same input. - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end --_.juxtapose(10, f, g, h) -- => 100, 11, 5 -+M.juxtapose(10, f, g, h) -- => 100, 11, 5 - ```` - - ### wrap (f, wrapper) -@@ -1344,7 +1748,7 @@ Wraps a function inside a wrapper. Allows the wrapper to execute code before and - - ```lua - local greet = function(name) return "hi: " .. name end --local greet_backwards = _.wrap(greet, function(f,arg) -+local greet_backwards = M.wrap(greet, function(f,arg) - return f(arg) ..'\nhi: ' .. arg:reverse() - end) - greet_backwards('John') -@@ -1353,13 +1757,13 @@ greet_backwards('John') - -- => hi: nhoJ - ```` - --### times (n, iter, ...) -+### times (iter [, n]) - - Calls a given function `n` times. - - ```lua - local f = ('Lua programming'):gmatch('.') --_.times(3,f) -- => {'L','u','a'} -+M.times(f, 3) -- => {'L','u','a'} - ```` - - ### bind (f, v) -@@ -1367,7 +1771,7 @@ _.times(3,f) -- => {'L','u','a'} - Binds a value to be the first argument to a function. - - ```lua --local sqrt2 = _.bind(math.sqrt,2) -+local sqrt2 = M.bind(math.sqrt,2) - sqrt2() -- => 1.4142135623731 - ```` - -@@ -1376,7 +1780,7 @@ sqrt2() -- => 1.4142135623731 - Binds a value to be the second argument to a function. - - ```lua --local last2 = _.bind(_.last,2) -+local last2 = M.bind(M.last,2) - last2({1,2,3,4,5,6}) -- => {5,6} - ```` - -@@ -1386,12 +1790,12 @@ Binds a variable number of values to be the first arguments to a function. - - ```lua - local function out(...) return table.concat {...} end --local out = _.bindn(out,'OutPut',':',' ') -+local out = M.bindn(out,'OutPut',':',' ') - out(1,2,3) -- => OutPut: 123 - out('a','b','c','d') -- => OutPut: abcd - ```` - --### bindAll (obj, ...) -+### bindall (obj, ...) - - Binds methods to object. As such, when calling any of these methods, they will receive object as a first argument. - -@@ -1401,7 +1805,7 @@ local window = { - setName = function(w,name) w.name = name end, - getName = function(w) return w.name end, - } --window = _.bindAll(window, 'setPos', 'setName', 'getName') -+window = M.bindall(window, 'setPos', 'setName', 'getName') - window.setPos(10,15) - print(window.x, window.y) -- => 10,15 - -@@ -1411,50 +1815,184 @@ print(window.name) -- => 'fooApp' - print(window.getName()) -- => 'fooApp' - ```` - --### uniqueId (template, ...) --*Aliases: `_.uid`*. -+### cond (conds) -+ -+Returns a function which iterate over an array list of conditions. It invokes each predicate, passing it given values. It returns the value of the corresponding function of the first predicate to return a non-nil value -+ -+```lua -+local multipleOf = M.cond({ -+ {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end}, -+ {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end}, -+ {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end}, -+ {function() return true end, function(v) return 'could not find an answer for '..v end} -+}) -+for i = 15, 20 do -+ print(multipleOf(i)) -+end -+ -+-- => 15 is multiple of 3 -+-- => 16 is multiple of 2 -+-- => could not find an answer for 17 -+-- => 18 is multiple of 2 -+-- => could not find an answer for 19 -+-- => 20 is multiple of 2 -+```` -+ -+### both (...) -+ -+Returns a validation function. Given a set of functions, the validation function -+evaluates to `true` only when all its funcs returns `true`. -+ -+```lua -+local f = M.both( -+ function(x) return x > 0 end, -+ function(x) return x < 10 end, -+ function(x) return x % 2 == 0 end -+) -+f(2) -- => true -+f(8) -- => true -+f(9) -- => false -+```` -+ -+### either (...) -+ -+Returns a validation function. Given a set of functions, the validation function -+evaluates to `true` when one of its funcs returns `true`. -+ -+```lua -+local f = M.either( -+ function(x) return x > 0 end, -+ function(x) return x % 2 == 0 end -+) -+f(0) -- => true -+f(-3) -- => false -+```` -+ -+### neither (...) -+ -+Returns a validation function. Given a set of functions, the validation function -+evaluates to `true` when neither of its funcs returns `true`. -+ -+```lua -+local f = M.neither( -+ function(x) return x > 10 end, -+ function(x) return x % 2 == 0 end -+) -+f(12) -- => false -+f(8) -- => false -+f(7) -- => true -+```` -+ -+### uniqueId ([template]) -+*Aliases: `uid`*. - - Returns an unique integer ID. - - ```lua --_.uniqueId() -- => 1 -+M.uniqueId() -- => 1 - ```` - - Can handle string templates for formatted output. - - ```lua --_.uniqueId('ID%s') -- => 'ID2' -+M.uniqueId('ID%s') -- => 'ID2' - ```` - - Or a function, for the same purpose. - - ```lua - local formatter = function(ID) return '$'..ID..'$' end --_.uniqueId(formatter) -- => '$ID1$' -+M.uniqueId(formatter) -- => '$ID1$' - ```` - --### iterator(f, x) --*Aliases: `_.iter`*. -+### iterator (f, value [, n]) -+*Aliases: `iter`*. - --Returns an iterator function which constinuously applies a function `f` onto an input `x`. --For example, let us go through the powers of two. -+Returns an iterator function which constinuously applies a function `f` onto an input `value`. -+For example, let us go through the powers of two using `iterator`. - - ```lua - local function po2(x) return x*2 end --local function iter_po2 = _.iterator(po2, 1) -+local function iter_po2 = M.iterator(po2, 1) - iter_po2() -- => 2 - iter_po2() -- => 4 - iter_po2() -- => 8 - ```` - --### array (...) -+if `n` is supplied, it will run at maximum `n` times. -+ -+```lua -+local function po2(x) return x*2 end -+local function iter_po2 = M.iterator(po2, 1, 3) -+iter_po2() -- => 2 -+iter_po2() -- => 4 -+iter_po2() -- => 8 -+iter_po2() -- => nil -+```` -+ -+### skip (iter [, n = 1]) -+ -+Consumes the first `n` values of a iterator then returns it. -+ -+```lua -+local w = "hello" -+local char = string.gmatch(w,'.') -+local iter = M.skip(char, 3) -+for w in iter do print(w) end -- => 'l', 'o' -+```` -+ -+`n` defaults to 1 when not given. -+ -+```lua -+local w = "hello" -+local char = string.gmatch(w,'.') -+local iter = M.skip(char) -+for w in iter do print(w) end -- => 'e', 'l', 'l', 'o' -+```` -+ -+### tabulate (...) - - Iterates a given iterator function and returns its values packed in an array. - - ```lua - local text = 'letters' - local chars = string.gmatch(text, '.') --local letters = _.array(chars) -- => {'l','e','t','t','e','r','s'} -+M.tabulate(chars) -- => {'l','e','t','t','e','r','s'} -+```` -+ -+### iterlen (...) -+ -+Returns the length of an iterator. -+ -+```lua -+local text = 'letters' -+local chars = string.gmatch(text, '.') -+M.iterlen(chars) -- => 7 -+```` -+ -+It consumes the iterator itself. -+ -+```lua -+local text = 'lua' -+local chars = string.gmatch(text, '.') -+M.iterlen(chars) -- => 3 -+chars() -- => nil -+```` -+ -+### castArray (value) -+ -+Casts the passed-in value to an array containing the value itself. -+ -+```lua -+M.castArray(true) -- => {true} -+M.castArray(2) -- => {2} -+```` -+ -+It leaves the given value untouched in case it is already a table. -+ -+```lua -+local t = {1} -+print(M.castArray(t) == t) -- => true - ```` - - ### flip (f) -@@ -1463,17 +2001,80 @@ Creates a function of `f` with arguments flipped in reverse order. - - ```lua - local function f(...) return table.concat({...}) end --local flipped = _.flip(f) -+local flipped = M.flip(f) - flipped('a','b','c') -- => 'cba' - ```` - -+### nthArg (n) -+ -+Returns a function that gets the nth argument. -+ -+```lua -+local f = M.nthArg(3) -+f('a','b','c') -- => 'c' -+```` -+ -+If n is negative, the nth argument from the end is returned. -+ -+```lua -+local f = M.nthArg(-2) -+f('a','b','c') -- => 'b' -+```` -+ -+### unary (f) -+ -+Returns a function which accepts up to one argument. It ignores any additional arguments. -+ -+```lua -+local f = M.unary(function (...) return ... end) -+f('a') - ==> 'a' -+f('a','b','c') -- => 'a' -+```` -+ -+### ary (f [, n = 1]) -+*Aliases: `nAry`*. -+ -+Returns a function which accepts up to `n` args. It ignores any additional arguments. -+ -+```lua -+local f = M.ary(function (...) return ... end, 2) -+f(1,2) - ==> 1,2 -+f(1,2,3,4) -- => 1,2 -+```` -+ -+If `n` is not given, it defaults to `1`. -+ -+```lua -+local f = M.unary(function (...) return ... end) -+f('a','b','c') -- => 'a' -+```` -+ -+### noarg (f) -+ -+Returns a function with an arity of 0. The new function ignores any arguments passed to it. -+ -+```lua -+local f = M.noarg(function (x) return x or 'default' end) -+f(1) -- => 'default' -+f(function() end, 3) -- => 'default' -+```` -+ -+### rearg (f, indexes) -+ -+Returns a function which runs with arguments arranged according to given `indexes`. -+ -+```lua -+local f = M.rearg(function (...) return ... end, {5,4,3,2,1}) -+f('a','b','c','d','e') -- => 'e','d','c','b','a' -+```` -+ - ### over (...) - - Creates a function that invokes a set of transforms with the arguments it receives.
    - One can use use for example to get the tuple of min and max values from a set of values - - ```lua --local minmax = _.over(math.min, math.max) -+local minmax = M.over(math.min, math.max) - minmax(5,10,12,4,3) -- => {3,12} - ```` - -@@ -1496,7 +2097,7 @@ local function allpositive(...) - return true - end - --local allok = _.overEvery(alleven, allpositive) -+local allok = M.overEvery(alleven, allpositive) - - allok(2,4,-1,8) -- => false - allok(10,3,2,6) -- => false -@@ -1522,7 +2123,7 @@ local function allpositive(...) - return true - end - --local anyok = _.overSome(alleven,allpositive) -+local anyok = M.overSome(alleven,allpositive) - - anyok(2,4,-1,8) -- => false - anyok(10,3,2,6) -- => true -@@ -1537,7 +2138,7 @@ Creates a function that invokes `f` with its arguments transformed - local function f(x, y) return x, y end - local function triple(x) retun x*3 end - local function square(x) retun x^2 end --local new_f = _.overArgs(f, triple, square) -+local new_f = M.overArgs(f, triple, square) - - new_f(1,2) -- => 3, 4 - new_f(10,10) -- => 30, 100 -@@ -1549,19 +2150,31 @@ In case the number of arguments is greater than the number of transforms, the re - local function f(x, y, z) return x, y, z end - local function triple(x) retun x*3 end - local function square(x) retun x^2 end --local new_f = _.overArgs(f, triple, square) -+local new_f = M.overArgs(f, triple, square) - - new_f(1,2,3) -- => 3, 4, 3 - new_f(10,10,10) -- => 30, 100, 10 - ```` - -+### converge (f, g, h) -+ -+Converges two functions into one. -+ -+```lua -+local function pow2(x) return x*x end -+local function pow3(x) return x*x*x end -+local function sum(a,b) return a+b end -+local poly = M.converge(sum, pow2, pow3) -+poly(5) -- => 150 (ie. 5*5 + 5*5*5) -+```` -+ - ### partial (f, ...) - - Partially apply a function by filling in any number of its arguments. - - ```lua - local function diff(a, b) return a - b end --local diffFrom20 = _.partial(diff, 20) -- arg 'a' will be 20 by default -+local diffFrom20 = M.partial(diff, 20) -- arg 'a' will be 20 by default - diffFrom20(5) -- => 15 - ```` - -@@ -1569,23 +2182,23 @@ The string `'_'` can be used as a placeholder in the list of arguments to specif - - ```lua - local function diff(a, b) return a - b end --local remove5 = _.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5 -+local remove5 = M.partial(diff, '_', 5) -- arg 'a' will be given at call-time, but 'b' is set to 5 - remove5(20) -- => 15 - ```` - - ### partialRight (f, ...) - --Like `_.partial`, it partially applies a function by filling in any number of its arguments, but from the right. -+Like `M.partial`, it partially applies a function by filling in any number of its arguments, but from the right. - - ```lua - local function concat(...) return table.concat({...},',') end --local concat_right = _.partialRight(concat,'a','b','c') -+local concat_right = M.partialRight(concat,'a','b','c') - concat_right('d') -- => d,a,b,c - --concat_right = _.partialRight(concat,'a','b') -+concat_right = M.partialRight(concat,'a','b') - concat_right('c','d') -- => c,d,a,b - --concat_right = _.partialRight(concat,'a') -+concat_right = M.partialRight(concat,'a') - concat_right('b','c','d') -- => b,c,d,a - ``` - -@@ -1594,24 +2207,24 @@ In that case, the first args supplied at runtime will be used to fill the initia - - ```lua - local function concat(...) return table.concat({...},',') end --local concat_right = _.partialRight(concat,'a','_','c') -+local concat_right = M.partialRight(concat,'a','_','c') - concat_right('d','b') -- => b,a,d,c - --concat_right = _.partialRight(concat,'a','b','_') -+concat_right = M.partialRight(concat,'a','b','_') - concat_right('c','d') -- => d,a,b,c - --concat_right = _.partialRight(concat,'_','a') -+concat_right = M.partialRight(concat,'_','a') - concat_right('b','c','d') -- => c,d,b,a - ```` - --### curry (f, n_args) -+### curry (f [, n_args = 2]) - - Curries a function. If the given function `f` takes multiple arguments, it returns another version of `f` that takes a single argument - (the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result. - - ```lua - local function sumOf3args(x,y,z) return x + y + z end --local curried_sumOf3args = _.curry(sumOf3args, 3) -+local curried_sumOf3args = M.curry(sumOf3args, 3) - sumOf3args(1)(2)(3)) -- => 6 - sumOf3args(0)(6)(9)) -- => 15 - ```` -@@ -1620,13 +2233,13 @@ sumOf3args(0)(6)(9)) -- => 15 - - ```lua - local function product(x,y) return x * y end --local curried_product = _.curry(product) -+local curried_product = M.curry(product) - curried_product(5)(4) -- => 20 - curried_product(3)(-5) -- => -15 - curried_product(0)(1) -- => 0 - ```` - --### time (f, ...) -+### time (f [, ...]) - - Returns the execution time of `f (...)` in seconds and its results. - -@@ -1637,8 +2250,8 @@ local function wait_count(n) - return i - end - --local time, i = _.time(wait_count, 1e6) -- => 0.002 1000000 --local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000 -+local time, i = M.time(wait_count, 1e6) -- => 0.002 1000000 -+local time, i = M.time(wait_count, 1e7) -- => 0.018 10000000 - ```` - - **[[⬆]](#TOC)** -@@ -1650,8 +2263,8 @@ local time, i = _.time(wait_count, 1e7) -- => 0.018 10000000 - Collects the names of an object attributes. - - ```lua --_.keys({1,2,3}) -- => "{1,2,3}" --_.keys({x = 0, y = 1}) -- => "{'y','x'}" -+M.keys({1,2,3}) -- => "{1,2,3}" -+M.keys({x = 0, y = 1}) -- => "{'y','x'}" - ```` - - ### values (obj) -@@ -1659,8 +2272,49 @@ _.keys({x = 0, y = 1}) -- => "{'y','x'}" - Collects the values of an object attributes. - - ```lua --_.values({1,2,3}) -- => "{1,2,3}" --_.values({x = 0, y = 1}) -- => "{1,0}" -+M.values({1,2,3}) -- => "{1,2,3}" -+M.values({x = 0, y = 1}) -- => "{1,0}" -+```` -+ -+### path (obj, ...) -+ -+Returns the value at a given path in an object. -+ -+```lua -+local entity = { -+ pos = {x = 1, y = 2}, -+ engine = { -+ left = {status = 'active', damage = 5}, -+ right = {status = 'off', damage = 10} -+ }, -+ boost = false -+} -+ -+M.path(entity,'pos','x') -- => 1 -+M.path(entity,'pos','y') -- => 2 -+M.path(entity,'engine','left','status') -- => 'active' -+M.path(entity,'engine','right','damage') -- => 10 -+M.path(entity,'boost') -- => false -+```` -+ -+### spreadPath (obj, ...) -+ -+Spreads object under property path onto provided object. It is similar to `flattenPath`, but removes object under the property path. -+ -+```lua -+local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} -+M.spreadPath(obj, 'c', 'f') -+-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {f = {}}} -+```` -+ -+### flattenPath (obj, ...) -+ -+Flattens object under property path onto provided object. It is similar to `spreadPath`, but preserves object under the property path. -+ -+```lua -+local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} -+M.spreadPath(obj, 'c', 'f') -+-- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}} - ```` - - ### kvpairs (obj) -@@ -1669,7 +2323,7 @@ Converts an object to an array-list of key-value pairs. - - ```lua - local obj = {x = 1, y = 2, z = 3} --_.each(_.kvpairs(obj), function(k,v) -+M.each(M.kvpairs(obj), function(v,k) - print(k, table.concat(v,',')) - end) - -@@ -1678,23 +2332,33 @@ end) - -- => 3 z,3 - ```` - --### toObj -+### toObj (kvpairs) - - Converts an array list of `kvpairs` to an object where keys are taken from the 1rst column in the `kvpairs` sequence, associated with values in the 2nd column. - - ```lua - local list_pairs = {{'x',1},{'y',2},{'z',3}} --obj = _.toObj(list_pairs) -+obj = M.toObj(list_pairs) - - -- => {x = 1, y = 2, z = 3} - ```` - -+### invert (obj) -+*Aliases: `mirror`*. -+ -+Switches key-value pairs: -+ -+```lua -+M.invert {'a','b','c'} -- => "{a=1, b=2, c=3}" -+M.invert {x = 1, y = 2} -- => "{'x','y'}" -+```` -+ - ### property (key) - - Returns a function that will return the key property of any passed-in object. - - ```lua --local who = _.property('name') -+local who = M.property('name') - local people = {name = 'Henry'} - who(people) -- => 'Henry' - ```` -@@ -1705,7 +2369,7 @@ Returns a function that will return the key property of any passed-in object. - - ```lua - local people = {name = 'Henry'} --print(_.propertyOf(people)('name')) -- => 'Henry' -+print(M.propertyOf(people)('name')) -- => 'Henry' - ```` - - ### toBoolean (value) -@@ -1713,11 +2377,11 @@ print(_.propertyOf(people)('name')) -- => 'Henry' - Converts a given value to a boolean. - - ```lua --_.toBoolean(true) -- => true --_.toBoolean(false) -- => false --_.toBoolean(nil) -- => false --_.toBoolean({}) -- => true --_.toBoolean(1) -- => true -+M.toBoolean(true) -- => true -+M.toBoolean(false) -- => false -+M.toBoolean(nil) -- => false -+M.toBoolean({}) -- => true -+M.toBoolean(1) -- => true - ```` - - ### extend (destObj, ...) -@@ -1725,40 +2389,50 @@ _.toBoolean(1) -- => true - Extends a destination object with the properties of some source objects. - - ```lua --_.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}" -+M.extend({},{a = 'b', c = 'd'}) -- => "{a = 'b', c = 'd'}" - ```` - --### functions (obj, recurseMt) --*Aliases: `_.methods`*. -+### functions (obj [, recurseMt]) -+*Aliases: `methods`*. - - Returns all functions names within an object. - - ```lua --_.functions(coroutine) -- => "{'create','resume','running','status','wrap','yield'}" -+M.functions(coroutine) -+-- => "{'yield','wrap','status','resume','running','create'}" - ```` - --### clone (obj, shallow) -+When given `recurseMt`, will also include `obj` metatable's functions. -+ -+````lua -+local mt = {print = print} -+local t = {assert = assert} -+setmetatable(t, {__index = mt}) -+M.functions(t, true) -- => "{'assert','print'}" -+```` -+ -+### clone (obj [, shallow]) - - Clones a given object. - - ```lua - local obj = {1,2,3} --local obj2 = _.clone(obj) -+local obj2 = M.clone(obj) - print(obj2 == obj) -- => false --print(_.isEqual(obj2, obj)) -- => true -+print(M.isEqual(obj2, obj)) -- => true - ```` - --### tap (obj, f, ...) -+### tap (obj, f) - - Invokes a given interceptor function on some object, and then returns the object itself. Useful to tap into method chaining to hook intermediate results. --The pased-interceptor is prototyped as `f(obj,...)`. -+The passed-in interceptor should be prototyped as `f(obj,...)`. - - ```lua --local v = _.chain({1,2,3,4,5,6,7,8,9,10) -- :filter(function(k,v) return v%2~=0 end) -- filters even values -- :tap(function(v) print('Max is', _.max(v) end) -- Tap max values -- :map(function(k,v) return k^2) -- :value() -- => Max is 9 -+local v = M.chain({1,2,3,4,5,6,7,8,9,10}) -+ :filter(function(v) return v%2~=0 end) -- retain odd values -+ :tap(function(v) print('Max is', M.max(v) end) -- Tap max value -+ :map(function(v) return v^2 end) -+ :value() -- => Max is 89 - ```` - - ### has (obj, key) -@@ -1766,60 +2440,60 @@ local v = _.chain({1,2,3,4,5,6,7,8,9,10) - Checks if an object has a given attribute. - - ```lua --_.has(_,'has') -- => true --_.has(coroutine,'resume') -- => true --_.has(math,'random') -- => true -+M.has(_,'has') -- => true -+M.has(coroutine,'resume') -- => true -+M.has(math,'random') -- => true - ```` - - ### pick (obj, ...) --*Aliases: `_.choose`*. -+*Aliases: `choose`*. - - Collects whilelisted properties of a given object. - - ```lua - local object = {a = 1, b = 2, c = 3} --_.pick(object,'a','c') -- => "{a = 1, c = 3}" -+M.pick(object,'a','c') -- => "{a = 1, c = 3}" - ```` - - ### omit (obj, ...) --*Aliases: `_.drop`*. -+*Aliases: `drop`*. - - Omits blacklisted properties of a given object. - - ```lua - local object = {a = 1, b = 2, c = 3} --_.omit(object,'a','c') -- => "{b = 2}" -+M.omit(object,'a','c') -- => "{b = 2}" - ```` - --### template (obj, template) --*Aliases: `_.defaults`*. -+### template (obj [, template]) -+*Aliases: `defaults`*. - - Applies a template on an object, preserving existing properties. - - ```lua - local obj = {a = 0} --_.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}" -+M.template(obj,{a = 1, b = 2, c = 3}) -- => "{a=0, c=3, b=2}" - ```` - --### isEqual (objA, objB, useMt) --*Aliases: `_.compare`*. -+### isEqual (objA, objB [, useMt]) -+*Aliases: `compare`, `M.matches`*. - - Compares objects: - - ```lua --_.isEqual(1,1) -- => true --_.isEqual(true,false) -- => false --_.isEqual(3.14,math.pi) -- => false --_.isEqual({3,4,5},{3,4,{5}}) -- => false -+M.isEqual(1,1) -- => true -+M.isEqual(true,false) -- => false -+M.isEqual(3.14,math.pi) -- => false -+M.isEqual({3,4,5},{3,4,{5}}) -- => false - ```` - --### result (obj, method, ...) -+### result (obj, method) - - Calls an object method, passing it as a first argument the object itself. - - ```lua --_.result('abc','len') -- => 3 --_.result({'a','b','c'},table.concat) -- => 'abc' -+M.result('abc','len') -- => 3 -+M.result({'a','b','c'},table.concat) -- => 'abc' - ```` - - ### isTable (t) -@@ -1827,9 +2501,9 @@ _.result({'a','b','c'},table.concat) -- => 'abc' - Is the given argument an object (i.e a table) ? - - ```lua --_.isTable({}) -- => true --_.isTable(math) -- => true --_.isTable(string) -- => true -+M.isTable({}) -- => true -+M.isTable(math) -- => true -+M.isTable(string) -- => true - ```` - - ### isCallable (obj) -@@ -1837,10 +2511,10 @@ _.isTable(string) -- => true - Is the given argument callable ? - - ```lua --_.isCallable(print) -- => true --_.isCallable(function() end) -- => true --_.isCallable(setmetatable({},{__index = string}).upper) -- => true --_.isCallable(setmetatable({},{__call = function() return end})) -- => true -+M.isCallable(print) -- => true -+M.isCallable(function() end) -- => true -+M.isCallable(setmetatable({},{__index = string}).upper) -- => true -+M.isCallable(setmetatable({},{__call = function() return end})) -- => true - ```` - - ### isArray (obj) -@@ -1848,9 +2522,9 @@ _.isCallable(setmetatable({},{__call = function() return end})) -- => true - Is the given argument an array (i.e. a sequence) ? - - ```lua --_.isArray({}) -- => true --_.isArray({1,2,3}) -- => true --_.isArray({'a','b','c'}) -- => true -+M.isArray({}) -- => true -+M.isArray({1,2,3}) -- => true -+M.isArray({'a','b','c'}) -- => true - ```` - - ### isIterable (obj) -@@ -1858,20 +2532,31 @@ _.isArray({'a','b','c'}) -- => true - Checks if the given object is iterable with `pairs`. - - ```lua --_.isIterable({}) -- => true --_.isIterable(function() end) -- => false --_.isIterable(false) -- => false --_.isIterable(1) -- => false -+M.isIterable({}) -- => true -+M.isIterable(function() end) -- => false -+M.isIterable(false) -- => false -+M.isIterable(1) -- => false - ```` - --### isEmpty (obj) -+### type (obj) -+ -+Extends Lua's `type` function. It returns the type of the given object and also recognises 'file' userdata -+ -+```lua -+M.type('string') -- => 'string' -+M.type(table) -- => 'table' -+M.type(function() end) -- => 'function' -+M.type(io.open('f','w')) -- => 'file' -+```` -+ -+### isEmpty ([obj]) - - Is the given argument empty ? - - ```lua --_.isEmpty('') -- => true --_.isEmpty({}) -- => true --_.isEmpty({'a','b','c'}) -- => false -+M.isEmpty('') -- => true -+M.isEmpty({}) -- => true -+M.isEmpty({'a','b','c'}) -- => false - ```` - - ### isString (obj) -@@ -1879,9 +2564,9 @@ _.isEmpty({'a','b','c'}) -- => false - Is the given argument a string ? - - ```lua --_.isString('') -- => true --_.isString('Hello') -- => false --_.isString({}) -- => false -+M.isString('') -- => true -+M.isString('Hello') -- => false -+M.isString({}) -- => false - ```` - - ### isFunction (obj) -@@ -1889,9 +2574,9 @@ _.isString({}) -- => false - Is the given argument a function ? - - ```lua --_.isFunction(print) -- => true --_.isFunction(function() end) -- => true --_.isFunction({}) -- => false -+M.isFunction(print) -- => true -+M.isFunction(function() end) -- => true -+M.isFunction({}) -- => false - ```` - - ### isNil (obj) -@@ -1899,9 +2584,9 @@ _.isFunction({}) -- => false - Is the given argument nil ? - - ```lua --_.isNil(nil) -- => true --_.isNil() -- => true --_.isNil({}) -- => false -+M.isNil(nil) -- => true -+M.isNil() -- => true -+M.isNil({}) -- => false - ```` - - ### isNumber (obj) -@@ -1909,10 +2594,10 @@ _.isNil({}) -- => false - Is the given argument a number ? - - ```lua --_.isNumber(math.pi) -- => true --_.isNumber(math.huge) -- => true --_.isNumber(0/0) -- => true --_.isNumber() -- => false -+M.isNumber(math.pi) -- => true -+M.isNumber(math.huge) -- => true -+M.isNumber(0/0) -- => true -+M.isNumber() -- => false - ```` - - ### isNaN (obj) -@@ -1920,8 +2605,8 @@ _.isNumber() -- => false - Is the given argument NaN ? - - ```lua --_.isNaN(1) -- => false --_.isNaN(0/0) -- => true -+M.isNaN(1) -- => false -+M.isNaN(0/0) -- => true - ```` - - ### isFinite (obj) -@@ -1929,11 +2614,11 @@ _.isNaN(0/0) -- => true - Is the given argument a finite number ? - - ```lua --_.isFinite(99e99) -- => true --_.isFinite(math.pi) -- => true --_.isFinite(math.huge) -- => false --_.isFinite(1/0) -- => false --_.isFinite(0/0) -- => false -+M.isFinite(99e99) -- => true -+M.isFinite(math.pi) -- => true -+M.isFinite(math.huge) -- => false -+M.isFinite(1/0) -- => false -+M.isFinite(0/0) -- => false - ```` - - ### isBoolean (obj) -@@ -1941,10 +2626,10 @@ _.isFinite(0/0) -- => false - Is the given argument a boolean ? - - ```lua --_.isBoolean(true) -- => true --_.isBoolean(false) -- => true --_.isBoolean(1==1) -- => true --_.isBoolean(print) -- => false -+M.isBoolean(true) -- => true -+M.isBoolean(false) -- => true -+M.isBoolean(1==1) -- => true -+M.isBoolean(print) -- => false - ```` - - ### isInteger (obj) -@@ -1952,9 +2637,9 @@ _.isBoolean(print) -- => false - Is the given argument an integer ? - - ```lua --_.isInteger(math.pi) -- => false --_.isInteger(1) -- => true --_.isInteger(-1) -- => true -+M.isInteger(math.pi) -- => false -+M.isInteger(1) -- => true -+M.isInteger(-1) -- => true - ```` - - **[[⬆]](#TOC)** -@@ -1962,8 +2647,9 @@ _.isInteger(-1) -- => true - ## Chaining - - *Method chaining* (also known as *name parameter idiom*), is a technique for invoking consecutively method calls in object-oriented style. --Each method returns an object, and methods calls are chained together. -+Each method returns an object, and method calls are chained together. - Moses offers chaining for your perusal.
    -+ - Let's use chaining to get the count of evey single word in some lyrics (case won't matter here). - - -@@ -1975,16 +2661,17 @@ local lyrics = { - "He sleeps all night and he works all day" - } - --local stats = _.chain(lyrics) -- :map(function(k,line) -- local t = {} -- for w in line:gmatch('(%w+)') do -- t[#t+1] = w -- end -- return t -- end) -+-- split a text into words -+local function words(line) -+ local t = {} -+ for w in line:gmatch('(%w+)') do t[#t+1] = w end -+ return t -+end -+ -+local stats = M.chain(lyrics) -+ :map(words) - :flatten() -- :countBy(function(i,v) return v:lower() end) -+ :countBy(string.lower) - :value() - - -- => "{ -@@ -1994,7 +2681,7 @@ local stats = _.chain(lyrics) - -- => }" - ```` - --For convenience, you can also use `_(value)` to start chaining methods, instead of `_.chain(value)`. -+For convenience, you can also use `M(value)` to start chaining methods, instead of `M.chain(value)`. - - Note that one can use `:value()` to unwrap a chained object. - -@@ -2011,7 +2698,7 @@ All library functions can be imported in a context using `import` into a specifi - - ```lua - local context = {} --_.import(context) -+M.import(context) - - context.each({1,2,3},print) - -@@ -2020,10 +2707,10 @@ context.each({1,2,3},print) - -- => 3 3 - ```` - --When no `context` was provided, it defaults to the global environment `_G`. -+When no `context` was provided, it defaults to the current environment, `_ENV` or `_G`. - - ```lua --_.import() -+M.import() - - each({1,2,3},print) - -@@ -2036,7 +2723,7 @@ Passing `noConflict` argument leaves untouched conflicting keys while importing - - ```lua - local context = {each = 1} --_.import(context, true) -+M.import(context, true) - - print(context.each) -- => 1 - context.eachi({1,2,3},print) -diff --git a/extra/moses/moses.lua b/extra/moses/moses.lua -index f3d484a..ccf628e 100644 ---- a/extra/moses/moses.lua -+++ b/extra/moses/moses.lua -@@ -1,33 +1,34 @@ - --- Utility-belt library for functional programming in Lua ([source](http://github.com/Yonaba/Moses)) - -- @author [Roland Yonaba](http://github.com/Yonaba) ---- @copyright 2012-2017 -+-- @copyright 2012-2018 - -- @license [MIT](http://www.opensource.org/licenses/mit-license.php) ---- @release 1.6.1 -+-- @release 2.1.0 - -- @module moses -+-- @set sort=true - --local _MODULEVERSION = '1.6.1' -+local _MODULEVERSION = '2.1.0' - - -- Internalisation --local next, type, select, pcall = next, type, select, pcall -+local next, type, pcall = next, type, pcall - local setmetatable, getmetatable = setmetatable, getmetatable - local t_insert, t_sort = table.insert, table.sort - local t_remove,t_concat = table.remove, table.concat - local randomseed, random, huge = math.randomseed, math.random, math.huge --local floor, max, min = math.floor, math.max, math.min -+local floor, max, min, ceil = math.floor, math.max, math.min, math.ceil -+local wrap = coroutine.wrap -+local yield = coroutine.yield - local rawget = rawget - local unpack = table.unpack or unpack - local pairs,ipairs = pairs,ipairs --local clock = os.clock --local _ = {} -+local error = error -+local clock = os and os.clock or nil -+local M = {} - - - -- ======== Private helpers - - local function f_max(a,b) return a>b end - local function f_min(a,b) return ab and b or var) end --local function isTrue(_,value) return value and true end --local function iNot(value) return not value end - - local function count(t) -- raw count of items in an map-table - local i = 0 -@@ -36,13 +37,13 @@ local function count(t) -- raw count of items in an map-table - end - - local function extract(list,comp,transform,...) -- extracts value from a list -- local _ans -- local transform = transform or _.identity -- for index,value in pairs(list) do -- if not _ans then _ans = transform(value,...) -+ transform = transform or M.identity -+ local _ans -+ for k,v in pairs(list) do -+ if not _ans then _ans = transform(v,...) - else -- local value = transform(value,...) -- _ans = comp(_ans,value) and _ans or value -+ local val = transform(v,...) -+ _ans = comp(_ans,val) and _ans or val - end - end - return _ans -@@ -50,7 +51,7 @@ end - - local function partgen(t, n, f, pad) -- generates array partitions - for i = 0, #t, n do -- local s = _.slice(t, i+1, i+n) -+ local s = M.slice(t, i+1, i+n) - if #s>0 then - while (#s < n and pad) do s[#s+1] = pad end - f(s) -@@ -58,9 +59,9 @@ local function partgen(t, n, f, pad) -- generates array partitions - end - end - --local function partgen2(t, n, f, pad) -- generates sliding array partitions -+local function partgen2(t, n, f, pad) -- generates overlapping array partitions - for i = 0, #t, n-1 do -- local s = _.slice(t, i+1, i+n) -+ local s = M.slice(t, i+1, i+n) - if #s>0 and i+1<#t then - while (#s < n and pad) do s[#s+1] = pad end - f(s) -@@ -68,6 +69,16 @@ local function partgen2(t, n, f, pad) -- generates sliding array partitions - end - end - -+local function partgen3(t, n, f, pad) -- generates sliding array partitions -+ for i = 0, #t, 1 do -+ local s = M.slice(t, i+1, i+n) -+ if #s>0 and i+n<=#t then -+ while (#s < n and pad) do s[#s+1] = pad end -+ f(s) -+ end -+ end -+end -+ - local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.html - if n == 0 then f(t) end - for i = 1,n do -@@ -77,9 +88,158 @@ local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.ht - end - end - -+local function signum(a) return a>=0 and 1 or -1 end -+ - -- Internal counter for unique ids generation - local unique_id_counter = -1 - -+--- Operator functions -+-- @section Operator functions -+ -+M.operator = {} -+--- Returns a + b. Aliased as `op.add`. -+-- @name operator.add -+-- @param a a value -+-- @param b a value -+-- @return a + b -+M.operator.add = function(a,b) return a + b end -+ -+--- Returns a - b. Aliased as `op.sub`. -+-- @name operator.sub -+-- @param a a value -+-- @param b a value -+-- @return a - b -+M.operator.sub = function(a,b) return a - b end -+ -+--- Returns a * b. Aliased as `op.mul`. -+-- @name operator.mul -+-- @param a a value -+-- @param b a value -+-- @return a * b -+M.operator.mul = function(a,b) return a * b end -+ -+--- Returns a / b. Aliased as `op.div`. -+-- @name operator.div -+-- @param a a value -+-- @param b a value -+-- @return a / b -+M.operator.div = function(a,b) return a / b end -+ -+--- Returns a % b. Aliased as `op.mod`. -+-- @name operator.mod -+-- @param a a value -+-- @param b a value -+-- @return a % b -+M.operator.mod = function(a,b) return a % b end -+ -+--- Returns a ^ b. Aliased as `op.exp`, `op.pow`. -+-- @name operator.exp -+-- @param a a value -+-- @param b a value -+-- @return a ^ b -+M.operator.exp = function(a,b) return a ^ b end -+M.operator.pow = M.operator.exp -+ -+--- Returns -a. Aliased as `op.unm`, `op.neg`. -+-- @name operator.unm -+-- @param a a value -+-- @return -a -+M.operator.unm = function(a) return -a end -+M.operator.neg = M.operator.unm -+ -+--- Performs floor division (//) between `a` and `b`. It rounds the quotient towards minus infinity. -+-- Aliased as `op.floordiv`. -+-- @name operator.floordiv -+-- @param a a value -+-- @param b a value -+-- @return a // b -+M.operator.floordiv = function(a, b) return floor(a/b) end -+ -+--- Performs integer division between `a` and `b`. Aliased as `op.intdiv`. -+-- @name operator.intdiv -+-- @param a a value -+-- @param b a value -+-- @return a / b -+M.operator.intdiv = function(a,b) -+ return a>=0 and floor(a/b) or ceil(a/b) -+end -+ -+--- Checks if a equals b. Aliased as `op.eq`. -+-- @name operator.eq -+-- @param a a value -+-- @param b a value -+-- @return a == b -+M.operator.eq = function(a,b) return a == b end -+ -+--- Checks if a not equals b. Aliased as `op.neq`. -+-- @name operator.neq -+-- @param a a value -+-- @param b a value -+-- @return a ~= b -+M.operator.neq = function(a,b) return a ~= b end -+ -+--- Checks if a is strictly less than b. Aliased as `op.lt`. -+-- @name operator.lt -+-- @param a a value -+-- @param b a value -+-- @return a < b -+M.operator.lt = function(a,b) return a < b end -+ -+--- Checks if a is strictly greater than b. Aliased as `op.gt`. -+-- @name operator.gt -+-- @param a a value -+-- @param b a value -+-- @return a > b -+M.operator.gt = function(a,b) return a > b end -+ -+--- Checks if a is less or equal to b. Aliased as `op.le`. -+-- @name operator.le -+-- @param a a value -+-- @param b a value -+-- @return a <= b -+M.operator.le = function(a,b) return a <= b end -+ -+--- Checks if a is greater or equal to b. Aliased as `op.ge`. -+-- @name operator.ge -+-- @param a a value -+-- @param b a value -+-- @return a >= b -+M.operator.ge = function(a,b) return a >= b end -+ -+--- Returns logical a and b. Aliased as `op.land`. -+-- @name operator.land -+-- @param a a value -+-- @param b a value -+-- @return a and b -+M.operator.land = function(a,b) return a and b end -+ -+--- Returns logical a or b. Aliased as `op.lor`. -+-- @name operator.lor -+-- @param a a value -+-- @param b a value -+-- @return a or b -+M.operator.lor = function(a,b) return a or b end -+ -+--- Returns logical not a. Aliased as `op.lnot`. -+-- @name operator.lnot -+-- @param a a value -+-- @return not a -+M.operator.lnot = function(a) return not a end -+ -+--- Returns concatenation of a and b. Aliased as `op.concat`. -+-- @name operator.concat -+-- @param a a value -+-- @param b a value -+-- @return a .. b -+M.operator.concat = function(a,b) return a..b end -+ -+--- Returns the length of a. Aliased as `op.len`. -+-- @name operator.length -+-- @param a a value -+-- @return #a -+M.operator.length = function(a) return #a end -+M.operator.len = M.operator.length -+ - --- Table functions - -- @section Table functions - -@@ -87,39 +247,37 @@ local unique_id_counter = -1 - -- @name clear - -- @param t a table - -- @return the given table, cleared. --function _.clear(t) -+function M.clear(t) - for k in pairs(t) do t[k] = nil end - return t - end - ----- Iterates on key-value pairs, calling `f (k, v)` at every step. -+ -+ -+--- Iterates on key-value pairs, calling `f (v, k)` at every step. - --
    Aliased as `forEach`. - -- @name each - -- @param t a table ---- @param f a function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f a function, prototyped as `f (v, k)` - -- @see eachi --function _.each(t, f, ...) -+function M.each(t, f) - for index,value in pairs(t) do -- f(index,value,...) -+ f(value, index) - end - end - ----- Iterates on integer key-value pairs, calling `f(k, v)` every step. -+--- Iterates on integer key-value pairs, calling `f(v, k)` every step. - -- Only applies to values located at integer keys. The table can be a sparse array. - -- Iteration will start from the lowest integer key found to the highest one. - --
    Aliased as `forEachi`. - -- @name eachi - -- @param t a table ---- @param f a function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f a function, prototyped as `f (v, k)` - -- @see each --function _.eachi(t, f, ...) -- local lkeys = _.sort(_.select(_.keys(t), function(k,v) -- return _.isInteger(v) -- end)) -+function M.eachi(t, f) -+ local lkeys = M.sort(M.select(M.keys(t), M.isInteger)) - for k, key in ipairs(lkeys) do -- f(key, t[key],...) -+ f(t[key], key) - end - end - -@@ -128,41 +286,75 @@ end - -- @param t a table - -- @param ... A variable number of keys to collect values - -- @return an array-list of values --function _.at(t, ...) -+function M.at(t, ...) - local values = {} -- for i, key in ipairs({...}) do -- if _.has(t, key) then values[#values+1] = t[key] end -- end -+ for i, key in ipairs({...}) do values[#values+1] = t[key] end - return values - end - -+--- Adjusts the value at a given key using a function or a value. In case `f` is a function, -+-- it should be prototyped `f(v)`. It does not mutate the given table, but rather -+-- returns a new array. In case the given `key` does not exist in `t`, it throws an error. -+-- @param t a table -+-- @param key a key -+-- @param f a function, prototyped as `f(v)` or a value -+function M.adjust(t, key, f) -+ if (t[key] == nil) then error("key not existing in table") end -+ local _t = M.clone(t) -+ _t[key] = type(f) == 'function' and f(_t[key]) or f -+ return _t -+end -+ - --- Counts occurrences of a given value in a table. Uses @{isEqual} to compare values. - -- @name count - -- @param t a table ---- @param[opt] value a value to be searched in the table. If not given, the @{size} of the table will be returned -+-- @param[opt] val a value to be searched in the table. If not given, the @{size} of the table will be returned - -- @return the count of occurrences of the given value - -- @see countf - -- @see size --function _.count(t, value) -- if _.isNil(value) then return _.size(t) end -+function M.count(t, val) -+ if val == nil then return M.size(t) end - local count = 0 -- _.each(t, function(k,v) -- if _.isEqual(v, value) then count = count + 1 end -- end) -+ for k, v in pairs(t) do -+ if M.isEqual(v, val) then count = count + 1 end -+ end - return count - end - ----- Counts occurrences validating a predicate. Same as @{count}, but uses an iterator. ---- Returns the count for values passing the test `f (k, v, ...)` -+--- Counts the number of values passing a predicate test. Same as @{count}, but uses an iterator. -+-- Returns the count for values passing the test `f (v, k)` - -- @name countf - -- @param t a table ---- @param f an iterator function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function, prototyped as `f (v, k)` - -- @return the count of values validating the predicate - -- @see count - -- @see size --function _.countf(t, f, ...) -- return _.count(_.map(t, f, ...), true) -+function M.countf(t, f) -+ local count = 0 -+ for k, v in pairs(t) do -+ if f(v, k) then count = count + 1 end -+ end -+ return count -+end -+ -+--- Checks if all values in a collection are equal. Uses an optional `comp` function which is used -+-- to compare values and defaults to @{isEqual} when not given. -+--
    Aliased as `alleq`. -+-- @name allEqual -+-- @param t a table -+-- @param[opt] comp a comparison function. Defaults to `isEqual` -+-- @return `true` when all values in `t` are equal, `false` otherwise. -+-- @see isEqual -+function M.allEqual(t, comp) -+ local k, pivot = next(t) -+ for k, v in pairs(t) do -+ if comp then -+ if not comp(pivot, v) then return false end -+ else -+ if not M.isEqual(pivot, v) then return false end -+ end -+ end -+ return true - end - - --- Loops `n` times through a table. In case `n` is omitted, it will loop forever. -@@ -170,11 +362,11 @@ end - --
    Aliased as `loop`. - -- @name cycle - -- @param t a table ---- @param n the number of loops ---- @return an iterator function yielding key-value pairs from the passed-in table. --function _.cycle(t, n) -+-- @param[opt] n the number of loops -+-- @return an iterator function yielding value-key pairs from the passed-in table. -+function M.cycle(t, n) - n = n or 1 -- if n<=0 then return _.noop end -+ if n<=0 then return M.noop end - local k, fk - local i = 0 - while true do -@@ -187,22 +379,39 @@ function _.cycle(t, n) - return - end - end -- return k, t[k] -+ return t[k], k - end - end - end - ----- Maps `f (k, v)` on key-value pairs, collects and returns the results. -+--- Maps `f (v, k)` on value-key pairs, collects and returns the results. -+-- Uses `pairs` to iterate over elements in `t`. - --
    Aliased as `collect`. - -- @name map - -- @param t a table ---- @param f an iterator function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function, prototyped as `f (v, k)` - -- @return a table of results --function _.map(t, f, ...) -+-- @see mapi -+function M.map(t, f) - local _t = {} - for index,value in pairs(t) do -- local k, kv, v = index, f(index,value,...) -+ local k, kv, v = index, f(value, index) -+ _t[v and kv or k] = v or kv -+ end -+ return _t -+end -+ -+--- Maps `f (v, k)` on value-key pairs, collects and returns the results. -+-- Uses `ipairs` to iterate over elements in `t`. -+-- @name mapi -+-- @param t a table -+-- @param f an iterator function, prototyped as `f (v, k)` -+-- @return a table of results -+-- @see map -+function M.mapi(t, f) -+ local _t = {} -+ for index,value in ipairs(t) do -+ local k, kv, v = index, f(value, index) - _t[v and kv or k] = v or kv - end - return _t -@@ -217,10 +426,11 @@ end - -- @param f an iterator function, prototyped as `f (state, value)` - -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. - -- @return the final state of reduction -+-- @see best - -- @see reduceRight ---- @see reduceby --function _.reduce(t, f, state) -- for __,value in pairs(t) do -+-- @see reduceBy -+function M.reduce(t, f, state) -+ for k,value in pairs(t) do - if state == nil then state = value - else state = f(state,value) - end -@@ -228,18 +438,40 @@ function _.reduce(t, f, state) - return state - end - -+--- Returns the best value passing a selector function. Acts as a special case of -+-- @{reduce}, using the first value in `t` as an initial state. It thens folds the given table, -+-- testing each of its values `v` and selecting the value passing the call `f(state,v)` every time. -+-- @name best -+-- @param t a table -+-- @param f an iterator function, prototyped as `f (state, value)` -+-- @return the final state of reduction -+-- @see reduce -+-- @see reduceRight -+-- @see reduceBy -+function M.best(t, f) -+ local _, state = next(t) -+ for k,value in pairs(t) do -+ if state == nil then state = value -+ else state = f(state,value) and state or value -+ end -+ end -+ return state -+end -+ - --- Reduces values in a table passing a given predicate. Folds the table left-to-right, considering - -- only values validating a given predicate. ---- @name reduceby -+-- @name reduceBy - -- @param t a table - -- @param f an iterator function, prototyped as `f (state, value)` ---- @param state an initial state of reduction. ---- @param pred a predicate function `pred (k, v, ...)` to select values to be considered for reduction ---- @param[opt] ... optional args to be passed to `pred` -+-- @param pred a predicate function `pred (v, k)` to select values to be considered for reduction -+-- @param[opt] state an initial state of reduction. Defaults to the first value in the table of selected values. -+-- @param[optchain] ... optional args to be passed to `pred` - -- @return the final state of reduction - -- @see reduce --function _.reduceby(t, f, state, pred, ...) -- return _.reduce(_.select(t, pred, ...), f, state) -+-- @see best -+-- @see reduceRight -+function M.reduceBy(t, f, pred, state) -+ return M.reduce(M.select(t, pred), f, state) - end - - --- Reduces a table, right-to-left. Folds the table from the last element to the first element -@@ -252,8 +484,10 @@ end - -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. - -- @return the final state of reduction - -- @see reduce --function _.reduceRight(t, f, state) -- return _.reduce(_.reverse(t),f,state) -+-- @see best -+-- @see reduceBy -+function M.reduceRight(t, f, state) -+ return M.reduce(M.reverse(t),f,state) - end - - --- Reduces a table while saving intermediate states. Folds the table left-to-right -@@ -266,7 +500,7 @@ end - -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. - -- @return an array of states - -- @see mapReduceRight --function _.mapReduce(t, f, state) -+function M.mapReduce(t, f, state) - local _t = {} - for i,value in pairs(t) do - _t[i] = not state and value or f(state,value) -@@ -285,8 +519,8 @@ end - -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. - -- @return an array of states - -- @see mapReduce --function _.mapReduceRight(t, f, state) -- return _.mapReduce(_.reverse(t),f,state) -+function M.mapReduceRight(t, f, state) -+ return M.mapReduce(M.reverse(t),f,state) - end - - --- Performs a linear search for a value in a table. It does not work for nested tables. -@@ -298,9 +532,9 @@ end - -- @param value a value to search for - -- @return a boolean : `true` when found, `false` otherwise - -- @see detect --function _.include(t, value) -- local _iter = _.isFunction(value) and value or _.isEqual -- for __,v in pairs(t) do -+function M.include(t, value) -+ local _iter = (type(value) == 'function') and value or M.isEqual -+ for k,v in pairs(t) do - if _iter(v,value) then return true end - end - return false -@@ -308,14 +542,16 @@ end - - --- Performs a linear search for a value in a table. Returns the key of the value if found. - -- The given value can be a function prototyped as `f (v, value)` which should return true when ---- any v in the table equals the value being searched. -+-- any v in the table equals the value being searched. This function is similar to @{find}, -+-- which is mostly meant to work with array. - -- @name detect - -- @param t a table - -- @param value a value to search for - -- @return the key of the value when found or __nil__ - -- @see include --function _.detect(t, value) -- local _iter = _.isFunction(value) and value or _.isEqual -+-- @see find -+function M.detect(t, value) -+ local _iter = (type(value) == 'function') and value or M.isEqual - for key,arg in pairs(t) do - if _iter(arg,value) then return key end - end -@@ -327,8 +563,8 @@ end - -- @param props a set of keys - -- @return an array of values from the passed-in table - -- @see findWhere --function _.where(t, props) -- local r = _.select(t, function(__,v) -+function M.where(t, props) -+ local r = M.select(t, function(v) - for key in pairs(props) do - if v[key] ~= props[key] then return false end - end -@@ -343,8 +579,8 @@ end - -- @param props a set of keys - -- @return a value from the passed-in table - -- @see where --function _.findWhere(t, props) -- local index = _.detect(t, function(v) -+function M.findWhere(t, props) -+ local index = M.detect(t, function(v) - for key in pairs(props) do - if props[key] ~= v[key] then return false end - end -@@ -357,14 +593,13 @@ end - --
    Aliased as `filter`. - -- @name select - -- @param t a table ---- @param f an iterator function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function, prototyped as `f (v, k)` - -- @return the selected values - -- @see reject --function _.select(t, f, ...) -+function M.select(t, f) - local _t = {} - for index,value in pairs(t) do -- if f(index, value,...) then _t[#_t+1] = value end -+ if f(value,index) then _t[#_t+1] = value end - end - return _t - end -@@ -373,15 +608,13 @@ end - --
    Aliased as `discard` - -- @name reject - -- @param t a table ---- @param f an iterator function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function, prototyped as `f (v, k)` - -- @return the remaining values - -- @see select --function _.reject(t, f, ...) -- local _mapped = _.map(t,f,...) -+function M.reject(t, f) - local _t = {} -- for index,value in pairs (_mapped) do -- if not value then _t[#_t+1] = t[index] end -+ for index,value in pairs (t) do -+ if not f(value,index) then _t[#_t+1] = value end - end - return _t - end -@@ -390,37 +623,37 @@ end - --
    Aliased as `every` - -- @name all - -- @param t a table ---- @param f an iterator function, prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function, prototyped as `f (v, k)` - -- @return `true` if all values passes the predicate, `false` otherwise --function _.all(t, f, ...) -- return ((#_.select(_.map(t,f,...), isTrue)) == count(t)) -+function M.all(t, f) -+ for index,value in pairs(t) do -+ if not f(value,index) then return false end -+ end -+ return true - end - - --- Invokes a method on each value in a table. - -- @name invoke - -- @param t a table ---- @param method a function, prototyped as `f (v, ...)` ---- @param[opt] ... Optional args to be passed to `method` ---- @return the result of the call `f (v, ...)` -+-- @param method a function, prototyped as `f (v, k)` -+-- @return the result of the call `f (v, k)` - -- @see pluck --function _.invoke(t, method, ...) -- local args = {...} -- return _.map(t, function(__,v) -- if _.isTable(v) then -- if _.has(v,method) then -- if _.isCallable(v[method]) then -- return v[method](v,unpack(args)) -+function M.invoke(t, method) -+ return M.map(t, function(v, k) -+ if (type(v) == 'table') then -+ if v[method] then -+ if M.isCallable(v[method]) then -+ return v[method](v,k) - else - return v[method] - end - else -- if _.isCallable(method) then -- return method(v,unpack(args)) -+ if M.isCallable(method) then -+ return method(v,k) - end - end -- elseif _.isCallable(method) then -- return method(v,unpack(args)) -+ elseif M.isCallable(method) then -+ return method(v,k) - end - end) - end -@@ -430,51 +663,34 @@ end - -- @param t a table - -- @param key a key, will be used to index in each value: `value[key]` - -- @return an array of values having the given key --function _.pluck(t, key) -- return _.reject(_.map(t,function(__,value) -- return value[key] -- end), iNot) -+function M.pluck(t, key) -+ local _t = {} -+ for k, v in pairs(t) do -+ if v[key] then _t[#_t+1] = v[key] end -+ end -+ return _t - end - ----- Returns the max value in a collection. If an transformation function is passed, it will -+--- Returns the max value in a collection. If a `transform` function is passed, it will - -- be used to evaluate values by which all objects will be sorted. - -- @name max - -- @param t a table ---- @param[opt] transform a transformation function, prototyped as `transform (v, ...)`, defaults to @{identity} ---- @param[optchain] ... Optional args to be passed to `transform` -+-- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} - -- @return the max value found - -- @see min --function _.max(t, transform, ...) -- return extract(t, f_max, transform, ...) -+function M.max(t, transform) -+ return extract(t, f_max, transform) - end - ----- Returns the min value in a collection. If an transformation function is passed, it will -+--- Returns the min value in a collection. If a `transform` function is passed, it will - -- be used to evaluate values by which all objects will be sorted. - -- @name min - -- @param t a table ---- @param[opt] transform a transformation function, prototyped as `transform (v, ...)`, defaults to @{identity} ---- @param[optchain] ... Optional args to be passed to `transform` -+-- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} - -- @return the min value found - -- @see max --function _.min(t, transform, ...) -- return extract(t, f_min, transform, ...) --end -- ----- Returns a shuffled copy of a given collection. If a seed is provided, it will ---- be used to init the pseudo random number generator (using `math.randomseed`). ---- @name shuffle ---- @param t a table ---- @param[opt] seed a seed ---- @return a shuffled copy of the given table --function _.shuffle(t, seed) -- if seed then randomseed(seed) end -- local _shuffled = {} -- _.each(t,function(index,value) -- local randPos = floor(random()*index)+1 -- _shuffled[index] = _shuffled[randPos] -- _shuffled[randPos] = value -- end) -- return _shuffled -+function M.min(t, transform) -+ return extract(t, f_min, transform) - end - - --- Checks if two tables are the same. It compares if both tables features the same values, -@@ -483,76 +699,103 @@ end - -- @param a a table - -- @param b another table - -- @return `true` or `false` --function _.same(a, b) -- return _.all(a, function (i,v) return _.include(b,v) end) -- and _.all(b, function (i,v) return _.include(a,v) end) -+function M.same(a, b) -+ return M.all(a, function(v) return M.include(b,v) end) -+ and M.all(b, function(v) return M.include(a,v) end) - end - - --- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. - -- @name sort - -- @param t a table - -- @param[opt] comp a comparison function prototyped as `comp (a, b)`, defaults to < operator. ---- @return the initial table, sorted. -+-- @return the given table, sorted. - -- @see sortBy --function _.sort(t, comp) -+function M.sort(t, comp) - t_sort(t, comp) - return t - end - -+--- Iterates on values with respect to key order. Keys are sorted using `comp` function -+-- which defaults to `math.min`. It returns upon each call a `key, value` pair. -+-- @name sortedk -+-- @param t a table -+-- @param[opt] comp a comparison function. Defaults to `<` operator -+-- @return an iterator function -+-- @see sortedv -+function M.sortedk(t, comp) -+ local keys = M.keys(t) -+ t_sort(keys, comp) -+ local i = 0 -+ return function () -+ i = i + 1 -+ return keys[i], t[keys[i]] -+ end -+end -+ -+--- Iterates on values with respect to values order. Values are sorted using `comp` function -+-- which defaults to `math.min`. It returns upon each call a `key, value` pair. -+-- @name sortedv -+-- @param t a table -+-- @param[opt] comp a comparison function. Defaults to `<` operator -+-- @return an iterator function -+-- @see sortedk -+function M.sortedv(t, comp) -+ local keys = M.keys(t) -+ comp = comp or f_min -+ t_sort(keys, function(a,b) return comp(t[a],t[b]) end) -+ local i = 0 -+ return function () -+ i = i + 1 -+ return keys[i], t[keys[i]] -+ end -+end -+ - --- Sorts a table in-place using a transform. Values are ranked in a custom order of the results of - -- running `transform (v)` on all values. `transform` may also be a string name property sort by. - -- `comp` is a comparison function. - -- @name sortBy - -- @param t a table - -- @param[opt] transform a `transform` function to sort elements prototyped as `transform (v)`. Defaults to @{identity} ---- @param[optchain] comp a comparision function, defaults to the `<` operator -+-- @param[optchain] comp a comparison function, defaults to the `<` operator - -- @return a new array of sorted values - -- @see sort --function _.sortBy(t, transform, comp) -- local f = transform or _.identity -- if _.isString(transform) then -+function M.sortBy(t, transform, comp) -+ local f = transform or M.identity -+ if (type(transform) == 'string') then - f = function(t) return t[transform] end - end - comp = comp or f_min -- local _t = {} -- _.each(t, function(__,v) -- _t[#_t+1] = {value = v, transform = f(v)} -- end) -- t_sort(_t, function(a,b) return comp(a.transform, b.transform) end) -- return _.pluck(_t, 'value') -+ t_sort(t, function(a,b) return comp(f(a), f(b)) end) -+ return t - end - - --- Splits a table into subsets groups. - -- @name groupBy - -- @param t a table ---- @param iter an iterator function, prototyped as `iter (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `iter` -+-- @param iter an iterator function, prototyped as `iter (v, k)` - -- @return a table of subsets groups --function _.groupBy(t, iter, ...) -- local vararg = {...} -+function M.groupBy(t, iter) - local _t = {} -- _.each(t, function(i,v) -- local _key = iter(i,v, unpack(vararg)) -- if _t[_key] then _t[_key][#_t[_key]+1] = v -- else _t[_key] = {v} -- end -- end) -+ for k,v in pairs(t) do -+ local _key = iter(v,k) -+ if _t[_key] then _t[_key][#_t[_key]+1] = v -+ else _t[_key] = {v} -+ end -+ end - return _t - end - - --- Groups values in a collection and counts them. - -- @name countBy - -- @param t a table ---- @param iter an iterator function, prototyped as `iter (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `iter` -+-- @param iter an iterator function, prototyped as `iter (v, k)` - -- @return a table of subsets groups names paired with their count --function _.countBy(t, iter, ...) -- local vararg = {...} -+function M.countBy(t, iter) - local stats = {} -- _.each(t,function(i,v) -- local key = iter(i,v,unpack(vararg)) -- stats[key] = (stats[key] or 0) +1 -- end) -+ for i,v in pairs(t) do -+ local key = iter(v,i) -+ stats[key] = (stats[key] or 0)+1 -+ end - return stats - end - -@@ -563,14 +806,10 @@ end - -- @return a count - -- @see count - -- @see countf --function _.size(...) -+function M.size(...) - local args = {...} - local arg1 = args[1] -- if _.isTable(arg1) then -- return count(args[1]) -- else -- return count(args) -- end -+ return (type(arg1) == 'table') and count(args[1]) or count(args) - end - - --- Checks if all the keys of `other` table exists in table `t`. It does not -@@ -581,7 +820,7 @@ end - -- @param other another table - -- @return `true` or `false` - -- @see sameKeys --function _.containsKeys(t, other) -+function M.containsKeys(t, other) - for key in pairs(other) do - if not t[key] then return false end - end -@@ -594,7 +833,7 @@ end - -- @param tB another table - -- @return `true` or `false` - -- @see containsKeys --function _.sameKeys(tA, tB) -+function M.sameKeys(tA, tB) - for key in pairs(tA) do - if not tB[key] then return false end - end -@@ -614,16 +853,16 @@ end - -- @param array an array - -- @param[opt] n a number of elements to be sampled. Defaults to 1. - -- @param[optchain] seed an optional seed for shuffling ---- @return an array of selected values or a single value when `n` == 1 -+-- @return an array of selected values - -- @see sampleProb --function _.sample(array, n, seed) -- n = n or 1 -- if n < 1 then return end -+function M.sample(array, n, seed) -+ n = n or 1 -+ if n == 0 then return {} end - if n == 1 then - if seed then randomseed(seed) end -- return array[random(1, #array)] -+ return {array[random(1, #array)]} - end -- return _.slice(_.shuffle(array, seed), 1, n) -+ return M.slice(M.shuffle(array, seed), 1, n) - end - - --- Return elements from a sequence with a given probability. It considers each value independently. -@@ -635,16 +874,57 @@ end - -- @param[opt] seed an optional seed for deterministic sampling - -- @return an array of selected values - -- @see sample --function _.sampleProb(array, prob, seed) -+function M.sampleProb(array, prob, seed) - if seed then randomseed(seed) end -- return _.select(array, function(_,v) return random() < prob end) -+ local t = {} -+ for k, v in ipairs(array) do -+ if random() < prob then t[#t+1] = v end -+ end -+ return t -+end -+ -+--- Returns the n-top values satisfying a predicate. It takes a comparison function -+-- `comp` used to sort array values, and then picks the top n-values. It leaves the original array untouched. -+-- @name nsorted -+-- @param array an array -+-- @param[opt] n a number of values to retrieve. Defaults to 1. -+-- @param[optchain] comp a comparison function. Defaults to `<` operator. -+-- @return an array of top n values -+function M.nsorted(array, n, comp) -+ comp = comp or f_min -+ n = n or 1 -+ local values, count = {}, 0 -+ for k, v in M.sortedv(array, comp) do -+ if count < n then -+ count = count + 1 -+ values[count] = v -+ end -+ end -+ return values -+end -+ -+--- Returns a shuffled copy of a given array. If a seed is provided, it will -+-- be used to init the built-in pseudo random number generator (using `math.randomseed`). -+-- @name shuffle -+-- @param array an array -+-- @param[opt] seed a seed -+-- @return a shuffled copy of the given array -+function M.shuffle(array, seed) -+ if seed then randomseed(seed) end -+ local _shuffled = {} -+ for index, value in ipairs(array) do -+ local randPos = floor(random()*index)+1 -+ _shuffled[index] = _shuffled[randPos] -+ _shuffled[randPos] = value -+ end -+ return _shuffled - end - - --- Converts a list of arguments to an array. ---- @name toArray -+-- @name pack - -- @param ... a list of arguments - -- @return an array of all passed-in args --function _.toArray(...) return {...} end -+function M.pack(...) return {...} end - - --- Looks for the first occurrence of a given value in an array. Returns the value index if found. - -- Uses @{isEqual} to compare values. -@@ -653,9 +933,10 @@ function _.toArray(...) return {...} end - -- @param value a value to lookup for - -- @param[opt] from the index from where the search will start. Defaults to 1. - -- @return the index of the value if found in the array, `nil` otherwise. --function _.find(array, value, from) -+-- @see detect -+function M.find(array, value, from) - for i = from or 1, #array do -- if _.isEqual(array[i], value) then return i end -+ if M.isEqual(array[i], value) then return i end - end - end - -@@ -663,7 +944,7 @@ end - -- @name reverse - -- @param array an array - -- @return a reversed array --function _.reverse(array) -+function M.reverse(array) - local _array = {} - for i = #array,1,-1 do - _array[#_array+1] = array[i] -@@ -672,33 +953,57 @@ function _.reverse(array) - end - - --- Replaces elements in a given array with a given value. In case `i` and `j` are given ---- it will only replaces values at indexes between `[i,j]`. In case `j` is greather than the array ---- size, it will append new values, increasing the array. -+-- it will only replaces values at indexes between `[i,j]`. In case `j` is greater than the array -+-- size, it will append new values, increasing the array size. - -- @name fill - -- @param array an array - -- @param value a value - -- @param[opt] i the index from which to start replacing values. Defaults to 1. - -- @param[optchain] j the index where to stop replacing values. Defaults to the array size. - -- @return the original array with values changed --function _.fill(array, value, i, j) -- j = j or _.size(array) -+function M.fill(array, value, i, j) -+ j = j or M.size(array) - for i = i or 1, j do array[i] = value end - return array - end - -+--- Returns an array of `n` zeros. -+-- @name zeros -+-- @param n a number -+-- @return an array -+-- @see ones -+-- @see vector -+function M.zeros(n) return M.fill({}, 0, 1, n) end -+ -+--- Returns an array of `n` 1's. -+-- @name ones -+-- @param n a number -+-- @return an array -+-- @see zeros -+-- @see vector -+function M.ones(n) return M.fill({}, 1, 1, n) end -+ -+--- Returns an array of `n` times a given value. -+-- @name vector -+-- @param value a value -+-- @param n a number -+-- @return an array -+-- @see zeros -+-- @see ones -+function M.vector(value, n) return M.fill({}, value, 1, n) end -+ - --- Collects values from a given array. The passed-in array should not be sparse. - -- This function collects values as long as they satisfy a given predicate and returns on the first falsy test. - --
    Aliased as `takeWhile` - -- @name selectWhile - -- @param array an array ---- @param f an iterator function prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function prototyped as `f (v, k)` - -- @return a new table containing all values collected - -- @see dropWhile --function _.selectWhile(array, f, ...) -+function M.selectWhile(array, f) - local t = {} - for i,v in ipairs(array) do -- if f(i,v,...) then t[i] = v else break end -+ if f(v,i) then t[i] = v else break end - end - return t - end -@@ -708,20 +1013,19 @@ end - --
    Aliased as `rejectWhile` - -- @name dropWhile - -- @param array an array ---- @param f an iterator function prototyped as `f (k,v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function prototyped as `f (v, k)` - -- @return a new table containing all values collected ---- @selectWhile --function _.dropWhile(array, f, ...) -+-- @see selectWhile -+function M.dropWhile(array, f) - local _i - for i,v in ipairs(array) do -- if not f(i,v,...) then -+ if not f(v, i) then - _i = i - break - end - end -- if _.isNil(_i) then return {} end -- return _.rest(array,_i) -+ if (_i == nil) then return {} end -+ return M.rest(array,_i) - end - - --- Returns the index at which a value should be inserted. This index is evaluated so -@@ -733,22 +1037,22 @@ end - -- @param[opt] comp an comparison function prototyped as `f (a, b)`, defaults to < operator. - -- @param[optchain] sort whether or not the passed-in array should be sorted - -- @return number the index at which the passed-in value should be inserted --function _.sortedIndex(array, value, comp, sort) -+function M.sortedIndex(array, value, comp, sort) - local _comp = comp or f_min -- if sort then _.sort(array,_comp) end -+ if (sort == true) then t_sort(array,_comp) end - for i = 1,#array do - if not _comp(array[i],value) then return i end - end - return #array+1 - end - ----- Returns the index of the first occurence of value in an array. -+--- Returns the index of the first occurrence of value in an array. - -- @name indexOf - -- @param array an array - -- @param value the value to search for - -- @return the index of the passed-in value - -- @see lastIndexOf --function _.indexOf(array, value) -+function M.indexOf(array, value) - for k = 1,#array do - if array[k] == value then return k end - end -@@ -760,33 +1064,31 @@ end - -- @param value the value to search for - -- @return the index of the last occurrence of the passed-in value or __nil__ - -- @see indexOf --function _.lastIndexOf(array, value) -- local key = _.indexOf(_.reverse(array),value) -+function M.lastIndexOf(array, value) -+ local key = M.indexOf(M.reverse(array),value) - if key then return #array-key+1 end - end - - --- Returns the first index at which a predicate returns true. - -- @name findIndex - -- @param array an array ---- @param predicate a predicate function prototyped as `predicate (k, v, ...)` ---- @param[opt] ... optional arguments to `pred` -+-- @param pred a predicate function prototyped as `pred (v, k)` - -- @return the index found or __nil__ - -- @see findLastIndex --function _.findIndex(array, predicate, ...) -+function M.findIndex(array, pred) - for k = 1, #array do -- if predicate(k,array[k],...) then return k end -+ if pred(array[k],k) then return k end - end - end - - --- Returns the last index at which a predicate returns true. - -- @name findLastIndex - -- @param array an array ---- @param predicate a predicate function prototyped as `predicate (k, v, ...)` ---- @param[opt] ... optional arguments to `pred` -+-- @param pred a predicate function prototyped as `pred (k, v)` - -- @return the index found or __nil__ - -- @see findIndex --function _.findLastIndex(array, predicate, ...) -- local key = _.findIndex(_.reverse(array),predicate,...) -+function M.findLastIndex(array, pred) -+ local key = M.findIndex(M.reverse(array),pred) - if key then return #array-key+1 end - end - -@@ -796,31 +1098,50 @@ end - -- @param array an array - -- @param ... a variable number of arguments - -- @return the passed-in array with new values added -+-- @see prepend - -- @see push --function _.addTop(array, ...) -- _.each({...},function(i,v) t_insert(array,1,v) end) -+function M.addTop(array, ...) -+ for k,v in ipairs({...}) do -+ t_insert(array,1,v) -+ end - return array - end - -+--- Adds all passed-in values at the top of an array. As opposed to @{addTop}, it preserves the order -+-- of the passed-in elements. -+-- @name prepend -+-- @param array an array -+-- @param ... a variable number of arguments -+-- @return the passed-in array with new values added -+-- @see addTop -+-- @see push -+function M.prepend(array, ...) -+ return M.append({...}, array) -+end -+ - --- Pushes all passed-in values at the end of an array. - -- @name push - -- @param array an array - -- @param ... a variable number of arguments - -- @return the passed-in array with new added values - -- @see addTop --function _.push(array, ...) -- _.each({...}, function(i,v) array[#array+1] = v end) -+-- @see prepend -+function M.push(array, ...) -+ local args = {...} -+ for k,v in ipairs({...}) do -+ array[#array+1] = v -+ end - return array - end - - --- Removes and returns the values at the top of a given array. ----
    Aliased as `shift` ---- @name pop -+--
    Aliased as `pop` -+-- @name shift - -- @param array an array - -- @param[opt] n the number of values to be popped. Defaults to 1. - -- @return the popped values - -- @see unshift --function _.pop(array, n) -+function M.shift(array, n) - n = min(n or 1, #array) - local ret = {} - for i = 1, n do -@@ -836,8 +1157,8 @@ end - -- @param array an array - -- @param[opt] n the number of values to be unshifted. Defaults to 1. - -- @return the values ---- @see pop --function _.unshift(array, n) -+-- @see shift -+function M.unshift(array, n) - n = min(n or 1, #array) - local ret = {} - for i = 1, n do -@@ -854,84 +1175,83 @@ end - -- @param array an array - -- @param ... a variable number of values to be removed from the array - -- @return the passed-in array with values removed --function _.pull(array, ...) -- for __, rmValue in ipairs({...}) do -- for i = #array, 1, -1 do -- if _.isEqual(array[i], rmValue) then -- t_remove(array, i) -+function M.pull(array, ...) -+ local values = {...} -+ for i = #array, 1, -1 do -+ local remval = false -+ for k, rmValue in ipairs(values) do -+ if (remval == false) then -+ if M.isEqual(array[i], rmValue) then -+ t_remove(array, i) -+ remval = true -+ end - end - end - end - return array - end - ----- Removes values at index within the range `[start, finish]`. -+--- Removes values at an index within the range `[start, finish]`. - --
    Aliased as `rmRange`, `chop` - -- @name removeRange - -- @param array an array - -- @param[opt] start the lower bound index, defaults to the first index in the array. - -- @param[optchain] finish the upper bound index, defaults to the array length. - -- @return the passed-in array with values removed --function _.removeRange(array, start, finish) -- local array = _.clone(array) -- local i,n = (next(array)),#array -- if n < 1 then return array end -- -- start = clamp(start or i,i,n) -- finish = clamp(finish or n,i,n) -- -- if finish < start then return array end -- -- local count = finish - start + 1 -- local i = start -- while count > 0 do -- t_remove(array,i) -- count = count - 1 -+function M.removeRange(array, start, finish) -+ start = start or 1 -+ finish = finish or #array -+ if start > finish then -+ error("start cannot be greater than finish.") -+ end -+ for i = finish, start, -1 do -+ t_remove(array, i) - end - return array - end - - --- Chunks together consecutive values. Values are chunked on the basis of the return ---- value of a provided predicate `f (k, v, ...)`. Consecutive elements which return -+-- value of a provided predicate `f (v, k)`. Consecutive elements which return - -- the same value are chunked together. Leaves the first argument untouched if it is not an array. - -- @name chunk - -- @param array an array ---- @param f an iterator function prototyped as `f (k, v, ...)` ---- @param[opt] ... Optional args to be passed to `f` -+-- @param f an iterator function prototyped as `f (v, k)`. Defaults to @{identity}. - -- @return a table of chunks (arrays) - -- @see zip --function _.chunk(array, f, ...) -- if not _.isArray(array) then return array end -- local ch, ck, prev = {}, 0 -- local mask = _.map(array, f,...) -- _.each(mask, function(k,v) -- prev = (prev==nil) and v or prev -- ck = ((v~=prev) and (ck+1) or ck) -+function M.chunk(array, f) -+ local ch, ck, prev, val = {}, 0 -+ f = f or M.identity -+ for k,v in ipairs(array) do -+ val = f(v, k) -+ ck = ((val~=prev) and (ck+1) or ck) -+ prev = (prev==nil) and val or prev - if not ch[ck] then - ch[ck] = {array[k]} - else - ch[ck][#ch[ck]+1] = array[k] - end -- prev = v -- end) -+ prev = val -+ end - return ch - end - - --- Slices values indexed within `[start, finish]` range. ----
    Aliased as `_.sub` -+--
    Aliased as `M.sub` - -- @name slice - -- @param array an array - -- @param[opt] start the lower bound index, defaults to the first index in the array. - -- @param[optchain] finish the upper bound index, defaults to the array length. - -- @return a new array of sliced values --function _.slice(array, start, finish) -- return _.select(array, function(index) -- return (index >= (start or next(array)) and index <= (finish or #array)) -- end) -+function M.slice(array, start, finish) -+ local t = {} -+ for k = start or 1, finish or #array do -+ t[#t+1] = array[k] -+ end -+ return t - end - - --- Returns the first N values in an array. ----
    Aliased as `head`, `take` -+--
    Aliased as `head`, `take` - -- @name first - -- @param array an array - -- @param[opt] n the number of values to be collected, defaults to 1. -@@ -939,9 +1259,13 @@ end - -- @see initial - -- @see last - -- @see rest --function _.first(array, n) -- local n = n or 1 -- return _.slice(array,1, min(n,#array)) -+function M.first(array, n) -+ n = n or 1 -+ local t = {} -+ for k = 1, n do -+ t[k] = array[k] -+ end -+ return t - end - - --- Returns all values in an array excluding the last N values. -@@ -952,9 +1276,14 @@ end - -- @see first - -- @see last - -- @see rest --function _.initial(array, n) -- if n and n < 0 then return end -- return _.slice(array,1, n and #array-(min(n,#array)) or #array-1) -+function M.initial(array, n) -+ local l = #array -+ n = n and l-(min(n,l)) or l-1 -+ local t = {} -+ for k = 1, n do -+ t[k] = array[k] -+ end -+ return t - end - - --- Returns the last N values in an array. -@@ -965,12 +1294,17 @@ end - -- @see first - -- @see initial - -- @see rest --function _.last(array, n) -- if n and n <= 0 then return end -- return _.slice(array,n and #array-min(n-1,#array-1) or 2,#array) -+function M.last(array, n) -+ local l = #array -+ n = n and l-min(n-1,l-1) or 2 -+ local t = {} -+ for k = n, l do -+ t[#t+1] = array[k] -+ end -+ return t - end - ----- Removes all values before index. -+--- Returns all values after index. - --
    Aliased as `tail` - -- @name rest - -- @param array an array -@@ -979,9 +1313,12 @@ end - -- @see first - -- @see initial - -- @see last --function _.rest(array,index) -- if index and index > #array then return {} end -- return _.slice(array,index and max(1,min(index,#array)) or 1,#array) -+function M.rest(array, index) -+ local t = {} -+ for k = index or 1, #array do -+ t[#t+1] = array[k] -+ end -+ return t - end - - --- Returns the value at a given index. -@@ -989,33 +1326,35 @@ end - -- @param array an array - -- @param index an index - -- @return the value at the given index --function _.nth(array, index) -+function M.nth(array, index) - return array[index] - end - ----- Removes all falsy (false and nil) values. -+--- Returns all truthy values (removes `falses` and `nils`). - -- @name compact - -- @param array an array - -- @return a new array --function _.compact(array) -- return _.reject(array, function (_,value) -- return not value -- end) -+function M.compact(array) -+ local t = {} -+ for k,v in pairs(array) do -+ if v then t[#t+1] = v end -+ end -+ return t - end - - --- Flattens a nested array. Passing `shallow` will only flatten at the first level. - -- @name flatten - -- @param array an array ---- @param[opt] shallow specifies the flattening depth ---- @return a new array, flattened --function _.flatten(array, shallow) -- local shallow = shallow or false -+-- @param[opt] shallow specifies the flattening depth. Defaults to `false`.` -+-- @return a flattened array -+function M.flatten(array, shallow) -+ shallow = shallow or false - local new_flattened - local _flat = {} -- for key,value in pairs(array) do -- if _.isTable(value) then -- new_flattened = shallow and value or _.flatten (value) -- _.each(new_flattened, function(_,item) _flat[#_flat+1] = item end) -+ for key,value in ipairs(array) do -+ if type(value) == 'table' then -+ new_flattened = shallow and value or M.flatten (value) -+ for k,item in ipairs(new_flattened) do _flat[#_flat+1] = item end - else _flat[#_flat+1] = value - end - end -@@ -1031,11 +1370,11 @@ end - -- @see union - -- @see intersection - -- @see symmetricDifference --function _.difference(array, array2) -- if not array2 then return _.clone(array) end -- return _.select(array,function(i,value) -- return not _.include(array2,value) -- end) -+function M.difference(array, array2) -+ if not array2 then return M.clone(array) end -+ return M.select(array,function(value) -+ return not M.include(array2,value) -+ end) - end - - --- Returns the duplicate-free union of all passed in arrays. -@@ -1045,32 +1384,40 @@ end - -- @see difference - -- @see intersection - -- @see symmetricDifference --function _.union(...) -- return _.uniq(_.flatten({...})) -+function M.union(...) -+ return M.unique(M.flatten({...})) - end - - --- Returns the intersection of all passed-in arrays. - -- Each value in the result is present in each of the passed-in arrays. - -- @name intersection ---- @param array an array - -- @param ... a variable number of array arguments - -- @return a new array - -- @see difference - -- @see union - -- @see symmetricDifference --function _.intersection(array, ...) -+function M.intersection(...) - local arg = {...} -+ local array = arg[1] -+ t_remove(arg, 1) - local _intersect = {} - for i,value in ipairs(array) do -- if _.all(arg,function(i,v) -- return _.include(v,value) -- end) then -- t_insert(_intersect,value) -+ if M.all(arg,function(v) return M.include(v,value) end) then -+ _intersect[#_intersect+1] = value - end - end - return _intersect - end - -+--- Checks if all passed in arrays are disjunct. -+-- @name disjoint -+-- @param ... a variable number of arrays -+-- @return `true` if the intersection of all arrays is not empty, `false` otherwise. -+-- @see intersection -+function M.disjoint(...) -+ return (#M.intersection(...) == 0) -+end -+ - --- Performs a symmetric difference. Returns values from `array` not present in `array2` and also values - -- from `array2` not present in `array`. - --
    Aliased as `symdiff` -@@ -1081,10 +1428,10 @@ end - -- @see difference - -- @see union - -- @see intersection --function _.symmetricDifference(array, array2) -- return _.difference( -- _.union(array, array2), -- _.intersection(array,array2) -+function M.symmetricDifference(array, array2) -+ return M.difference( -+ M.union(array, array2), -+ M.intersection(array,array2) - ) - end - -@@ -1094,10 +1441,11 @@ end - -- @param array an array - -- @return a new array, duplicate-free - -- @see isunique --function _.unique(array) -+-- @see duplicates -+function M.unique(array) - local ret = {} - for i = 1, #array do -- if not _.find(ret, array[i]) then -+ if not M.find(ret, array[i]) then - ret[#ret+1] = array[i] - end - end -@@ -1111,8 +1459,25 @@ end - -- @param array an array - -- @return `true` if the given array is unique, `false` otherwise. - -- @see unique --function _.isunique(array) -- return _.isEqual(array, _.unique(array)) -+-- @see duplicates -+function M.isunique(array) -+ return #array == #(M.unique(array)) -+end -+ -+--- Returns an array list of all duplicates in array. -+-- @name duplicates -+-- @param array an array -+-- @return an array-list of duplicates -+-- @see unique -+function M.duplicates(array) -+ local dict = M.invert(array) -+ local dups = {} -+ for k, v in ipairs(array) do -+ if dict[v] ~= k and not M.find(dups, v) then -+ dups[#dups+1] = v -+ end -+ end -+ return dups - end - - --- Merges values of each of the passed-in arrays in subsets. -@@ -1121,24 +1486,45 @@ end - -- @name zip - -- @param ... a variable number of array arguments - -- @return a new array --function _.zip(...) -- local arg = {...} -- local _len = _.max(_.map(arg,function(i,v) -- return #v -- end)) -+-- @see zipWith -+function M.zip(...) -+ local args = {...} -+ local n = M.max(args, function(array) return #array end) -+ local _ans = {} -+ for i = 1,n do -+ if not _ans[i] then _ans[i] = {} end -+ for k, array in ipairs(args) do -+ if (array[i]~= nil) then _ans[i][#_ans[i]+1] = array[i] end -+ end -+ end -+ return _ans -+end -+ -+--- Merges values using a given function. -+-- Only values indexed with the same key in the given arrays are merged in the same subset. -+-- Function `f` is used to combine values. -+--
    Aliased as `transposeWith` -+-- @name zipWith -+-- @param f a function -+-- @param ... a variable number of array arguments -+-- @return a flat array of results -+-- @see zip -+function M.zipWith(f, ...) -+ local args = {...} -+ local n = M.max(args, function(array) return #array end) - local _ans = {} -- for i = 1,_len do -- _ans[i] = _.pluck(arg,i) -+ for i = 1,n do -+ _ans[i] = f(unpack(M.pluck(args,i))) - end - return _ans - end - ----- Clones `array` and appends `other` values. -+--- Clones array and appends values from another array. - -- @name append - -- @param array an array - -- @param other an array - -- @return a new array --function _.append(array, other) -+function M.append(array, other) - local t = {} - for i,v in ipairs(array) do t[i] = v end - for i,v in ipairs(other) do t[#t+1] = v end -@@ -1151,39 +1537,51 @@ end - -- @param ... a variable list of arrays - -- @return a new array - -- @see interpose --function _.interleave(...) return _.flatten(_.zip(...)) end -+function M.interleave(...) -+ local args = {...} -+ local n = M.max(args, M.size) -+ local t = {} -+ for i = 1, n do -+ for k, array in ipairs(args) do -+ if array[i] then t[#t+1] = array[i] end -+ end -+ end -+ return t -+end - ----- Interposes value in-between consecutive pair of values in `array`. -+--- Interposes value in-between consecutive pair of values in array. -+--
    Aliased as `intersperse` - -- @name interpose ---- @param value a value - -- @param array an array -+-- @param value a value - -- @return a new array - -- @see interleave --function _.interpose(value, array) -- return _.flatten(_.zip(array, _.rep(value, #array-1))) -+function M.interpose(array, value) -+ for k = #array, 2,-1 do -+ t_insert(array, k, value) -+ end -+ return array - end - ----- Produces a flexible list of numbers. If one positive value is passed, will count from 0 to that value, ---- with a default step of 1. If two values are passed, will count from the first one to the second one, with the ---- same default step of 1. A third value passed will be considered a step value. -+--- Produces a flexible list of numbers. If one value is passed, will count from 1 to that value, -+-- with a default step of 1 (or -1). If two values are passed, will count from the first one to the second one, -+-- using a default step of 1 (or -1). A third value passed will be considered a step value. - -- @name range - -- @param[opt] from the initial value of the range - -- @param[optchain] to the final value of the range ---- @param[optchain] step the step of count -+-- @param[optchain] step the step of count. Defaults to 1 or -1. - -- @return a new array of numbers --function _.range(...) -- local arg = {...} -- local _start,_stop,_step -- if #arg==0 then return {} -- elseif #arg==1 then _stop,_start,_step = arg[1],0,1 -- elseif #arg==2 then _start,_stop,_step = arg[1],arg[2],1 -- elseif #arg == 3 then _start,_stop,_step = arg[1],arg[2],arg[3] -- end -- if (_step and _step==0) then return {} end -- local _ranged = {} -- local _steps = max(floor((_stop-_start)/_step),0) -- for i=1,_steps do _ranged[#_ranged+1] = _start+_step*i end -- if #_ranged>0 then t_insert(_ranged,1,_start) end -+function M.range(from, to, step) -+ if (from == nil) and (to == nil) and (step ==nil) then -+ return {} -+ elseif (from ~= nil) and (to == nil) and (step == nil) then -+ from, to, step = signum(from), from, signum(from) -+ elseif (from ~= nil) and (to ~= nil) and (step == nil) then -+ step = signum(to - from) -+ end -+ local _ranged = {from} -+ local steps = max(floor((to-from)/step),0) -+ for i=1,steps do _ranged[#_ranged+1] = from+step*i end - return _ranged - end - -@@ -1192,65 +1590,99 @@ end - -- @param value a value to be repeated - -- @param n the number of repetitions of value. - -- @return a new array of `n` values --function _.rep(value, n) -+function M.rep(value, n) - local ret = {} -- for i = 1, n do ret[#ret+1] = value end -+ for i = 1, n do ret[i] = value end - return ret - end - -+--- Returns the powerset of array values. For instance, when given the set {1,2,3}, -+-- returns `{{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}}`. -+-- @name powerset -+-- @param array an array -+-- @return an array -+function M.powerset(array) -+ local n = #array -+ local powerset = {} -+ for i, v in ipairs(array) do -+ for j = 1, #powerset do -+ local set = powerset[j] -+ t_insert(powerset, M.push(M.slice(set), v)) -+ end -+ t_insert(powerset, {v}) -+ end -+ t_insert(powerset, {}) -+ return powerset -+end -+ - --- Iterator returning partitions of an array. It returns arrays of length `n` - -- made of values from the given array. If the last partition has lower elements than `n` and - -- `pad` is supplied, it will be adjusted to `n` of elements with `pad` value. - -- @name partition - -- @param array an array ---- @param[opt] n the size of partitions. Should be greater than 0. Defaults to 1. ---- @param[optchain] pad a value to adjust the last subsequence to the `n` elements -+-- @param[opt] n the size of partitions. Defaults to 1. -+-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements - -- @return an iterator function --function _.partition(array, n, pad) -+-- @see overlapping -+-- @see aperture -+function M.partition(array, n, pad) - if n<=0 then return end -- return coroutine.wrap(function() -- partgen(array, n or 1, coroutine.yield, pad) -+ return wrap(function() -+ partgen(array, n or 1, yield, pad) - end) - end - ----- Iterator returning sliding partitions of an array. It returns overlapping subsequences ---- of length `n`. If the last subsequence has lower elements than `n` and `pad` is -+--- Iterator returning overlapping partitions of an array. -+-- If the last subsequence has lower elements than `n` and `pad` is - -- supplied, it will be adjusted to `n` elements with `pad` value. ---- @name sliding. -+-- @name overlapping -+-- @param array an array -+-- @param[opt] n the size of partitions. Defaults to 2. -+-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements -+-- @return an iterator function -+-- @see partition -+-- @see aperture -+function M.overlapping(array, n, pad) -+ if n<=1 then return end -+ return wrap(function() -+ partgen2(array, n or 2, yield, pad) -+ end) -+end -+ -+--- Iterator returning sliding partitions of an array. -+--
    Aliased as `sliding` -+-- @name aperture - -- @param array an array ---- @param[opt] n the size of partitions. Should be greater than 1. Defaults to 2. ---- @param[optchain] pad a value to adjust the last subsequence to the `n` elements -+-- @param[opt] n the size of partitions. Defaults to 2 (and then behaves like @{pairwise}) - -- @return an iterator function --function _.sliding(array, n, pad) -+-- @see partition -+-- @see overlapping -+-- @see pairwise -+function M.aperture(array, n) - if n<=1 then return end -- return coroutine.wrap(function() -- partgen2(array, n or 2, coroutine.yield, pad) -+ return wrap(function() -+ partgen3(array, n or 2, yield) - end) - end - -+--- Iterator returning sliding pairs of an array. -+-- @name pairwise -+-- @param array an array -+-- @return an iterator function -+-- @see overlapping -+function M.pairwise(array) return M.aperture(array, 2) end -+ - --- Iterator returning the permutations of an array. It returns arrays made of all values - -- from the passed-in array, with values permuted. - -- @name permutation - -- @param array an array - -- @return an iterator function --function _.permutation(array) -- return coroutine.wrap(function() -- permgen(array, #array, coroutine.yield) -+function M.permutation(array) -+ return wrap(function() -+ permgen(array, #array, yield) - end) - end - ----- Swaps keys with values. Produces a new array where previous keys are now values, ---- while previous values are now keys. ----
    Aliased as `mirror` ---- @name invert ---- @param array a given array ---- @return a new array --function _.invert(array) -- local _ret = {} -- _.each(array,function(i,v) _ret[v] = i end) -- return _ret --end -- - --- Concatenates values in a given array. Handles booleans as well. If `sep` string is - -- passed, it will be used as a separator. Passing `i` and `j` will result in concatenating - -- only values within `[i, j]` range. -@@ -1261,54 +1693,245 @@ end - -- @param[optchain] i the starting index, defaults to 1. - -- @param[optchain] j the final index, defaults to the array length. - -- @return a string --function _.concat(array, sep, i, j) -- local _array = _.map(array,function(i,v) -- return tostring(v) -- end) -- return t_concat(_array,sep,i or 1,j or #array) -+function M.concat(array, sep, i, j) -+ return t_concat(M.map(array,tostring),sep,i,j) -+end -+ -+--- Returns all possible pairs built from given arrays. -+-- @name xprod -+-- @param array a first array -+-- @param array2 a second array -+-- @return an array list of all pairs -+function M.xprod(array, array2) -+ local p = {} -+ for i, v1 in ipairs(array) do -+ for j, v2 in ipairs(array2) do -+ p[#p+1] = {v1, v2} -+ end -+ end -+ return p -+end -+ -+--- Creates pairs from value and array. Value is always prepended to the pair. -+-- @name xpairs -+-- @param valua a value -+-- @param array an array -+-- @return an array list of all pairs -+function M.xpairs(value, array) -+ local xpairs = {} -+ for k, v in ipairs(array) do -+ xpairs[k] = {value, v} -+ end -+ return xpairs -+end - -+--- Creates pairs from value and array. Value is always appended as the last item to the pair. -+-- @name xpairsRight -+-- @param valua a value -+-- @param array an array -+-- @return an array list of all pairs -+function M.xpairsRight(value, array) -+ local xpairs = {} -+ for k, v in ipairs(array) do -+ xpairs[k] = {v, value} -+ end -+ return xpairs -+end -+ -+--- Returns the sum of array values. -+-- @name sum -+-- @param array a given array -+-- @return the sum of array values -+function M.sum(array) -+ local s = 0 -+ for k, v in ipairs(array) do s = s + v end -+ return s -+end -+ -+--- Returns the product of array values. -+-- @name product -+-- @param array a given array -+-- @return the product of array values -+function M.product(array) -+ local p = 1 -+ for k, v in ipairs(array) do p = p * v end -+ return p -+end -+ -+--- Returns the mean of an array of numbers. -+--
    Aliased as `average` -+-- @name mean -+-- @param array an array of numbers -+-- @return a number -+-- @see sum -+-- @see product -+-- @see median -+function M.mean(array) -+ return M.sum(array)/(#array) -+end -+ -+--- Returns the median of an array of numbers. -+-- @name median -+-- @param array an array of numbers -+-- @return a number -+-- @see sum -+-- @see product -+-- @see mean -+function M.median(array) -+ local t = M.sort(M.clone(array)) -+ local n = #t -+ if n == 0 then -+ return -+ elseif n==1 then -+ return t[1] -+ end -+ local mid = ceil(n/2) -+ return n%2==0 and (t[mid] + t[mid+1])/2 or t[mid] - end - - --- Utility functions - -- @section Utility functions - ----- The no-operation function. -+--- The no operation function. - -- @name noop - -- @return nothing --function _.noop() return end -+function M.noop() return end - - --- Returns the passed-in value. This function is used internally - -- as a default iterator. - -- @name identity - -- @param value a value - -- @return the passed-in value --function _.identity(value) return value end -+function M.identity(value) return value end -+ -+--- Calls `f` with the supplied arguments. Returns the results of `f(...)`. -+-- @name call -+-- @param f a function -+-- @param[opt] ... a vararg list of args to `f` -+-- @return the result of `f(...)` call. -+function M.call(f, ...) -+ return f(...) -+end - - --- Creates a constant function which returns the same output on every call. -+--
    Aliased as `always` - -- @name constant - -- @param value a constant value - -- @return a constant function --function _.constant(value) return function() return value end end -+function M.constant(value) -+ return function() return value end -+end -+ -+--- Returns a function which applies `specs` on args. This function produces an object having -+-- the same structure than `specs` by mapping each property to the result of calling its -+-- associated function with the supplied arguments -+-- @name applySpec -+-- @param specs a table -+-- @return a function -+function M.applySpec(specs) -+ return function (...) -+ local spec = {} -+ for i, f in pairs(specs) do spec[i] = f(...) end -+ return spec -+ end -+end -+ -+--- Threads `value` through a series of functions. If a function expects more than one args, -+-- it can be specified using an array list, where the first item is the function and the following -+-- are the remaining args neeeded. The value is used as the first input. -+-- @name thread -+-- @param value a value -+-- @param ... a vararg list of functions or arrays -+-- @return a value -+-- @see threadRight -+function M.thread(value, ...) -+ local state = value -+ local arg = {...} -+ for k, t in ipairs(arg) do -+ if type(t) == 'function' then -+ state = t(state) -+ elseif type(t) == 'table' then -+ local f = t[1] -+ t_remove(t, 1) -+ state = M.reduce(t, f, state) -+ end -+ end -+ return state -+end -+ -+--- Threads `value` through a series of functions. If a function expects more than one args, -+-- it can be specified using an array list, where the first item is the function and the following -+-- are the remaining args neeeded. The value is used as the last input. -+-- @name threadRight -+-- @param value a value -+-- @param ... a vararg list of functions or arrays -+-- @return a value -+-- @see thread -+function M.threadRight(value, ...) -+ local state = value -+ local arg = {...} -+ for k, t in ipairs(arg) do -+ if type(t) == 'function' then -+ state = t(state) -+ elseif type(t) == 'table' then -+ local f = t[1] -+ t_remove(t, 1) -+ t_insert(t, state) -+ state = M.reduce(t, f) -+ end -+ end -+ return state -+end -+ -+--- Returns a dispatching function. When called with arguments, this function invokes each of its functions -+-- in the passed-in order and returns the results of the first non-nil evaluation. -+-- @name dispatch -+-- @param ... a vararg list of functions -+-- @return a dispatch function -+function M.dispatch(...) -+ local funcs = {...} -+ return function (...) -+ for k, f in ipairs(funcs) do -+ local r = {f(...)} -+ if #r > 0 then return unpack(r) end -+ end -+ end -+end - - --- Memoizes a given function by caching the computed result. ---- Useful for speeding-up slow-running functions. If a `hash` function is passed, ---- it will be used to compute hash keys for a set of input values for caching. -+-- Useful for speeding-up slow-running functions. - --
    Aliased as `cache` - -- @name memoize - -- @param f a function ---- @param[opt] hash a hash function, defaults to @{identity} - -- @return a new function --function _.memoize(f, hash) -+function M.memoize(f) - local _cache = setmetatable({},{__mode = 'kv'}) -- local _hasher = hash or _.identity -- return function (...) -- local _hashKey = _hasher(...) -- local _result = _cache[_hashKey] -- if not _result then _cache[_hashKey] = f(...) end -- return _cache[_hashKey] -+ return function (key) -+ if (_cache[key] == nil) then -+ _cache[key] = f(key) -+ end -+ return _cache[key] - end - end - -+--- Builds a list from a seed value. Accepts an iterator function, which -+-- returns either nil to stop iteration or two values : the value to add to the list -+-- of results and the seed to be used in the next call to the iterator function. -+-- @name unfold -+-- @param f an iterator function -+-- @param seed a seed value -+-- @return an array of values -+function M.unfold(f, seed) -+ local t, result = {} -+ while true do -+ result, seed = f(seed) -+ if result ~= nil then t[#t+1] = result -+ else break -+ end -+ end -+ return t -+end -+ - --- Returns a version of `f` that runs only once. Successive calls to `f` - -- will keep yielding the same output, no matter what the passed-in arguments are. - -- It can be used to initialize variables. -@@ -1317,7 +1940,7 @@ end - -- @return a new function - -- @see before - -- @see after --function _.once(f) -+function M.once(f) - local _internal = 0 - local _args = {} - return function(...) -@@ -1327,7 +1950,7 @@ function _.once(f) - end - end - ----- Returns a version of `f` that will run no more than `count` times. Next calls will -+--- Returns a version of `f` that will run no more than count times. Next calls will - -- keep yielding the results of the count-th call. - -- @name before - -- @param f a function -@@ -1335,7 +1958,7 @@ end - -- @return a new function - -- @see once - -- @see after --function _.before(f, count) -+function M.before(f, count) - local _internal = 0 - local _args = {} - return function(...) -@@ -1353,7 +1976,7 @@ end - -- @return a new function - -- @see once - -- @see before --function _.after(f, count) -+function M.after(f, count) - local _limit,_internal = count, 0 - return function(...) - _internal = _internal+1 -@@ -1367,9 +1990,9 @@ end - -- @param ... a variable number of functions - -- @return a new function - -- @see pipe --function _.compose(...) -+function M.compose(...) - -- See: https://github.com/Yonaba/Moses/pull/15#issuecomment-139038895 -- local f = _.reverse {...} -+ local f = M.reverse {...} - return function (...) - local first, _temp = true - for i, func in ipairs(f) do -@@ -1391,8 +2014,8 @@ end - -- @param ... a variable number of functions - -- @return the result of the composition of function calls. - -- @see compose --function _.pipe(value, ...) -- return _.compose(...)(value) -+function M.pipe(value, ...) -+ return M.compose(...)(value) - end - - --- Returns the logical complement of a given function. For a given input, the returned -@@ -1401,7 +2024,7 @@ end - -- @name complement - -- @param f a function - -- @return the logical complement of the given function `f`. --function _.complement(f) -+function M.complement(f) - return function(...) return not f(...) end - end - -@@ -1412,9 +2035,11 @@ end - -- @param value a value - -- @param ... a variable number of functions - -- @return a list of results --function _.juxtapose(value, ...) -+function M.juxtapose(value, ...) - local res = {} -- _.each({...}, function(_,f) res[#res+1] = f(value) end) -+ for i, func in ipairs({...}) do -+ res[i] = func(value) -+ end - return unpack(res) - end - -@@ -1425,20 +2050,19 @@ end - -- @param f a function to be wrapped, prototyped as `f (...)` - -- @param wrapper a wrapper function, prototyped as `wrapper (f, ...)` - -- @return the results --function _.wrap(f, wrapper) -+function M.wrap(f, wrapper) - return function (...) return wrapper(f,...) end - end - - --- Runs `iter` function `n` times. Collects the results of each run and returns them in an array. - -- @name times ---- @param n the number of times `iter` should be called ---- @param iter an iterator function, prototyped as `iter (i, ...)` ---- @param ... args to be passed to `iter` function -+-- @param iter an iterator function, prototyped as `iter (i)` -+-- @param[opt] n the number of times `iter` should be called. Defaults to 1. - -- @return table an array of results --function _.times(n, iter, ...) -+function M.times(iter, n) - local results = {} -- for i = 1,n do -- results[i] = iter(i,...) -+ for i = 1, (n or 1) do -+ results[i] = iter(i) - end - return results - end -@@ -1450,11 +2074,11 @@ end - -- @return a function - -- @see bind2 - -- @see bindn ---- @see bindAll --function _.bind(f, v) -+-- @see bindall -+function M.bind(f, v) - return function (...) -- return f(v,...) -- end -+ return f(v,...) -+ end - end - - --- Binds `v` to be the second argument to `f`. Calling `f (a, ...)` will result to `f (a, v, ...)`. -@@ -1464,8 +2088,8 @@ end - -- @return a function - -- @see bind - -- @see bindn ---- @see bindAll --function _.bind2(f, v) -+-- @see bindall -+function M.bind2(f, v) - return function (t, ...) - return f(t, v, ...) - end -@@ -1479,86 +2103,246 @@ end - -- @return a function - -- @see bind - -- @see bind2 ---- @see bindAll --function _.bindn(f, ...) -- local iArg = {...} -+-- @see bindall -+function M.bindn(f, ...) -+ local args = {...} - return function (...) -- return f(unpack(_.append(iArg,{...}))) -+ return f(unpack(M.append(args,{...}))) - end - end - - --- Binds methods to object. As such, whenever any of these methods is invoked, it - -- always receives the object as its first argument. ---- @name bindAll -+-- @name bindall - -- @param obj an abject - -- @param ... a variable number of method names - -- @return the passed-in object with all methods bound to the object itself. - -- @see bind - -- @see bind2 - -- @see bindn --function _.bindAll(obj, ...) -+function M.bindall(obj, ...) - local methodNames = {...} -- for __, methodName in ipairs(methodNames) do -+ for i, methodName in ipairs(methodNames) do - local method = obj[methodName] -- if method then obj[methodName] = _.bind(method, obj) end -+ if method then obj[methodName] = M.bind(method, obj) end - end - return obj - end - -+--- Returns a function which iterate over a set of conditions. It invokes each predicate, -+-- passing it given values. It returns the value of the corresponding function of the first -+-- predicate to return a non-nil value. -+-- @name cond -+-- @param conds an array list of predicate-function pairs -+-- @return the result of invoking `f(...)` of the first predicate to return a non-nil value -+function M.cond(conds) -+ return function(...) -+ for k, condset in ipairs(conds) do -+ if condset[1](...) then -+ return condset[2](...) -+ end -+ end -+ end -+end -+ -+--- Returns a validation function. Given a set of functions, the validation function evaluates -+-- to `true` only when all its funcs returns `true`. -+-- @name both -+-- @param ... an array list of functions -+-- @return `true` when all given funcs returns true with input, false otherwise -+function M.both(...) -+ local funcs = {...} -+ return function (...) -+ for k, f in ipairs(funcs) do -+ if not f(...) then return false end -+ end -+ return true -+ end -+end -+ -+--- Returns a validation function. Given a set of functions, the validation function evaluates -+-- to `true` when at least one of its funcs returns `true`. -+-- @name either -+-- @param ... an array list of functions -+-- @return `true` when one of the given funcs returns `true` with input, `false` otherwise -+function M.either(...) -+ local funcs = {...} -+ return function (...) -+ for k, f in ipairs(funcs) do -+ if f(...) then return true end -+ end -+ return false -+ end -+end -+ -+--- Returns a validation function. Given a set of functions, the validation function evaluates -+-- to `true` when neither of its func return `true`. -+-- @name neither -+-- @param ... an array list of functions -+-- @return `true` when neither of the given funcs returns `true` with input, `false` otherwise -+function M.neither(...) -+ local funcs = {...} -+ return function (...) -+ for k, f in ipairs(funcs) do -+ if f(...) then return false end -+ end -+ return true -+ end -+end -+ - --- Generates an unique ID for the current session. If given a string `template`, it - -- will use this template for output formatting. Otherwise, if `template` is a function, it ---- will evaluate `template (id, ...)`. -+-- will evaluate `template (id)`. - --
    Aliased as `uid`. - -- @name uniqueId - -- @param[opt] template either a string or a function template to format the ID ---- @param[optchain] ... a variable number of arguments to be passed to `template`, in case it is a function. - -- @return value an ID --function _.uniqueId(template, ...) -+function M.uniqueId(template) - unique_id_counter = unique_id_counter + 1 - if template then -- if _.isString(template) then -+ if type(template) == 'string' then - return template:format(unique_id_counter) -- elseif _.isFunction(template) then -- return template(unique_id_counter,...) -+ elseif type(template) == 'function' then -+ return template(unique_id_counter) - end - end - return unique_id_counter - end - - --- Produces an iterator which repeatedly apply a function `f` onto an input. ---- Yields x, then f(x), then f(f(x)), continuously. -+-- Yields `value`, then `f(value)`, then `f(f(value))`, continuously. -+--
    Aliased as `iter`. - -- @name iterator - -- @param f a function ---- @param x an initial input to `f` ---- @return an iterator fnction ----
    Aliased as `iter`. --function _.iterator(f, x) -+-- @param value an initial input to `f` -+-- @param[opt] n the number of times the iterator should run -+-- @return an iterator function -+function M.iterator(f, value, n) -+ local cnt = 0 - return function() -- x = f(x) -- return x -+ cnt = cnt + 1 -+ if n and cnt > n then return end -+ value = f(value) -+ return value - end - end - ----- Iterates an iterator and returns its values in an array. ---- @name array ---- @param ... an iterator (a function, a table and a value) -+--- Consumes the first `n` values of a iterator then returns it. -+-- @name skip -+-- @param iter an iterator function -+-- @param[opt] n a number. Defaults to 1. -+-- @return the given iterator -+function M.skip(iter, n) -+ for i = 1, (n or 1) do -+ if iter() == nil then return end -+ end -+ return iter -+end -+ -+--- Iterates over an iterator and returns its values in an array. -+-- @name tabulate -+-- @param ... an iterator function (returning a generator, a state and a value) - -- @return an array of results --function _.array(...) -+function M.tabulate(...) - local r = {} - for v in ... do r[#r+1] = v end - return r - end - -+--- Returns the length of an iterator. It consumes the iterator itself. -+-- @name iterlen -+-- @param ... an iterator function (returning a generator, a state and a value) -+-- @return the iterator length -+function M.iterlen(...) -+ local l = 0 -+ for v in ... do l = l + 1 end -+ return l -+end -+ -+--- Casts value as an array if it is not one. -+-- @name castArray -+-- @param value a value -+-- @return an array containing the given value -+function M.castArray(value) -+ return (type(value)~='table') and {value} or value -+end -+ - --- Creates a function of `f` with arguments flipped in reverse order. - -- @name flip - -- @param f a function - -- @return a function --function _.flip(f) -+function M.flip(f) - return function(...) -- return f(unpack(_.reverse({...}))) -+ return f(unpack(M.reverse({...}))) - end - end - -+--- Returns a function that gets the nth argument. -+-- If n is negative, the nth argument from the end is returned. -+-- @name nthArg -+-- @param n a number -+-- @return a function -+function M.nthArg(n) -+ return function (...) -+ local args = {...} -+ return args[(n < 0) and (#args + n + 1) or n] -+ end -+end -+ -+--- Returns a function which accepts up to one arg. It ignores any additional arguments. -+-- @name unary -+-- @param f a function -+-- @return a function -+-- @see ary -+function M.unary(f) -+ return function (...) -+ local args = {...} -+ return f(args[1]) -+ end -+end -+ -+--- Returns a function which accepts up to `n` args. It ignores any additional arguments. -+--
    Aliased as `nAry`. -+-- @name ary -+-- @param f a function -+-- @param[opt] n a number. Defaults to 1. -+-- @return a function -+-- @see unary -+function M.ary(f, n) -+ n = n or 1 -+ return function (...) -+ local args = {...} -+ local fargs = {} -+ for i = 1, n do fargs[i] = args[i] end -+ return f(unpack(fargs)) -+ end -+end -+ -+--- Returns a function with an arity of 0. The new function ignores any arguments passed to it. -+-- @name noarg -+-- @param f a function -+-- @return a new function -+function M.noarg(f) -+ return function () -+ return f() -+ end -+end -+ -+--- Returns a function which runs with arguments rearranged. Arguments are passed to the -+-- returned function in the order of supplied `indexes` at call-time. -+-- @name rearg -+-- @param f a function -+-- @param indexes an array list of indexes -+-- @return a function -+function M.rearg(f, indexes) -+ return function(...) -+ local args = {...} -+ local reargs = {} -+ for i, arg in ipairs(indexes) do reargs[i] = args[arg] end -+ return f(unpack(reargs)) -+ end -+end -+ - --- Creates a function that runs transforms on all arguments it receives. - -- @name over - -- @param ... a set of functions which will receive all arguments to the returned function -@@ -1566,11 +2350,11 @@ end - -- @see overEvery - -- @see overSome - -- @see overArgs --function _.over(...) -+function M.over(...) - local transforms = {...} - return function(...) - local r = {} -- for __,transform in ipairs(transforms) do -+ for i,transform in ipairs(transforms) do - r[#r+1] = transform(...) - end - return r -@@ -1585,10 +2369,10 @@ end - -- @see over - -- @see overSome - -- @see overArgs --function _.overEvery(...) -- local f = _.over(...) -+function M.overEvery(...) -+ local f = M.over(...) - return function(...) -- return _.reduce(f(...),function(state,v) return state and v end) -+ return M.reduce(f(...),function(state,v) return state and v end) - end - end - -@@ -1600,10 +2384,10 @@ end - -- @see over - -- @see overEvery - -- @see overArgs --function _.overSome(...) -- local f = _.over(...) -+function M.overSome(...) -+ local f = M.over(...) - return function(...) -- return _.reduce(f(...),function(state,v) return state or v end) -+ return M.reduce(f(...),function(state,v) return state or v end) - end - end - -@@ -1616,20 +2400,28 @@ end - -- @see over - -- @see overEvery - -- @see overSome --function _.overArgs(f,...) -+function M.overArgs(f,...) - local _argf = {...} - return function(...) - local _args = {...} - for i = 1,#_argf do -- local f = _argf[i] -- if _args[i] then _args[i] = f(_args[i]) end -+ local func = _argf[i] -+ if _args[i] then _args[i] = func(_args[i]) end - end - return f(unpack(_args)) - end - end - -+--- Converges two functions into one. -+-- @name converge -+-- @param f a function -+-- @param g a function -+-- @param h a function -+-- @return a new version of function f -+function M.converge(f, g, h) return function(...) return f(g(...),h(...)) end end -+ - --- Partially apply a function by filling in any number of its arguments. ---- One may pass a string `'_'` as a placeholder in the list of arguments to specify an argument -+-- One may pass a string `'M'` as a placeholder in the list of arguments to specify an argument - -- that should not be pre-filled, but left open to be supplied at call-time. - -- @name partial - -- @param f a function -@@ -1637,15 +2429,15 @@ end - -- @return a new version of function f having some of it original arguments filled - -- @see partialRight - -- @see curry --function _.partial(f,...) -+function M.partial(f,...) - local partial_args = {...} - return function (...) - local n_args = {...} - local f_args = {} - for k,v in ipairs(partial_args) do -- f_args[k] = (v == '_') and _.pop(n_args) or v -+ f_args[k] = (v == '_') and M.shift(n_args) or v - end -- return f(unpack(_.append(f_args,n_args))) -+ return f(unpack(M.append(f_args,n_args))) - end - end - -@@ -1656,15 +2448,15 @@ end - -- @return a new version of function f having some of it original arguments filled - -- @see partialRight - -- @see curry --function _.partialRight(f,...) -+function M.partialRight(f,...) - local partial_args = {...} - return function (...) - local n_args = {...} - local f_args = {} - for k = 1,#partial_args do -- f_args[k] = (partial_args[k] == '_') and _.pop(n_args) or partial_args[k] -+ f_args[k] = (partial_args[k] == '_') and M.shift(n_args) or partial_args[k] - end -- return f(unpack(_.append(n_args, f_args))) -+ return f(unpack(M.append(n_args, f_args))) - end - end - -@@ -1677,7 +2469,7 @@ end - -- @return a curried version of `f` - -- @see partial - -- @see partialRight --function _.curry(f, n_args) -+function M.curry(f, n_args) - n_args = n_args or 2 - local _args = {} - local function scurry(v) -@@ -1699,67 +2491,133 @@ end - -- @param f a function - -- @param[opt] ... optional args to `f` - -- @return the execution time and the results of `f (...)` --function _.time(f, ...) -+function M.time(f, ...) - local stime = clock() - local r = {f(...)} - return clock() - stime, unpack(r) - end - - --- Object functions ----@section Object functions -+-- @section Object functions - - --- Returns the keys of the object properties. - -- @name keys - -- @param obj an object - -- @return an array --function _.keys(obj) -- local _oKeys = {} -- _.each(obj,function(key) _oKeys[#_oKeys+1]=key end) -- return _oKeys -+function M.keys(obj) -+ local keys = {} -+ for key in pairs(obj) do keys[#keys+1] = key end -+ return keys - end - - --- Returns the values of the object properties. - -- @name values - -- @param obj an object ---- @return an array --function _.values(obj) -- local _oValues = {} -- _.each(obj,function(_,value) _oValues[#_oValues+1]=value end) -- return _oValues -+-- @return an array of values -+function M.values(obj) -+ local values = {} -+ for key, value in pairs(obj) do values[#values+1] = value end -+ return values -+end -+ -+--- Returns the value at a given path in an object. -+-- Path is given as a vararg list of keys. -+-- @name path -+-- @param obj an object -+-- @param ... a vararg list of keys -+-- @return a value or nil -+function M.path(obj, ...) -+ local value, path = obj, {...} -+ for i, p in ipairs(path) do -+ if (value[p] == nil) then return end -+ value = value[p] -+ end -+ return value - end - ----- Converts keys and values a an array-list of [k, v]. -+--- Spreads object under property path onto provided object. -+-- It is similar to @{flattenPath}, but removes object under the property path. -+-- @name spreadPath -+-- @param obj an object -+-- @param ... a property path given as a vararg list -+-- @return the passed-in object with changes -+-- @see flattenPath -+function M.spreadPath(obj, ...) -+ local path = {...} -+ for _, p in ipairs(path) do -+ if obj[p] then -+ for k, v in pairs(obj[p]) do -+ obj[k] = v -+ obj[p][k] = nil -+ end -+ end -+ end -+ return obj -+end -+ -+--- Flattens object under property path onto provided object. -+-- It is similar to @{spreadPath}, but preserves object under the property path. -+-- @name flattenPath -+-- @param obj an object -+-- @param ... a property path given as a vararg list -+-- @return the passed-in object with changes -+-- @see spreadPath -+function M.flattenPath(obj, ...) -+ local path = {...} -+ for _, p in ipairs(path) do -+ if obj[p] then -+ for k, v in pairs(obj[p]) do obj[k] = v end -+ end -+ end -+ return obj -+end -+ -+--- Converts key-value pairs to an array-list of `[k, v]` pairs. - -- @name kvpairs - -- @param obj an object ---- @return an array list of key-values pairs -+-- @return an array list of key-value pairs - -- @see toObj --function _.kvpairs(obj) -+function M.kvpairs(obj) - local t = {} -- _.each(obj, function(k,v) t[#t+1] = {k,v} end) -+ for k,v in pairs(obj) do t[#t+1] = {k,v} end - return t - end - ----- Converts an array list of `kvpairs` to an object. Keys are taken ---- from the 1rst column in the `kvpairs` sequence, associated with values in the 2nd ---- column -+--- Converts an array list of `[k,v]` pairs to an object. Keys are taken -+-- from the 1rst column in the `[k,v]` pairs sequence, associated with values in the 2nd -+-- column. - -- @name toObj ---- @param kvpairs an array-list of `kvpairs` -+-- @param kvpairs an array-list of `[k,v]` pairs - -- @return an object - -- @see kvpairs --function _.toObj(kvpairs) -+function M.toObj(kvpairs) - local obj = {} -- for __, v in ipairs(kvpairs) do -+ for k, v in ipairs(kvpairs) do - obj[v[1]] = v[2] - end - return obj - end - -+--- Swaps keys with values. Produces a new object where previous keys are now values, -+-- while previous values are now keys. -+--
    Aliased as `mirror` -+-- @name invert -+-- @param obj a given object -+-- @return a new object -+function M.invert(obj) -+ local _ret = {} -+ for k, v in pairs(obj) do -+ _ret[v] = k -+ end -+ return _ret -+end -+ - --- Returns a function that will return the key property of any passed-in object. - -- @name property - -- @param key a key property name - -- @return a function which should accept an object as argument - -- @see propertyOf --function _.property(key) -+function M.property(key) - return function(obj) return obj[key] end - end - -@@ -1768,7 +2626,7 @@ end - -- @param obj an object - -- @return a function which should accept a key property argument - -- @see property --function _.propertyOf(obj) -+function M.propertyOf(obj) - return function(key) return obj[key] end - end - -@@ -1776,7 +2634,7 @@ end - -- @name toBoolean - -- @param value a value. Can be of any type - -- @return `true` if value is true, `false` otherwise (false or nil). --function _.toBoolean(value) -+function M.toBoolean(value) - return not not value - end - -@@ -1787,15 +2645,13 @@ end - -- @param destObj a destination object - -- @param ... a list of objects - -- @return the destination object extended --function _.extend(destObj, ...) -+function M.extend(destObj, ...) - local sources = {...} -- _.each(sources,function(__,source) -- if _.isTable(source) then -- _.each(source,function(key,value) -- destObj[key] = value -- end) -+ for k, source in ipairs(sources) do -+ if type(source) == 'table' then -+ for key, value in pairs(source) do destObj[key] = value end - end -- end) -+ end - return destObj - end - -@@ -1806,25 +2662,24 @@ end - -- @name functions - -- @param[opt] obj an object. Defaults to Moses library functions. - -- @return an array-list of methods names --function _.functions(obj, recurseMt) -- obj = obj or _ -+function M.functions(obj, recurseMt) -+ obj = obj or M - local _methods = {} -- _.each(obj,function(key,value) -- if _.isFunction(value) then -- _methods[#_methods+1]=key -+ for key, value in pairs(obj) do -+ if type(value) == 'function' then -+ _methods[#_methods+1] = key - end -- end) -- if not recurseMt then -- return _.sort(_methods) - end -- local mt = getmetatable(obj) -- if mt and mt.__index then -- local mt_methods = _.functions(mt.__index) -- _.each(mt_methods, function(k,fn) -- _methods[#_methods+1] = fn -- end) -+ if recurseMt then -+ local mt = getmetatable(obj) -+ if mt and mt.__index then -+ local mt_methods = M.functions(mt.__index, recurseMt) -+ for k, fn in ipairs(mt_methods) do -+ _methods[#_methods+1] = fn -+ end -+ end - end -- return _.sort(_methods) -+ return _methods - end - - --- Clones a given object properties. If `shallow` is passed will also clone nested array properties. -@@ -1832,19 +2687,19 @@ end - -- @param obj an object - -- @param[opt] shallow whether or not nested array-properties should be cloned, defaults to false. - -- @return a copy of the passed-in object --function _.clone(obj, shallow) -- if not _.isTable(obj) then return obj end -+function M.clone(obj, shallow) -+ if type(obj) ~= 'table' then return obj end - local _obj = {} -- _.each(obj,function(i,v) -- if _.isTable(v) then -+ for i,v in pairs(obj) do -+ if type(v) == 'table' then - if not shallow then -- _obj[i] = _.clone(v,shallow) -+ _obj[i] = M.clone(v,shallow) - else _obj[i] = v - end - else - _obj[i] = v - end -- end) -+ end - return _obj - end - -@@ -1853,11 +2708,10 @@ end - -- on intermediate results within the chain. - -- @name tap - -- @param obj an object ---- @param f an interceptor function, should be prototyped as `f (obj, ...)` ---- @param[opt] ... args to be passed to `f` -+-- @param f an interceptor function, should be prototyped as `f (obj)` - -- @return the passed-in object --function _.tap(obj, f, ...) -- f(obj,...) -+function M.tap(obj, f) -+ f(obj) - return obj - end - -@@ -1866,7 +2720,7 @@ end - -- @param obj an object - -- @param key a key property to be checked - -- @return `true` or `false` --function _.has(obj, key) -+function M.has(obj, key) - return obj[key]~=nil - end - -@@ -1876,14 +2730,14 @@ end - -- @param obj an object - -- @param ... a variable number of string keys - -- @return the filtered object --function _.pick(obj, ...) -- local whitelist = _.flatten {...} -+function M.pick(obj, ...) -+ local whitelist = M.flatten {...} - local _picked = {} -- _.each(whitelist,function(key,property) -- if not _.isNil(obj[property]) then -- _picked[property] = obj[property] -- end -- end) -+ for key, property in pairs(whitelist) do -+ if (obj[property])~=nil then -+ _picked[property] = obj[property] -+ end -+ end - return _picked - end - -@@ -1893,14 +2747,14 @@ end - -- @param obj an object - -- @param ... a variable number of string keys - -- @return the filtered object --function _.omit(obj, ...) -- local blacklist = _.flatten {...} -+function M.omit(obj, ...) -+ local blacklist = M.flatten {...} - local _picked = {} -- _.each(obj,function(key,value) -- if not _.include(blacklist,key) then -- _picked[key] = value -- end -- end) -+ for key, value in pairs(obj) do -+ if not M.include(blacklist,key) then -+ _picked[key] = value -+ end -+ end - return _picked - end - -@@ -1908,12 +2762,13 @@ end - --
    Aliased as `defaults`. - -- @name template - -- @param obj an object ---- @param[opt] template a template object. Defaults to an empty table `{}`. -+-- @param[opt] template a template object. If `nil`, leaves `obj` untouched. - -- @return the passed-in object filled --function _.template(obj, template) -- _.each(template or {},function(i,v) -- if not obj[i] then obj[i] = v end -- end) -+function M.template(obj, template) -+ if not template then return obj end -+ for i, v in pairs(template) do -+ if not obj[i] then obj[i] = v end -+ end - return obj - end - -@@ -1921,13 +2776,14 @@ end - -- (by reference), nil, booleans. Compares tables by reference or by values. If `useMt` - -- is passed, the equality operator `==` will be used if one of the given objects has a - -- metatable implementing `__eq`. ----
    Aliased as `_.compare` -+--
    Aliased as `M.compare`, `M.matches` - -- @name isEqual - -- @param objA an object - -- @param objB another object - -- @param[opt] useMt whether or not `__eq` should be used, defaults to false. - -- @return `true` or `false` --function _.isEqual(objA, objB, useMt) -+-- @see allEqual -+function M.isEqual(objA, objB, useMt) - local typeObjA = type(objA) - local typeObjB = type(objB) - -@@ -1943,16 +2799,16 @@ function _.isEqual(objA, objB, useMt) - end - end - -- if _.size(objA)~=_.size(objB) then return false end -- -- for i,v1 in pairs(objA) do -- local v2 = objB[i] -- if _.isNil(v2) or not _.isEqual(v1,v2,useMt) then return false end -+ if M.size(objA)~=M.size(objB) then return false end -+ -+ local vB -+ for i,vA in pairs(objA) do -+ vB = objB[i] -+ if vB == nil or not M.isEqual(vA, vB, useMt) then return false end - end - -- for i,v1 in pairs(objB) do -- local v2 = objA[i] -- if _.isNil(v2) then return false end -+ for i in pairs(objB) do -+ if objA[i] == nil then return false end - end - - return true -@@ -1963,17 +2819,16 @@ end - -- @name result - -- @param obj an object - -- @param method a string key to index in object `obj`. ---- @param[opt] ... Optional args to be passed to `method` ---- @return the returned value of `method (obj, ...)` call --function _.result(obj, method, ...) -+-- @return the returned value of `method (obj)` call -+function M.result(obj, method) - if obj[method] then -- if _.isCallable(obj[method]) then -- return obj[method](obj,...) -+ if M.isCallable(obj[method]) then -+ return obj[method](obj) - else return obj[method] - end - end -- if _.isCallable(method) then -- return method(obj,...) -+ if M.isCallable(method) then -+ return method(obj) - end - end - -@@ -1981,7 +2836,7 @@ end - -- @name isTable - -- @param t a value to be tested - -- @return `true` or `false` --function _.isTable(t) -+function M.isTable(t) - return type(t) == 'table' - end - -@@ -1990,10 +2845,11 @@ end - -- @name isCallable - -- @param obj an object - -- @return `true` or `false` --function _.isCallable(obj) -- return (_.isFunction(obj) or -- (_.isTable(obj) and getmetatable(obj) -- and getmetatable(obj).__call~=nil) or false) -+function M.isCallable(obj) -+ return -+ ((type(obj) == 'function') or -+ ((type(obj) == 'table') and getmetatable(obj) and getmetatable(obj).__call~=nil) or -+ false) - end - - --- Checks if the given argument is an array. Assumes `obj` is an array -@@ -2001,14 +2857,14 @@ end - -- @name isArray - -- @param obj an object - -- @return `true` or `false` --function _.isArray(obj) -- if not _.isTable(obj) then return false end -+function M.isArray(obj) -+ if not (type(obj) == 'table') then return false end - -- Thanks @Wojak and @Enrique García Cota for suggesting this - -- See : http://love2d.org/forums/viewtopic.php?f=3&t=77255&start=40#p163624 - local i = 0 -- for __ in pairs(obj) do -+ for k in pairs(obj) do - i = i + 1 -- if _.isNil(obj[i]) then return false end -+ if obj[i] == nil then return false end - end - return true - end -@@ -2017,8 +2873,25 @@ end - -- @name isIterable - -- @param obj an object - -- @return `true` if the object can be iterated with `pairs` (or `ipairs`), `false` otherwise --function _.isIterable(obj) -- return _.toBoolean((pcall(pairs, obj))) -+function M.isIterable(obj) -+ return M.toBoolean((pcall(pairs, obj))) -+end -+ -+--- Extends Lua's `type` function. It returns the type of the given object and also recognises -+-- file userdata -+-- @name type -+-- @param obj an object -+-- @return the given object type -+function M.type(obj) -+ local tp = type(obj) -+ if tp == 'userdata' then -+ local mt = getmetatable(obj) -+ local stdout = io and io.stdout or nil -+ if stdout ~= nil and mt == getmetatable(stdout) then -+ return 'file' -+ end -+ end -+ return tp - end - - --- Checks if the given pbject is empty. If `obj` is a string, will return `true` -@@ -2027,10 +2900,10 @@ end - -- @name isEmpty - -- @param[opt] obj an object - -- @return `true` or `false` --function _.isEmpty(obj) -- if _.isNil(obj) then return true end -- if _.isString(obj) then return #obj==0 end -- if _.isTable(obj) then return next(obj)==nil end -+function M.isEmpty(obj) -+ if (obj == nil) then return true end -+ if type(obj) == 'string' then return #obj==0 end -+ if type(obj) == 'table' then return next(obj)==nil end - return true - end - -@@ -2038,7 +2911,7 @@ end - -- @name isString - -- @param obj an object - -- @return `true` or `false` --function _.isString(obj) -+function M.isString(obj) - return type(obj) == 'string' - end - -@@ -2046,7 +2919,7 @@ end - -- @name isFunction - -- @param obj an object - -- @return `true` or `false` --function _.isFunction(obj) -+function M.isFunction(obj) - return type(obj) == 'function' - end - -@@ -2054,7 +2927,7 @@ end - -- @name isNil - -- @param obj an object - -- @return `true` or `false` --function _.isNil(obj) -+function M.isNil(obj) - return obj==nil - end - -@@ -2063,7 +2936,7 @@ end - -- @param obj an object - -- @return `true` or `false` - -- @see isNaN --function _.isNumber(obj) -+function M.isNumber(obj) - return type(obj) == 'number' - end - -@@ -2072,16 +2945,16 @@ end - -- @param obj an object - -- @return `true` or `false` - -- @see isNumber --function _.isNaN(obj) -- return _.isNumber(obj) and obj~=obj -+function M.isNaN(obj) -+ return type(obj) == 'number' and obj~=obj - end - - --- Checks if the given argument is a finite number. - -- @name isFinite - -- @param obj an object - -- @return `true` or `false` --function _.isFinite(obj) -- if not _.isNumber(obj) then return false end -+function M.isFinite(obj) -+ if type(obj) ~= 'number' then return false end - return obj > -huge and obj < huge - end - -@@ -2089,7 +2962,7 @@ end - -- @name isBoolean - -- @param obj an object - -- @return `true` or `false` --function _.isBoolean(obj) -+function M.isBoolean(obj) - return type(obj) == 'boolean' - end - -@@ -2097,8 +2970,8 @@ end - -- @name isInteger - -- @param obj an object - -- @return `true` or `false` --function _.isInteger(obj) -- return _.isNumber(obj) and floor(obj)==obj -+function M.isInteger(obj) -+ return type(obj) == 'number' and floor(obj)==obj - end - - -- Aliases -@@ -2106,60 +2979,68 @@ end - do - - -- Table functions aliases -- _.forEach = _.each -- _.forEachi = _.eachi -- _.loop = _.cycle -- _.collect = _.map -- _.inject = _.reduce -- _.foldl = _.reduce -- _.injectr = _.reduceRight -- _.foldr = _.reduceRight -- _.mapr = _.mapReduce -- _.maprr = _.mapReduceRight -- _.any = _.include -- _.some = _.include -- _.contains = _.include -- _.filter = _.select -- _.discard = _.reject -- _.every = _.all -+ M.forEach = M.each -+ M.forEachi = M.eachi -+ M.update = M.adjust -+ M.alleq = M.allEqual -+ M.loop = M.cycle -+ M.collect = M.map -+ M.inject = M.reduce -+ M.foldl = M.reduce -+ M.injectr = M.reduceRight -+ M.foldr = M.reduceRight -+ M.mapr = M.mapReduce -+ M.maprr = M.mapReduceRight -+ M.any = M.include -+ M.some = M.include -+ M.contains = M.include -+ M.filter = M.select -+ M.discard = M.reject -+ M.every = M.all - - -- Array functions aliases -- _.takeWhile = _.selectWhile -- _.rejectWhile = _.dropWhile -- _.shift = _.pop -- _.remove = _.pull -- _.rmRange = _.removeRange -- _.chop = _.removeRange -- _.sub = _.slice -- _.head = _.first -- _.take = _.first -- _.tail = _.rest -- _.skip = _.last -- _.without = _.difference -- _.diff = _.difference -- _.symdiff = _.symmetricDifference -- _.xor = _.symmetricDifference -- _.uniq = _.unique -- _.isuniq = _.isunique -- _.transpose = _.zip -- _.part = _.partition -- _.perm = _.permutation -- _.mirror = _.invert -- _.join = _.concat -+ M.takeWhile = M.selectWhile -+ M.rejectWhile = M.dropWhile -+ M.pop = M.shift -+ M.remove = M.pull -+ M.rmRange = M.removeRange -+ M.chop = M.removeRange -+ M.sub = M.slice -+ M.head = M.first -+ M.take = M.first -+ M.tail = M.rest -+ M.without = M.difference -+ M.diff = M.difference -+ M.symdiff = M.symmetricDifference -+ M.xor = M.symmetricDifference -+ M.uniq = M.unique -+ M.isuniq = M.isunique -+ M.transpose = M.zip -+ M.part = M.partition -+ M.perm = M.permutation -+ M.transposeWith = M.zipWith -+ M.intersperse = M.interpose -+ M.sliding = M.aperture -+ M.mirror = M.invert -+ M.join = M.concat -+ M.average = M.mean - - -- Utility functions aliases -- _.cache = _.memoize -- _.juxt = _.juxtapose -- _.uid = _.uniqueId -- _.iter = _.iterator -- -- -- Object functions aliases -- _.methods = _.functions -- _.choose = _.pick -- _.drop = _.omit -- _.defaults = _.template -- _.compare = _.isEqual -+ M.always = M.constant -+ M.cache = M.memoize -+ M.juxt = M.juxtapose -+ M.uid = M.uniqueId -+ M.iter = M.iterator -+ M.nAry = M.ary - -+ -- Object functions aliases -+ M.methods = M.functions -+ M.choose = M.pick -+ M.drop = M.omit -+ M.defaults = M.template -+ M.compare = M.isEqual -+ M.matches = M.isEqual -+ - end - - -- Setting chaining and building interface -@@ -2170,27 +3051,26 @@ do - local f = {} - - -- Will be returned upon requiring, indexes into the wrapper -- local __ = {} -- __.__index = f -+ local Moses = {} -+ Moses.__index = f - - -- Wraps a value into an instance, and returns the wrapped object - local function new(value) -- local i = {_value = value, _wrapped = true} -- return setmetatable(i, __) -+ return setmetatable({_value = value, _wrapped = true}, Moses) - end - -- setmetatable(__,{ -+ setmetatable(Moses,{ - __call = function(self,v) return new(v) end, -- Calls returns to instantiation - __index = function(t,key,...) return f[key] end -- Redirects to the wrapper - }) - - --- Returns a wrapped object. Calling library functions as methods on this object -- -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `_(value)`. -+ -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `M(value)`. - -- @class function - -- @name chain - -- @param value a value to be wrapped - -- @return a wrapped object -- function __.chain(value) -+ function Moses.chain(value) - return new(value) - end - -@@ -2198,53 +3078,59 @@ do - -- @class function - -- @name obj:value - -- @return the value previously wrapped -- function __:value() -+ function Moses:value() - return self._value - end - - -- Register chaining methods into the wrapper -- f.chain, f.value = __.chain, __.value -+ f.chain, f.value = Moses.chain, Moses.value - - -- Register all functions into the wrapper -- for fname,fct in pairs(_) do -- f[fname] = function(v, ...) -- local wrapped = _.isTable(v) and v._wrapped or false -- if wrapped then -- local _arg = v._value -- local _rslt = fct(_arg,...) -- return new(_rslt) -- else -- return fct(v,...) -+ for fname,fct in pairs(M) do -+ if fname ~= 'operator' then -- Prevents from wrapping op functions -+ f[fname] = function(v, ...) -+ local wrapped = type(v) == 'table' and rawget(v,'_wrapped') or false -+ if wrapped then -+ local _arg = v._value -+ local _rslt = fct(_arg,...) -+ return new(_rslt) -+ else -+ return fct(v,...) -+ end - end - end - end -+ -+ -- Exports all op functions -+ f.operator = M.operator -+ f.op = M.operator - - --- Imports all library functions into a context. - -- @name import -- -- @param[opt] context a context. Defaults to `_G` (global environment) when not given. -- -- @param[optchain] noConflict if supplied, will not import functions having a key existing in the destination context. -+ -- @param[opt] context a context. Defaults to `_ENV or `_G`` (current environment). -+ -- @param[optchain] noConflict if supplied, will not import conflicting functions in the destination context. - -- @return the passed-in context - f.import = function(context, noConflict) - context = context or _ENV or _G -- local funcs = _.functions() -- _.each(funcs, function(k, fname) -- if rawget(context, fname) then -+ local funcs = M.functions() -+ for k, fname in ipairs(funcs) do -+ if rawget(context, fname)~= nil then - if not noConflict then -- context[fname] = _[fname] -+ rawset(context, fname, M[fname]) - end - else -- context[fname] = _[fname] -+ rawset(context, fname, M[fname]) - end -- end) -+ end - return context - end - - -- Descriptive tags -- __._VERSION = 'Moses v'.._MODULEVERSION -- __._URL = 'http://github.com/Yonaba/Moses' -- __._LICENSE = 'MIT ' -- __._DESCRIPTION = 'utility-belt library for functional programming in Lua' -- -- return __ -+ Moses._VERSION = 'Moses v'.._MODULEVERSION -+ Moses._URL = 'http://github.com/Yonaba/Moses' -+ Moses._LICENSE = 'MIT ' -+ Moses._DESCRIPTION = 'utility-belt library for functional programming in Lua' - -+ return Moses -+ - end -diff --git a/extra/moses/moses_min.lua b/extra/moses/moses_min.lua -index bb67dcc..8963d6e 100644 ---- a/extra/moses/moses_min.lua -+++ b/extra/moses/moses_min.lua -@@ -1,364 +1,574 @@ --local _ba='1.6.1'local aba,bba,cba,dba=next,type,select,pcall;local _ca,aca=setmetatable,getmetatable --local bca,cca=table.insert,table.sort;local dca,_da=table.remove,table.concat --local ada,bda,cda=math.randomseed,math.random,math.huge;local dda,__b,a_b=math.floor,math.max,math.min;local b_b=rawget --local c_b=table.unpack or unpack;local d_b,_ab=pairs,ipairs;local aab=os.clock;local bab={} --local function cab(dcb,_db)return dcb>_db end;local function dab(dcb,_db)return dcb<_db end --local function _bb(dcb,_db,adb)return(dcb<_db)and _db or --(dcb>adb and adb or dcb)end;local function abb(dcb,_db)return _db and true end --local function bbb(dcb)return not dcb end --local function cbb(dcb)local _db=0;for adb,bdb in d_b(dcb)do _db=_db+1 end;return _db end --local function dbb(dcb,_db,adb,...)local bdb;local cdb=adb or bab.identity;for ddb,__c in d_b(dcb)do --if not bdb then bdb=cdb(__c,...)else --local a_c=cdb(__c,...)bdb=_db(bdb,a_c)and bdb or a_c end end;return bdb end --local function _cb(dcb,_db,adb,bdb)for i=0,#dcb,_db do local cdb=bab.slice(dcb,i+1,i+_db) --if#cdb>0 then while --(#cdb<_db and bdb)do cdb[#cdb+1]=bdb end;adb(cdb)end end end --local function acb(dcb,_db,adb,bdb) --for i=0,#dcb,_db-1 do local cdb=bab.slice(dcb,i+1,i+_db)if --#cdb>0 and i+1 <#dcb then while(#cdb<_db and bdb)do cdb[#cdb+1]=bdb end --adb(cdb)end end end --local function bcb(dcb,_db,adb)if _db==0 then adb(dcb)end --for i=1,_db do dcb[_db],dcb[i]=dcb[i],dcb[_db]bcb(dcb,_db- --1,adb)dcb[_db],dcb[i]=dcb[i],dcb[_db]end end;local ccb=-1 --function bab.clear(dcb)for _db in d_b(dcb)do dcb[_db]=nil end;return dcb end --function bab.each(dcb,_db,...)for adb,bdb in d_b(dcb)do _db(adb,bdb,...)end end --function bab.eachi(dcb,_db,...) --local adb=bab.sort(bab.select(bab.keys(dcb),function(bdb,cdb)return bab.isInteger(cdb)end))for bdb,cdb in _ab(adb)do _db(cdb,dcb[cdb],...)end end --function bab.at(dcb,...)local _db={}for adb,bdb in _ab({...})do --if bab.has(dcb,bdb)then _db[#_db+1]=dcb[bdb]end end;return _db end --function bab.count(dcb,_db)if bab.isNil(_db)then return bab.size(dcb)end;local adb=0 --bab.each(dcb,function(bdb,cdb)if --bab.isEqual(cdb,_db)then adb=adb+1 end end)return adb end --function bab.countf(dcb,_db,...)return bab.count(bab.map(dcb,_db,...),true)end --function bab.cycle(dcb,_db)_db=_db or 1;if _db<=0 then return bab.noop end;local adb,bdb;local cdb=0 -+local SWFtRywD='2.1.0'local e,v,l6Sm5=next,type,pcall;local oUA,QFKEzBf=setmetatable,getmetatable -+local odpE,p=table.insert,table.sort;local lIpFkbLI,JdUtcU=table.remove,table.concat -+local GQLN,toXyq,S9TO=math.randomseed,math.random,math.huge;local pS78Y,BCf7,RlMSrmdD,VCD=math.floor,math.max,math.min,math.ceil -+local OV7=coroutine.wrap;local X83a=coroutine.yield;local PizLA9mj=rawget -+local hUL=table.unpack or unpack;local l,kyWtqIf0=pairs,ipairs;local zupvsz=error;local Mw=os and os.clock or nil -+local S1wg_DG={}local function sf0(cmWo_v,RoXZEsn)return cmWo_v>RoXZEsn end;local function qxZa6ozV(BKLwtAVx,BMZNmf0) -+return BKLwtAVx0 then while(#ZmzyNm0 and cpdLk+1 <#akG0mUnS then while -+(#FbQX0 and gUfudNUg+eS0X<=#ejMVLYZd then while -+(#d3 =0 and 1 or-1 end -+local qJExeUn2=-1;S1wg_DG.operator={} -+S1wg_DG.operator.add=function(fBI,wMSY)return fBI+wMSY end -+S1wg_DG.operator.sub=function(_nD2rl,aVh8xSly)return _nD2rl-aVh8xSly end -+S1wg_DG.operator.mul=function(i,P_NNVDyt)return i*P_NNVDyt end -+S1wg_DG.operator.div=function(cVEyN,uj2AiF)return cVEyN/uj2AiF end -+S1wg_DG.operator.mod=function(W,lbHN2)return W%lbHN2 end -+S1wg_DG.operator.exp=function(PwgW3lfq,z)return PwgW3lfq^z end;S1wg_DG.operator.pow=S1wg_DG.operator.exp;S1wg_DG.operator.unm=function(K)return -+-K end -+S1wg_DG.operator.neg=S1wg_DG.operator.unm -+S1wg_DG.operator.floordiv=function(xx,aYb)return pS78Y(xx/aYb)end -+S1wg_DG.operator.intdiv=function(JM2,bmAjLT)return JM2 >=0 and pS78Y(JM2/bmAjLT)or -+VCD(JM2/bmAjLT)end -+S1wg_DG.operator.eq=function(eExYnwnh,XMBmJyiP)return eExYnwnh==XMBmJyiP end -+S1wg_DG.operator.neq=function(nowqEU6m,iKD8V)return nowqEU6m~=iKD8V end -+S1wg_DG.operator.lt=function(YtRS,A)return YtRSQ57BJ end -+S1wg_DG.operator.le=function(vM,JeGCDX)return vM<=JeGCDX end -+S1wg_DG.operator.ge=function(A,UFZlp)return A>=UFZlp end -+S1wg_DG.operator.land=function(VsrKM,uhIq)return VsrKM and uhIq end -+S1wg_DG.operator.lor=function(EEOUzhy,hbrt)return EEOUzhy or hbrt end;S1wg_DG.operator.lnot=function(D)return not D end;S1wg_DG.operator.concat=function(Q,mRqle)return -+Q..mRqle end;S1wg_DG.operator.length=function(sBEZ8)return -+#sBEZ8 end -+S1wg_DG.operator.len=S1wg_DG.operator.length;function S1wg_DG.clear(WhHB0ygh)for rYSD0 in l(WhHB0ygh)do WhHB0ygh[rYSD0]=nil end -+return WhHB0ygh end;function S1wg_DG.each(BIL5,GQLlkH)for aN4J2zRQ,eWca in l(BIL5)do -+GQLlkH(eWca,aN4J2zRQ)end end -+function S1wg_DG.eachi(AGUR2QK,FK) -+local _=S1wg_DG.sort(S1wg_DG.select(S1wg_DG.keys(AGUR2QK),S1wg_DG.isInteger)) -+for YQZ729qQ,rZh2wG in kyWtqIf0(_)do FK(AGUR2QK[rZh2wG],rZh2wG)end end;function S1wg_DG.at(sef4eW6Q,...)local Z={} -+for UacO6D,FdnzjW in kyWtqIf0({...})do Z[#Z+1]=sef4eW6Q[FdnzjW]end;return Z end -+function S1wg_DG.adjust(o,lMAL,CpQ) -+if( -+o[lMAL]==nil)then zupvsz("key not existing in table")end;local L=S1wg_DG.clone(o)L[lMAL]= -+v(CpQ)=='function'and CpQ(L[lMAL])or CpQ;return L end -+function S1wg_DG.count(HnQS_Z,rib) -+if rib==nil then return S1wg_DG.size(HnQS_Z)end;local hgW2H5=0;for w,YT6wZ in l(HnQS_Z)do -+if S1wg_DG.isEqual(YT6wZ,rib)then hgW2H5=hgW2H5+1 end end;return hgW2H5 end -+function S1wg_DG.countf(VYv,gU)local hgW2H5=0;for JzG8W4Ya,dZ54oc in l(VYv)do -+if gU(dZ54oc,JzG8W4Ya)then hgW2H5=hgW2H5+1 end end;return hgW2H5 end -+function S1wg_DG.allEqual(v_LoR,gRY)local z,ad=e(v_LoR) -+for z,Ui0Qa in l(v_LoR)do if gRY then -+if not gRY(ad,Ui0Qa)then return false end else -+if not S1wg_DG.isEqual(ad,Ui0Qa)then return false end end end;return true end -+function S1wg_DG.cycle(g,Itx)Itx=Itx or 1;if Itx<=0 then return S1wg_DG.noop end -+local JpoaGH,cyAcCT;local RCA=0 - while true do - return --function()adb=adb and --aba(dcb,adb)or aba(dcb) --bdb=not bdb and adb or bdb;if _db then cdb=(adb==bdb)and cdb+1 or cdb --if cdb>_db then return end end;return adb,dcb[adb]end end end --function bab.map(dcb,_db,...)local adb={} --for bdb,cdb in d_b(dcb)do local ddb,__c,a_c=bdb,_db(bdb,cdb,...)adb[a_c and __c or ddb]= --a_c or __c end;return adb end;function bab.reduce(dcb,_db,adb) --for bdb,cdb in d_b(dcb)do if adb==nil then adb=cdb else adb=_db(adb,cdb)end end;return adb end;function bab.reduceby(dcb,_db,adb,bdb,...)return --bab.reduce(bab.select(dcb,bdb,...),_db,adb)end;function bab.reduceRight(dcb,_db,adb)return --bab.reduce(bab.reverse(dcb),_db,adb)end --function bab.mapReduce(dcb,_db,adb) --local bdb={}for cdb,ddb in d_b(dcb)do bdb[cdb]=not adb and ddb or _db(adb,ddb) --adb=bdb[cdb]end;return bdb end;function bab.mapReduceRight(dcb,_db,adb) --return bab.mapReduce(bab.reverse(dcb),_db,adb)end --function bab.include(dcb,_db)local adb= --bab.isFunction(_db)and _db or bab.isEqual;for bdb,cdb in d_b(dcb)do if adb(cdb,_db)then --return true end end;return false end --function bab.detect(dcb,_db) --local adb=bab.isFunction(_db)and _db or bab.isEqual;for bdb,cdb in d_b(dcb)do if adb(cdb,_db)then return bdb end end end --function bab.where(dcb,_db) --local adb=bab.select(dcb,function(bdb,cdb) --for ddb in d_b(_db)do if cdb[ddb]~=_db[ddb]then return false end end;return true end)return#adb>0 and adb or nil end --function bab.findWhere(dcb,_db) --local adb=bab.detect(dcb,function(bdb)for cdb in d_b(_db)do --if _db[cdb]~=bdb[cdb]then return false end end;return true end)return adb and dcb[adb]end --function bab.select(dcb,_db,...)local adb={}for bdb,cdb in d_b(dcb)do --if _db(bdb,cdb,...)then adb[#adb+1]=cdb end end;return adb end --function bab.reject(dcb,_db,...)local adb=bab.map(dcb,_db,...)local bdb={}for cdb,ddb in d_b(adb)do if not ddb then --bdb[#bdb+1]=dcb[cdb]end end;return bdb end --function bab.all(dcb,_db,...)return( (#bab.select(bab.map(dcb,_db,...),abb))== --cbb(dcb))end --function bab.invoke(dcb,_db,...)local adb={...} -+function()JpoaGH=JpoaGH and e(g,JpoaGH)or e(g)cyAcCT=not -+cyAcCT and JpoaGH or cyAcCT;if Itx then RCA= -+(JpoaGH==cyAcCT)and RCA+1 or RCA -+if RCA>Itx then return end end;return g[JpoaGH],JpoaGH end end end -+function S1wg_DG.map(L46S,GKTYT)local hXSTz8FJ={} -+for C24r7o4G,b_4Q38cU in l(L46S)do local N,JbPw,j=C24r7o4G,GKTYT(b_4Q38cU,C24r7o4G)hXSTz8FJ[ -+j and JbPw or N]=j or JbPw end;return hXSTz8FJ end -+function S1wg_DG.mapi(S,cg4FV7bl)local flf9sWX={}for uNoS,ZWoH9V08 in kyWtqIf0(S)do -+local RWo,GWBQL,PCldTUn9=uNoS,cg4FV7bl(ZWoH9V08,uNoS) -+flf9sWX[PCldTUn9 and GWBQL or RWo]=PCldTUn9 or GWBQL end;return -+flf9sWX end -+function S1wg_DG.reduce(sO_,ALbdmINL,b)for DUgF0E,vGxJ6f in l(sO_)do -+if b==nil then b=vGxJ6f else b=ALbdmINL(b,vGxJ6f)end end;return b end -+function S1wg_DG.best(a4ga2I,syGyB_)local VO,J1r=e(a4ga2I) -+for iBcU3_7D,N in l(a4ga2I)do if J1r==nil then J1r=N else -+J1r=syGyB_(J1r,N)and J1r or N end end;return J1r end -+function S1wg_DG.reduceBy(M4V,_feve,OPz_7bk,H64aD)return -+S1wg_DG.reduce(S1wg_DG.select(M4V,OPz_7bk),_feve,H64aD)end -+function S1wg_DG.reduceRight(ny7,QDj6GAX,k6pXzd)return -+S1wg_DG.reduce(S1wg_DG.reverse(ny7),QDj6GAX,k6pXzd)end -+function S1wg_DG.mapReduce(hsLwu,R,JKZ)local yHbsh={}for d4z,i in l(hsLwu)do -+yHbsh[d4z]=not JKZ and i or R(JKZ,i)JKZ=yHbsh[d4z]end;return yHbsh end -+function S1wg_DG.mapReduceRight(HyEk4lbh,PhU,rWwbNge)return -+S1wg_DG.mapReduce(S1wg_DG.reverse(HyEk4lbh),PhU,rWwbNge)end -+function S1wg_DG.include(SKxD,o3uQKvJ)local vAZm=(v(o3uQKvJ)=='function')and o3uQKvJ or -+S1wg_DG.isEqual;for q,fFuE in l(SKxD)do if -+vAZm(fFuE,o3uQKvJ)then return true end end;return false end -+function S1wg_DG.detect(KypMW,JJT4nKO)local TFLF=(v(JJT4nKO)=='function')and JJT4nKO or -+S1wg_DG.isEqual;for hEoAa,PGN in l(KypMW)do if -+TFLF(PGN,JJT4nKO)then return hEoAa end end end -+function S1wg_DG.where(K2_kF5,YpimJ) -+local Gg7Ttui=S1wg_DG.select(K2_kF5,function(_)for EGeAf in l(YpimJ)do -+if _[EGeAf]~=YpimJ[EGeAf]then return false end end;return true end)return#Gg7Ttui>0 and Gg7Ttui or nil end -+function S1wg_DG.findWhere(ymP,z5pHKyoa) -+local h=S1wg_DG.detect(ymP,function(xwT)for y33ux in l(z5pHKyoa)do if z5pHKyoa[y33ux]~=xwT[y33ux]then -+return false end end;return true end)return h and ymP[h]end -+function S1wg_DG.select(Ut,GOijBp)local oUi={}for b2a3,xer in l(Ut)do -+if GOijBp(xer,b2a3)then oUi[#oUi+1]=xer end end;return oUi end -+function S1wg_DG.reject(SQHAAR,qybRcP1)local z={}for N0NaR,FBfW in l(SQHAAR)do -+if not qybRcP1(FBfW,N0NaR)then z[#z+1]=FBfW end end;return z end;function S1wg_DG.all(lnM4,_oDmX_) -+for t,K in l(lnM4)do if not _oDmX_(K,t)then return false end end;return true end -+function S1wg_DG.invoke(ppm021I,ASUXhD) - return --bab.map(dcb,function(bdb,cdb) --if bab.isTable(cdb)then --if bab.has(cdb,_db)then --if --bab.isCallable(cdb[_db])then return cdb[_db](cdb,c_b(adb))else return cdb[_db]end else --if bab.isCallable(_db)then return _db(cdb,c_b(adb))end end elseif bab.isCallable(_db)then return _db(cdb,c_b(adb))end end)end --function bab.pluck(dcb,_db)return --bab.reject(bab.map(dcb,function(adb,bdb)return bdb[_db]end),bbb)end;function bab.max(dcb,_db,...)return dbb(dcb,cab,_db,...)end;function bab.min(dcb,_db,...)return --dbb(dcb,dab,_db,...)end --function bab.shuffle(dcb,_db)if _db then ada(_db)end --local adb={} --bab.each(dcb,function(bdb,cdb)local ddb=dda(bda()*bdb)+1;adb[bdb]=adb[ddb] --adb[ddb]=cdb end)return adb end --function bab.same(dcb,_db) -+S1wg_DG.map(ppm021I,function(KCm,u) -+if( -+v(KCm)=='table')then -+if KCm[ASUXhD]then -+if S1wg_DG.isCallable(KCm[ASUXhD])then return -+KCm[ASUXhD](KCm,u)else return KCm[ASUXhD]end else -+if S1wg_DG.isCallable(ASUXhD)then return ASUXhD(KCm,u)end end elseif S1wg_DG.isCallable(ASUXhD)then return ASUXhD(KCm,u)end end)end -+function S1wg_DG.pluck(fDk,gxYY)local sVMxk={}for SyD,v4 in l(fDk)do -+if v4[gxYY]then sVMxk[#sVMxk+1]=v4[gxYY]end end;return sVMxk end;function S1wg_DG.max(j7siW,Hl)return z5i2i(j7siW,sf0,Hl)end;function S1wg_DG.min(AP060rq,DIEKD10)return -+z5i2i(AP060rq,qxZa6ozV,DIEKD10)end -+function S1wg_DG.same(lLJ,EicsS) - return --bab.all(dcb,function(adb,bdb)return bab.include(_db,bdb)end)and --bab.all(_db,function(adb,bdb)return bab.include(dcb,bdb)end)end;function bab.sort(dcb,_db)cca(dcb,_db)return dcb end --function bab.sortBy(dcb,_db,adb) --local bdb=_db or bab.identity --if bab.isString(_db)then bdb=function(ddb)return ddb[_db]end end;adb=adb or dab;local cdb={} --bab.each(dcb,function(ddb,__c) --cdb[#cdb+1]={value=__c,transform=bdb(__c)}end) --cca(cdb,function(ddb,__c)return adb(ddb.transform,__c.transform)end)return bab.pluck(cdb,'value')end --function bab.groupBy(dcb,_db,...)local adb={...}local bdb={} --bab.each(dcb,function(cdb,ddb)local __c=_db(cdb,ddb,c_b(adb)) -+S1wg_DG.all(lLJ,function(JubU)return -+S1wg_DG.include(EicsS,JubU)end)and -+S1wg_DG.all(EicsS,function(L)return S1wg_DG.include(lLJ,L)end)end;function S1wg_DG.sort(JKci,SsBe)p(JKci,SsBe)return JKci end -+function S1wg_DG.sortedk(o,ZOmcmO) -+local _G19JrRB=S1wg_DG.keys(o)p(_G19JrRB,ZOmcmO)local m0r3_J=0 -+return function()m0r3_J=m0r3_J+1;return _G19JrRB[m0r3_J], -+o[_G19JrRB[m0r3_J]]end end -+function S1wg_DG.sortedv(MLrs,hP5)local oqjhEZb0=S1wg_DG.keys(MLrs)hP5=hP5 or qxZa6ozV -+p(oqjhEZb0,function(G,MOrzq4)return -+hP5(MLrs[G],MLrs[MOrzq4])end)local Pha=0;return -+function()Pha=Pha+1;return oqjhEZb0[Pha],MLrs[oqjhEZb0[Pha]]end end -+function S1wg_DG.sortBy(bEMp,dd,MOQN)local O=dd or S1wg_DG.identity;if(v(dd)=='string')then O=function(bEMp)return -+bEMp[dd]end end;MOQN= -+MOQN or qxZa6ozV -+p(bEMp,function(FEpet,P)return MOQN(O(FEpet),O(P))end)return bEMp end -+function S1wg_DG.groupBy(G,EcLLM)local wo={}for ur,XTX in l(G)do local wc8hjKp1=EcLLM(XTX,ur) -+if wo[wc8hjKp1]then wo[wc8hjKp1][# -+wo[wc8hjKp1]+1]=XTX else wo[wc8hjKp1]={XTX}end end;return wo end -+function S1wg_DG.countBy(f,Hjag)local Yg={}for uc,bw in l(f)do local ad=Hjag(bw,uc) -+Yg[ad]=(Yg[ad]or 0)+1 end;return Yg end -+function S1wg_DG.size(...)local EG344W={...}local MVlUhPEM=EG344W[1]return -+ -+(v(MVlUhPEM)=='table')and hgW2H5(EG344W[1])or hgW2H5(EG344W)end;function S1wg_DG.containsKeys(LT,pfiWYrg) -+for smnX9H6 in l(pfiWYrg)do if not LT[smnX9H6]then return false end end;return true end -+function S1wg_DG.sameKeys(FzRhHR,mMBxOoQa) -+for xYSLIT in -+l(FzRhHR)do if not mMBxOoQa[xYSLIT]then return false end end -+for Eae7ILmk in l(mMBxOoQa)do if not FzRhHR[Eae7ILmk]then return false end end;return true end -+function S1wg_DG.sample(Jy23ZRAA,V8IWw,uyYdf)V8IWw=V8IWw or 1;if V8IWw==0 then return{}end;if V8IWw==1 then if uyYdf then -+GQLN(uyYdf)end -+return{Jy23ZRAA[toXyq(1,#Jy23ZRAA)]}end;return -+S1wg_DG.slice(S1wg_DG.shuffle(Jy23ZRAA,uyYdf),1,V8IWw)end -+function S1wg_DG.sampleProb(K,ZX,tbdC)if tbdC then GQLN(tbdC)end;local VaY3={} -+for HK7Mbgze,VXPfx in kyWtqIf0(K)do if toXyq()0 do --dca(bdb,a_c)__c=__c-1 end;return bdb end --function bab.chunk(dcb,_db,...)if not bab.isArray(dcb)then return dcb end;local adb,bdb,cdb={},0 --local ddb=bab.map(dcb,_db,...) --bab.each(ddb,function(__c,a_c)cdb=(cdb==nil)and a_c or cdb;bdb=( --(a_c~=cdb)and(bdb+1)or bdb) --if not adb[bdb]then adb[bdb]={dcb[__c]}else adb[bdb][ --#adb[bdb]+1]=dcb[__c]end;cdb=a_c end)return adb end --function bab.slice(dcb,_db,adb)return --bab.select(dcb,function(bdb)return --(bdb>= (_db or aba(dcb))and bdb<= (adb or#dcb))end)end;function bab.first(dcb,_db)local adb=_db or 1 --return bab.slice(dcb,1,a_b(adb,#dcb))end --function bab.initial(dcb,_db) --if _db and _db<0 then return end;return --bab.slice(dcb,1,_db and#dcb- (a_b(_db,#dcb))or#dcb-1)end;function bab.last(dcb,_db)if _db and _db<=0 then return end --return bab.slice(dcb,_db and --#dcb-a_b(_db-1,#dcb-1)or 2,#dcb)end;function bab.rest(dcb,_db)if _db and --_db>#dcb then return{}end --return bab.slice(dcb, --_db and __b(1,a_b(_db,#dcb))or 1,#dcb)end;function bab.nth(dcb,_db) --return dcb[_db]end;function bab.compact(dcb)return --bab.reject(dcb,function(_db,adb)return not adb end)end --function bab.flatten(dcb,_db)local adb= --_db or false;local bdb;local cdb={} --for ddb,__c in d_b(dcb)do --if bab.isTable(__c)then bdb=adb and __c or --bab.flatten(__c) --bab.each(bdb,function(a_c,b_c)cdb[#cdb+1]=b_c end)else cdb[#cdb+1]=__c end end;return cdb end --function bab.difference(dcb,_db)if not _db then return bab.clone(dcb)end;return --bab.select(dcb,function(adb,bdb)return not --bab.include(_db,bdb)end)end --function bab.union(...)return bab.uniq(bab.flatten({...}))end --function bab.intersection(dcb,...)local _db={...}local adb={} --for bdb,cdb in _ab(dcb)do if --bab.all(_db,function(ddb,__c)return bab.include(__c,cdb)end)then bca(adb,cdb)end end;return adb end --function bab.symmetricDifference(dcb,_db)return --bab.difference(bab.union(dcb,_db),bab.intersection(dcb,_db))end --function bab.unique(dcb)local _db={}for i=1,#dcb do if not bab.find(_db,dcb[i])then --_db[#_db+1]=dcb[i]end end;return _db end --function bab.isunique(dcb)return bab.isEqual(dcb,bab.unique(dcb))end --function bab.zip(...)local dcb={...} --local _db=bab.max(bab.map(dcb,function(bdb,cdb)return#cdb end))local adb={}for i=1,_db do adb[i]=bab.pluck(dcb,i)end;return adb end --function bab.append(dcb,_db)local adb={}for bdb,cdb in _ab(dcb)do adb[bdb]=cdb end;for bdb,cdb in _ab(_db)do --adb[#adb+1]=cdb end;return adb end --function bab.interleave(...)return bab.flatten(bab.zip(...))end;function bab.interpose(dcb,_db)return --bab.flatten(bab.zip(_db,bab.rep(dcb,#_db-1)))end --function bab.range(...) --local dcb={...}local _db,adb,bdb --if#dcb==0 then return{}elseif#dcb==1 then adb,_db,bdb=dcb[1],0,1 elseif#dcb==2 then --_db,adb,bdb=dcb[1],dcb[2],1 elseif#dcb==3 then _db,adb,bdb=dcb[1],dcb[2],dcb[3]end;if(bdb and bdb==0)then return{}end;local cdb={} --local ddb=__b(dda((adb-_db)/bdb),0)for i=1,ddb do cdb[#cdb+1]=_db+bdb*i end;if#cdb>0 then --bca(cdb,1,_db)end;return cdb end --function bab.rep(dcb,_db)local adb={}for i=1,_db do adb[#adb+1]=dcb end;return adb end;function bab.partition(dcb,_db,adb)if _db<=0 then return end --return coroutine.wrap(function() --_cb(dcb,_db or 1,coroutine.yield,adb)end)end;function bab.sliding(dcb,_db,adb)if --_db<=1 then return end --return coroutine.wrap(function() --acb(dcb,_db or 2,coroutine.yield,adb)end)end --function bab.permutation(dcb)return --coroutine.wrap(function()bcb(dcb, --#dcb,coroutine.yield)end)end;function bab.invert(dcb)local _db={} --bab.each(dcb,function(adb,bdb)_db[bdb]=adb end)return _db end --function bab.concat(dcb,_db,adb,bdb) --local cdb=bab.map(dcb,function(ddb,__c)return --tostring(__c)end)return _da(cdb,_db,adb or 1,bdb or#dcb)end;function bab.noop()return end;function bab.identity(dcb)return dcb end;function bab.constant(dcb)return --function()return dcb end end --function bab.memoize(dcb,_db) --local adb=_ca({},{__mode='kv'})local bdb=_db or bab.identity;return --function(...)local cdb=bdb(...)local ddb=adb[cdb]if not ddb then --adb[cdb]=dcb(...)end;return adb[cdb]end end;function bab.once(dcb)local _db=0;local adb={} --return function(...)_db=_db+1;if _db<=1 then adb={...}end --return dcb(c_b(adb))end end --function bab.before(dcb,_db) --local adb=0;local bdb={}return --function(...)adb=adb+1;if adb<=_db then bdb={...}end;return dcb(c_b(bdb))end end --function bab.after(dcb,_db)local adb,bdb=_db,0;return --function(...)bdb=bdb+1;if bdb>=adb then return dcb(...)end end end --function bab.compose(...)local dcb=bab.reverse{...} --return function(...)local _db,adb=true --for bdb,cdb in _ab(dcb)do if _db then _db=false --adb=cdb(...)else adb=cdb(adb)end end;return adb end end --function bab.pipe(dcb,...)return bab.compose(...)(dcb)end --function bab.complement(dcb)return function(...)return not dcb(...)end end;function bab.juxtapose(dcb,...)local _db={} --bab.each({...},function(adb,bdb)_db[#_db+1]=bdb(dcb)end)return c_b(_db)end --function bab.wrap(dcb,_db)return function(...)return --_db(dcb,...)end end --function bab.times(dcb,_db,...)local adb={}for i=1,dcb do adb[i]=_db(i,...)end;return adb end --function bab.bind(dcb,_db)return function(...)return dcb(_db,...)end end;function bab.bind2(dcb,_db) --return function(adb,...)return dcb(adb,_db,...)end end;function bab.bindn(dcb,...)local _db={...} --return function(...)return --dcb(c_b(bab.append(_db,{...})))end end --function bab.bindAll(dcb,...)local _db={...} --for adb,bdb in --_ab(_db)do local cdb=dcb[bdb]if cdb then dcb[bdb]=bab.bind(cdb,dcb)end end;return dcb end --function bab.uniqueId(dcb,...)ccb=ccb+1 --if dcb then if bab.isString(dcb)then return dcb:format(ccb)elseif --bab.isFunction(dcb)then return dcb(ccb,...)end end;return ccb end --function bab.iterator(dcb,_db)return function()_db=dcb(_db)return _db end end --function bab.array(...)local dcb={}for _db in...do dcb[#dcb+1]=_db end;return dcb end;function bab.flip(dcb)return --function(...)return dcb(c_b(bab.reverse({...})))end end;function bab.over(...) --local dcb={...} --return function(...)local _db={}for adb,bdb in _ab(dcb)do _db[#_db+1]=bdb(...)end --return _db end end;function bab.overEvery(...) --local dcb=bab.over(...) --return function(...)return --bab.reduce(dcb(...),function(_db,adb)return _db and adb end)end end;function bab.overSome(...) --local dcb=bab.over(...) --return function(...)return --bab.reduce(dcb(...),function(_db,adb)return _db or adb end)end end --function bab.overArgs(dcb,...) --local _db={...}return --function(...)local adb={...}for i=1,#_db do local bdb=_db[i] --if adb[i]then adb[i]=bdb(adb[i])end end;return dcb(c_b(adb))end end --function bab.partial(dcb,...)local _db={...} --return --function(...)local adb={...}local bdb={}for cdb,ddb in _ab(_db)do bdb[cdb]= --(ddb=='_')and bab.pop(adb)or ddb end;return --dcb(c_b(bab.append(bdb,adb)))end end --function bab.partialRight(dcb,...)local _db={...} --return --function(...)local adb={...}local bdb={} --for k=1,#_db do bdb[k]= --(_db[k]=='_')and bab.pop(adb)or _db[k]end;return dcb(c_b(bab.append(adb,bdb)))end end --function bab.curry(dcb,_db)_db=_db or 2;local adb={} --local function bdb(cdb)if _db==1 then return dcb(cdb)end;if cdb~=nil then --adb[#adb+1]=cdb end;if#adb<_db then return bdb else local ddb={dcb(c_b(adb))}adb={}return --c_b(ddb)end end;return bdb end --function bab.time(dcb,...)local _db=aab()local adb={dcb(...)}return aab()-_db,c_b(adb)end;function bab.keys(dcb)local _db={} --bab.each(dcb,function(adb)_db[#_db+1]=adb end)return _db end;function bab.values(dcb)local _db={} --bab.each(dcb,function(adb,bdb)_db[ --#_db+1]=bdb end)return _db end;function bab.kvpairs(dcb)local _db={} --bab.each(dcb,function(adb,bdb)_db[ --#_db+1]={adb,bdb}end)return _db end --function bab.toObj(dcb)local _db={}for adb,bdb in --_ab(dcb)do _db[bdb[1]]=bdb[2]end;return _db end --function bab.property(dcb)return function(_db)return _db[dcb]end end --function bab.propertyOf(dcb)return function(_db)return dcb[_db]end end;function bab.toBoolean(dcb)return not not dcb end --function bab.extend(dcb,...)local _db={...} --bab.each(_db,function(adb,bdb)if --bab.isTable(bdb)then --bab.each(bdb,function(cdb,ddb)dcb[cdb]=ddb end)end end)return dcb end --function bab.functions(dcb,_db)dcb=dcb or bab;local adb={} --bab.each(dcb,function(cdb,ddb)if bab.isFunction(ddb)then --adb[#adb+1]=cdb end end)if not _db then return bab.sort(adb)end;local bdb=aca(dcb) -+S1wg_DG.isEqual(sDjMr[BLEXN_],FNSk_)then lIpFkbLI(sDjMr,BLEXN_)Ljc=true end end end end;return sDjMr end -+function S1wg_DG.removeRange(LmE,pZTFVP,XL)pZTFVP=pZTFVP or 1;XL=XL or#LmE;if pZTFVP>XL then -+zupvsz("start cannot be greater than finish.")end -+for L5vC0Jx=XL,pZTFVP,-1 do lIpFkbLI(LmE,L5vC0Jx)end;return LmE end -+function S1wg_DG.chunk(vpONJ,A)local LN,dA14qP,JcQc,hDih6_D={},0;A=A or S1wg_DG.identity -+for QKbZ464i,F1TsZ in -+kyWtqIf0(vpONJ)do hDih6_D=A(F1TsZ,QKbZ464i)dA14qP=( -+(hDih6_D~=JcQc)and(dA14qP+1)or dA14qP)JcQc= -+(JcQc==nil)and hDih6_D or JcQc;if not LN[dA14qP]then -+LN[dA14qP]={vpONJ[QKbZ464i]}else -+LN[dA14qP][#LN[dA14qP]+1]=vpONJ[QKbZ464i]end;JcQc=hDih6_D end;return LN end;function S1wg_DG.slice(uF2,T,pC_)local ju={} -+for deu1=T or 1,pC_ or#uF2 do ju[#ju+1]=uF2[deu1]end;return ju end;function S1wg_DG.first(IgZ6,kVRiv3F)kVRiv3F= -+kVRiv3F or 1;local kWMf={} -+for DawC=1,kVRiv3F do kWMf[DawC]=IgZ6[DawC]end;return kWMf end -+function S1wg_DG.initial(cP,w)local UZ= -+#cP -+w=w and UZ- (RlMSrmdD(w,UZ))or UZ-1;local tdH={}for ymt=1,w do tdH[ymt]=cP[ymt]end;return tdH end -+function S1wg_DG.last(WxGA,jBuHkH)local E3=#WxGA;jBuHkH= -+jBuHkH and E3-RlMSrmdD(jBuHkH-1,E3-1)or 2;local CZi_zK={}for _6KCMph=jBuHkH,E3 do -+CZi_zK[#CZi_zK+1]=WxGA[_6KCMph]end;return CZi_zK end;function S1wg_DG.rest(PY3VqYZ8,V)local y={} -+for QF=V or 1,#PY3VqYZ8 do y[#y+1]=PY3VqYZ8[QF]end;return y end;function S1wg_DG.nth(hN,hVflx4kh)return -+hN[hVflx4kh]end -+function S1wg_DG.compact(GP)local oCZYv2dT={} -+for RLaqM3,PoH in l(GP)do if PoH then oCZYv2dT[ -+#oCZYv2dT+1]=PoH end end;return oCZYv2dT end -+function S1wg_DG.flatten(xM709D,z50)z50=z50 or false;local sAPD;local AVFi={} -+for GGKI,gWaGu in kyWtqIf0(xM709D)do - if --bdb and bdb.__index then local cdb=bab.functions(bdb.__index)bab.each(cdb,function(ddb,__c) --adb[#adb+1]=__c end)end;return bab.sort(adb)end --function bab.clone(dcb,_db)if not bab.isTable(dcb)then return dcb end;local adb={} --bab.each(dcb,function(bdb,cdb)if --bab.isTable(cdb)then --if not _db then adb[bdb]=bab.clone(cdb,_db)else adb[bdb]=cdb end else adb[bdb]=cdb end end)return adb end;function bab.tap(dcb,_db,...)_db(dcb,...)return dcb end;function bab.has(dcb,_db)return --dcb[_db]~=nil end --function bab.pick(dcb,...)local _db=bab.flatten{...} --local adb={} --bab.each(_db,function(bdb,cdb) --if not bab.isNil(dcb[cdb])then adb[cdb]=dcb[cdb]end end)return adb end --function bab.omit(dcb,...)local _db=bab.flatten{...}local adb={} --bab.each(dcb,function(bdb,cdb)if --not bab.include(_db,bdb)then adb[bdb]=cdb end end)return adb end;function bab.template(dcb,_db) --bab.each(_db or{},function(adb,bdb)if not dcb[adb]then dcb[adb]=bdb end end)return dcb end --function bab.isEqual(dcb,_db,adb) --local bdb=bba(dcb)local cdb=bba(_db)if bdb~=cdb then return false end --if bdb~='table'then return(dcb==_db)end;local ddb=aca(dcb)local __c=aca(_db)if adb then -+v(gWaGu)=='table'then -+sAPD=z50 and gWaGu or S1wg_DG.flatten(gWaGu) -+for SFKM,j6jQmlbr in kyWtqIf0(sAPD)do AVFi[#AVFi+1]=j6jQmlbr end else AVFi[#AVFi+1]=gWaGu end end;return AVFi end -+function S1wg_DG.difference(m403CY,dL) -+if not dL then return S1wg_DG.clone(m403CY)end -+return S1wg_DG.select(m403CY,function(PrTsHeT) -+return not S1wg_DG.include(dL,PrTsHeT)end)end;function S1wg_DG.union(...) -+return S1wg_DG.unique(S1wg_DG.flatten({...}))end -+function S1wg_DG.intersection(...)local eNI3MT7={...} -+local Rfoo=eNI3MT7[1]lIpFkbLI(eNI3MT7,1)local eUJhGD={} -+for wot8,j9vJ in kyWtqIf0(Rfoo)do - if --(ddb or __c)and(ddb.__eq or __c.__eq)then return --ddb.__eq(dcb,_db)or __c.__eq(_db,dcb)or(dcb==_db)end end;if bab.size(dcb)~= --bab.size(_db)then return false end;for a_c,b_c in d_b(dcb)do local c_c=_db[a_c] -+S1wg_DG.all(eNI3MT7,function(J6Qr27Mh)return -+S1wg_DG.include(J6Qr27Mh,j9vJ)end)then eUJhGD[#eUJhGD+1]=j9vJ end end;return eUJhGD end;function S1wg_DG.disjoint(...)return -+(#S1wg_DG.intersection(...)==0)end -+function S1wg_DG.symmetricDifference(AwxW8Do,_u)return -+S1wg_DG.difference(S1wg_DG.union(AwxW8Do,_u),S1wg_DG.intersection(AwxW8Do,_u))end -+function S1wg_DG.unique(B)local cdxFVpZw={} -+for Y=1,#B do if not S1wg_DG.find(cdxFVpZw,B[Y])then cdxFVpZw[# -+cdxFVpZw+1]=B[Y]end end;return cdxFVpZw end;function S1wg_DG.isunique(o9Uh)return -+#o9Uh==# (S1wg_DG.unique(o9Uh))end -+function S1wg_DG.duplicates(BuX1r) -+local Wyf83f2=S1wg_DG.invert(BuX1r)local P0olj={}for z,EHCCkt in kyWtqIf0(BuX1r)do -+if Wyf83f2[EHCCkt]~=z and -+not S1wg_DG.find(P0olj,EHCCkt)then P0olj[#P0olj+1]=EHCCkt end end;return P0olj end -+function S1wg_DG.zip(...)local x={...} -+local xNWVmS=S1wg_DG.max(x,function(Pkis6H28)return#Pkis6H28 end)local kGWnkgDu={} -+for tSE=1,xNWVmS do -+if not kGWnkgDu[tSE]then kGWnkgDu[tSE]={}end -+for abKH,LDp in kyWtqIf0(x)do if(LDp[tSE]~=nil)then -+kGWnkgDu[tSE][#kGWnkgDu[tSE]+1]=LDp[tSE]end end end;return kGWnkgDu end -+function S1wg_DG.zipWith(GWouUlzZ,...)local MqJhIr={...} -+local Q9=S1wg_DG.max(MqJhIr,function(qnZ81I)return#qnZ81I end)local c={}for N9uN=1,Q9 do -+c[N9uN]=GWouUlzZ(hUL(S1wg_DG.pluck(MqJhIr,N9uN)))end;return c end -+function S1wg_DG.append(QGC,K8iFU)local gbU={}for h,hS7 in kyWtqIf0(QGC)do gbU[h]=hS7 end;for KQjMKhN,R6PYgHHE in -+kyWtqIf0(K8iFU)do gbU[#gbU+1]=R6PYgHHE end;return gbU end -+function S1wg_DG.interleave(...)local ZwCXrLO={...} -+local lI=S1wg_DG.max(ZwCXrLO,S1wg_DG.size)local iMSMP5Lp={} -+for WoARZdZ3=1,lI do for n,Uj in kyWtqIf0(ZwCXrLO)do if Uj[WoARZdZ3]then -+iMSMP5Lp[#iMSMP5Lp+1]=Uj[WoARZdZ3]end end end;return iMSMP5Lp end;function S1wg_DG.interpose(HpN_N,yP3QEJ)for pwi=#HpN_N,2,-1 do odpE(HpN_N,pwi,yP3QEJ)end;return -+HpN_N end -+function S1wg_DG.range(QP,Iy,O9P0mj) - if --bab.isNil(c_c)or not bab.isEqual(b_c,c_c,adb)then return false end end --for a_c,b_c in d_b(_db)do --local c_c=dcb[a_c]if bab.isNil(c_c)then return false end end;return true end --function bab.result(dcb,_db,...) --if dcb[_db]then if bab.isCallable(dcb[_db])then return dcb[_db](dcb,...)else return --dcb[_db]end end;if bab.isCallable(_db)then return _db(dcb,...)end end;function bab.isTable(dcb)return bba(dcb)=='table'end --function bab.isCallable(dcb)return --( --bab.isFunction(dcb)or --(bab.isTable(dcb)and aca(dcb)and aca(dcb).__call~=nil)or false)end --function bab.isArray(dcb)if not bab.isTable(dcb)then return false end;local _db=0 --for adb in --d_b(dcb)do _db=_db+1;if bab.isNil(dcb[_db])then return false end end;return true end --function bab.isIterable(dcb)return bab.toBoolean((dba(d_b,dcb)))end --function bab.isEmpty(dcb)if bab.isNil(dcb)then return true end;if bab.isString(dcb)then --return#dcb==0 end --if bab.isTable(dcb)then return aba(dcb)==nil end;return true end;function bab.isString(dcb)return bba(dcb)=='string'end;function bab.isFunction(dcb)return --bba(dcb)=='function'end;function bab.isNil(dcb) --return dcb==nil end --function bab.isNumber(dcb)return bba(dcb)=='number'end --function bab.isNaN(dcb)return bab.isNumber(dcb)and dcb~=dcb end --function bab.isFinite(dcb)if not bab.isNumber(dcb)then return false end;return --dcb>-cda and dcb0 then -+return hUL(UQ)end end end end -+function S1wg_DG.memoize(FG)local vLzqjJw=oUA({},{__mode='kv'}) -+return function(v2dsC21)if -+(vLzqjJw[v2dsC21]==nil)then vLzqjJw[v2dsC21]=FG(v2dsC21)end;return -+vLzqjJw[v2dsC21]end end -+function S1wg_DG.unfold(O,wx)local u,V_84V={}while true do V_84V,wx=O(wx) -+if V_84V~=nil then u[#u+1]=V_84V else break end end;return u end -+function S1wg_DG.once(qF)local IZbOX7TW=0;local Dd6ZLpU={}return -+function(...)IZbOX7TW=IZbOX7TW+1 -+if IZbOX7TW<=1 then Dd6ZLpU={...}end;return qF(hUL(Dd6ZLpU))end end;function S1wg_DG.before(MP,hgW2H5)local w4c=0;local C58={} -+return function(...)w4c=w4c+1;if w4c<=hgW2H5 then C58={...}end;return -+MP(hUL(C58))end end -+function S1wg_DG.after(Jk6Nh,hgW2H5) -+local s1Ws,desLYv=hgW2H5,0;return -+function(...)desLYv=desLYv+1;if desLYv>=s1Ws then return Jk6Nh(...)end end end -+function S1wg_DG.compose(...)local COq2NY9I=S1wg_DG.reverse{...} -+return -+function(...)local aoBEg65S,x6=true -+for t3cNa2l,Ik in -+kyWtqIf0(COq2NY9I)do if aoBEg65S then aoBEg65S=false;x6=Ik(...)else x6=Ik(x6)end end;return x6 end end -+function S1wg_DG.pipe(SeHOs,...)return S1wg_DG.compose(...)(SeHOs)end;function S1wg_DG.complement(P2rGsUx) -+return function(...)return not P2rGsUx(...)end end;function S1wg_DG.juxtapose(c,...)local v12AhMm={}for F2uxGC,Xs0 in -+kyWtqIf0({...})do v12AhMm[F2uxGC]=Xs0(c)end -+return hUL(v12AhMm)end -+function S1wg_DG.wrap(QK8ibF,TEio7k0z)return function(...)return -+TEio7k0z(QK8ibF,...)end end;function S1wg_DG.times(u,N)local O2YgxDc={} -+for VLsC67=1,(N or 1)do O2YgxDc[VLsC67]=u(VLsC67)end;return O2YgxDc end -+function S1wg_DG.bind(OHw4,FKZ)return function(...)return -+OHw4(FKZ,...)end end;function S1wg_DG.bind2(Fl,QhS8FvKI) -+return function(FaZIJL,...)return Fl(FaZIJL,QhS8FvKI,...)end end;function S1wg_DG.bindn(sOT2O5,...)local x={...} -+return function(...)return -+sOT2O5(hUL(S1wg_DG.append(x,{...})))end end -+function S1wg_DG.bindall(Wswd_OC,...) -+local E={...} -+for A0Un,nRHrI in kyWtqIf0(E)do local k=Wswd_OC[nRHrI]if k then -+Wswd_OC[nRHrI]=S1wg_DG.bind(k,Wswd_OC)end end;return Wswd_OC end;function S1wg_DG.cond(Zp) -+return function(...) -+for A,_L_ in kyWtqIf0(Zp)do if _L_[1](...)then return _L_[2](...)end end end end -+function S1wg_DG.both(...) -+local WHpm={...} -+return function(...) -+for g,HiR3yiw in kyWtqIf0(WHpm)do if not HiR3yiw(...)then return false end end;return true end end -+function S1wg_DG.either(...)local KeKbiDqN={...}return -+function(...)for WfrZqHH8,YX9s9O in kyWtqIf0(KeKbiDqN)do -+if YX9s9O(...)then return true end end;return false end end -+function S1wg_DG.neither(...)local y64dF={...} -+return function(...) -+for sNSsH,K in kyWtqIf0(y64dF)do if K(...)then return false end end;return true end end -+function S1wg_DG.uniqueId(o8T)qJExeUn2=qJExeUn2+1;if o8T then -+if v(o8T)=='string'then -+return o8T:format(qJExeUn2)elseif v(o8T)=='function'then return o8T(qJExeUn2)end end;return qJExeUn2 end -+function S1wg_DG.iterator(xeP,Tv_3VlmX,BT)local _y3z=0 -+return function()_y3z=_y3z+1;if BT and _y3z>BT then return end -+Tv_3VlmX=xeP(Tv_3VlmX)return Tv_3VlmX end end;function S1wg_DG.skip(rdl,NAP_5jYs) -+for BZnlpW=1,(NAP_5jYs or 1)do if rdl()==nil then return end end;return rdl end -+function S1wg_DG.tabulate(...) -+local isN={}for yRADzw1v in...do isN[#isN+1]=yRADzw1v end;return isN end -+function S1wg_DG.iterlen(...)local Jafp=0;for XWh8Ee in...do Jafp=Jafp+1 end;return Jafp end;function S1wg_DG.castArray(kpezL1e)return -+(v(kpezL1e)~='table')and{kpezL1e}or kpezL1e end -+function S1wg_DG.flip(h)return function(...)return -+h(hUL(S1wg_DG.reverse({...})))end end -+function S1wg_DG.nthArg(R7yfz_l9)return -+function(...)local D35PFLu={...}return -+D35PFLu[(R7yfz_l9 <0)and -+(#D35PFLu+R7yfz_l9+1)or R7yfz_l9]end end;function S1wg_DG.unary(wK) -+return function(...)local qeEwE={...}return wK(qeEwE[1])end end -+function S1wg_DG.ary(cbtvFnSa,fYKH_)fYKH_=fYKH_ or 1 -+return function(...) -+local W={...}local o={}for Mm99M=1,fYKH_ do o[Mm99M]=W[Mm99M]end -+return cbtvFnSa(hUL(o))end end -+function S1wg_DG.noarg(l6YH)return function()return l6YH()end end -+function S1wg_DG.rearg(gf2,F744Ew)return -+function(...)local zgxKF4={...}local UlvVvSBR={}for i2i,uRGAL in kyWtqIf0(F744Ew)do -+UlvVvSBR[i2i]=zgxKF4[uRGAL]end;return gf2(hUL(UlvVvSBR))end end -+function S1wg_DG.over(...)local UUlqXyb6={...}return -+function(...)local fOR92g8={}for jU26,WIPTsAPz in kyWtqIf0(UUlqXyb6)do -+fOR92g8[#fOR92g8+1]=WIPTsAPz(...)end;return fOR92g8 end end -+function S1wg_DG.overEvery(...)local DgUx8=S1wg_DG.over(...) -+return function(...) -+return S1wg_DG.reduce(DgUx8(...),function(imac,xX) -+return imac and xX end)end end -+function S1wg_DG.overSome(...)local Mfb6Kb=S1wg_DG.over(...) -+return function(...) -+return S1wg_DG.reduce(Mfb6Kb(...),function(RRjV,TDOaFo)return -+RRjV or TDOaFo end)end end -+function S1wg_DG.overArgs(tLo4,...)local m72l={...} -+return -+function(...)local npM3DSU={...}for HGp4e1=1,#m72l do local uzJt7E=m72l[HGp4e1] - if --b_b(bdb,a_c)then if not cdb then bdb[a_c]=bab[a_c]end else bdb[a_c]=bab[a_c]end end)return bdb end;_db._VERSION='Moses v'.._ba --_db._URL='http://github.com/Yonaba/Moses' --_db._LICENSE='MIT '_db._DESCRIPTION='utility-belt library for functional programming in Lua'return --_db end -\ No newline at end of file -+npM3DSU[HGp4e1]then npM3DSU[HGp4e1]=uzJt7E(npM3DSU[HGp4e1])end end;return -+tLo4(hUL(npM3DSU))end end -+function S1wg_DG.converge(sRe5S32N,Bp,rg)return -+function(...)return sRe5S32N(Bp(...),rg(...))end end -+function S1wg_DG.partial(S,...)local Fem={...} -+return -+function(...)local cHmVGY={...}local g29sXR={}for Vat,sfnkWAy8 in kyWtqIf0(Fem)do -+g29sXR[Vat]= -+(sfnkWAy8 =='_')and S1wg_DG.shift(cHmVGY)or sfnkWAy8 end;return -+S(hUL(S1wg_DG.append(g29sXR,cHmVGY)))end end -+function S1wg_DG.partialRight(hbJSGe9,...)local pI={...} -+return -+function(...)local B7jhm={...}local hj3={}for FKxU4=1,#pI do -+hj3[FKxU4]= -+(pI[FKxU4]=='_')and S1wg_DG.shift(B7jhm)or pI[FKxU4]end;return -+hbJSGe9(hUL(S1wg_DG.append(B7jhm,hj3)))end end -+function S1wg_DG.curry(UW,tReY)tReY=tReY or 2;local lex={} -+local function h79Pm(vksQpy4) -+if tReY==1 then return UW(vksQpy4)end;if vksQpy4 ~=nil then lex[#lex+1]=vksQpy4 end -+if#lex-S9TO and -+AN9 = 5.1, < 5.4" -+} -+build = { -+ type = "builtin", -+ modules = { -+ ["moses"] = "moses.lua", -+ ["moses_min"] = "moses_min.lua", -+ }, -+ copy_directories = {"doc","spec"} -+} -\ No newline at end of file -diff --git a/extra/moses/rockspec/moses-2.1.0-1.rockspec b/extra/moses/rockspec/moses-2.1.0-1.rockspec -new file mode 100644 -index 0000000..718c0c2 ---- /dev/null -+++ b/extra/moses/rockspec/moses-2.1.0-1.rockspec -@@ -0,0 +1,26 @@ -+package = "moses" -+version = "2.1.0-1" -+source = { -+ url = "https://github.com/Yonaba/Moses/archive/Moses-2.1.0-1.tar.gz", -+ dir = "Moses-Moses-2.1.0-1" -+} -+description = { -+ summary = "Utility-belt library for functional programming in Lua", -+ detailed = [[ -+ A utility-belt library for functional programming, which complements the built-in -+ Lua table library, making easier operations on arrays, lists, collections. -+ ]], -+ homepage = "http://yonaba.github.com/Moses/", -+ license = "MIT " -+} -+dependencies = { -+ "lua >= 5.1, < 5.4" -+} -+build = { -+ type = "builtin", -+ modules = { -+ ["moses"] = "moses.lua", -+ ["moses_min"] = "moses_min.lua", -+ }, -+ copy_directories = {"doc","spec"} -+} -\ No newline at end of file -diff --git a/extra/moses/spec/array_spec.lua b/extra/moses/spec/array_spec.lua -index e79a6f7..725d6ec 100644 ---- a/extra/moses/spec/array_spec.lua -+++ b/extra/moses/spec/array_spec.lua -@@ -1,713 +1,934 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Array functions specs', function() -+describe('Array functions specs', function() - -- context('sample', function() -+ describe('sample', function() - -- test('samples n values from array', function() -- local array = _.range(1,20) -- local sample = _.sample(array, 5) -- assert_equal(#sample, 5) -- _.each(sample, function(__,v) -- assert_true(_.include(array, v)) -+ it('samples n values from array', function() -+ local array = M.range(1,20) -+ local sample = M.sample(array, 5) -+ assert.equal(#sample, 5) -+ M.each(sample, function(__,v) -+ assert.is_true(M.include(array, v)) - end) - end) - -- test('when not given, n defaults to 1', function() -- local array = _.range(1,20) -- local sample = _.sample(array) -- assert_true(_.include(array, sample)) -+ it('when not given, n defaults to 1', function() -+ local array = M.range(1,20) -+ local sample = M.sample(array) -+ assert.equal(#sample, 1) -+ assert.is_true(M.include(array, sample[1])) - end) - -+ it('if n == 0, returns an empty array', function() -+ local array = M.range(1,5) -+ local sample = M.sample(array, 0) -+ assert.is_true(#sample == 0) -+ end) -+ -+ it('if n < 0, returns an empty array', function() -+ local array = M.range(1,5) -+ assert.is_true(#M.sample(array, -1) == 0) -+ end) -+ - end) - -- context('sampleProb', function() -+ describe('sampleProb', function() - -- test('returns a sample of an array values', function() -- local array = _.range(1,20) -- local sample = _.sampleProb(array, 0.2) -- _.each(sample, function(__,v) -- assert_true(_.include(array, v)) -+ it('returns a sample of an array values', function() -+ local array = M.range(1,20) -+ local sample = M.sampleProb(array, 0.2) -+ M.each(sample, function(__,v) -+ assert.is_true(M.include(array, v)) - end) - end) - - end) -+ -+ describe('nsorted', function() -+ -+ it('returns the top n-values from an array', function() -+ local array = M.range(1,20) -+ assert.is_true(M.isEqual(M.nsorted(array,5),{1,2,3,4,5})) -+ -+ local function comp(a,b) return a > b end -+ assert.is_true(M.isEqual(M.nsorted(array,3,comp),{20,19,18})) -+ end) -+ -+ end) - -- context('toArray', function() -+ describe('shuffle', function() -+ -+ it('shuffles values and objects in a collection', function() -+ local values = {'a','b','c','d'} -+ assert.is_true(M.same(M.shuffle (values),values)) -+ end) -+ -+ it('can accept a seed value to init randomization', function() -+ local values = {'a','b','c','d'} -+ local seed = os.time() -+ assert.is_true(M.same(M.shuffle(values,seed),values)) -+ end) -+ -+ it('shuffled table has the same elements in a different order', function() -+ local values = {'a','b','c','d'} -+ assert.is_true(M.same(M.shuffle(values),values)) -+ assert.is_true(M.same(M.shuffle(values),values)) -+ end) -+ -+ end) -+ -+ describe('pack', function() - -- test('converts a vararg list to an array', function() -- assert_true(_.isArray(_.toArray(1,2,3,4))) -- assert_true(_.isEqual(_.toArray(1,2,8,'d','a',0),{1,2,8,'d','a',0})) -+ it('converts a vararg list to an array', function() -+ assert.is_true(M.isArray(M.pack(1,2,3,4))) -+ assert.is_true(M.isEqual(M.pack(1,2,8,'d','a',0),{1,2,8,'d','a',0})) - end) - -- test('preserves input order', function() -- local args = _.toArray(1,2,3,4,5) -- for i = 1, 5 do assert_equal(args[i], i) end -+ it('preserves input order', function() -+ local args = M.pack(1,2,3,4,5) -+ for i = 1, 5 do assert.equal(args[i], i) end - end) - - end) - -- context('find', function() -+ describe('find', function() - -- test('looks for a value in a given array and returns its position', function() -- assert_equal(_.find({4,3,2,1},2), 3) -+ it('looks for a value in a given array and returns its position', function() -+ assert.equal(M.find({4,3,2,1},2), 3) - end) - -- test('uses _.isEqual to compare values', function() -- assert_equal(_.find({{4},{3},{2},{1}},{3}), 2) -+ it('uses M.isEqual to compare values', function() -+ assert.equal(M.find({{4},{3},{2},{1}},{3}), 2) - end) - -- test('returns the index of the first occurence', function() -- assert_equal(_.find({4,4,3,3,2,2,1,1},2),5) -+ it('returns the index of the first occurence', function() -+ assert.equal(M.find({4,4,3,3,2,2,1,1},2),5) - end) - -- test('can start the search at a specific position', function() -- assert_equal(_.find({4,4,3,3,2,1,2,1,1},2,6),7) -+ it('can start the search at a specific position', function() -+ assert.equal(M.find({4,4,3,3,2,1,2,1,1},2,6),7) - end) - - end) - -- context('reverse', function() -+ describe('reverse', function() - -- test('reverse values and objects in a given array', function() -- assert_true(_.isEqual(_.reverse({1,2,3,'d'}),{'d',3,2,1})) -+ it('reverse values and objects in a given array', function() -+ assert.is_true(M.isEqual(M.reverse({1,2,3,'d'}),{'d',3,2,1})) - end) - - end) - -- context('fill', function() -+ describe('fill', function() - -- test('fills an array with a value', function() -- local array = _.range(1,5) -- assert_true(_.isEqual(_.fill(array,0),{0,0,0,0,0})) -+ it('fills an array with a value', function() -+ local array = M.range(1,5) -+ assert.is_true(M.isEqual(M.fill(array,0),{0,0,0,0,0})) - end) - -- test('fills an array starting from an index', function() -- local array = _.range(1,5) -- assert_true(_.isEqual(_.fill(array,0,4),{1,2,3,0,0})) -+ it('fills an array starting from an index', function() -+ local array = M.range(1,5) -+ assert.is_true(M.isEqual(M.fill(array,0,4),{1,2,3,0,0})) - end) - -- test('fills an array replacing values inside a range', function() -- local array = _.range(1,5) -- assert_true(_.isEqual(_.fill(array,0,3,4),{1,2,0,0,5})) -+ it('fills an array replacing values inside a range', function() -+ local array = M.range(1,5) -+ assert.is_true(M.isEqual(M.fill(array,0,3,4),{1,2,0,0,5})) - end) - -- test('enlarges the array when the last index is greater than array size', function() -- local array = _.range(1,5) -- assert_true(_.isEqual(_.fill(array,0,3,8),{1,2,0,0,0,0,0,0})) -+ it('enlarges the array when the last index is greater than array size', function() -+ local array = M.range(1,5) -+ assert.is_true(M.isEqual(M.fill(array,0,3,8),{1,2,0,0,0,0,0,0})) - end) - - end) - -- context('selectWhile', function() -+ describe('zeros', function() -+ -+ it('returns an array of n zeros', function() -+ assert.is_true(M.isEqual(M.zeros(5), {0,0,0,0,0})) -+ assert.is_true(M.isEqual(M.zeros(2), {0,0})) -+ assert.is_true(M.isEqual(M.zeros(1), {0})) -+ end) - -- test('collect values from an array while they pass a thruth test', function() -- assert_true(_.isEqual(_.selectWhile({2,4,6,8}, function(i,v) -- return v%2==0 -- end),{2,4,6,8})) -+ end) -+ -+ describe('ones', function() -+ -+ it('returns an array of n zeros', function() -+ assert.is_true(M.isEqual(M.ones(5), {1,1,1,1,1})) -+ assert.is_true(M.isEqual(M.ones(3), {1,1,1})) -+ assert.is_true(M.isEqual(M.ones(1), {1})) - end) -+ -+ end) -+ -+ describe('vector', function() - -- test('breaks as soon as one value do not pass the test', function() -- assert_true(_.isEqual(_.selectWhile({2,4,6,8,9,10,12}, function(i,v) -- return v%2==0 -- end),{2,4,6,8})) -+ it('returns an array of n times a given value', function() -+ assert.is_true(M.isEqual(M.vector(false,4), {false, false, false, false})) -+ local f = function() end -+ assert.is_true(M.isEqual(M.vector(f,2), {f, f})) -+ end) -+ -+ end) -+ -+ describe('selectWhile', function() -+ -+ it('collect values from an array while they pass a thruth test', function() -+ assert.is_true(M.isEqual(M.selectWhile({2,4,6,8}, function(v)return v%2==0 end),{2,4,6,8})) -+ end) -+ -+ it('breaks as soon as one value do not pass the test', function() -+ assert.is_true(M.isEqual(M.selectWhile({2,4,6,8,9,10,12}, function(v) return v%2==0 end),{2,4,6,8})) - end) - - end) - -- context('dropWhile', function() -+ describe('dropWhile', function() - -- test('rejects values from an array while they pass a thruth test', function() -- assert_true(_.isEqual(_.dropWhile({2,4,6,8}, function(i,v) -- return v%2==0 -- end),{})) -+ it('rejects values from an array while they pass a thruth test', function() -+ assert.is_true(M.isEqual(M.dropWhile({2,4,6,8}, function(v) return v%2==0 end),{})) - end) - -- test('breaks as soon as one value do not pass the test', function() -- assert_true(_.isEqual(_.dropWhile({2,4,6,8,9,10,12}, function(i,v) -- return v%2==0 -- end),{9,10,12})) -+ it('breaks as soon as one value do not pass the test', function() -+ assert.is_true(M.isEqual(M.dropWhile({2,4,6,8,9,10,12}, function(v) return v%2==0 end),{9,10,12})) - end) - - end) - -- context('sortedIndex', function() -+ describe('sortedIndex', function() - -- test('returns the index at which a value should be inserted to preserve order', function() -+ it('returns the index at which a value should be inserted to preserve order', function() - local comp = function(a,b) return a5 end)) -+ it('returns nil when nothing was found', function() -+ assert.is_nil(M.findIndex({1,2,3,4,5},function(_,v) return v>5 end)) - end) - - end) - -- context('findLastIndex', function() -+ describe('findLastIndex', function() - -- test('returns the last index at which a predicate passes a truth test', function() -- assert_equal(_.findLastIndex({1,2,3,4,5},function(_,v) return v%2==0 end),4) -+ it('returns the last index at which a predicate passes a truth test', function() -+ assert.equal(M.findLastIndex({1,2,3,4,5},function(_,v) return v%2==0 end),4) - end) - -- test('returns nil when nothing was found', function() -- assert_nil(_.findLastIndex({1,2,3,4,5},function(_,v) return v>5 end)) -+ it('returns nil when nothing was found', function() -+ assert.is_nil(M.findLastIndex({1,2,3,4,5},function(_,v) return v>5 end)) - end) - - end) - -- context('addTop', function() -+ describe('addTop', function() - -- test('adds values at the top of an array', function() -- assert_true(_.isEqual(_.addTop({},1,2,3),{3,2,1})) -- assert_true(_.isEqual(_.addTop({},'a',true,3),{3,true,'a'})) -+ it('adds values at the top of an array', function() -+ assert.is_true(M.isEqual(M.addTop({},1,2,3),{3,2,1})) -+ assert.is_true(M.isEqual(M.addTop({},'a',true,3),{3,true,'a'})) - end) - -- test('preserves the existing elements', function() -- assert_true(_.isEqual(_.addTop({1,2},1,2,3),{3,2,1,1,2})) -- assert_true(_.isEqual(_.addTop({'i','j'},'a',true,3),{3,true,'a','i','j'})) -+ it('preserves the existing elements', function() -+ assert.is_true(M.isEqual(M.addTop({1,2},1,2,3),{3,2,1,1,2})) -+ assert.is_true(M.isEqual(M.addTop({'i','j'},'a',true,3),{3,true,'a','i','j'})) - end) - - end) - -- context('push', function() -+ describe('prepend', function() - -- test('appends values at the end of an array', function() -- assert_true(_.isEqual(_.push({},1,2,3),{1,2,3})) -- assert_true(_.isEqual(_.push({},'a',true,3),{'a',true,3})) -+ it('adds values at the top of an array, preserving order', function() -+ assert.is_true(M.isEqual(M.prepend({},1,2,3),{1,2,3})) -+ assert.is_true(M.isEqual(M.prepend({},'a',true,3),{'a',true,3})) -+ end) -+ -+ it('preserves the existing elements', function() -+ assert.is_true(M.isEqual(M.prepend({1,2},1,2,3),{1,2,3,1,2})) -+ assert.is_true(M.isEqual(M.prepend({'i','j'},'a',true,3),{'a',true,3,'i','j'})) -+ end) -+ -+ end) -+ -+ describe('push', function() -+ -+ it('appends values at the end of an array', function() -+ assert.is_true(M.isEqual(M.push({},1,2,3),{1,2,3})) -+ assert.is_true(M.isEqual(M.push({},'a',true,3),{'a',true,3})) - end) - -- test('preserves the existing elements', function() -- assert_true(_.isEqual(_.push({1,2},1,2,3),{1,2,1,2,3})) -- assert_true(_.isEqual(_.push({'i','j'},'a',true,3),{'i','j','a',true,3})) -+ it('preserves the existing elements', function() -+ assert.is_true(M.isEqual(M.push({1,2},1,2,3),{1,2,1,2,3})) -+ assert.is_true(M.isEqual(M.push({'i','j'},'a',true,3),{'i','j','a',true,3})) - end) - - end) - -- context('pop', function() -+ describe('shift', function() - -- test('returns the value at the top of a given array', function() -- assert_equal(_.pop {1,7,9} ,1) -+ it('returns the value at the top of a given array', function() -+ assert.equal(M.shift {1,7,9} ,1) - end) - -- test('also removes this value from the given array', function() -+ it('also removes this value from the given array', function() - local array = {1,7,9} -- assert_equal(_.pop(array),1) -- assert_true(_.isEqual(array,{7,9})) -+ assert.equal(M.shift(array),1) -+ assert.is_true(M.isEqual(array,{7,9})) - end) - - end) - -- context('unshift', function() -+ describe('unshift', function() - -- test('returns the value at the end of a given array', function() -- assert_equal(_.unshift {1,7,9} ,9) -+ it('returns the value at the end of a given array', function() -+ assert.equal(M.unshift {1,7,9} ,9) - end) - -- test('also removes this value from the given array', function() -+ it('also removes this value from the given array', function() - local array = {1,7,9} -- assert_equal(_.unshift(array),9) -- assert_true(_.isEqual(array,{1,7})) -+ assert.equal(M.unshift(array),9) -+ assert.is_true(M.isEqual(array,{1,7})) - end) - - end) - -- context('pull', function() -+ describe('pull', function() - -- test('removes all listed values in a given array', function() -- assert_true(_.same(_.pull({1,4,3,1,2,3},1),{4,3,2,3})) -- assert_true(_.same(_.pull({1,4,3,1,2,3},1,3),{4,2})) -+ it('removes all listed values in a given array', function() -+ assert.is_true(M.same(M.pull({1,4,3,1,2,3},1),{4,3,2,3})) -+ assert.is_true(M.same(M.pull({1,4,3,1,2,3},1,3),{4,2})) - end) - - end) - -- context('removeRange', function() -+ describe('removeRange', function() - -- test('removes all values within "start" and "finish" indexes', function() -- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},2,4),{1,5,6})) -+ it('removes all values within "start" and "finish" indexes', function() -+ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6},2,4),{1,5,6})) - end) - -- test('arg "finish" defaults to the end of the array when not given ', function() -- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},3),{1,2})) -+ it('arg "finish" defaults to the end of the array when not given ', function() -+ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6},3),{1,2})) - end) - -- test('arg "start" defaults to the initial index when not given ', function() -- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6}),{})) -+ it('arg "start" defaults to the initial index when not given ', function() -+ assert.is_true(M.isEqual(M.removeRange({1,2,3,4,5,6}),{})) - end) -- -- test('args "start" and "finish" are be clamped to the array bound ', function() -- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},0,100),{})) -- end) - -- test('leaves the array untouched when "finish" < "start"', function() -- assert_true(_.isEqual(_.removeRange({1,2,3,4,5,6},4,2),{1,2,3,4,5,6})) -+ it('throws an error when "finish" < "start"', function() -+ assert.error(function()M.removeRange({1,2,3,4,5,6},4,2) end) - end) - - end) - -- context('chunk', function() -+ describe('chunk', function() - -- test('chunks in blocks consecutive values returning the same value from a given function', function() -+ it('chunks in blocks consecutive values returning the same value from a given function', function() - local t = {1,2,2,3,3,4,4} -- local v = _.chunk(t, function(k,v) return v%2==0 end) -- assert_equal(#v, 4) -- _.each(v[1], function(k) -- assert_equal(v[1][k],1) -- end) -- assert_equal(#v[1],1) -- _.each(v[2], function(k) -- assert_equal(v[2][k],2) -- end) -- assert_equal(#v[2],2) -- _.each(v[3], function(k) -- assert_equal(v[3][k],3) -- end) -- assert_equal(#v[3],2) -- _.each(v[4], function(k) -- assert_equal(v[4][k],4) -- end) -- assert_equal(#v[4],2) -- end) -- -- test('Returns the first argument in case it is not an array', function() -- local t = {a = 1, b = 2} -- assert_equal(_.chunk(t, function(k,v) return v%2==0 end), t) -+ local v = M.chunk(t, function(v) return v%2==0 end) -+ assert.equal(#v, 4) -+ assert.is_true(M.isEqual(v[1], {1})) -+ assert.is_true(M.isEqual(v[2], {2,2})) -+ assert.is_true(M.isEqual(v[3], {3,3})) -+ assert.is_true(M.isEqual(v[4], {4,4})) -+ end) -+ -+ it('chunks in blocks consecutive values when using identity as function', function() -+ local t = {1,1,2,2,3,3,4} -+ local v = M.chunk(t, function(v) return v end) -+ assert.is_nil(v[0]) -+ assert.equal(#v, 4) -+ assert.is_true(M.isEqual(v[1], {1,1})) -+ assert.is_true(M.isEqual(v[2], {2,2})) -+ assert.is_true(M.isEqual(v[3], {3,3})) -+ assert.is_true(M.isEqual(v[4], {4})) - end) - - end) - -- context('slice',function() -+ describe('slice',function() - -- test('slices a portion of an array',function() -- assert_true(_.isEqual(_.slice({'a','b','c','d','e'},2,3),{'b','c'})) -+ it('slices a portion of an array',function() -+ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'},2,3),{'b','c'})) - end) - -- test('arg "right" bound defaults to the array length when not given',function() -- assert_true(_.isEqual(_.slice({'a','b','c','d','e'},3),{'c','d','e'})) -+ it('arg "right" bound defaults to the array length when not given',function() -+ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'},3),{'c','d','e'})) - end) - -- test('arg "left" bound defaults to the initial index when not given',function() -- assert_true(_.isEqual(_.slice({'a','b','c','d','e'}),{'a','b','c','d','e'})) -+ it('arg "left" bound defaults to the initial index when not given',function() -+ assert.is_true(M.isEqual(M.slice({'a','b','c','d','e'}),{'a','b','c','d','e'})) - end) - - end) - -- context('first',function() -+ describe('first',function() - -- test('returns the n-first elements', function() -- assert_true(_.isEqual(_.first({5,8,12,20},2),{5,8})) -+ it('returns the n-first elements', function() -+ assert.is_true(M.isEqual(M.first({5,8,12,20},2),{5,8})) - end) - -- test('arg "n" defaults 1 when not given', function() -- assert_true(_.isEqual(_.first({5,8,12,20}),{5})) -+ it('arg "n" defaults 1 when not given', function() -+ assert.is_true(M.isEqual(M.first({5,8,12,20}),{5})) - end) - - end) - -- context('initial',function() -+ describe('initial',function() - -- test('exludes the last N elements', function() -- assert_true(_.isEqual(_.initial({5,8,12,20},3),{5})) -- assert_true(_.isEqual(_.initial({5,8,12,20},4),{})) -+ it('exludes the last N elements', function() -+ assert.is_true(M.isEqual(M.initial({5,8,12,20},3),{5})) -+ assert.is_true(M.isEqual(M.initial({5,8,12,20},4),{})) - end) - -- test('returns all values but the last one if arg "n" was not given', function() -- assert_true(_.isEqual(_.initial({5,8,12,20}),{5,8,12})) -+ it('returns all values but the last one if arg "n" was not given', function() -+ assert.is_true(M.isEqual(M.initial({5,8,12,20}),{5,8,12})) - end) - -- test('passing "n" greather than the array size returns an empty', function() -- assert_true(_.isEqual(_.initial({5,8,12,20},5),{})) -+ it('passing "n" greather than the array size returns an empty', function() -+ assert.is_true(M.isEqual(M.initial({5,8,12,20},5),{})) - end) - -- test('returns the whole array when "n" equals 0', function() -- assert_true(_.isEqual(_.initial({5,8,12,20},0),{5,8,12,20})) -- end) -- -- test('returns "nil" when arg "n" < 0', function() -- assert_nil(_.initial({5,8,12,20},-1)) -- end) -+ it('returns the whole array when "n" equals 0', function() -+ assert.is_true(M.isEqual(M.initial({5,8,12,20},0),{5,8,12,20})) -+ end) - - end) - -- context('last',function() -+ describe('last',function() - -- test('returns the last N elements', function() -- assert_true(_.isEqual(_.last({5,8,12,20},3),{8,12,20})) -- assert_true(_.isEqual(_.last({5,8,12,20},1),{20})) -- assert_true(_.isEqual(_.last({5,8,12,20},2),{12,20})) -- assert_true(_.isEqual(_.last({5,8,12,20},4),{5,8,12,20})) -+ it('returns the last N elements', function() -+ assert.is_true(M.isEqual(M.last({5,8,12,20},3),{8,12,20})) -+ assert.is_true(M.isEqual(M.last({5,8,12,20},1),{20})) -+ assert.is_true(M.isEqual(M.last({5,8,12,20},2),{12,20})) -+ assert.is_true(M.isEqual(M.last({5,8,12,20},4),{5,8,12,20})) - end) - -- test('returns all values but the first one if arg "n" was not given', function() -- assert_true(_.isEqual(_.last({5,8,12,20}),{8,12,20})) -+ it('returns all values but the first one if arg "n" was not given', function() -+ assert.is_true(M.isEqual(M.last({5,8,12,20}),{8,12,20})) - end) - -- test('if arg "n" is lower than the array size, returns all values', function() -- assert_true(_.isEqual(_.last({5,8,12,20},5),{5,8,12,20})) -- end) -- -- test('returns "nil" when arg "n" <= 0', function() -- assert_nil(_.last({5,8,12,20},0)) -- assert_nil(_.last({5,8,12,20},-1)) -- end) -+ it('if arg "n" is lower than the array size, returns all values', function() -+ assert.is_true(M.isEqual(M.last({5,8,12,20},5),{5,8,12,20})) -+ end) - - end) - -- context('rest',function() -+ describe('rest',function() - -- test('excludes all values before a given index', function() -- assert_true(_.isEqual(_.rest({5,8,12,20},2),{8,12,20})) -- assert_true(_.isEqual(_.rest({5,8,12,20},1),{5,8,12,20})) -- assert_true(_.isEqual(_.rest({5,8,12,20},4),{20})) -+ it('excludes all values before a given index', function() -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},2),{8,12,20})) -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},1),{5,8,12,20})) -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},4),{20})) - end) - -- test('returns an empty array when arg "index" > #array', function() -- assert_true(_.isEqual(_.rest({5,8,12,20},5),{})) -+ it('returns an empty array when arg "index" > #array', function() -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},5),{})) - end) - -- test('returns all values if arg "index" <= 0', function() -- assert_true(_.isEqual(_.rest({5,8,12,20},0),{5,8,12,20})) -- assert_true(_.isEqual(_.rest({5,8,12,20},-1),{5,8,12,20})) -+ it('returns all values if arg "index" <= 0', function() -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},0),{5,8,12,20})) -+ assert.is_true(M.isEqual(M.rest({5,8,12,20},-1),{5,8,12,20})) - end) - - end) - -- context('nth', function() -+ describe('nth', function() - -- test('returns the value at "index"', function() -- assert_equal(3, _.nth({1,2,3,4,5,6}, 3)) -+ it('returns the value at "index"', function() -+ assert.equal(3, M.nth({1,2,3,4,5,6}, 3)) - end) - - end) - -- context('compact',function() -+ describe('compact',function() - -- test('trims out all falsy values from an array', function() -- assert_true(_.isEqual(_.compact({a,'a',false,'b',true}),{'a','b',true})) -+ it('trims out all falsy values from an array', function() -+ assert.is_true(M.isEqual(M.compact({a,'a',false,'b',true}),{'a','b',true})) - end) - - end) - -- context('flatten',function() -+ describe('flatten',function() - -- test('flattens nested arrays', function() -- assert_true(_.isEqual(_.flatten({1,{2,3},{4,5,{6,7}}}),{1,2,3,4,5,6,7})) -+ it('flattens nested arrays', function() -+ assert.is_true(M.isEqual(M.flatten({1,{2,3},{4,5,{6,7}}}),{1,2,3,4,5,6,7})) - end) - -- test('when given arg "shallow", flatten only first level', function() -- assert_true(_.isEqual(_.flatten({1,{2,3},{4,5,{6,7}}},true),{1,2,3,4,5,{6,7}})) -+ it('when given arg "shallow", flatten only first level', function() -+ assert.is_true(M.isEqual(M.flatten({1,{2,3},{4,5,{6,7}}},true),{1,2,3,4,5,{6,7}})) - end) - - end) - -- context('difference',function() -+ describe('difference',function() - -- test('returns values in the first array not present in the second array', function() -+ it('returns values in the first array not present in the second array', function() - local array = {1,2,'a',4,5} -- assert_true(_.isEqual(_.difference(array,{1,'a'}),{2,4,5})) -- assert_true(_.isEqual(_.difference(array,{5}),{1,2,'a',4})) -+ assert.is_true(M.isEqual(M.difference(array,{1,'a'}),{2,4,5})) -+ assert.is_true(M.isEqual(M.difference(array,{5}),{1,2,'a',4})) - end) - -- test('ignores values in the second array not found in the first array', function() -+ it('ignores values in the second array not found in the first array', function() - local array = {1,2,'a',4,5} -- assert_true(_.isEqual(_.difference(array,{1,'a','b','c'}),{2,4,5})) -+ assert.is_true(M.isEqual(M.difference(array,{1,'a','b','c'}),{2,4,5})) - end) - -- test('leaves array untouched when given no extra-args', function() -- assert_true(_.isEqual(_.difference({1,2,'a',4,5}),{1,2,'a',4,5})) -+ it('leaves array untouched when given no extra-args', function() -+ assert.is_true(M.isEqual(M.difference({1,2,'a',4,5}),{1,2,'a',4,5})) - end) - - end) - -- context('union',function() -+ describe('union',function() - -- test('returns the duplicate-free union of all passed-in arrays', function() -+ it('returns the duplicate-free union of all passed-in arrays', function() - local a = {"a"}; local b = {1,2,3}; local c = {2,10} -- assert_true(_.isEqual(_.union(a,b,c),{'a',1,2,3,10})) -+ assert.is_true(M.isEqual(M.union(a,b,c),{'a',1,2,3,10})) - end) - -- test('accepts nested arrays as well', function() -+ it('accepts nested arrays as well', function() - local a = {"a",{"b","c"}}; local b = {1,{2},3}; local c = {2,10} -- assert_true(_.isEqual(_.union(a,b,c),{'a','b','c',1,2,3,10})) -+ assert.is_true(M.isEqual(M.union(a,b,c),{'a','b','c',1,2,3,10})) - end) - - end) - -- context('intersection',function() -+ describe('intersection',function() - -- test('returns the intersection of all passed-in arrays', function() -+ it('returns the intersection of all passed-in arrays', function() - local a = {1,3}; local b = {4,2,3}; local c = {2,3,10} -- assert_true(_.isEqual(_.intersection(a,b,c),{3})) -+ assert.is_true(M.isEqual(M.intersection(a,b,c),{3})) - end) - -- test('fails with nested arrays', function() -+ it('fails with nested arrays', function() - local a = {1,{3}}; local b = {4,2,3}; local c = {2,3,10} -- assert_true(_.isEqual(_.intersection(a,b,c),{})) -+ assert.is_true(M.isEqual(M.intersection(a,b,c),{})) - end) - - end) - -- context('symmetricDifference',function() -+ describe('disjoint',function() - -- test('returns the symmetric difference from two arrays', function() -+ it('checks if all passed-in arrays are disjoint', function() -+ local A = {'a'} -+ local B = {'a',1,3} -+ local C = {3,10,2} -+ -+ assert.is_false(M.disjoint(A,B)) -+ assert.is_true(M.disjoint(A,C)) -+ assert.is_false(M.disjoint(B,C)) -+ end) -+ -+ end) -+ -+ describe('symmetricDifference',function() -+ -+ it('returns the symmetric difference from two arrays', function() - local a = {1,3}; local b = {4,2,3}; local c = {2,3,10} -- assert_true(_.same(_.symmetricDifference(a, b), {1,4,2})) -- assert_true(_.same(_.symmetricDifference(a, c), {1,2,10})) -- assert_true(_.same(_.symmetricDifference(b, c), {4,10})) -+ assert.is_true(M.same(M.symmetricDifference(a, b), {1,4,2})) -+ assert.is_true(M.same(M.symmetricDifference(a, c), {1,2,10})) -+ assert.is_true(M.same(M.symmetricDifference(b, c), {4,10})) - end) - - end) - -- context('unique',function() -+ describe('unique',function() - -- test('returns a duplicate-free array',function() -- assert_true(_.isEqual(_.unique({1,1,2,2,3,3,4,4,4,5}),{1,2,3,4,5})) -+ it('returns a duplicate-free array',function() -+ assert.is_true(M.isEqual(M.unique({1,1,2,2,3,3,4,4,4,5}),{1,2,3,4,5})) - end) - - end) - -- context('isunique',function() -+ describe('isunique',function() - -- test('Checks if a given array is duplicate-free',function() -- assert_true(_.isunique({1,2,3,4,5})) -- assert_false(_.isunique({1,2,3,4,4})) -+ it('Checks if a given array is duplicate-free',function() -+ assert.is_true(M.isunique({1,2,3,4,5})) -+ assert.is_false(M.isunique({1,2,3,4,4})) - end) - - end) - -- context('zip',function() -- test('zips together values from different arrays sharing the same index', function() -+ describe('duplicates',function() -+ it('returns a list of all duplicates in array', function() -+ assert.is_true(M.isEqual(M.duplicates({1,2,3,3,8,8,3,2,4}),{2,3,8})) -+ assert.is_true(M.isEqual(M.duplicates({true, false, true, 1, '5', '1', '5'}),{true, '5'})) -+ end) -+ end) -+ -+ describe('zip',function() -+ it('zips together values from different arrays sharing the same index', function() - local names = {'Bob','Alice','James'}; local ages = {22, 23} -- assert_true(_.isEqual(_.zip(names,ages),{{'Bob',22},{'Alice',23},{'James'}})) -+ assert.is_true(M.isEqual(M.zip(names,ages),{{'Bob',22},{'Alice',23},{'James'}})) -+ assert.is_true(M.isEqual(M.zip({false},{false}),{{false,false}})) - end) - end) - -- context('append',function() -+ describe('zipWith',function() -+ it('zips together values from different arrays sharing the same index using a function', function() -+ local names = {'Bob','Alice','James'}; local ages = {22, 23, 25} -+ local function introduce(name, age) return 'My name is '..name..' and I am '..age..' years old.' end -+ local t = M.zipWith(introduce,names,ages) -+ assert.equal(t[1],'My name is Bob and I am 22 years old.') -+ assert.equal(t[2],'My name is Alice and I am 23 years old.') -+ assert.equal(t[3],'My name is James and I am 25 years old.') -+ end) -+ end) -+ -+ describe('append',function() - -- test('appends two arrays together', function() -- assert_true(_.isEqual(_.append({1,2,3},{'a','b'}),{1,2,3,'a','b'})) -+ it('appends two arrays together', function() -+ assert.is_true(M.isEqual(M.append({1,2,3},{'a','b'}),{1,2,3,'a','b'})) - end) - - end) - -- context('interleave',function() -+ describe('interleave',function() - -- test('interleaves values from passed-in arrays', function() -- assert_true(_.isEqual(_.interleave({1,2,3},{'a','b','c'}),{1,'a',2,'b',3,'c'})) -- assert_true(_.isEqual(_.interleave({1},{'a','b','c'}),{1,'a','b','c'})) -+ it('interleaves values from passed-in arrays', function() -+ assert.is_true(M.isEqual(M.interleave({1,2,3},{'a','b','c'}),{1,'a',2,'b',3,'c'})) -+ assert.is_true(M.isEqual(M.interleave({1},{'a','b','c'}),{1,'a','b','c'})) - end) - - end) - -- context('interpose',function() -+ describe('interpose',function() - -- test('interposes a value in-between values from a passed-in array', function() -- assert_true(_.isEqual(_.interpose('a',{1,2,3}),{1,'a',2,'a',3})) -- assert_true(_.isEqual(_.interpose(false,{5,5,5,5}),{5,false,5,false,5,false,5})) -- end) -+ it('interposes a value in-between values from a passed-in array', function() -+ assert.is_true(M.isEqual(M.interpose({1,2,3},'a'),{1,'a',2,'a',3})) -+ assert.is_true(M.isEqual(M.interpose({5,5,5,5}, false),{5,false,5,false,5,false,5})) -+ end) -+ -+ it('leaves the array untouched if containing a single element', function() -+ assert.is_true(M.isEqual(M.interpose({1},'a'),{1})) -+ end) - - end) - -- context('range',function() -+ describe('range',function() - -- test('generate an arithmetic progression', function() -- assert_true(_.isEqual(_.range(1,5,1),{1,2,3,4,5})) -- assert_true(_.isEqual(_.range(-2,5,1),{-2,-1,0,1,2,3,4,5})) -- assert_true(_.isEqual(_.range(1,5,2),{1,3,5})) -- end) -- -- test('arg "step" default to 1 when no given', function() -- assert_true(_.isEqual(_.range(1,5),{1,2,3,4,5})) -+ it('generate an arithmetic progression', function() -+ assert.is_true(M.isEqual(M.range(1,5,1),{1,2,3,4,5})) -+ assert.is_true(M.isEqual(M.range(-2,5,1),{-2,-1,0,1,2,3,4,5})) -+ assert.is_true(M.isEqual(M.range(1,5,2),{1,3,5})) - end) - -- test('when a limit cannot be reached via "step", returns an empty array', function() -- assert_true(_.isEqual(_.range(1,5,0),{})) -- assert_true(_.isEqual(_.range(1,5,-1),{})) -+ it('arg "step" default to 1 or -1 when no given', function() -+ assert.is_true(M.isEqual(M.range(1,5),{1,2,3,4,5})) -+ assert.is_true(M.isEqual(M.range(5,1),{5,4,3,2,1})) - end) - -- test('handles real values as well', function() -- assert_true(_.isEqual(_.range(3.2,5,0.5),{3.2,3.7,4.2,4.7})) -+ it('handles real values as well', function() -+ assert.is_true(M.isEqual(M.range(3.2,5,0.5),{3.2,3.7,4.2,4.7})) - end) - -- test('when only one arg is passed, counts from 0', function() -- assert_true(_.isEqual(_.range(3),{0,1,2,3})) -+ it('when only one arg is passed, counts from 1', function() -+ assert.is_true(M.isEqual(M.range(3),{1,2,3})) -+ assert.is_true(M.isEqual(M.range(-3),{-1,-2,-3})) - end) - - end) - -- context('rep',function() -+ describe('rep',function() - -- test('generates a list of n repetitions of a value', function() -- assert_true(_.isEqual(_.rep('a',4),{'a','a','a','a'})) -- assert_true(_.isEqual(_.rep(false,3),{false, false, false})) -+ it('generates a list of n repetitions of a value', function() -+ assert.is_true(M.isEqual(M.rep('a',4),{'a','a','a','a'})) -+ assert.is_true(M.isEqual(M.rep(false,3),{false, false, false})) -+ end) -+ -+ end) -+ -+ describe('powerset',function() -+ -+ it('generates the powerset of a given array', function() -+ assert.is_true(M.same(M.powerset({1,2,3}),{{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}})) -+ assert.is_true(M.same(M.powerset({1,2,3,4}),{{},{1},{2},{3},{4},{1,2},{1,3},{1,4},{2,3},{2,4},{3,4},{1,2,3},{1,2,4},{1,3,4},{2,3,4},{1,2,3,4}})) - end) - - end) - -- context('partition',function() -+ describe('partition',function() - -- test('iterates on partitions of a given array', function() -- local array = _.range(1,10) -- local split5 = {_.range(1,5), _.range(6,10)} -- local split3 = {_.range(1,3), _.range(4,6), _.range(7,9), {10}} -+ it('iterates on partitions of a given array', function() -+ local array = M.range(1,10) -+ local split5 = {M.range(1,5), M.range(6,10)} -+ local split3 = {M.range(1,3), M.range(4,6), M.range(7,9), {10}} - local i = 0 -- for p in _.partition(array,5) do -+ for p in M.partition(array,5) do - i = i + 1 -- assert_true(_.isEqual(p, split5[i])) -+ assert.is_true(M.isEqual(p, split5[i])) - end - i = 0 -- for p in _.partition(array,3) do -+ for p in M.partition(array,3) do - i = i + 1 -- assert_true(_.isEqual(p, split3[i])) -+ assert.is_true(M.isEqual(p, split3[i])) - end - end) - -- test('if a 3rd argument pad is supplied, will adjust the last partition', function() -- local array = _.range(1,10) -+ it('if a 3rd argument pad is supplied, will adjust the last partition', function() -+ local array = M.range(1,10) - local split4 = {{1,2,3,4},{5,6,7,8},{9,10,0,0}} - local i = 0 -- for p in _.partition(array,4,0) do -+ for p in M.partition(array,4,0) do - i = i + 1 -- assert_true(_.isEqual(p, split4[i])) -+ assert.is_true(M.isEqual(p, split4[i])) - end - end) - - end) - -- context('sliding',function() -+ describe('overlapping',function() - -- test('returns overlapping subsequences', function() -- local array = _.range(1,10) -+ it('returns overlapping subsequences', function() -+ local array = M.range(1,10) - local sliding2 = {{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10}} - local sliding3 = {{1,2,3},{3,4,5},{5,6,7},{7,8,9},{9,10}} - local sliding5 = {{1,2,3,4,5},{5,6,7,8,9},{9,10}} - local i = 0 -- for p in _.sliding(array,2) do -+ for p in M.overlapping(array,2) do - i = i + 1 -- assert_true(_.isEqual(p, sliding2[i])) -+ assert.is_true(M.isEqual(p, sliding2[i])) - end - i = 0 -- for p in _.sliding(array,3) do -+ for p in M.overlapping(array,3) do - i = i + 1 -- assert_true(_.isEqual(p, sliding3[i])) -+ assert.is_true(M.isEqual(p, sliding3[i])) - end - i = 0 -- for p in _.sliding(array,5) do -+ for p in M.overlapping(array,5) do - i = i + 1 -- assert_true(_.isEqual(p, sliding5[i])) -+ assert.is_true(M.isEqual(p, sliding5[i])) - end - end) - -- test('if a 3rd argument pad is supplied, will adjust the last subsequence', function() -- local array = _.range(1,10) -+ it('if a 3rd argument pad is supplied, will adjust the last subsequence', function() -+ local array = M.range(1,10) - local sliding3 = {{1,2,3},{3,4,5},{5,6,7},{7,8,9},{9,10,0}} - local sliding5 = {{1,2,3,4,5},{5,6,7,8,9},{9,10,0,0,0}} - local i = 0 -- for p in _.sliding(array,3,0) do -+ for p in M.overlapping(array,3,0) do - i = i + 1 -- assert_true(_.isEqual(p, sliding3[i])) -+ assert.is_true(M.isEqual(p, sliding3[i])) - end - i = 0 -- for p in _.sliding(array,5,0) do -+ for p in M.overlapping(array,5,0) do - i = i + 1 -- assert_true(_.isEqual(p, sliding5[i])) -+ assert.is_true(M.isEqual(p, sliding5[i])) - end - end) - -- end) -- -- context('permutation',function() -+ end) -+ -+ describe('aperture', function() - -- test('iterates on permutations of a given array', function() -- local array = {'a','b', 'c'} -- local perm = {'abc','acb', 'bac', 'bca', 'cab', 'cba'} -- for p in _.permutation(array) do -- local strp = _.concat(p) -- _.pull(perm, strp) -+ it('returns sliding partitions of a given array', function() -+ local array = M.range(1,5) -+ local slides2 = {{1,2},{2,3},{3,4},{4,5}} -+ local slides3 = {{1,2,3},{2,3,4},{3,4,5}} -+ local slides4 = {{1,2,3,4},{2,3,4,5}} -+ local slides5 = {{1,2,3,4,5}} -+ -+ local i = 0 -+ for p in M.aperture(array, 2) do -+ i = i + 1 -+ assert.is_true(M.isEqual(p, slides2[i])) - end -- assert_true(#perm == 0) -- end) -+ -+ i = 0 -+ for p in M.aperture(array, 3) do -+ i = i + 1 -+ assert.is_true(M.isEqual(p, slides3[i])) -+ end -+ -+ i = 0 -+ for p in M.aperture(array, 4) do -+ i = i + 1 -+ assert.is_true(M.isEqual(p, slides4[i])) -+ end -+ -+ i = 0 -+ for p in M.aperture(array, 5) do -+ i = i + 1 -+ assert.is_true(M.isEqual(p, slides5[i])) -+ end -+ end) -+ -+ end) -+ -+ describe('pairwise', function() -+ -+ it('returns sliding partitions of a given array', function() -+ local array = M.range(1,5) -+ local pw = {{1,2},{2,3},{3,4},{4,5}} -+ -+ local i = 0 -+ for p in M.pairwise(array) do -+ i = i + 1 -+ assert.is_true(M.isEqual(p, pw[i])) -+ end -+ end) - - end) - -- context('invert',function() -+ describe('permutation',function() - -- test('switches key-values pairs', function() -- assert_true(_.isEqual(_.invert({1,2,3}),{1,2,3})) -- assert_true(_.isEqual(_.invert({'a','b','c'}),{a = 1,b = 2,c = 3})) -- end) -+ it('iterates on permutations of a given array', function() -+ local array = {'a','b', 'c'} -+ local perm = {'abc','acb', 'bac', 'bca', 'cab', 'cba'} -+ for p in M.permutation(array) do -+ local strp = M.concat(p) -+ M.pull(perm, strp) -+ end -+ assert.is_true(#perm == 0) -+ end) - -- end) -+ end) - -- context('concat',function() -+ describe('concat',function() - -- test('concatenates an array contents', function() -- assert_equal(_.concat({1,2,3,4}),'1234') -- assert_equal(_.concat({'a',1,0,1,'b'}),'a101b') -+ it('concatenates an array contents', function() -+ assert.equal(M.concat({1,2,3,4}),'1234') -+ assert.equal(M.concat({'a',1,0,1,'b'}),'a101b') - end) - -- test('handles boolean values', function() -- assert_equal(_.concat({1,true,3,false}),'1true3false') -+ it('handles boolean values', function() -+ assert.equal(M.concat({1,true,3,false}),'1true3false') - end) - -- test('when arg "sep" is given, uses "sep" as a separator', function() -- assert_equal(_.concat({1,3,false,'A'},' '),'1 3 false A') -- assert_equal(_.concat({1,3,false,'A'},', '),'1, 3, false, A') -+ it('when arg "sep" is given, uses "sep" as a separator', function() -+ assert.equal(M.concat({1,3,false,'A'},' '),'1 3 false A') -+ assert.equal(M.concat({1,3,false,'A'},', '),'1, 3, false, A') - end) - -- test('when args "i" and/or "j" are given, concats values within "i" and "j" indexes', function() -- assert_equal(_.concat({1,3,false,'A'},' ',2,3),'3 false') -- assert_equal(_.concat({1,3,false,'A'},', ',2,3),'3, false') -- assert_equal(_.concat({1,3,false,'A','K'},' ',2),'3 false A K') -+ it('when args "i" and/or "j" are given, concats values within "i" and "j" indexes', function() -+ assert.equal(M.concat({1,3,false,'A'},' ',2,3),'3 false') -+ assert.equal(M.concat({1,3,false,'A'},', ',2,3),'3, false') -+ assert.equal(M.concat({1,3,false,'A','K'},' ',2),'3 false A K') - end) - -+ end) -+ -+ describe('xprod',function() -+ -+ it('returns all possible pairs', function() -+ local r = M.xprod({1,2,3},{'a','b'}) -+ assert.is_true(M.isEqual(r[1],{1,'a'})) -+ assert.is_true(M.isEqual(r[2],{1,'b'})) -+ assert.is_true(M.isEqual(r[3],{2,'a'})) -+ assert.is_true(M.isEqual(r[4],{2,'b'})) -+ assert.is_true(M.isEqual(r[5],{3,'a'})) -+ assert.is_true(M.isEqual(r[6],{3,'b'})) -+ end) -+ -+ end) -+ -+ describe('xpairs',function() -+ -+ it('create pairs by prepending value to array values', function() -+ local r = M.xpairs(1,{1,2,3}) -+ assert.is_true(M.isEqual(r[1],{1,1})) -+ assert.is_true(M.isEqual(r[2],{1,2})) -+ assert.is_true(M.isEqual(r[3],{1,3})) -+ end) -+ -+ end) -+ -+ describe('xpairsRight',function() -+ -+ it('create pairs by appending value to array values', function() -+ local r = M.xpairsRight(1,{1,2,3}) -+ assert.is_true(M.isEqual(r[1],{1,1})) -+ assert.is_true(M.isEqual(r[2],{2,1})) -+ assert.is_true(M.isEqual(r[3],{3,1})) -+ end) -+ -+ end) -+ -+ describe('sum',function() -+ -+ it('returns the sum of array values', function() -+ assert.equal(M.sum {1,2,3,4,5}, 15) -+ assert.equal(M.sum {1,2,3,4}, 10) -+ assert.equal(M.sum {1,2,3}, 6) -+ end) -+ -+ end) -+ -+ describe('product',function() -+ -+ it('returns the product of array values', function() -+ assert.equal(M.product {1,2,3,4,5}, 120) -+ assert.equal(M.product {1,2,3,4}, 24) -+ assert.equal(M.product {1,2,3}, 6) -+ end) -+ -+ end) -+ -+ describe('mean',function() -+ -+ it('returns the mean of array values', function() -+ assert.equal(M.mean {1,2,3,4,5}, 3) -+ assert.equal(M.mean {1,2,3,4}, 2.5) -+ end) -+ -+ end) -+ -+ describe('meadian',function() -+ -+ it('returns the median of array values', function() -+ assert.equal(M.median {1,2,3,4,5}, 3) -+ assert.equal(M.median {1,2,3,4}, 2.5) -+ end) -+ - end) - - end) -\ No newline at end of file -diff --git a/extra/moses/spec/chaining_spec.lua b/extra/moses/spec/chaining_spec.lua -index 065491a..fb11b90 100644 ---- a/extra/moses/spec/chaining_spec.lua -+++ b/extra/moses/spec/chaining_spec.lua -@@ -1,34 +1,34 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Chaining specs', function() -+describe('Chaining specs', function() - -- context('chain', function() -+ describe('chain', function() - -- test('Chains a value',function() -- local v = _.chain({1,2,3,4}) -- :filter(function(i,k) return k%2~=0 end) -+ it('Chains a value',function() -+ local v = M.chain({1,2,3,4}) -+ :filter(function(k) return k%2~=0 end) - :max() - :value() -- assert_equal(v, 3) -+ assert.equal(v, 3) - end) - -- test('_(value) is the same as _.chain(value)', function() -- local v = _({1,2,3,4}) -- :filter(function(i,k) return k%2~=0 end) -+ it('M(value) is the same as M.chain(value)', function() -+ local v = M({1,2,3,4}) -+ :filter(function(k) return k%2~=0 end) - :max() - :value() -- assert_equal(v, 3) -+ assert.equal(v, 3) - end) - - end) - -- context('value', function() -+ describe('value', function() - -- test('Unwraps a chained object',function() -+ it('Unwraps a chained object',function() - local t = {1,2,3} -- assert_equal(_.chain(t):value(), t) -- assert_equal(_(t):value(), t) -+ assert.equal(M.chain(t):value(), t) -+ assert.equal(M(t):value(), t) - end) - - end) -diff --git a/extra/moses/spec/func_spec.lua b/extra/moses/spec/func_spec.lua -index 2803922..627f519 100644 ---- a/extra/moses/spec/func_spec.lua -+++ b/extra/moses/spec/func_spec.lua -@@ -1,537 +1,789 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Utility functions specs', function() -+describe('Utility functions specs', function() - -- context('noop', function() -+ describe('noop', function() - -- test('the no-operation function',function() -- assert_nil(_.noop()) -- assert_nil(_.noop(nil)) -- assert_nil(_.noop(false)) -- assert_nil(_.noop({})) -- assert_nil(_.noop(function() end)) -- assert_nil(_.noop(_.noop)) -+ it('the no-operation function',function() -+ assert.is_nil(M.noop()) -+ assert.is_nil(M.noop(nil)) -+ assert.is_nil(M.noop(false)) -+ assert.is_nil(M.noop({})) -+ assert.is_nil(M.noop(function() end)) -+ assert.is_nil(M.noop(M.noop)) - end) - - end) - -- context('identity', function() -+ describe('identity', function() - -- test('returns the received value',function() -- assert_equal(_.identity(1),1) -+ it('returns the received value',function() -+ assert.equal(M.identity(1),1) - local v = {x = 0} -- assert_equal(_.identity(v),v) -- assert_not_equal(v,{x = 0}) -+ assert.equal(M.identity(v),v) -+ assert.is_not.equals(v,{x = 0}) - end) - - end) - -- context('constant', function() -+ describe('call', function() - -- test('creates a constant function',function() -- local gravity = _.constant(9.81) -- assert_equal(gravity(),9.81) -- assert_equal(gravity(10), 9.81) -- assert_equal(gravity(nil), 9.81) -+ it('calls f(...) and returns the results',function() -+ assert.equal(M.call(math.pow, 2, 3), 8) -+ assert.equal(M.call(string.len, 'hello' ), 5) -+ assert.equal(M.call(table.concat, {1,2,3,4,5}, ',', 2, 4),"2,3,4") - end) - - end) - -- context('memoize', function() -+ describe('applySpec', function() -+ -+ it('returns a spec function which produces objects',function() -+ local stats = M.applySpec({ -+ min = function(...) return math.min(...) end, -+ max = function(...) return math.max(...) end, -+ }) - -- local fib_time, fib_value, mfib_time, mfib_value -- local fib, mfib -- -- before(function() -- local function fib(n) -- return n < 2 and n or fib(n-1)+fib(n-2) -- end -- local times = 10 -- local mfib = _.memoize(fib) -- fib_time = os.clock() -- for i = 1, times do fib_value = (fib_value or 0)+fib(20) end -- fib_time = (os.clock()-fib_time)*1000 -- -- mfib_time = os.clock() -- for i = 1, times do mfib_value = (mfib_value or 0)+mfib(20) end -- mfib_time = (os.clock()-mfib_time )*1000 -- end) -+ for i = 1, 10 do -+ local mn, mx = math.random(1,10), math.random(11,20) -+ local t = M.range(mn, mx) -+ table.sort(t) -+ local unpack = unpack or table.unpack -+ local r = stats(unpack(t)) -+ assert.equal(r.min, t[1]) -+ assert.equal(r.max, t[#t]) -+ end -+ end) -+ -+ end) -+ -+ describe('thread', function() -+ -+ it('threads a value through functions',function() -+ local function inc(x) return x + 1 end -+ local function double(x) return 2 * x end -+ local function square(x) return x * x end -+ assert.equal(M.thread(2, inc, double, square), 36) -+ assert.equal(M.thread(3, double, inc, square), 49) -+ assert.equal(M.thread(4, square, double, inc), 33) -+ assert.equal(M.thread(5, square, inc, double), 52) -+ end) -+ -+ it('accepts funcs taking more than one arg',function() -+ local function inc(x) return x + 1 end -+ local function add(x, y) return x + y end -+ local function pow(x, y) return x ^ y end -+ assert.equal(M.thread(2, inc, {add, 3}, {pow, 2}), 36) -+ assert.equal(M.thread(2, {add, 4}, inc, {pow, 2}), 49) -+ end) -+ -+ end) -+ -+ describe('threadRight', function() -+ -+ it('threads a value through functions',function() -+ local function inc(x) return x + 1 end -+ local function double(x) return 2 * x end -+ local function square(x) return x * x end -+ assert.equal(M.threadRight(2, inc, double, square), 36) -+ assert.equal(M.threadRight(3, double, inc, square), 49) -+ assert.equal(M.threadRight(4, square, double, inc), 33) -+ assert.equal(M.threadRight(5, square, inc, double), 52) -+ end) -+ -+ it('accepts funcs taking more than one arg',function() -+ local function inc(x) return x + 1 end -+ local function add(x, y) return x + y end -+ local function pow(x, y) return x ^ y end -+ assert.equal(M.threadRight(2, inc, {add, 3}, {pow, 2}), 64) -+ assert.equal(M.threadRight(2, {add, 4}, inc, {pow, 2}), 128) -+ end) -+ -+ end) -+ -+ describe('dispatch', function() - -- test('memoizes an expensive function by caching its results',function() -- assert_true(mfib_time<=fib_time) -+ it('produces a dispatch function',function() -+ local f = M.dispatch( -+ function() return nil end, -+ function (v) return v+1 end, -+ function (v) return 2*v end -+ ) -+ assert.equal(f(5),6) -+ assert.equal(f(7),8) - end) - -- test('can take a hash function to compute an unique output for multiple args',function() -- -- local function hash(a,b) return (a^13+b^19) end -- local function fact(a) return a <= 1 and 1 or a*fact(a-1) end -- local diffFact = function(a,b) return fact(a)-fact(b) end -- local mdiffFact = _.memoize(function(a,b) return fact(a)-fact(b) end,hash) -- local times, rep = 100, 10 -- -- local time = os.clock() -- for j = 1,times do -- for ai = 1,rep do -- for aj = 1,rep do diffFact(ai,aj) end -- end -- end -- time = (os.clock()-time)*1000 -- -- local mtime = os.clock() -- for j = 1,times do -- for ai = 1,rep do -- for aj = 1,rep do mdiffFact(ai,aj) end -- end -- end -- mtime = (os.clock()-mtime)*1000 -- -- assert_true(mtime<=time) -+ end) -+ -+ describe('memoize', function() - -+ local fib_time, fib_value, mfib_time, mfib_value -+ local fib, mfib -+ -+ it('memoizes an expensive function by caching its results',function() -+ local function fib(n) return n < 2 and n or fib(n-1)+fib(n-2) end -+ local mfib = M.memoize(fib) -+ assert.equal(fib(10), mfib(10)) -+ assert.equal(fib(15), mfib(15)) -+ assert.equal(fib(8), mfib(8)) -+ assert.equal(fib(13), mfib(13)) - end) - -- end) -+ end) -+ -+ describe('unfold', function() -+ -+ it('builds a list from a seed value using a iterator',function() -+ local function iter(seed) -+ if seed < 100 then return seed, seed * 2 end -+ end -+ assert.is_true(M.isEqual(M.unfold(iter,1),{1,2,4,8,16,32,64})) -+ assert.is_true(M.isEqual(M.unfold(iter,5),{5, 10,20,40,80})) -+ assert.is_true(M.isEqual(M.unfold(iter,10),{10,20,40,80})) -+ end) -+ -+ end) - -- context('once', function() -+ describe('once', function() - -- test('returns a version of a function that runs once',function() -- local sq = _.once(function(a) return a*a end) -- assert_equal(sq(2),4) -+ it('returns a version of a function that runs once',function() -+ local sq = M.once(function(a) return a*a end) -+ assert.equal(sq(2),4) - end) - -- test('successive calls will keep yielding the original answer',function() -- local sq = _.once(function(a) return a*a end) -+ it('successive calls will keep yielding the original answer',function() -+ local sq = M.once(function(a) return a*a end) - for i = 1,10 do -- assert_equal(sq(i),1) -+ assert.equal(sq(i),1) - end - end) - - end) - -- context('before', function() -+ describe('before', function() - -- test('returns a version of a function that runs no more than count-th calls',function() -+ it('returns a version of a function that runs no more than count-th calls',function() - local function say(something) return something end -- local speak3times = _.before(say, 3) -- assert_equal(speak3times('a'), 'a') -- assert_equal(speak3times('b'), 'b') -- assert_equal(speak3times('c'), 'c') -- assert_equal(speak3times('d'), 'c') -- assert_equal(speak3times('e'), 'c') -- assert_equal(speak3times('f'), 'c') -+ local speak3times = M.before(say, 3) -+ assert.equal(speak3times('a'), 'a') -+ assert.equal(speak3times('b'), 'b') -+ assert.equal(speak3times('c'), 'c') -+ assert.equal(speak3times('d'), 'c') -+ assert.equal(speak3times('e'), 'c') -+ assert.equal(speak3times('f'), 'c') - end) - - end) - - -- context('after', function() -+ describe('after', function() - -- test('returns a function that will respond on its count-th call',function() -+ it('returns a function that will respond on its count-th call',function() - local function a(r) return (r) end -- a = _.after(a,5) -+ a = M.after(a,5) - for i = 1,10 do - if i < 5 then -- assert_nil(a(i)) -+ assert.is_nil(a(i)) - else -- assert_equal(a(i),i) -+ assert.equal(a(i),i) - end - end - end) - - end) - -- context('compose', function() -+ describe('compose', function() - -- test('can compose commutative functions',function() -+ it('can compose commutative functions',function() - local greet = function(name) return "hi: " .. name end - local exclaim = function(sentence) return sentence .. "!" end -- assert_equal(_.compose(greet,exclaim)('moe'),'hi: moe!') -- assert_equal(_.compose(exclaim,greet)('moe'),'hi: moe!') -+ assert.equal(M.compose(greet,exclaim)('moe'),'hi: moe!') -+ assert.equal(M.compose(exclaim,greet)('moe'),'hi: moe!') - end) - -- test('composes mutiple functions',function() -+ it('composes mutiple functions',function() - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end -- local compositae = _.compose(f,g,h) -- assert_equal(compositae(10),36) -- assert_equal(compositae(20),121) -+ local compositae = M.compose(f,g,h) -+ assert.equal(compositae(10),36) -+ assert.equal(compositae(20),121) - end) - -- test('compose non commutative functions in reverse order',function() -+ it('compose non commutative functions in reverse order',function() - local function f(s) return (s or '')..'f' end - local function g(s) return (s or '')..'g' end - local function h(s) return (s or '')..'h' end -- assert_equal(_.compose(f,g,h)(),'hgf') -- assert_equal(_.compose(h,g,f)(),'fgh') -- assert_equal(_.compose(f,h,g)(),'ghf') -- assert_equal(_.compose(g,h,f)(),'fhg') -+ assert.equal(M.compose(f,g,h)(),'hgf') -+ assert.equal(M.compose(h,g,f)(),'fgh') -+ assert.equal(M.compose(f,h,g)(),'ghf') -+ assert.equal(M.compose(g,h,f)(),'fhg') - end) - - end) - -- context('pipe', function() -+ describe('pipe', function() - -- test('pipes a value through a series of functions',function() -+ it('pipes a value through a series of functions',function() - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end -- assert_equal(_.pipe(10,f,g,h),36) -- assert_equal(_.pipe(20,f,g,h),121) -+ assert.equal(M.pipe(10,f,g,h),36) -+ assert.equal(M.pipe(20,f,g,h),121) - end) - - end) - -- context('complement', function() -+ describe('complement', function() - -- test('returns a function which returns the logical complement of a given function',function() -- assert_false(_.complement(function() return true end)()) -- assert_true(_.complement(function() return false end)()) -- assert_true(_.complement(function() return nil end)()) -- assert_false(_.complement(function() return 1 end)()) -+ it('returns a function which returns the logical complement of a given function',function() -+ assert.is_false(M.complement(function() return true end)()) -+ assert.is_true(M.complement(function() return false end)()) -+ assert.is_true(M.complement(function() return nil end)()) -+ assert.is_false(M.complement(function() return 1 end)()) - end) - - end) - -- context('juxtapose', function() -+ describe('juxtapose', function() - -- test('calls a sequence of functions with the same set of args',function() -+ it('calls a sequence of functions with the same set of args',function() - local function f(x) return x^2 end - local function g(x) return x+1 end - local function h(x) return x/2 end -- local rf, rg, rh = _.juxtapose(10, f, g, h) -- assert_equal(rf, 100) -- assert_equal(rg, 11) -- assert_equal(rh, 5) -+ local rf, rg, rh = M.juxtapose(10, f, g, h) -+ assert.equal(rf, 100) -+ assert.equal(rg, 11) -+ assert.equal(rh, 5) - end) - - end) - -- context('wrap', function() -+ describe('wrap', function() - -- test('wraps a function and passes args',function() -+ it('wraps a function and passes args',function() - local greet = function(name) return "hi: " .. name end -- local backwards = _.wrap(greet, function(f,arg) -+ local backwards = M.wrap(greet, function(f,arg) - return f(arg) ..'\nhi: ' .. arg:reverse() - end) -- assert_equal(backwards('john'),'hi: john\nhi: nhoj') -+ assert.equal(backwards('john'),'hi: john\nhi: nhoj') - end) - - end) - -- context('times', function() -+ describe('times', function() - -- test('calls a given function n times',function() -+ it('calls a given function n times',function() - local f = ('Lua programming'):gmatch('.') -- local r = _.times(3,f) -- assert_true(_.isEqual(r,{'L','u','a'})) -+ local r = M.times(f, 3) -+ assert.is_true(M.isEqual(r,{'L','u','a'})) - - local count = 0 - local function counter() count = count+1 end -- _.times(10,counter) -- assert_equal(count,10) -+ M.times(counter, 10) -+ assert.equal(count,10) - end) - - end) - -- context('bind', function() -+ describe('bind', function() - -- test('binds a value to the first arg of a function',function() -- local sqrt2 = _.bind(math.sqrt,2) -- assert_equal(sqrt2(),math.sqrt(2)) -+ it('binds a value to the first arg of a function',function() -+ local sqrt2 = M.bind(math.sqrt,2) -+ assert.equal(sqrt2(),math.sqrt(2)) - end) - - end) - -- context('bind2', function() -+ describe('bind2', function() - -- test('binds a value to the second arg of a function',function() -- local last2 = _.bind2(_.last,2) -+ it('binds a value to the second arg of a function',function() -+ local last2 = M.bind2(M.last,2) - local r = last2({1,2,3,4,5,6}) -- assert_true(_.isEqual(r, {5,6})) -+ assert.is_true(M.isEqual(r, {5,6})) - end) - - end) - -- context('bindn', function() -+ describe('bindn', function() - -- test('binds n values to as the n-first args of a function',function() -+ it('binds n values to as the n-first args of a function',function() - local function out(...) - return table.concat {...} - end -- out = _.bindn(out,'OutPut',':',' ') -- assert_equal(out(1,2,3),'OutPut: 123') -- assert_equal(out('a','b','c','d'),'OutPut: abcd') -+ out = M.bindn(out,'OutPut',':',' ') -+ assert.equal(out(1,2,3),'OutPut: 123') -+ assert.equal(out('a','b','c','d'),'OutPut: abcd') - end) - - end) - -- context('bindAll', function() -+ describe('bindall', function() - -- test('binds methods to object',function() -+ it('binds methods to object',function() - local window = { - setPos = function(w,x,y) w.x, w.y = x, y end, - setName = function(w,name) w.name = name end, - getName = function(w) return w.name end, - } -- window = _.bindAll(window, 'setPos', 'setName', 'getName') -+ window = M.bindall(window, 'setPos', 'setName', 'getName') - window.setPos(10,15) - window.setName('fooApp') - -- assert_equal(window.x, 10) -- assert_equal(window.y, 15) -- assert_equal(window.name, 'fooApp') -- assert_equal(window.getName(), 'fooApp') -+ assert.equal(window.x, 10) -+ assert.equal(window.y, 15) -+ assert.equal(window.name, 'fooApp') -+ assert.equal(window.getName(), 'fooApp') -+ end) -+ -+ end) -+ -+ describe('cond', function() -+ -+ it('return a function which runs a set of predicates',function() -+ local multipleOf = M.cond({ -+ {function(v) return v%2==0 end, function(v) return v..' is multiple of 2' end}, -+ {function(v) return v%3==0 end, function(v) return v..' is multiple of 3' end}, -+ {function(v) return v%5==0 end, function(v) return v..' is multiple of 5' end}, -+ {function() return true end, function(v) return 'could not find an answer for '..v end} -+ }) -+ for i = 15, 20 do -+ assert.equal(multipleOf(i), -+ i%2 == 0 and i..' is multiple of 2' or -+ i%3 == 0 and i..' is multiple of 3' or -+ 'could not find an answer for '..i) -+ end - end) - - end) - -- context('uniqueId', function() -+ describe('both', function() -+ -+ it('returns a truthy func when all funcs returns true',function() -+ local f = M.both( -+ function(x) return x > 0 end, -+ function(x) return x < 10 end, -+ function(x) return x % 2 == 0 end -+ ) -+ assert.is_true(f(2)) -+ assert.is_true(f(8)) -+ assert.is_false(f(9)) -+ end) -+ -+ end) -+ -+ describe('either', function() -+ -+ it('returns a truthy func when at least one of its funcs returns true',function() -+ local f = M.either( -+ function(x) return x > 0 end, -+ function(x) return x % 2 == 0 end -+ ) -+ assert.is_true(f(0)) -+ assert.is_false(f(-3)) -+ end) -+ -+ end) -+ -+ describe('neither', function() -+ -+ it('returns a truthy func when neither of its funcs returns true',function() -+ local f = M.neither( -+ function(x) return x > 10 end, -+ function(x) return x % 2 == 0 end -+ ) -+ assert.is_false(f(12)) -+ assert.is_false(f(8)) -+ assert.is_true(f(7)) -+ end) -+ -+ end) - -- test('returns an unique (for the current session) integer Id',function() -+ describe('uniqueId', function() -+ -+ it('returns an unique (for the current session) integer Id',function() - local ids = {} - for i = 1,100 do -- local newId = _.uniqueId() -- assert_false(_.include(ids,newId)) -- _.push(ids,newId) -+ local newId = M.uniqueId() -+ assert.is_false(M.include(ids,newId)) -+ M.push(ids,newId) - end - end) - -- test('accepts a string template to format the returned id',function() -+ it('accepts a string template to format the returned id',function() - local ids = {} - for i = 1,100 do -- local newId = _.uniqueId('ID:%s') -- assert_equal(newId,'ID:'..newId:sub(4)) -- assert_false(_.include(ids,newId)) -- _.push(ids,newId) -+ local newId = M.uniqueId('ID:%s') -+ assert.equal(newId,'ID:'..newId:sub(4)) -+ assert.is_false(M.include(ids,newId)) -+ M.push(ids,newId) - end - end) - -- test('accepts a function as argument to format the returned id',function() -+ it('accepts a function as argument to format the returned id',function() - local ids = {} - local formatter = function(ID) return '$'..ID..'$' end - for i = 1,100 do -- local newId = _.uniqueId(formatter) -- assert_not_nil(newId:match('^%$%d+%$$')) -- assert_false(_.include(ids,newId)) -- _.push(ids,newId) -+ local newId = M.uniqueId(formatter) -+ assert.is_true(newId:match('^%$%d+%$$') ~= nil) -+ assert.is_false(M.include(ids,newId)) -+ M.push(ids,newId) - end - end) - - end) - -- context('iterator', function() -+ describe('iterator', function() -+ -+ it('creates an iterator which continuously applies f on an input',function() -+ local next_even = M.iterator(function(x) return x + 2 end, 0) -+ assert.equal(next_even(), 2) -+ assert.equal(next_even(), 4) -+ assert.equal(next_even(), 6) -+ assert.equal(next_even(), 8) -+ assert.equal(next_even(),10) -+ end) -+ -+ it('can be set to run up to a maximum number of calls',function() -+ local next_even = M.iterator(function(x) return x + 2 end, 0, 3) -+ assert.equal(next_even(), 2) -+ assert.equal(next_even(), 4) -+ assert.equal(next_even(), 6) -+ assert.is_nil(next_even()) -+ end) -+ -+ end) -+ -+ describe('skip', function() - -- test('creates an iterator which continuously applies f on an input',function() -- local next_even = _.iterator(function(x) return x + 2 end, 0) -- assert_equal(next_even(), 2) -- assert_equal(next_even(), 4) -- assert_equal(next_even(), 6) -- assert_equal(next_even(), 8) -- assert_equal(next_even(),10) -+ it('consumes the first n values of an iterator',function() -+ local w = "hello" -+ local char = string.gmatch(w,'.') -+ local iter = M.skip(char, 3) -+ assert.equal(iter(), 'l') -+ assert.equal(iter(), 'o') - end) -+ -+ it('consumes the first n values of an iterator',function() -+ local w = "lua" -+ local char = string.gmatch(w,'.') -+ local iter = M.skip(char) -+ assert.equal(iter(), 'u') -+ assert.equal(iter(), 'a') -+ end) - - end) - -- context('array', function() -+ describe('tabulate', function() - -- test('iterates a given iterator and returns its values in an array',function() -- local letters = _.array(('Lua'):gmatch('.')) -- assert_true(_.isEqual(letters,{'L','u','a'})) -+ it('iterates a given iterator and returns its values in an array',function() -+ local letters = M.tabulate(('Lua'):gmatch('.')) -+ assert.is_true(M.isEqual(letters,{'L','u','a'})) - -- local numbers = _.array(pairs(_.range(1,10))) -- assert_true(_.isEqual(numbers,_.range(1,10))) -+ local numbers = M.tabulate(pairs(M.range(1,10))) -+ assert.is_true(M.isEqual(numbers,M.range(1,10))) - end) - -- end) -+ end) -+ -+ describe('iterlen', function() -+ -+ it('returns the iterator length',function() -+ local text = 'letters' -+ local chars = string.gmatch(text, '.') -+ assert.equal(M.iterlen(chars),7) -+ end) -+ -+ it('it consumes the iterator',function() -+ local text = 'lua' -+ local chars = string.gmatch(text, '.') -+ assert.equal(M.iterlen(chars),3) -+ assert.is_nil(chars()) -+ end) -+ -+ end) -+ -+ describe('castArray', function() -+ -+ it('converts value to an array',function() -+ assert.is_true(M.isEqual(M.castArray(1),{1})) -+ assert.is_true(M.isEqual(M.castArray(print),{print})) -+ assert.is_true(M.isEqual(M.castArray(true),{true})) -+ end) -+ -+ it('leaves given value untouched if it is an array',function() -+ local t1 = {1,2} -+ local t2 = {nil, true, false} -+ assert.is_true(M.isEqual(M.castArray(t1),t1)) -+ assert.is_true(M.isEqual(M.castArray(t2),t2)) -+ end) -+ -+ end) - -- context('flip', function() -+ describe('flip', function() - -- test('creates a function which runs f with arguments flipped',function() -+ it('creates a function which runs f with arguments flipped',function() - local function f(...) return table.concat({...}) end -- local flipped = _.flip(f) -- assert_equal(flipped('a','b','c'),'cba') -+ local flipped = M.flip(f) -+ assert.equal(flipped('a','b','c'),'cba') - end) - -- end) -+ end) -+ -+ describe('nthArg', function() -+ -+ it('creates a function which returns the nth arg',function() -+ local f2 = M.nthArg(2) -+ local f3 = M.nthArg(3) -+ local f4 = M.nthArg(4) -+ assert.equal(f2(4,8,5,4,6),8) -+ assert.equal(f3(4,8,5,4,6),5) -+ assert.equal(f4(4,8,5,4,6),4) -+ end) -+ -+ it('if n is negative, will count from the end',function() -+ local f2 = M.nthArg(-2) -+ local f3 = M.nthArg(-3) -+ local f4 = M.nthArg(-4) -+ assert.equal(f2(4,8,5,4,6),4) -+ assert.equal(f3(4,8,5,4,6),5) -+ assert.equal(f4(4,8,5,4,6),8) -+ end) -+ -+ end) -+ -+ describe('unary', function() -+ -+ it('creates a function which accepts only one arg',function() -+ local f = M.unary(function(...) return ... end) -+ assert.equal(f(1),1) -+ assert.equal(f(1,2),1) -+ assert.equal(f(1,2,3),1) -+ end) -+ -+ end) -+ -+ describe('ary', function() -+ -+ it('creates a function which accepts up to n args',function() -+ local f = M.ary(function(...) return ... end, 2) -+ assert.is_true(M.isEqual({f(1,2)},{1,2})) -+ assert.is_true(M.isEqual({f(1,2,3)},{1,2})) -+ assert.is_true(M.isEqual({f(1,2,3,4)},{1,2})) -+ end) -+ -+ end) -+ -+ describe('noarg', function() -+ -+ it('returns a function with an arity of 0',function() -+ local f = M.noarg(function (x) return x or 'default' end) -+ assert.equal(f(1), 'default') -+ assert.equal(f(function() end, 3), 'default') -+ assert.equal(f(nil), 'default') -+ end) -+ -+ end) -+ -+ describe('rearg', function() -+ -+ it('creates a function with args reordered',function() -+ local f = M.rearg(function(...) return ... end, {3,2,1}) -+ assert.is_true(M.isEqual({f(1,2,3)},{3,2,1})) -+ assert.is_true(M.isEqual({f(2,1,3)},{3,1,2})) -+ assert.is_true(M.isEqual({f(3,2,1)},{1,2,3})) -+ end) -+ -+ end) - -- context('over', function() -+ describe('over', function() - -- test('returns a function which applies a set of transforms to its args',function() -- local minmax = _.over(math.min, math.max) -- local maxmin = _.over(math.max, math.min) -- assert_true(_.isEqual(minmax(5,10,12,4,3),{3,12})) -- assert_true(_.isEqual(maxmin(5,10,12,4,3),{12,3})) -+ it('returns a function which applies a set of transforms to its args',function() -+ local minmax = M.over(math.min, math.max) -+ local maxmin = M.over(math.max, math.min) -+ assert.is_true(M.isEqual(minmax(5,10,12,4,3),{3,12})) -+ assert.is_true(M.isEqual(maxmin(5,10,12,4,3),{12,3})) - end) - - end) - -- context('overEvery', function() -+ describe('overEvery', function() - - local alleven, allpositive - -- before(function() -- alleven = function(...) -- for i, v in ipairs({...}) do if v%2~=0 then return false end end -- return true -- end -- -- allpositive = function(...) -- for i, v in ipairs({...}) do if v < 0 then return false end end -- return true -- end -- end) -+ alleven = function(...) -+ for i, v in ipairs({...}) do if v%2~=0 then return false end end -+ return true -+ end -+ -+ allpositive = function(...) -+ for i, v in ipairs({...}) do if v < 0 then return false end end -+ return true -+ end - -- test('checks if all predicates passes truth with args. ',function() -- local allok = _.overEvery(alleven, allpositive) -- assert_false(allok(2,4,-1,8)) -- assert_false(allok(10,3,2,6)) -- assert_true(allok(8,4,6,10)) -+ it('checks if all predicates passes truth with args. ',function() -+ local allok = M.overEvery(alleven, allpositive) -+ assert.is_false(allok(2,4,-1,8)) -+ assert.is_false(allok(10,3,2,6)) -+ assert.is_true(allok(8,4,6,10)) - end) - - end) - -- context('overSome', function() -+ describe('overSome', function() - - local alleven, allpositive - -- before(function() -- alleven = function(...) -- for i, v in ipairs({...}) do if v%2~=0 then return false end end -- return true -- end -- -- allpositive = function(...) -- for i, v in ipairs({...}) do if v < 0 then return false end end -- return true -- end -- end) -+ alleven = function(...) -+ for i, v in ipairs({...}) do if v%2~=0 then return false end end -+ return true -+ end -+ -+ allpositive = function(...) -+ for i, v in ipairs({...}) do if v < 0 then return false end end -+ return true -+ end - -- test('checks if all predicates passes truth with args. ',function() -- local anyok = _.overSome(alleven, allpositive) -- assert_false(anyok(2,4,-1,8)) -- assert_true(anyok(10,3,2,6)) -- assert_false(anyok(-1,-5,-3)) -+ it('checks if all predicates passes truth with args. ',function() -+ local anyok = M.overSome(alleven, allpositive) -+ assert.is_false(anyok(2,4,-1,8)) -+ assert.is_true(anyok(10,3,2,6)) -+ assert.is_false(anyok(-1,-5,-3)) - end) - - end) - -- context('overArgs', function() -+ describe('overArgs', function() - -- test('Creates a function that invokes `f` with its arguments transformed',function() -+ it('Creates a function that invokes `f` with its arguments transformed',function() - local function f(x, y) return {x, y} end - local function triple(x) return x*3 end - local function square(x) return x^2 end -- local new_f = _.overArgs(f, triple, square) -- assert_true(_.isEqual(new_f(1,2), {3,4})) -- assert_true(_.isEqual(new_f(10,10), {30,100})) -+ local new_f = M.overArgs(f, triple, square) -+ assert.is_true(M.isEqual(new_f(1,2), {3,4})) -+ assert.is_true(M.isEqual(new_f(10,10), {30,100})) - end) - -- test('when supplied more args than transforms, remaining are left as-is',function() -+ it('when supplied more args than transforms, remaining are left as-is',function() - local function f(x, y, z, k) return {x, y, z, k} end - local function triple(x) return x*3 end - local function square(x) return x^2 end -- local new_f = _.overArgs(f, triple, square) -- assert_true(_.isEqual(new_f(1,2,3,4), {3,4,3,4})) -- assert_true(_.isEqual(new_f(10,10,10,10), {30,100,10,10})) -+ local new_f = M.overArgs(f, triple, square) -+ assert.is_true(M.isEqual(new_f(1,2,3,4), {3,4,3,4})) -+ assert.is_true(M.isEqual(new_f(10,10,10,10), {30,100,10,10})) - end) - - end) - -- context('partial', function() -+ describe('converge', function() -+ -+ it('', function() -+ local function pow2(x) return x*x end -+ local function pow3(x) return x*x*x end -+ local function sum(a,b) return a+b end -+ local poly = M.converge(sum, pow2, pow3) -+ assert.equal(poly(5), 150) -+ assert.equal(poly(1), 2) -+ assert.equal(poly(3), 36) -+ end) -+ -+ end) -+ -+ describe('partial', function() - -- test('applies partially f',function() -+ it('applies partially f',function() - local function diff(a, b) return a - b end -- local diffFrom20 = _.partial(diff, 20) -- assert_equal(diffFrom20(5), 15) -- assert_equal(diffFrom20(10), 10) -- assert_equal(diffFrom20(-5), 25) -+ local diffFrom20 = M.partial(diff, 20) -+ assert.equal(diffFrom20(5), 15) -+ assert.equal(diffFrom20(10), 10) -+ assert.equal(diffFrom20(-5), 25) - end) - -- test('\'_\' can be used as a placeholder',function() -+ it('\'_\' can be used as a placeholder',function() - local function diff(a, b) return a - b end -- local remove10 = _.partial(diff, '_',10) -- assert_equal(remove10(5), -5) -- assert_equal(remove10(10), 0) -- assert_equal(remove10(15), 5) -+ local remove10 = M.partial(diff, '_',10) -+ assert.equal(remove10(5), -5) -+ assert.equal(remove10(10), 0) -+ assert.equal(remove10(15), 5) - end) - - end) - -- context('partialRight', function() -+ describe('partialRight', function() - -- test('applies partial but from the right',function() -+ it('applies partial but from the right',function() - local function concat(a,b,c,d) return a..b..c..d end -- assert_equal(_.partialRight(concat,'a','b','c')('d'), 'dabc') -- assert_equal(_.partialRight(concat,'a','b')('c','d'), 'cdab') -- assert_equal(_.partialRight(concat,'a')('b','c','d'), 'bcda') -+ assert.equal(M.partialRight(concat,'a','b','c')('d'), 'dabc') -+ assert.equal(M.partialRight(concat,'a','b')('c','d'), 'cdab') -+ assert.equal(M.partialRight(concat,'a')('b','c','d'), 'bcda') - end) - -- test('\'_\' can be used as a placeholder',function() -+ it('\'_\' can be used as a placeholder',function() - local function concat(a,b,c,d) return a..b..c..d end -- assert_equal(_.partialRight(concat,'a','_','c')('d','b'), 'badc') -- assert_equal(_.partialRight(concat,'a','b','_')('c','d'), 'dabc') -- assert_equal(_.partialRight(concat,'_','a')('b','c','d'), 'cdba') -+ assert.equal(M.partialRight(concat,'a','_','c')('d','b'), 'badc') -+ assert.equal(M.partialRight(concat,'a','b','_')('c','d'), 'dabc') -+ assert.equal(M.partialRight(concat,'_','a')('b','c','d'), 'cdba') - end) - - end) - -- context('curry', function() -+ describe('curry', function() - -- test('curries a function for a specific number of args',function() -+ it('curries a function for a specific number of args',function() - local function sumOf5args(a,b,c,d,e) return a+b+c+d+e end -- local curried_sumOf5args = _.curry(sumOf5args, 5) -- assert_equal(curried_sumOf5args(1)(2)(3)(4)(5),15) -- assert_equal(curried_sumOf5args(8)(-2)(4)(-10)(1),1) -+ local curried_sumOf5args = M.curry(sumOf5args, 5) -+ assert.equal(curried_sumOf5args(1)(2)(3)(4)(5),15) -+ assert.equal(curried_sumOf5args(8)(-2)(4)(-10)(1),1) - end) - -- test('n_args defaults to 2 when not supplied',function() -+ it('n_args defaults to 2 when not supplied',function() - local function prod(x,y) return x*y end -- local curried_prod = _.curry(prod) -- assert_equal(curried_prod(2)(3), (_.curry(prod,2))(2)(3)) -- assert_equal(curried_prod(-2)(6), (_.curry(prod,2))(-2)(6)) -+ local curried_prod = M.curry(prod) -+ assert.equal(curried_prod(2)(3), (M.curry(prod,2))(2)(3)) -+ assert.equal(curried_prod(-2)(6), (M.curry(prod,2))(-2)(6)) - end) - -- test('n_args can be equal to 1',function() -- local curried_identity = _.curry(_.identity,1) -- assert_equal(curried_identity('value'), 'value') -- assert_equal(curried_identity(1), 1) -- assert_equal(curried_identity(true), true) -- assert_equal(curried_identity(false), false) -+ it('n_args can be equal to 1',function() -+ local curried_identity = M.curry(M.identity,1) -+ assert.equal(curried_identity('value'), 'value') -+ assert.equal(curried_identity(1), 1) -+ assert.equal(curried_identity(true), true) -+ assert.equal(curried_identity(false), false) - end) - -- test('giving more args than n_args will raise an error',function() -+ it('giving more args than n_args will raise an error',function() - local function add(a,b) return a+b end -- local curried_add = _.curry(add, 2) -- assert_error(function() curried_add(1)(2)(3) end) -- assert_error(function() curried_add(4)(5)(6)(7)(8) end) -+ local curried_add = M.curry(add, 2) -+ assert.error(function() curried_add(1)(2)(3) end) -+ assert.error(function() curried_add(4)(5)(6)(7)(8) end) - end) - -- test('When given less than n_args, it will wait for missing args',function() -+ it('When given less than n_args, it will wait for missing args',function() - local function add(a,b,c) return a+b+c end -- local curried_add = _.curry(add, 3) -+ local curried_add = M.curry(add, 3) - local c1 = curried_add(1) - local c2 = c1(2) - local c3 = c2(3) -- assert_type(c1, 'function') -- assert_type(c2, 'function') -- assert_equal(c3, 6) -+ assert.equal(type(c1), 'function') -+ assert.equal(type(c2), 'function') -+ assert.equal(c3, 6) - end) - - end) - -- context('time', function() -+ describe('time', function() - -- test('returns the execution time of a function and its results', function() -+ it('returns the execution time of a function and its results', function() - local function f(...) return ... end - -- local duration, r = _.time(f, 'a') -- assert_type(duration, 'number') -- assert_equal(r, 'a') -+ local duration, r = M.time(f, 'a') -+ assert.equal(type(duration), 'number') -+ assert.equal(r, 'a') - -- local duration, a, b, c = _.time(f, 1, 2, 3) -- assert_type(duration, 'number') -- assert_true(a == 1 and b == 2 and c == 3) -+ local duration, a, b, c = M.time(f, 1, 2, 3) -+ assert.equal(type(duration), 'number') -+ assert.is_true(a == 1 and b == 2 and c == 3) - end) - - end) -diff --git a/extra/moses/spec/import_spec.lua b/extra/moses/spec/import_spec.lua -index c158510..1a64cd3 100644 ---- a/extra/moses/spec/import_spec.lua -+++ b/extra/moses/spec/import_spec.lua -@@ -1,32 +1,20 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Import specs', function() -+describe('Import specs', function() - -- test('imports all library function to a given context', function() -- local funcs = _.functions() -- local context = _.import({}) -- assert_true(_.all(funcs, function(k, n) return _.has(context, n) end)) -+ it('imports all library function to a given context', function() -+ local funcs = M.functions() -+ local context = M.import({}) -+ assert.is_true(M.all(funcs, function(n) return M.has(context, n) end)) - end) - -- test('passing "noConflict" will preserve already existing keys', function() -- local funcs = _.functions() -- local context = _.import({each = 1, all = 2}, true) -- assert_true(_.all(funcs, function(k, n) return _.has(context, n) end)) -- assert_equal(context.each, 1) -- assert_equal(context.all, 2) -- end) -- -- test('The context will default to the global _G if not supplied', function() -- local oldG = _.clone(_G,true) -- assert_not_equal(_G, oldG) -- _.import() -- local funcs = _.functions() -- _.each(funcs, function(__, fname) -- assert_not_nil(_G[fname]) -- assert_true(type(_G[fname]) == 'function') -- end) -- _G = oldG -+ it('passing "noConflict" will preserve already existing keys', function() -+ local funcs = M.functions() -+ local context = M.import({each = 1, all = 2}, true) -+ assert.is_true(M.all(funcs, function(n) return M.has(context, n) end)) -+ assert.equal(context.each, 1) -+ assert.equal(context.all, 2) - end) - - end) -\ No newline at end of file -diff --git a/extra/moses/spec/object_spec.lua b/extra/moses/spec/object_spec.lua -index 139083a..c82bb42 100644 ---- a/extra/moses/spec/object_spec.lua -+++ b/extra/moses/spec/object_spec.lua -@@ -1,612 +1,681 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Object functions specs', function() -+describe('Object functions specs', function() - -- context('keys', function() -+ describe('keys', function() - -- test('collects a given object attributes',function() -- assert_true(_.isEqual(_.keys({1,2,3}),{1,2,3})) -- assert_true(_.isEqual(_.keys({4,5,6}),{1,2,3})) -- assert_true(_.same(_.keys({x = 1, y = 2, 3}),{'x','y',1})) -+ it('collects a given object attributes',function() -+ assert.is_true(M.isEqual(M.keys({1,2,3}),{1,2,3})) -+ assert.is_true(M.isEqual(M.keys({4,5,6}),{1,2,3})) -+ assert.is_true(M.same(M.keys({x = 1, y = 2, 3}),{'x','y',1})) - end) - - end) - -- context('values', function() -+ describe('values', function() - -- test('collects an given object values',function() -- assert_true(_.isEqual(_.values({1,2,3}),{1,2,3})) -- assert_true(_.isEqual(_.values({4,5,6}),{4,5,6})) -- assert_true(_.same(_.values({x = 1, y = 2, 3}),{1,2,3})) -+ it('collects an given object values',function() -+ assert.is_true(M.isEqual(M.values({1,2,3}),{1,2,3})) -+ assert.is_true(M.isEqual(M.values({4,5,6}),{4,5,6})) -+ assert.is_true(M.same(M.values({x = 1, y = 2, 3}),{1,2,3})) - end) - -- end) -+ end) -+ -+ describe('path', function() -+ -+ it('return the value at a given path in object',function() -+ local entity = { -+ pos = {x = 1}, -+ engine = { -+ left = {status = 'active'}, -+ right = {damage = 10} -+ }, -+ boost = false -+ } -+ assert.equal(M.path(entity, 'pos','x'), 1) -+ assert.equal(M.path(entity, 'engine','left','status'), 'active') -+ assert.equal(M.path(entity, 'engine','right','damage'), 10) -+ assert.equal(M.path(entity, 'boost'), false) -+ assert.is_nil(M.path(entity, 'x')) -+ end) -+ -+ end) - -- context('kvpairs', function() -+ describe('spreadPath', function() -+ -+ it('spreads objects under property path',function() -+ local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} -+ M.spreadPath(obj, 'c', 'f') -+ assert.equal(obj.a, 1) -+ assert.equal(obj.b, 2) -+ assert.equal(obj.d, 3) -+ assert.equal(obj.e, 4) -+ assert.equal(obj.g, 5) -+ assert.is_true(M.isEmpty(obj.c)) -+ assert.is_true(M.isEmpty(obj.c.f)) -+ end) -+ -+ end) -+ -+ describe('flattenPath', function() -+ -+ it('flattens objects under property path',function() -+ local obj = {a = 1, b = 2, c = {d = 3, e = 4, f = {g = 5}}} -+ M.flattenPath(obj, 'c', 'f') -- => {a = 1, b = 2, d = 3, e = 4, g = 5, c = {d = 3, e = 4, f = {g = 5}}} -+ assert.equal(obj.a, 1) -+ assert.equal(obj.b, 2) -+ assert.equal(obj.d, 3) -+ assert.equal(obj.e, 4) -+ assert.equal(obj.g, 5) -+ assert.equal(M.size(obj.c), 3) -+ assert.equal(obj.c.d, 3) -+ assert.equal(obj.c.e, 4) -+ assert.equal(M.size(obj.c.f), 1) -+ assert.equal(obj.c.f.g, 5) -+ end) -+ -+ end) -+ -+ describe('kvpairs', function() - -- test('converts key-values pairs in object to array-list of k,v pairs',function() -- local obj = _.kvpairs({x = 1, y = 2, z = 3}) -+ it('converts key-values pairs in object to array-list of k,v pairs',function() -+ local obj = M.kvpairs({x = 1, y = 2, z = 3}) - table.sort(obj, function(a,b) return a[1] < b[1] end) -- assert_true(_.isEqual(obj[1],{'x',1})) -- assert_true(_.isEqual(obj[2],{'y',2})) -- assert_true(_.isEqual(obj[3],{'z',3})) -+ assert.is_true(M.isEqual(obj[1],{'x',1})) -+ assert.is_true(M.isEqual(obj[2],{'y',2})) -+ assert.is_true(M.isEqual(obj[3],{'z',3})) - end) - - end) - -- context('toObj', function() -+ describe('toObj', function() - -- test('converts an array-list of {k,v} pairs to an object',function() -- local obj = _.toObj({{'x',1},{'y',2},{'z',3}}) -- assert_equal(obj.x,1) -- assert_equal(obj.y,2) -- assert_equal(obj.z,3) -+ it('converts an array-list of {k,v} pairs to an object',function() -+ local obj = M.toObj({{'x',1},{'y',2},{'z',3}}) -+ assert.equal(obj.x,1) -+ assert.equal(obj.y,2) -+ assert.equal(obj.z,3) - end) - - end) - -- context('property', function() -+ describe('invert',function() - -- test('Returns a function that will return the key property of any passed-in object.',function() -- assert_equal(_.property('sin')(math), math.sin) -- assert_equal(_.property('find')(string), string.find) -- assert_equal(_.property('insert')(table), table.insert) -- assert_equal(_.property('yield')(coroutine), coroutine.yield) -+ it('switches key-values pairs', function() -+ assert.is_true(M.isEqual(M.invert({1,2,3}),{1,2,3})) -+ assert.is_true(M.isEqual(M.invert({'a','b','c'}),{a = 1,b = 2,c = 3})) -+ assert.is_true(M.isEqual(M.invert({x = 4, y = 2}),{[2]='y',[4]='x'})) -+ end) -+ -+ end) -+ -+ describe('property', function() -+ -+ it('Returns a function that will return the key property of any passed-in object.',function() -+ assert.equal(M.property('sin')(math), math.sin) -+ assert.equal(M.property('find')(string), string.find) -+ assert.equal(M.property('insert')(table), table.insert) -+ assert.equal(M.property('yield')(coroutine), coroutine.yield) - end) - - end) - -- context('propertyOf', function() -+ describe('propertyOf', function() - -- test('Returns a function which will return the value of an object property.',function() -- assert_equal(_.propertyOf(math)('cos'), math.cos) -- assert_equal(_.propertyOf(string)('char'), string.char) -- assert_equal(_.propertyOf(table)('remove'), table.remove) -- assert_equal(_.propertyOf(_)('propertyOf'), _.propertyOf) -+ it('Returns a function which will return the value of an object property.',function() -+ assert.equal(M.propertyOf(math)('cos'), math.cos) -+ assert.equal(M.propertyOf(string)('char'), string.char) -+ assert.equal(M.propertyOf(table)('remove'), table.remove) -+ assert.equal(M.propertyOf(M)('propertyOf'), M.propertyOf) - end) - - end) - - -- context('toBoolean', function() -- -- test('converts a value to a boolean',function() -- assert_true(type(_.toBoolean(true)) == 'boolean') -- assert_true(type(_.toBoolean(1)) == 'boolean') -- assert_true(type(_.toBoolean(false)) == 'boolean') -- assert_true(type(_.toBoolean(nil)) == 'boolean') -- assert_true(type(_.toBoolean({})) == 'boolean') -- assert_true(type(_.toBoolean(1/0)) == 'boolean') -+ describe('toBoolean', function() -+ -+ it('converts a value to a boolean',function() -+ assert.is_true(type(M.toBoolean(true)) == 'boolean') -+ assert.is_true(type(M.toBoolean(1)) == 'boolean') -+ assert.is_true(type(M.toBoolean(false)) == 'boolean') -+ assert.is_true(type(M.toBoolean(nil)) == 'boolean') -+ assert.is_true(type(M.toBoolean({})) == 'boolean') -+ assert.is_true(type(M.toBoolean(1/0)) == 'boolean') - -- assert_true(_.toBoolean(true)) -- assert_true(_.toBoolean(1)) -- assert_false(_.toBoolean(false)) -- assert_false(_.toBoolean(nil)) -- assert_true(_.toBoolean({})) -- assert_true(_.toBoolean(1/0)) -+ assert.is_true(M.toBoolean(true)) -+ assert.is_true(M.toBoolean(1)) -+ assert.is_false(M.toBoolean(false)) -+ assert.is_false(M.toBoolean(nil)) -+ assert.is_true(M.toBoolean({})) -+ assert.is_true(M.toBoolean(1/0)) - end) - - end) - -- context('extend', function() -+ describe('extend', function() - -- test('extends a destination objects with key-values a source object',function() -- assert_true(_.isEqual(_.extend({},{a = 'b'}),{a = 'b'})) -+ it('extends a destination objects with key-values a source object',function() -+ assert.is_true(M.isEqual(M.extend({},{a = 'b'}),{a = 'b'})) - end) - -- test('source properties overrides destination properties',function() -- assert_true(_.isEqual(_.extend({a = 'a'},{a = 'b'}),{a = 'b'})) -+ it('source properties overrides destination properties',function() -+ assert.is_true(M.isEqual(M.extend({a = 'a'},{a = 'b'}),{a = 'b'})) - end) - -- test('leaves source object untouched',function() -+ it('leaves source object untouched',function() - local source = {i = 'i'} -- assert_true(_.isEqual(_.extend({a = 'a'},source),{a = 'a',i = 'i'})) -- assert_true(_.isEqual(source,{i = 'i'})) -+ assert.is_true(M.isEqual(M.extend({a = 'a'},source),{a = 'a',i = 'i'})) -+ assert.is_true(M.isEqual(source,{i = 'i'})) - end) - -- test('can extend destination from multiple sources',function() -+ it('can extend destination from multiple sources',function() - local sourceA = {a = 'a'}; local sourceBC = {b = 'b', c = 'c'} -- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{a = 'a', b = 'b', c = 'c'})) -+ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{a = 'a', b = 'b', c = 'c'})) - end) - -- test('extending from multiple source, latter properties overrides',function() -+ it('extending from multiple source, latter properties overrides',function() - local sourceA = {a = 'a'}; local sourceBC = {b = 'b', a = 'c'} -- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{a = 'c', b = 'b'})) -+ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{a = 'c', b = 'b'})) - end) - -- test('will not copy nil values',function() -+ it('will not copy nil values',function() - local sourceA = {a = nil}; local sourceBC = {b = 'b', c = nil} -- assert_true(_.isEqual(_.extend({},sourceA, sourceBC),{b = 'b'})) -+ assert.is_true(M.isEqual(M.extend({},sourceA, sourceBC),{b = 'b'})) - end) - end) - -- context('functions', function() -+ describe('functions', function() - -- test('collects function names within an object',function() -+ it('collects function names within an object',function() - local x = {} - function x.a() return end; function x.b() return end -- assert_true(_.isEqual(_.functions(x),{'a','b'})) -+ assert.is_true(M.same(M.functions(x),{'a','b'})) - end) - -- test('collects metatable functions if "recurseMt" arg is supplied',function() -+ it('collects metatable functions if "recurseMt" arg is supplied',function() - local x = {} ; x.__index = x - function x.a() return end; function x.b() return end - local xx = setmetatable({},x) - function xx.c() return end -- assert_true(_.same(_.functions(xx),{'c'})) -- assert_true(_.same(_.functions(xx,true),{'a','b','c'})) -+ assert.is_true(M.same(M.functions(xx),{'c'})) -+ assert.is_true(M.same(M.functions(xx,true),{'a','b','c'})) - end) - -- test('when given no obj as argument, returns all library functions',function() -- local functions = _.functions() -- _.each(functions, function(k,v) -- assert_true(_.isFunction(_[v])) -+ it('when given no obj as argument, returns all library functions',function() -+ local functions = M.functions() -+ M.each(functions, function(v) -+ assert.is_true(M.isFunction(M[v])) - end) - end) - - end) - -- context('clone', function() -+ describe('clone', function() - -- test('clones the attributes of an object',function() -+ it('clones the attributes of an object',function() - local vector = {x = 0, y = 0} -- assert_true(_.isEqual(_.clone(vector),vector)) -+ assert.is_true(M.isEqual(M.clone(vector),vector)) - end) - -- test('By default, cloning is deep (clones nested tables)',function() -+ it('By default, cloning is deep (clones nested tables)',function() - local particle = {position = {x = 0,y=0},mass = 1} -- local particle_clone = _.clone (particle) -- assert_true(_.isEqual(particle_clone,particle)) -+ local particle_clone = M.clone (particle) -+ assert.is_true(M.isEqual(particle_clone,particle)) - particle_clone.position.x = 3 -- assert_false(_.isEqual(particle_clone,particle)) -+ assert.is_false(M.isEqual(particle_clone,particle)) - end) - -- test('Unless "shallow" arg is provided',function() -+ it('Unless "shallow" arg is provided',function() - local particle = {position = {x = 0,y=0},mass = 1} -- local particle_clone = _.clone (particle,true) -- assert_true(_.isEqual(particle_clone,particle)) -+ local particle_clone = M.clone (particle,true) -+ assert.is_true(M.isEqual(particle_clone,particle)) - particle_clone.position.x = 3 -- assert_true(_.isEqual(particle_clone,particle)) -+ assert.is_true(M.isEqual(particle_clone,particle)) - end) - -- test('Non objects are simply returned',function() -- assert_equal(_.clone(1),1) -- assert_equal(_.clone(false),false) -- assert_equal(_.clone(true),true) -- assert_equal(_.clone(nil),nil) -- assert_equal(_.clone('hello'),'hello') -- assert_equal(_.clone(print),print) -+ it('Non objects are simply returned',function() -+ assert.equal(M.clone(1),1) -+ assert.equal(M.clone(false),false) -+ assert.equal(M.clone(true),true) -+ assert.equal(M.clone(nil),nil) -+ assert.equal(M.clone('hello'),'hello') -+ assert.equal(M.clone(print),print) - end) - - end) - -- context('tap', function() -+ describe('tap', function() - -- test('tap-into a method chain', function() -+ it('tap-into a method chain', function() - local t = {} -- local catchMax = function(k) t[#t+1] = _.max(k) end -- local catchMin = function(k) t[#t+1] = _.min(k) end -+ local catchMax = function(k) t[#t+1] = M.max(k) end -+ local catchMin = function(k) t[#t+1] = M.min(k) end - -- _.chain({1,2,3}) -- :map(function(i,j) return j*2 end) -+ M.chain({1,2,3}) -+ :map(function(j) return j*2 end) - :tap(catchMax) -- :map(function(i,k) return k^2 end) -+ :map(function(k) return k^2 end) - :tap(catchMin) - :value() - -- assert_equal(t[1],6) -- assert_equal(t[2],4) -+ assert.equal(t[1],6) -+ assert.equal(t[2],4) - end) - - end) - -- context('has', function() -+ describe('has', function() - -- test('checks if an object has an attribute',function() -- assert_true(_.has(_,'has')) -- assert_true(_.has(table,'concat')) -- assert_true(_.has(string,'format')) -- assert_true(_.has(os,'time')) -- assert_true(_.has(math,'random')) -+ it('checks if an object has an attribute',function() -+ assert.is_true(M.has(M,'has')) -+ assert.is_true(M.has(table,'concat')) -+ assert.is_true(M.has(string,'format')) -+ assert.is_true(M.has(os,'time')) -+ assert.is_true(M.has(math,'random')) - end) - - end) - -- context('pick', function() -+ describe('pick', function() - -- test('collect specified properties',function() -+ it('collect specified properties',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.pick(object,'a','c'),{a = 1, c = 3})) -+ assert.is_true(M.isEqual(M.pick(object,'a','c'),{a = 1, c = 3})) - end) - -- test('given args can be nested as well',function() -+ it('given args can be nested as well',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.pick(object,{{'b','a'}},'c'),{a = 1,b = 2, c = 3})) -+ assert.is_true(M.isEqual(M.pick(object,{{'b','a'}},'c'),{a = 1,b = 2, c = 3})) - end) - -- test('will ignore properties the object do not have',function() -+ it('will ignore properties the object do not have',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.pick(object,{{'k'}},'c'),{c = 3})) -+ assert.is_true(M.isEqual(M.pick(object,{{'k'}},'c'),{c = 3})) - end) - -- test('returns an empty table when given no properties to pick',function() -+ it('returns an empty table when given no properties to pick',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.pick(object),{})) -+ assert.is_true(M.isEqual(M.pick(object),{})) - end) - -- test('should also pick attributes having falsy values',function() -+ it('should also pick attributes having falsy values',function() - local object = {a = false, b = false, c = true} -- assert_true(_.isEqual(_.pick(object,'a','b'),{a = false,b = false})) -+ assert.is_true(M.isEqual(M.pick(object,'a','b'),{a = false,b = false})) - end) - - end) - -- context('omit', function() -+ describe('omit', function() - -- test('collect all properties leaving those given as args',function() -+ it('collect all properties leaving those given as args',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.omit(object,'a','c'),{b=2})) -+ assert.is_true(M.isEqual(M.omit(object,'a','c'),{b=2})) - end) - -- test('given args can be nested as well',function() -+ it('given args can be nested as well',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.omit(object,{{'b'}},'c'),{a = 1})) -+ assert.is_true(M.isEqual(M.omit(object,{{'b'}},'c'),{a = 1})) - end) - -- test('will ignore properties the object do not have',function() -+ it('will ignore properties the object do not have',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.omit(object,{{'k'}},'c'),{a = 1, b=2})) -+ assert.is_true(M.isEqual(M.omit(object,{{'k'}},'c'),{a = 1, b=2})) - end) - -- test('returns the original object clone when given no properties to omit',function() -+ it('returns the original object clone when given no properties to omit',function() - local object = {a = 1, b = 2, c = 3} -- assert_true(_.isEqual(_.omit(object),{a = 1, b = 2, c = 3})) -+ assert.is_true(M.isEqual(M.omit(object),{a = 1, b = 2, c = 3})) - end) - - end) - -- context('template', function() -+ describe('template', function() - -- test('applies a template on an object',function() -- assert_true(_.isEqual(_.template({},{a = 1, b = 2, c = 3}),{a = 1, b = 2, c = 3})) -+ it('applies a template on an object',function() -+ assert.is_true(M.isEqual(M.template({},{a = 1, b = 2, c = 3}),{a = 1, b = 2, c = 3})) - end) - -- test('does not override existing properies',function() -- assert_true(_.isEqual(_.template({a = 10, b = 10},{a = 1, b = 2, c = 3}),{a = 10, b = 10, c = 3})) -+ it('does not override existing properies',function() -+ assert.is_true(M.isEqual(M.template({a = 10, b = 10},{a = 1, b = 2, c = 3}),{a = 10, b = 10, c = 3})) - end) - -- test('returns the object when given no template arg',function() -- assert_true(_.isEqual(_.template({a = 10, b = 10}),{a = 10, b = 10})) -+ it('returns the object when given no template arg',function() -+ assert.is_true(M.isEqual(M.template({a = 10, b = 10}),{a = 10, b = 10})) - end) - - end) - -- context('isEqual', function() -+ describe('isEqual', function() - -- test('compares values',function() -- assert_true(_.isEqual(1,1)) -- assert_true(_.isEqual(1.0,1)) -- assert_false(_.isEqual(1,2)) -- assert_false(_.isEqual(2,2.0001)) -+ it('compares values',function() -+ assert.is_true(M.isEqual(1,1)) -+ assert.is_true(M.isEqual(1.0,1)) -+ assert.is_false(M.isEqual(1,2)) -+ assert.is_false(M.isEqual(2,2.0001)) - end) - -- test('compares objects by reference and components',function() -+ it('compares objects by reference and components',function() - local oldprint = print -- assert_true(_.isEqual(print,oldprint)) -+ assert.is_true(M.isEqual(print,oldprint)) - - local t = {} - local v = t -- assert_true(_.isEqual(t,v)) -- assert_true(_.isEqual({},{})) -+ assert.is_true(M.isEqual(t,v)) -+ assert.is_true(M.isEqual({},{})) - -- assert_false(_.isEqual('a','b')) -+ assert.is_false(M.isEqual('a','b')) - -- assert_false(_.isEqual(true, false)) -- assert_false(_.isEqual(nil, false)) -- assert_false(_.isEqual(true, nil)) -+ assert.is_false(M.isEqual(true, false)) -+ assert.is_false(M.isEqual(nil, false)) -+ assert.is_false(M.isEqual(true, nil)) - - end) - -- test('compares nested properties',function() -- assert_true(_.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =0}}})) -- assert_false(_.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =1}}})) -+ it('compares nested properties',function() -+ assert.is_true(M.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =0}}})) -+ assert.is_false(M.isEqual({x = 0,{x1 = 0,{x2 =0}}}, {x = 0,{x1 = 0,{x2 =1}}})) - end) - -- test('can compare tables on the basis of their metatable',function() -+ it('can compare tables on the basis of their metatable',function() - local a, b = {x = 1, y = 2}, {x = 2, y = 1} - setmetatable(a,{__eq = function(a,b) return (a.x and b.x and a.y and b.y)~=nil end}) -- assert_false(_.isEqual(a, b)) -- assert_true(_.isEqual(a, b, true)) -+ assert.is_false(M.isEqual(a, b)) -+ assert.is_true(M.isEqual(a, b, true)) - end) - - - end) - -- context('result', function() -+ describe('result', function() - -- test('calls an object method, passing it as a first arg the object itself',function() -- assert_equal(_.result('a','len'),1) -- assert_equal(_.result('hello','reverse'),'olleh') -- assert_equal(_.result({'a','b','c'},table.concat),'abc') -+ it('calls an object method, passing it as a first arg the object itself',function() -+ assert.equal(M.result('a','len'),1) -+ assert.equal(M.result('hello','reverse'),'olleh') -+ assert.equal(M.result({'a','b','c'},table.concat),'abc') - end) - -- test('handles extra-args to be passed to the so-called method',function() -- assert_equal(_.result('Hello','match','%u'),'H') -- assert_equal(_.result({'a','b','c'},table.concat,' '),'a b c') -- end) -- -- test('returns the property itself if not callable',function() -- assert_equal(_.result({size = 0},'size'),0) -+ it('returns the property itself if not callable',function() -+ assert.equal(M.result({size = 0},'size'),0) - end) - - end) - -- context('isTable', function() -- -- test('returns "true" if arg is table or array',function() -- assert_true(_.isTable({})) -- assert_true(_.isTable({1,2})) -- assert_true(_.isTable({x = 1, 2})) -- assert_true(_.isTable(string)) -- assert_true(_.isTable(table)) -- assert_true(_.isTable(math)) -+ describe('isTable', function() -+ -+ it('returns "true" if arg is table or array',function() -+ assert.is_true(M.isTable({})) -+ assert.is_true(M.isTable({1,2})) -+ assert.is_true(M.isTable({x = 1, 2})) -+ assert.is_true(M.isTable(string)) -+ assert.is_true(M.isTable(table)) -+ assert.is_true(M.isTable(math)) - - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isTable(1)) -- assert_false(_.isTable('')) -- assert_false(_.isTable(function() end)) -- assert_false(_.isTable(print)) -- assert_false(_.isTable(false)) -- assert_false(_.isTable(nil)) -- assert_false(_.isTable(true)) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isTable(1)) -+ assert.is_false(M.isTable('')) -+ assert.is_false(M.isTable(function() end)) -+ assert.is_false(M.isTable(print)) -+ assert.is_false(M.isTable(false)) -+ assert.is_false(M.isTable(nil)) -+ assert.is_false(M.isTable(true)) - end) - - end) - -- context('isCallable', function() -+ describe('isCallable', function() - -- test('returns "true" if arg is callable',function() -- assert_true(_.isCallable(print)) -- assert_true(_.isCallable(function() end)) -- assert_true(_.isCallable(string.gmatch)) -- assert_true(_.isCallable(setmetatable({},{__index = string}).upper)) -- assert_true(_.isCallable(setmetatable({},{__call = function() return end}))) -+ it('returns "true" if arg is callable',function() -+ assert.is_true(M.isCallable(print)) -+ assert.is_true(M.isCallable(function() end)) -+ assert.is_true(M.isCallable(string.gmatch)) -+ assert.is_true(M.isCallable(setmetatable({},{__index = string}).upper)) -+ assert.is_true(M.isCallable(setmetatable({},{__call = function() return end}))) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isCallable(1)) -- assert_false(_.isCallable('')) -- assert_false(_.isCallable({})) -- assert_false(_.isCallable(false)) -- assert_false(_.isCallable(nil)) -- assert_false(_.isCallable(true)) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isCallable(1)) -+ assert.is_false(M.isCallable('')) -+ assert.is_false(M.isCallable({})) -+ assert.is_false(M.isCallable(false)) -+ assert.is_false(M.isCallable(nil)) -+ assert.is_false(M.isCallable(true)) - end) - - end) - -- context('isArray', function() -+ describe('isArray', function() - -- test('returns "true" if arg is an array',function() -- assert_true(_.isArray({})) -- assert_true(_.isArray({1,2,3})) -- assert_true(_.isArray({'a','b','c',{}})) -- assert_true(_.isArray({false,true})) -- assert_true(_.isArray({1,nil})) -+ it('returns "true" if arg is an array',function() -+ assert.is_true(M.isArray({})) -+ assert.is_true(M.isArray({1,2,3})) -+ assert.is_true(M.isArray({'a','b','c',{}})) -+ assert.is_true(M.isArray({false,true})) -+ assert.is_true(M.isArray({1,nil})) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isArray(1)) -- assert_false(_.isArray('')) -- assert_false(_.isArray(false)) -- assert_false(_.isArray(nil)) -- assert_false(_.isArray(true)) -- assert_false(_.isArray(print)) -- assert_false(_.isArray({a = 1, x = 1})) -- assert_false(_.isArray({a = 1, 1, 2,3})) -- assert_false(_.isArray({1,nil,2})) -- assert_false(_.isArray({1,nil,3,k=4})) -- assert_false(_.isArray({a=1})) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isArray(1)) -+ assert.is_false(M.isArray('')) -+ assert.is_false(M.isArray(false)) -+ assert.is_false(M.isArray(nil)) -+ assert.is_false(M.isArray(true)) -+ assert.is_false(M.isArray(print)) -+ assert.is_false(M.isArray({a = 1, x = 1})) -+ assert.is_false(M.isArray({a = 1, 1, 2,3})) -+ assert.is_false(M.isArray({1,nil,2})) -+ assert.is_false(M.isArray({1,nil,3,k=4})) -+ assert.is_false(M.isArray({a=1})) - end) - -- test('returns false on "sparse arrays"',function() -- assert_false(_.isArray({[1] = true, [10] = false})) -+ it('returns false on "sparse arrays"',function() -+ assert.is_false(M.isArray({[1] = true, [10] = false})) - end) - - end) - -- context('isIterable', function() -+ describe('isIterable', function() - -- test('checks if the given object is iterable with pairs',function() -- assert_true(_.isIterable({})) -- assert_false(_.isIterable(function() end)) -- assert_false(_.isIterable(false)) -- assert_false(_.isIterable(1)) -+ it('checks if the given object is iterable with pairs',function() -+ assert.is_true(M.isIterable({})) -+ assert.is_true(M.isIterable({1,2,3})) - end) - - end) - -- context('isEmpty', function() -+ describe('type', function() -+ -+ it('returns the type of the passed-in object',function() -+ assert.equal(M.type('string'),'string') -+ assert.equal(M.type(table),'table') -+ assert.equal(M.type(1), 'number') -+ assert.equal(M.type(io.open('f','w')),'file') -+ end) -+ -+ end) -+ -+ describe('isEmpty', function() - -- test('returns "true" if arg is an empty array',function() -- assert_true(_.isEmpty({})) -+ it('returns "true" if arg is an empty array',function() -+ assert.is_true(M.isEmpty({})) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isEmpty({1,2,3})) -- assert_false(_.isEmpty({'a','b','c',{}})) -- assert_false(_.isEmpty({nil,false,true})) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isEmpty({1,2,3})) -+ assert.is_false(M.isEmpty({'a','b','c',{}})) -+ assert.is_false(M.isEmpty({nil,false,true})) - end) - -- test('booleans, nil and functions are considered empty',function() -- assert_true(_.isEmpty(print)) -- assert_true(_.isEmpty(nil)) -- assert_true(_.isEmpty(false)) -- assert_true(_.isEmpty(true)) -- end) -- -- test('handles strings',function() -- assert_true(_.isEmpty('')) -- assert_false(_.isEmpty('a')) -- assert_false(_.isEmpty('bcd')) -- assert_false(_.isEmpty(' ')) -+ it('booleans, nil and functions are considered empty',function() -+ assert.is_true(M.isEmpty(print)) -+ assert.is_true(M.isEmpty(nil)) -+ assert.is_true(M.isEmpty(false)) -+ assert.is_true(M.isEmpty(true)) -+ end) -+ -+ it('handles strings',function() -+ assert.is_true(M.isEmpty('')) -+ assert.is_false(M.isEmpty('a')) -+ assert.is_false(M.isEmpty('bcd')) -+ assert.is_false(M.isEmpty(' ')) - end) - - end) - -- context('isString', function() -+ describe('isString', function() - -- test('returns "true" if arg is a string',function() -- assert_true(_.isString('')) -- assert_true(_.isString('a')) -- assert_true(_.isString(' ')) -- assert_true(_.isString(type(nil))) -+ it('returns "true" if arg is a string',function() -+ assert.is_true(M.isString('')) -+ assert.is_true(M.isString('a')) -+ assert.is_true(M.isString(' ')) -+ assert.is_true(M.isString(type(nil))) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isString(false)) -- assert_false(_.isString(print)) -- assert_false(_.isString(nil)) -- assert_false(_.isString(true)) -- assert_false(_.isString({})) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isString(false)) -+ assert.is_false(M.isString(print)) -+ assert.is_false(M.isString(nil)) -+ assert.is_false(M.isString(true)) -+ assert.is_false(M.isString({})) - end) - - end) - -- context('isFunction', function() -+ describe('isFunction', function() - -- test('returns "true" if arg is a function',function() -- assert_true(_.isFunction(print)) -- assert_true(_.isFunction(string.match)) -- assert_true(_.isFunction(function() end)) -+ it('returns "true" if arg is a function',function() -+ assert.is_true(M.isFunction(print)) -+ assert.is_true(M.isFunction(string.match)) -+ assert.is_true(M.isFunction(function() end)) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isFunction({})) -- assert_false(_.isFunction(nil)) -- assert_false(_.isFunction(false)) -- assert_false(_.isFunction(true)) -- assert_false(_.isFunction('a')) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isFunction({})) -+ assert.is_false(M.isFunction(nil)) -+ assert.is_false(M.isFunction(false)) -+ assert.is_false(M.isFunction(true)) -+ assert.is_false(M.isFunction('a')) - end) - - end) - -- context('isNil', function() -+ describe('isNil', function() - -- test('returns "true" if arg is nil',function() -- assert_true(_.isNil(nil)) -- assert_true(_.isNil()) -- assert_true(_.isNil(a)) -+ it('returns "true" if arg is nil',function() -+ assert.is_true(M.isNil(nil)) -+ assert.is_true(M.isNil()) -+ assert.is_true(M.isNil(a)) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isNil(false)) -- assert_false(_.isNil(true)) -- assert_false(_.isNil(table)) -- assert_false(_.isNil(function() end)) -- assert_false(_.isNil('a')) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isNil(false)) -+ assert.is_false(M.isNil(true)) -+ assert.is_false(M.isNil(table)) -+ assert.is_false(M.isNil(function() end)) -+ assert.is_false(M.isNil('a')) - end) - - end) - -- context('isNumber', function() -- -- test('returns "true" if arg is a number',function() -- assert_true(_.isNumber(1)) -- assert_true(_.isNumber(0.5)) -- assert_true(_.isNumber(math.pi)) -- assert_true(_.isNumber(1/0)) -- assert_true(_.isNumber(math.huge)) -- assert_true(_.isNumber(0/0)) -- end) -- -- test('returns "false" otherwise',function() -- assert_false(_.isNumber(print)) -- assert_false(_.isNumber(nil)) -- assert_false(_.isNumber(true)) -- assert_false(_.isNumber(false)) -- assert_false(_.isNumber({1})) -- assert_false(_.isNumber('1')) -+ describe('isNumber', function() -+ -+ it('returns "true" if arg is a number',function() -+ assert.is_true(M.isNumber(1)) -+ assert.is_true(M.isNumber(0.5)) -+ assert.is_true(M.isNumber(math.pi)) -+ assert.is_true(M.isNumber(1/0)) -+ assert.is_true(M.isNumber(math.huge)) -+ assert.is_true(M.isNumber(0/0)) -+ end) -+ -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isNumber(print)) -+ assert.is_false(M.isNumber(nil)) -+ assert.is_false(M.isNumber(true)) -+ assert.is_false(M.isNumber(false)) -+ assert.is_false(M.isNumber({1})) -+ assert.is_false(M.isNumber('1')) - end) - - end) - -- context('isNaN', function() -- -- test('returns "true" if arg is NaN',function() -- assert_true(_.isNaN(0/0)) -- end) -- -- test('returns "false" for not NaN values',function() -- assert_false(_.isNaN(1/0)) -- assert_false(_.isNaN(math.huge)) -- assert_false(_.isNaN(math.pi)) -- assert_false(_.isNaN(1)) -- assert_false(_.isNaN('')) -- assert_false(_.isNaN('0')) -- assert_false(_.isNaN({})) -- assert_false(_.isNaN(nil)) -- assert_false(_.isNaN(false)) -- assert_false(_.isNaN(true)) -+ describe('isNaN', function() -+ -+ it('returns "true" if arg is NaN',function() -+ assert.is_true(M.isNaN(0/0)) -+ end) -+ -+ it('returns "false" for not NaN values',function() -+ assert.is_false(M.isNaN(1/0)) -+ assert.is_false(M.isNaN(math.huge)) -+ assert.is_false(M.isNaN(math.pi)) -+ assert.is_false(M.isNaN(1)) -+ assert.is_false(M.isNaN('')) -+ assert.is_false(M.isNaN('0')) -+ assert.is_false(M.isNaN({})) -+ assert.is_false(M.isNaN(nil)) -+ assert.is_false(M.isNaN(false)) -+ assert.is_false(M.isNaN(true)) - end) - - end) - -- context('isFinite', function() -+ describe('isFinite', function() - -- test('returns "true" if arg is a finite number',function() -- assert_true(_.isFinite(1)) -- assert_true(_.isFinite(0)) -- assert_true(_.isFinite(math.pi)) -- assert_true(_.isFinite(99e99)) -+ it('returns "true" if arg is a finite number',function() -+ assert.is_true(M.isFinite(1)) -+ assert.is_true(M.isFinite(0)) -+ assert.is_true(M.isFinite(math.pi)) -+ assert.is_true(M.isFinite(99e99)) - end) - -- test('returns "false" otherwise',function() -- assert_false(_.isFinite(math.huge)) -- assert_false(_.isFinite(1/0)) -- assert_false(_.isFinite(0/0)) -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isFinite(math.huge)) -+ assert.is_false(M.isFinite(1/0)) -+ assert.is_false(M.isFinite(0/0)) - end) - -- test('returns "false" for non-numbers',function() -- assert_false(_.isFinite('')) -- assert_false(_.isFinite(function() end)) -- assert_false(_.isFinite({})) -+ it('returns "false" for non-numbers',function() -+ assert.is_false(M.isFinite('')) -+ assert.is_false(M.isFinite(function() end)) -+ assert.is_false(M.isFinite({})) - end) - - end) - -- context('isBoolean', function() -+ describe('isBoolean', function() - -- test('returns "true" if arg is a boolean or a thruthy statement',function() -- assert_true(_.isBoolean(true)) -- assert_true(_.isBoolean(false)) -- assert_true(_.isBoolean(1==1)) -- assert_true(_.isBoolean(print==tostring)) -- end) -- -- test('returns "false" otherwise',function() -- assert_false(_.isBoolean('')) -- assert_false(_.isBoolean(nil)) -- assert_false(_.isBoolean({})) -- assert_false(_.isBoolean(function() end)) -+ it('returns "true" if arg is a boolean or a thruthy statement',function() -+ assert.is_true(M.isBoolean(true)) -+ assert.is_true(M.isBoolean(false)) -+ assert.is_true(M.isBoolean(1==1)) -+ assert.is_true(M.isBoolean(print==tostring)) -+ end) -+ -+ it('returns "false" otherwise',function() -+ assert.is_false(M.isBoolean('')) -+ assert.is_false(M.isBoolean(nil)) -+ assert.is_false(M.isBoolean({})) -+ assert.is_false(M.isBoolean(function() end)) - -- assert_false(_.isBoolean(0)) -- assert_false(_.isBoolean('1')) -+ assert.is_false(M.isBoolean(0)) -+ assert.is_false(M.isBoolean('1')) - end) - - end) - -- context('isInteger', function() -+ describe('isInteger', function() - -- test('returns "true" if arg is a integer, "false" otherwise',function() -- assert_true(_.isInteger(1)) -- assert_true(_.isInteger(0)) -- assert_false(_.isInteger(math.pi)) -- assert_true(_.isInteger(1/0)) -- assert_true(_.isInteger(math.huge)) -- assert_false(_.isInteger(0/0)) -+ it('returns "true" if arg is a integer, "false" otherwise',function() -+ assert.is_true(M.isInteger(1)) -+ assert.is_true(M.isInteger(0)) -+ assert.is_false(M.isInteger(math.pi)) -+ assert.is_true(M.isInteger(1/0)) -+ assert.is_true(M.isInteger(math.huge)) -+ assert.is_false(M.isInteger(0/0)) - end) - - end) -diff --git a/extra/moses/spec/op_spec.lua b/extra/moses/spec/op_spec.lua -new file mode 100644 -index 0000000..a6ceabd ---- /dev/null -+++ b/extra/moses/spec/op_spec.lua -@@ -0,0 +1,162 @@ -+require 'luacov' -+local M = require 'moses' -+ -+describe('Operators specs', function() -+ -+ describe('Arithmetic operators', function() -+ -+ it('add returns a + b', function() -+ assert.equal(M.operator.add(1,2), 3) -+ assert.equal(M.operator.add(0,0), 0) -+ assert.equal(M.operator.add(0,-5), -5) -+ end) -+ -+ it('sub returns a - b', function() -+ assert.equal(M.operator.sub(1,2), -1) -+ assert.equal(M.operator.sub(0,0), 0) -+ assert.equal(M.operator.sub(0,-5), 5) -+ end) -+ -+ it('mul returns a * b', function() -+ assert.equal(M.operator.mul(1,2), 2) -+ assert.equal(M.operator.mul(0,0), 0) -+ assert.equal(M.operator.mul(0,-5), 0) -+ end) -+ -+ it('div returns a / b', function() -+ assert.equal(M.operator.div(1,2), 0.5) -+ assert.equal(M.operator.div(5,5), 1) -+ assert.equal(M.operator.div(8,-2), -4) -+ end) -+ -+ it('mod returns a % b', function() -+ assert.equal(M.operator.mod(6,3), 0) -+ assert.equal(M.operator.mod(5,2), 1) -+ end) -+ -+ it('exp returns a ^ b', function() -+ assert.equal(M.operator.exp(3,3), 27) -+ assert.equal(M.operator.exp(5,2), 25) -+ end) -+ -+ it('unm returns -a', function() -+ assert.equal(M.operator.unm(3), -3) -+ assert.equal(M.operator.unm(-5), 5) -+ end) -+ -+ it('floordiv returns a//b', function() -+ assert.equal(M.operator.floordiv(5,2), 2) -+ end) -+ -+ it('intdiv performs integer division', function() -+ assert.equal(M.operator.intdiv(5,2), 2) -+ assert.equal(M.operator.intdiv(-5,2), -2) -+ end) -+ -+ end) -+ -+ describe('Relational operators', function() -+ -+ it('eq returns a == b', function() -+ assert.equal(M.operator.eq(5,5), true) -+ assert.equal(M.operator.eq(5,4.99), false) -+ end) -+ -+ it('neq returns a ~= b', function() -+ assert.equal(M.operator.neq(5,5), false) -+ assert.equal(M.operator.neq(5,4.99), true) -+ end) -+ -+ it('lt returns a < b', function() -+ assert.equal(M.operator.lt(5,5), false) -+ assert.equal(M.operator.lt(4.99,5), true) -+ end) -+ -+ it('gt returns a > b', function() -+ assert.equal(M.operator.gt(5,5), false) -+ assert.equal(M.operator.gt(5,4.99), true) -+ end) -+ -+ it('le returns a <= b', function() -+ assert.equal(M.operator.le(5,5), true) -+ assert.equal(M.operator.le(4.99,5), true) -+ assert.equal(M.operator.le(5,4.99), false) -+ end) -+ -+ it('ge returns a >= b', function() -+ assert.equal(M.operator.ge(5,5), true) -+ assert.equal(M.operator.ge(4.99,5), false) -+ assert.equal(M.operator.ge(5,4.99), true) -+ end) -+ -+ end) -+ -+ describe('Logical operators', function() -+ -+ it('land returns a and b', function() -+ assert.equal(M.operator.land(true, true),true) -+ assert.equal(M.operator.land(true, false),false) -+ assert.equal(M.operator.land(false, true),false) -+ assert.equal(M.operator.land(false, false),false) -+ assert.equal(M.operator.land(true, nil),nil) -+ assert.equal(M.operator.land(false, nil),false) -+ end) -+ -+ it('lor returns a or b', function() -+ assert.equal(M.operator.lor(true, true),true) -+ assert.equal(M.operator.lor(true, false),true) -+ assert.equal(M.operator.lor(false, true),true) -+ assert.equal(M.operator.lor(false, false),false) -+ assert.equal(M.operator.lor(true, nil),true) -+ assert.equal(M.operator.lor(false, nil),nil) -+ end) -+ -+ it('lnot returns not a', function() -+ assert.equal(M.operator.lnot(true),false) -+ assert.equal(M.operator.lnot(false),true) -+ assert.equal(M.operator.lnot(nil),true) -+ end) -+ -+ end) -+ -+ describe('Length operator', function() -+ -+ it('length returns #a', function() -+ assert.equal(M.operator.length({}),0) -+ assert.equal(M.operator.length({2}),1) -+ assert.equal(M.operator.length({3,5,3}),3) -+ assert.equal(M.operator.length('hello'),5) -+ end) -+ -+ end) -+ -+ describe('Concatenation operator', function() -+ -+ it('concat returns a..b', function() -+ assert.equal(M.operator.concat('a','b'),'ab') -+ assert.equal(M.operator.concat('1','2'),'12') -+ end) -+ -+ end) -+ -+ describe('Aliases', function() -+ -+ it('op is an alias to operator', function() -+ assert.equal(M.operator, M.op) -+ end) -+ -+ it('pow is an alias to exp', function() -+ assert.equal(M.operator.exp, M.operator.pow) -+ end) -+ -+ it('neg is an alias to unm', function() -+ assert.equal(M.operator.neg, M.operator.unm) -+ end) -+ -+ it('len is alias to length', function() -+ assert.equal(M.operator.length,M.operator.len) -+ end) -+ -+ end) -+ -+end) -\ No newline at end of file -diff --git a/extra/moses/spec/table_spec.lua b/extra/moses/spec/table_spec.lua -index 209969a..f808aed 100644 ---- a/extra/moses/spec/table_spec.lua -+++ b/extra/moses/spec/table_spec.lua -@@ -1,665 +1,754 @@ - require 'luacov' --local _ = require 'moses' -+local M = require 'moses' - --context('Table functions specs', function() -+describe('Table functions specs', function() - -- context('clear', function() -+ describe('clear', function() - -- test('', function() -- local t = _.clear({'a', true, 'hello'}) -- assert_true(_.isEqual(t,{})) -- assert_nil(next(t)) -+ it('', function() -+ local t = M.clear({'a', true, 'hello'}) -+ assert.is_true(M.isEqual(t,{})) -+ assert.is_nil(next(t)) - end) - - end) - -- context('each', function() -- -- test('provides values and iteration count ', function() -- local t = {1,2,3} -- local inc = 0 -- _.each(t,function(i,v) -- inc = inc+1 -- assert_equal(i,inc) -- assert_equal(t[i],v) -- end) -- end) -- -- test('can reference the given table', function() -- local t = {1,2,3} -- _.each(t,function(i,v,mul) -- t[i] = v*mul -- end,5) -- assert_true(_.isEqual(t,{5,10,15})) -- end) -- -- test('iterates over non-numeric keys and objects', function() -- local t = {one = 1, two = 2, three = 3} -- local copy = {} -- _.each(t,function(i,v) copy[i] = v end) -- assert_true(_.isEqual(t,copy)) -- end) -- -- end) -+ describe('each', function() -+ -+ it('provides values and iteration count ', function() -+ local t = {4,2,1} -+ local inc = 0 -+ M.each(t,function(v, i) -+ inc = inc+1 -+ assert.equal(i,inc) -+ assert.equal(t[i],v) -+ end) -+ end) -+ -+ it('can reference the given table', function() -+ local t = {1,2,3} -+ local mul = 5 -+ M.each(t,function(v,i) -+ t[i] = v*mul -+ end) -+ assert.is_true(M.isEqual(t,{5,10,15})) -+ end) -+ -+ it('iterates over non-numeric keys and objects', function() -+ local t = {one = 1, two = 2, three = 3} -+ local copy = {} -+ M.each(t,function(v,i) copy[i] = v end) -+ assert.is_true(M.isEqual(t,copy)) -+ end) - -- context('eachi', function() -+ end) -+ -+ describe('eachi', function() - -- test('provides values and iteration count for integer keys only, in a sorted way', function() -- local t = {1,2,3} -+ it('provides values and iteration count for integer keys only, in a sorted way', function() -+ local t = {4,2,1} - local inc = 0 -- _.eachi(t,function(i,v) -+ M.eachi(t,function(v,i) - inc = inc+1 -- assert_equal(i,inc) -- assert_equal(t[i],v) -+ assert.equal(i,inc) -+ assert.equal(t[i],v) - end) - end) - -- test('ignores non-integer keys', function() -+ it('ignores non-integer keys', function() - local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5} - local rk = {-1, 0, 1, 2} - local rv = {6, 1, 3, 5} - local inc = 0 -- _.eachi(t,function(i,v) -+ M.eachi(t,function(v,i) - inc = inc+1 -- assert_equal(i,rk[inc]) -- assert_equal(v,rv[inc]) -+ assert.equal(i,rk[inc]) -+ assert.equal(v,rv[inc]) - end) - end) - - end) - -- context('at', function() -+ describe('at', function() - -- test('returns an array of values at numeric keys', function() -+ it('returns an array of values at numeric keys', function() - local t = {4,5,6} -- local values = _.at(t,1,2,3) -- assert_true(_.isEqual(values, t)) -+ local values = M.at(t,1,2,3) -+ assert.is_true(M.isEqual(values, t)) - - local t = {a = 4, bb = true, ccc = false} -- local values = _.at(t,'a', 'ccc') -- assert_true(_.isEqual(values, {4, false})) -+ local values = M.at(t,'a', 'ccc') -+ assert.is_true(M.isEqual(values, {4, false})) - end) - - end) - -- context('count', function() -+ describe('adjust', function() -+ -+ it('adjusts a given value in a table using a function', function() -+ local double = function(v) return v * 2 end -+ local t = {1,2,3} -+ assert.is_true(M.isEqual(M.adjust(t,1,double),{2,2,3})) -+ assert.is_true(M.isEqual(M.adjust(t,2,double),{1,4,3})) -+ assert.is_true(M.isEqual(M.adjust(t,3,double),{1,2,6})) -+ end) -+ -+ it('adjusts a given value in a table using a value', function() -+ local t = {1,2,3} -+ assert.is_true(M.isEqual(M.adjust(t,1,5),{5,2,3})) -+ assert.is_true(M.isEqual(M.adjust(t,2,-2),{1,-2,3})) -+ end) -+ -+ it('throws an error if key is not found in table', function() -+ local double = function(v) return v * 2 end -+ local t = {x = 1} -+ assert.error(function() M.adjust(t,'y', 2) end) -+ end) -+ -+ end) -+ -+ describe('count', function() - -- test('count the occurences of value in a list', function() -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},1),2) -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},2),3) -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},3),4) -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},4),1) -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2},5),0) -- assert_equal(_.count({false, false, true},false),2) -- assert_equal(_.count({false, false, true},true),1) -- assert_equal(_.count({{1,1},{1,1},{1,1},{2,2}},{1,1}),3) -- assert_equal(_.count({{1,1},{1,1},{1,1},{2,2}},{2,2}),1) -+ it('count the occurences of value in a list', function() -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},1),2) -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},2),3) -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},3),4) -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},4),1) -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2},5),0) -+ assert.equal(M.count({false, false, true},false),2) -+ assert.equal(M.count({false, false, true},true),1) -+ assert.equal(M.count({{1,1},{1,1},{1,1},{2,2}},{1,1}),3) -+ assert.equal(M.count({{1,1},{1,1},{1,1},{2,2}},{2,2}),1) - end) - -- test('defaults to size when value is not given', function() -- assert_equal(_.count({1,1,2,3,3,3,2,4,3,2}),_.size({1,1,2,3,3,3,2,4,3,2})) -- assert_equal(_.count({false, false, true}),_.size({false, false, true})) -+ it('defaults to size when value is not given', function() -+ assert.equal(M.count({1,1,2,3,3,3,2,4,3,2}),M.size({1,1,2,3,3,3,2,4,3,2})) -+ assert.equal(M.count({false, false, true}),M.size({false, false, true})) - end) - end) - -- context('countf', function() -- -- test('count the occurences of values passing an iterator test in a list', function() -- assert_equal(_.countf({1,2,3,4,5,6}, function(i,v) -- return v%2==0 -- end),3) -- assert_equal(_.countf({print, pairs, os, assert, ipairs}, function(i,v) -- return type(v)=='function' -- end),4) -+ describe('countf', function() -+ it('count the occurences of values passing an iterator test in a list', function() -+ assert.equal(M.countf({1,2,3,4,5,6}, function(v) return v%2==0 end),3) -+ assert.equal(M.countf({print, pairs, ipairs}, function(v) return type(v)=='function' end),3) -+ end) -+ end) -+ -+ describe('allEq', function() -+ -+ it('checks if all values are equal', function() -+ assert.is_true(M.allEqual({1,1,1,1,1}, comp)) -+ assert.is_false(M.allEqual({1,1,2,1,1}, comp)) -+ -+ local t1 = {1, 2, {3}} -+ local t2 = {1, 2, {3}} -+ assert.is_true(M.allEqual({t1, t2})) -+ end) -+ -+ it('can use a custom comp function to compare values', function() -+ local t1 = {x = 1, y = 0} -+ local t2 = {x = 1, y = 0} -+ local t3 = {x = 1, y = 2} -+ local t4 = {x = 1, y = 2} -+ local function compx(a, b) return a.x == b.x end -+ local function compy(a, b) return a.y == b.y end -+ -+ assert.is_true(M.allEqual({t1, t2}, compx)) -+ assert.is_true(M.allEqual({t1, t2}, compy)) -+ assert.is_true(M.allEqual({t3, t4}, compx)) -+ assert.is_true(M.allEqual({t3, t4}, compy)) -+ assert.is_true(M.allEqual({t1, t2, t3, t4}, compx)) -+ assert.is_false(M.allEqual({t1, t2, t3, t4}, compy)) - end) -+ - end) - -- context('cycle', function() -+ describe('cycle', function() - -- test('loops n times on a list', function() -+ it('loops n times on a list', function() - local times = 3 - local t = {1,2,3,4,5} - local kv = {} -- for k,v in _.cycle(t,times) do -- assert_equal(t[k],v) -+ for v,k in M.cycle(t,times) do -+ assert.equal(t[k],v) - kv[#kv+1] = v - end - for k,v in ipairs(kv) do -- assert_equal(_.count(kv,v),times) -+ assert.equal(M.count(kv,v),times) - end - end) - -- test('support array-like and map-like tables', function() -+ it('support array-like and map-like tables', function() - local times = 10 - local t = {x = 1, z = 2} - local keys = {} - local values = {} -- for k,v in _.cycle(t,times) do -- assert_equal(t[k],v) -+ for v,k in M.cycle(t,times) do -+ assert.equal(t[k],v) - keys[#keys+1] = k - values[#values+1] = v - end - for k,v in ipairs(keys) do -- assert_equal(_.count(keys,v),times) -+ assert.equal(M.count(keys,v),times) - end - for k,v in ipairs(values) do -- assert_equal(_.count(values,v),times) -+ assert.equal(M.count(values,v),times) - end - end) - -- test('n defaults to 1, if not supplied', function() -+ it('n defaults to 1, if not supplied', function() - local t = {1,2,3,4,5} -- for k,v in _.cycle(t) do -+ for v,k in M.cycle(t) do - t[k] = v + 1 - end -- _.each(t, function(k, v) -- assert_equal(v, k + 1) -+ M.each(t, function(v, k) -+ assert.equal(v, k + 1) - end) - end) - -- test('if n is negative or equal to 0, it does nothing', function() -+ it('if n is negative or equal to 0, it does nothing', function() - local t = {1,2,3,4,5} -- for k,v in _.cycle(t, 0) do -+ for v,k in M.cycle(t, 0) do - t[k] = v + 1 - end -- for k,v in _.cycle(t, -2) do -+ for v,k in M.cycle(t, -2) do - t[k] = v + 1 - end -- _.each(t, function(k, v) -- assert_equal(v, k) -+ M.each(t, function(v, k) -+ assert.equal(v, k) - end) - end) - end) - -- context('map', function() -+ describe('map', function() - -- test('applies an iterator function over each key-value pair ', function() -- assert_true(_.isEqual(_.map({1,2,3},function(i,v) -+ it('applies an iterator function over each key-value pair ', function() -+ assert.is_true(M.isEqual(M.map({1,2,3},function(v) - return v+10 - end),{11,12,13})) - end) - -- test('iterates over non-numeric keys and objects', function() -- assert_true(_.isEqual(_.map({a = 1, b = 2},function(k,v) -+ it('iterates over non-numeric keys and objects', function() -+ assert.is_true(M.isEqual(M.map({a = 1, b = 2},function(v,k) - return k..v - end),{a = 'a1',b = 'b2'})) - end) - -- test('maps key-value pairs to key-value pairs', function() -- assert_true(_.isEqual(_.map({a = 1, b = 2}, function(k, v) -+ it('maps key-value pairs to key-value pairs', function() -+ assert.is_true(M.isEqual(M.map({a = 1, b = 2}, function(v, k) - return k .. k, v + 10 - end), {aa = 11, bb = 12})) - end) - - end) - -- context('reduce', function() -+ describe('mapi', function() -+ -+ it('applies an iterator function over each key-value pair ', function() -+ assert.is_true(M.isEqual(M.mapi({1,2,3},function(v) -+ return v+10 -+ end),{11,12,13})) -+ end) -+ -+ it('iterates only on array values', function() -+ assert.is_true(M.isEqual(M.mapi({a = 1, 2, 3, 4},function(v,k) -+ return k..v -+ end),{'12','23','34'})) -+ end) -+ -+ end) -+ -+ describe('reduce', function() - -- test('folds a collection (left to right) from an initial state', function() -- assert_equal(_.reduce({1,2,3,4},function(memo,v) return memo+v end,0),10) -+ it('folds a collection (left to right) from an initial state', function() -+ assert.equal(M.reduce({1,2,3,4},function(memo,v) return memo+v end,0),10) - end) - -- test('initial state defaults to the first value when not given', function() -- assert_equal(_.reduce({'a','b','c'},function(memo,v) return memo..v end),'abc') -+ it('initial state defaults to the first value when not given', function() -+ assert.equal(M.reduce({'a','b','c'},function(memo,v) return memo..v end),'abc') - end) - -- test('supports arrays of booleans', function() -- assert_equal(_.reduce({true, false, true, true},function(memo,v) return memo and v end), false) -- assert_equal(_.reduce({true, true, true},function(memo,v) return memo and v end), true) -- assert_equal(_.reduce({false, false, false},function(memo,v) return memo and v end), false) -- assert_equal(_.reduce({false, false, true},function(memo,v) return memo or v end), true) -+ it('supports arrays of booleans', function() -+ assert.equal(M.reduce({true, false, true, true},function(memo,v) return memo and v end), false) -+ assert.equal(M.reduce({true, true, true},function(memo,v) return memo and v end), true) -+ assert.equal(M.reduce({false, false, false},function(memo,v) return memo and v end), false) -+ assert.equal(M.reduce({false, false, true},function(memo,v) return memo or v end), true) -+ end) -+ -+ end) -+ -+ describe('best', function() -+ -+ it('select the best candidate in a table', function() -+ local words = {'Lua', 'Programming', 'Language'} -+ assert.equal(M.best(words, function(a,b) return #a > #b end), 'Programming') -+ assert.equal(M.best(words, function(a,b) return #a < #b end), 'Lua') - end) - - end) - -- context('reduceby', function() -+ describe('reduceBy', function() - -- test('folds a collection (left to right) for specific values', function() -- local function even(_,v) return v%2==0 end -- local function odd(_,v) return v%2~=0 end -- assert_equal(_.reduceby({1,2,3,4},function(memo,v) return memo+v end,0,even), 6) -- assert_equal(_.reduceby({1,2,3,4},function(memo,v) return memo+v end,0,odd), 4) -+ it('folds a collection (left to right) for specific values', function() -+ local function even(v) return v%2==0 end -+ local function odd(v) return v%2~=0 end -+ assert.equal(M.reduceBy({1,2,3,4},function(memo,v) return memo+v end,even,0), 6) -+ assert.equal(M.reduceBy({1,2,3,4},function(memo,v) return memo+v end,odd,0), 4) - end) - - end) - -- context('reduceRight', function() -+ describe('reduceRight', function() - -- test('folds a collection (right to left) from an initial state', function() -- assert_equal(_.reduceRight({1,2,4,16},function(memo,v) return memo/v end,256),2) -+ it('folds a collection (right to left) from an initial state', function() -+ assert.equal(M.reduceRight({1,2,4,16},function(memo,v) return memo/v end,256),2) - end) - -- test('initial state defaults to the first value when not given', function() -- assert_equal(_.reduceRight({'a','b','c'},function(memo,v) return memo..v end),'cba') -+ it('initial state defaults to the first value when not given', function() -+ assert.equal(M.reduceRight({'a','b','c'},function(memo,v) return memo..v end),'cba') - end) - - end) - -- context('mapReduce', function() -+ describe('mapReduce', function() - -- test('folds a collection (left to right) saving intermediate states', function() -- assert_true(_.isEqual(_.mapReduce({1,2,4,16},function(memo,v) -+ it('folds a collection (left to right) saving intermediate states', function() -+ assert.is_true(M.isEqual(M.mapReduce({1,2,4,16},function(memo,v) - return memo*v - end,0),{0,0,0,0})) - end) - -- test('initial state defaults to the first value when not given', function() -- assert_true(_.isEqual(_.mapReduce({'a','b','c'},function(memo,v) -+ it('initial state defaults to the first value when not given', function() -+ assert.is_true(M.isEqual(M.mapReduce({'a','b','c'},function(memo,v) - return memo..v - end),{'a','ab','abc'})) - end) - - end) - -- context('mapReduceRight', function() -+ describe('mapReduceRight', function() - -- test('folds a collection (right to left) saving intermediate states', function() -- assert_true(_.isEqual(_.mapReduceRight({1,2,4,16},function(memo,v) -+ it('folds a collection (right to left) saving intermediate states', function() -+ assert.is_true(M.isEqual(M.mapReduceRight({1,2,4,16},function(memo,v) - return memo/v - end,256),{16,4,2,2})) - end) - -- test('initial state defaults to the first value when not given', function() -- assert_true(_.isEqual(_.mapReduceRight({'a','b','c'},function(memo,v) -+ it('initial state defaults to the first value when not given', function() -+ assert.is_true(M.isEqual(M.mapReduceRight({'a','b','c'},function(memo,v) - return memo..v - end),{'c','cb','cba'})) - end) - - end) - -- context('include', function() -+ describe('include', function() - -- test('looks for a value in a collection, returns true when found', function() -- assert_true(_.include({6,8,10,16,29},16)) -+ it('looks for a value in a collection, returns true when found', function() -+ assert.is_true(M.include({6,8,10,16,29},16)) - end) - -- test('returns false when value was not found', function() -- assert_false(_.include({6,8,10,16,29},1)) -+ it('returns false when value was not found', function() -+ assert.is_false(M.include({6,8,10,16,29},1)) - end) - -- test('can lookup for a object', function() -- assert_true(_.include({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,{3}}})) -+ it('can lookup for a object', function() -+ assert.is_true(M.include({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,{3}}})) - end) - -- test('given an iterator, return the first value passing a truth test', function() -- assert_true(_.include({'a','B','c'}, function(array_value) -+ it('given an iterator, return the first value passing a truth test', function() -+ assert.is_true(M.include({'a','B','c'}, function(array_value) - return (array_value:upper() == array_value) - end)) - end) - - end) - -- context('detect', function() -+ describe('detect', function() - -- test('looks for the first occurence of value, returns the key where it was found', function() -- assert_equal(_.detect({6,8,10,16},8),2) -+ it('looks for the first occurence of value, returns the key where it was found', function() -+ assert.equal(M.detect({6,8,10,16},8),2) - end) - -- test('returns nil when value was not found', function() -- assert_nil(_.detect({nil,true,0,true,true},false)) -+ it('returns nil when value was not found', function() -+ assert.is_nil(M.detect({nil,true,0,true,true},false)) - end) - -- test('can lookup for a object', function() -- assert_equal(_.detect({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,6}}),2) -+ it('can lookup for a object', function() -+ assert.equal(M.detect({6,{18,{2,6}},10,{18,{2,{3}}},29},{18,{2,6}}),2) - end) - -- test('given an iterator, return the key of the first value passing a truth test', function() -- assert_equal(_.detect({'a','B','c'}, function(array_value) -+ it('given an iterator, return the key of the first value passing a truth test', function() -+ assert.equal(M.detect({'a','B','c'}, function(array_value) - return (array_value:upper() == array_value) - end),2) - end) - - end) - -- context('where', function() -+ describe('where', function() - -- test('Returns all values in a list having all of a given set of properties', function() -+ it('Returns all values in a list having all of a given set of properties', function() - local set = { - {a = 1, b = 2}, - {a = 2, b = 2}, - {a = 2, b = 4}, - {a = 3, b = 4} - } -- assert_true(_.isEqual(_.where(set, {a = 2}), {set[2],set[3]})) -- assert_true(_.isEqual(_.where(set, {b = 4}), {set[3],set[4]})) -- assert_true(_.isEqual(_.where(set, {a = 2, b = 2}), {set[2]})) -+ assert.is_true(M.isEqual(M.where(set, {a = 2}), {set[2],set[3]})) -+ assert.is_true(M.isEqual(M.where(set, {b = 4}), {set[3],set[4]})) -+ assert.is_true(M.isEqual(M.where(set, {a = 2, b = 2}), {set[2]})) - end) - -- test('returns nil when value was not found', function() -+ it('returns nil when value was not found', function() - local set = { - {a = 1, b = 2}, - {a = 2, b = 2}, - } -- assert_nil(_.where(set, {a = 3})) -- assert_nil(_.where(set, {b = 1})) -+ assert.is_nil(M.where(set, {a = 3})) -+ assert.is_nil(M.where(set, {b = 1})) - end) - - end) - -- context('findWhere', function() -+ describe('findWhere', function() - -- test('Returns the first value in a list having all of a given set of properties', function() -+ it('Returns the first value in a list having all of a given set of properties', function() - local a = {a = 1, b = 2} - local b = {a = 2, b = 3} - local c = {a = 3, b = 4} -- assert_equal(_.findWhere({a, b, c}, {a = 3, b = 4}), c) -+ assert.equal(M.findWhere({a, b, c}, {a = 3, b = 4}), c) - end) - -- test('returns nil when value was not found', function() -+ it('returns nil when value was not found', function() - local a = {a = 1, b = 2} - local b = {a = 2, b = 3} - local c = {a = 3, b = 4} -- assert_nil(_.findWhere({a, b, c}, {a = 3, b = 0})) -+ assert.is_nil(M.findWhere({a, b, c}, {a = 3, b = 0})) - end) - - end) - -- context('select', function() -+ describe('select', function() - -- test('collects all values passing a truth test with an iterator', function() -- assert_true(_.isEqual(_.select({1,2,3,4,5,6,7}, function(key,value) -+ it('collects all values passing a truth test with an iterator', function() -+ assert.is_true(M.isEqual(M.select({7,6,5,4,3,2,1}, function(value) - return (value%2==0) -- end),{2,4,6})) -+ end),{6,4,2})) - -- assert_true(_.isEqual(_.select({1,2,3,4,5,6,7}, function(key,value) -+ assert.is_true(M.isEqual(M.select({7,6,5,4,3,2,1}, function(value) - return (value%2~=0) -- end),{1,3,5,7})) -+ end),{7,5,3,1})) - end) - - end) - -- context('reject', function() -+ describe('reject', function() - -- test('collects all values failing a truth test with an iterator', function() -- assert_true(_.isEqual(_.reject({1,2,3,4,5,6,7}, function(key,value) -+ it('collects all values failing a truth test with an iterator', function() -+ assert.is_true(M.isEqual(M.reject({7,6,5,4,3,2,1}, function(value) - return (value%2==0) -- end),{1,3,5,7})) -+ end),{7,5,3,1})) - -- assert_true(_.isEqual(_.reject({1,2,3,4,5,6,7}, function(key,value) -+ assert.is_true(M.isEqual(M.reject({7,6,5,4,3,2,1}, function(value) - return (value%2~=0) -- end),{2,4,6})) -+ end),{6,4,2})) - end) - - end) - -- context('all', function() -+ describe('all', function() - -- test('returns whether all elements matches a truth test', function() -- assert_true(_.all({2,4,6}, function(key,value) -+ it('returns whether all elements matches a truth test', function() -+ assert.is_true(M.all({2,4,6}, function(value) - return (value%2==0) - end)) - -- assert_false(_.all({false,true,false}, function(key,value) -+ assert.is_false(M.all({false,true,false}, function(value) - return value == false - end)) - end) - - end) - -- context('invoke', function() -+ describe('invoke', function() - -- test('calls an iterator over each object, passing it as a first arg', function() -- assert_true(_.isEqual(_.invoke({'a','bea','cdhza'},string.len), -+ it('calls an iterator over each object, passing it as a first arg', function() -+ assert.is_true(M.isEqual(M.invoke({'a','bea','cdhza'},string.len), - {1,3,5})) - -- assert_true(_.isEqual(_.invoke({{2,3,2},{13,8,10},{0,-5}},_.sort), -+ assert.is_true(M.isEqual(M.invoke({{2,3,2},{13,8,10},{0,-5}},M.ary(M.sort,1)), - {{2,2,3},{8,10,13},{-5,0}})) - -- assert_true(_.isEqual(_.invoke({{x = 1, y = 2},{x = 3, y=4}},'x'), {1,3})) -+ assert.is_true(M.isEqual(M.invoke({{x = 1, y = 2},{x = 3, y=4}},'x'), {1,3})) - end) - -- test('given a string, calls the matching object property the same way', function() -+ it('given a string, calls the matching object property the same way', function() - local a = {}; function a:call() return self end - local b, c, d = {}, {}, {} - b.call, c.call, d.call = a.call, a.call, a.call -- assert_true(_.isEqual(_.invoke({a,b,c,d},'call'), -+ assert.is_true(M.isEqual(M.invoke({a,b,c,d},'call'), - {a,b,c,d})) - end) - - end) - -- context('pluck', function() -+ describe('pluck', function() - -- test('fetches a property value in a collection of objects', function() -+ it('fetches a property value in a collection of objects', function() - - local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} - -- assert_true(_.isEqual(_.pluck(peoples,'age'), -+ assert.is_true(M.isEqual(M.pluck(peoples,'age'), - {23,17,15,33})) -- assert_true(_.isEqual(_.pluck(peoples,'name'), -+ assert.is_true(M.isEqual(M.pluck(peoples,'name'), - {'John','Peter','Steve'})) - - end) - - end) - -- context('max', function() -+ describe('max', function() - -- test('returns the maximum targetted property value in a collection of objects', function() -+ it('returns the maximum targetted property value in a collection of objects', function() - local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} -- assert_equal(_.max(_.pluck(peoples,'age')),33) -- assert_equal(_.max(peoples,function(people) return people.age end),33) -+ assert.equal(M.max(M.pluck(peoples,'age')),33) -+ assert.equal(M.max(peoples,function(people) return people.age end),33) - end) - -- test('directly compares items when given no iterator', function() -- assert_equal(_.max({'a','b','c'}),'c') -+ it('directly compares items when given no iterator', function() -+ assert.equal(M.max({'a','b','c'}),'c') - end) - - end) - -- context('min', function() -+ describe('min', function() - -- test('returns the maximum targetted property value in a collection of objects', function() -+ it('returns the maximum targetted property value in a collection of objects', function() - local peoples = { - {name = 'John', age = 23},{name = 'Peter', age = 17}, - {name = 'Steve', age = 15},{age = 33}} -- assert_equal(_.min(_.pluck(peoples,'age')),15) -- assert_equal(_.min(peoples,function(people) return people.age end),15) -+ assert.equal(M.min(M.pluck(peoples,'age')),15) -+ assert.equal(M.min(peoples,function(people) return people.age end),15) - end) - -- test('directly compares items when given no iterator', function() -- assert_equal(_.min({'a','b','c'}),'a') -+ it('directly compares items when given no iterator', function() -+ assert.equal(M.min({'a','b','c'}),'a') - end) - -- end) -- -- context('shuffle', function() -- -- test('shuffles values and objects in a collection', function() -- local values = {'a','b','c','d'} -- assert_true(_.same(_.shuffle (values),values)) -- end) -- -- test('can accept a seed value to init randomization', function() -- local values = {'a','b','c','d'} -- local seed = os.time() -- assert_true(_.same(_.shuffle(values,seed),values)) -- end) -- -- test('shuffled table has the same elements in a different order', function() -- local values = {'a','b','c','d'} -- assert_true(_.same(_.shuffle(values),values)) -- assert_true(_.same(_.shuffle(values),values)) -- end) -- - end) - -- context('same', function() -+ describe('same', function() - -- test('returns whether all objects from both given tables exists in each other', function() -+ it('returns whether all objects from both given tables exists in each other', function() - local a = {'a','b','c','d'} - local b = {'b','a','d','c'} -- assert_true(_.same(a,b)) -+ assert.is_true(M.same(a,b)) - b[#b+1] = 'e' -- assert_false(_.same(a,b)) -+ assert.is_false(M.same(a,b)) - end) - - end) - -- context('sort', function() -+ describe('sort', function() - -- test('sorts a collection with respect to a given comparison function', function() -- assert_true(_.isEqual(_.sort({'b','a','d','c'}, function(a,b) -+ it('sorts a collection with respect to a given comparison function', function() -+ assert.is_true(M.isEqual(M.sort({'b','a','d','c'}, function(a,b) - return a:byte() < b:byte() - end),{'a','b','c','d'})) - end) - -- test('uses "<" operator when no comparison function is given', function() -- assert_true(_.isEqual(_.sort({'b','a','d','c'}),{'a','b','c','d'})) -+ it('uses "<" operator when no comparison function is given', function() -+ assert.is_true(M.isEqual(M.sort({'b','a','d','c'}),{'a','b','c','d'})) - end) - - end) -+ -+ describe('sortedk', function() -+ -+ it('iterates on sorted keys', function() -+ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+ local ok_tbl = {12, 6, 5, 10, 8} -+ local sorted = {} -+ for k, v in M.sortedk(tbl) do sorted[k] = v end -+ for k in ipairs(sorted) do -+ assert.equal(sorted[k], ok_tbl[k]) -+ end -+ end) -+ -+ it('can take a comparison function', function() -+ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+ local ok_tbl = {8, 10, 5, 6, 12} -+ local sorted = {} -+ for k, v in M.sortedk(tbl, function(a, b) return a > b end) do -+ sorted[#sorted +1] = {k, v} -+ end -+ for k, pr in ipairs(sorted) do -+ assert.equal(pr[2], ok_tbl[k]) -+ end -+ end) -+ -+ end) -+ -+ describe('sortedv', function() -+ -+ it('iterates on sorted values', function() -+ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+ local ok_tbl = {5, 6, 8, 10, 12} -+ local sorted = {} -+ for k, v in M.sortedv(tbl) do sorted[#sorted + 1] = {k, v} end -+ for k, pr in ipairs(sorted) do -+ assert.equal(pr[2], ok_tbl[k]) -+ end -+ end) -+ -+ it('can take a comparison function', function() -+ local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12 -+ local ok_tbl = {12, 10, 8, 6, 5} -+ local sorted = {} -+ for k, v in M.sortedv(tbl, function(a, b) return a > b end) do -+ sorted[#sorted +1] = {k, v} -+ end -+ for k, pr in ipairs(sorted) do -+ assert.equal(pr[2], ok_tbl[k]) -+ end -+ end) - -- context('sortBy', function() -+ end) -+ -+ describe('sortBy', function() - -- test('sort values on the result of a transform function', function() -- assert_true(_.isEqual(_.sortBy({1,2,3,4,5}, math.sin), {5,4,3,1,2})) -+ it('sort values on the result of a transform function', function() -+ assert.is_true(M.isEqual(M.sortBy({1,2,3,4,5}, math.sin), {5,4,3,1,2})) - end) - -- test('the transform function defaults to _.identity', function() -- assert_true(_.isEqual(_.sortBy({1,2,3,4,5}), {1,2,3,4,5})) -+ it('the transform function defaults to M.identity', function() -+ assert.is_true(M.isEqual(M.sortBy({1,2,3,4,5}), {1,2,3,4,5})) - end) - -- test('transform function can be a string name property', function() -+ it('transform function can be a string name property', function() - local unsorted = {{item = 1, value = 10},{item = 2, value = 5},{item = 3, value = 8}} - local sorted = {{item = 2, value = 5},{item = 3, value = 8},{item = 1, value = 10}} -- assert_true(_.isEqual(_.sortBy(unsorted, 'value'), sorted)) -+ assert.is_true(M.isEqual(M.sortBy(unsorted, 'value'), sorted)) - end) - -- test('can use a custom comparison function', function() -+ it('can use a custom comparison function', function() - local unsorted = {{item = 1, value = 10},{item = 2, value = 5},{item = 3, value = 8}} - local sorted = {{item = 1, value = 10},{item = 3, value = 8},{item = 2, value = 5}} - local function comp(a,b) return a > b end -- assert_true(_.isEqual(_.sortBy(unsorted, 'value', comp), sorted)) -+ assert.is_true(M.isEqual(M.sortBy(unsorted, 'value', comp), sorted)) - end) - - end) - -- context('groupBy', function() -+ describe('groupBy', function() - -- test('splits a collection into subsets of items behaving the same', function() -+ it('splits a collection into subsets of items behaving the same', function() - -- assert_true(_.isEqual(_.groupBy({0,1,2,3,4,5,6},function(i,value) -+ assert.is_true(M.isEqual(M.groupBy({0,1,2,3,4,5,6},function(value) - return value%2==0 and 'even' or 'odd' - end),{even = {0,2,4,6},odd = {1,3,5}})) -+ assert.is_true(M.isEqual(M.groupBy({0,'a',true, false,nil,b,0.5},type),{number = {0,0.5},string = {'a'},boolean = {true,false}})) -+ assert.is_true(M.isEqual(M.groupBy({'one','two','three','four','five'},string.len),{[3] = {'one','two'},[4] = {'four','five'},[5] = {'three'}})) - -- assert_true(_.isEqual(_.groupBy({0,'a',true, false,nil,b,0.5},function(i,value) -- return type(value) -- end),{number = {0,0.5},string = {'a'},boolean = {true,false}})) -- -- assert_true(_.isEqual(_.groupBy({'one','two','three','four','five'},function(i,value) -- return value:len() -- end),{[3] = {'one','two'},[4] = {'four','five'},[5] = {'three'}})) -- -- end) -- -- test('can takes extra-args', function() -- -- assert_true(_.isEqual(_.groupBy({3,9,10,12,15}, function(k,v,x) return v%x == 0 end,2), {[false] = {3,9,15}, [true] = {10,12}})) -- assert_true(_.isEqual(_.groupBy({3,9,10,12,15}, function(k,v,x) return v%x == 0 end,3), {[false] = {10}, [true] = {3,9,12,15}})) -- - end) - - end) - -- context('countBy', function() -+ describe('countBy', function() - -- test('splits a collection in subsets and counts items inside', function() -+ it('splits a collection in subsets and counts items inside', function() - -- assert_true(_.isEqual(_.countBy({0,1,2,3,4,5,6},function(i,value) -+ assert.is_true(M.isEqual(M.countBy({0,1,2,3,4,5,6},function(value) - return value%2==0 and 'even' or 'odd' - end),{even = 4,odd = 3})) - -- assert_true(_.isEqual(_.countBy({0,'a',true, false,nil,b,0.5},function(i,value) -- return type(value) -- end),{number = 2,string = 1,boolean = 2})) -+ assert.is_true(M.isEqual(M.countBy({0,'a',true, false,nil,b,0.5},type),{number = 2,string = 1,boolean = 2})) - -- assert_true(_.isEqual(_.countBy({'one','two','three','four','five'},function(i,value) -- return value:len() -- end),{[3] = 2,[4] = 2,[5] = 1})) -+ assert.is_true(M.isEqual(M.countBy({'one','two','three','four','five'},string.len),{[3] = 2,[4] = 2,[5] = 1})) - - end) - - end) - -- context('size', function() -+ describe('size', function() - -- test('counts the very number of objects in a collection', function() -- assert_equal(_.size {1,2,3},3) -+ it('counts the very number of objects in a collection', function() -+ assert.equal(M.size {1,2,3},3) - end) - -- test('counts nested tables elements as an unique value', function() -- assert_equal(_.size {1,2,3,{4,5}},4) -+ it('counts nested tables elements as an unique value', function() -+ assert.equal(M.size {1,2,3,{4,5}},4) - end) - -- test('leaves nil values', function() -- assert_equal(_.size {1,2,3,nil,8},4) -+ it('leaves nil values', function() -+ assert.equal(M.size {1,2,3,nil,8},4) - end) - -- test('counts objects', function() -- assert_equal(_.size {one = 1,2,b = 3, [{}] = 'nil', 'c', [function() end] = 'foo'},6) -+ it('counts objects', function() -+ assert.equal(M.size {one = 1,2,b = 3, [{}] = 'nil', 'c', [function() end] = 'foo'},6) - end) - -- test('returns the size of the first arg when it is a table', function() -- assert_equal(_.size ({1,2},3,4,5),2) -+ it('returns the size of the first arg when it is a table', function() -+ assert.equal(M.size ({1,2},3,4,5),2) - end) - -- test('counts the number of non-nil args when the first one is not a table', function() -- assert_equal(_.size (1,3,4,5),4) -- assert_equal(_.size (nil,1,3,4,5),4) -- assert_equal(_.size (nil,1,3,4,nil,5),4) -+ it('counts the number of non-nil args when the first one is not a table', function() -+ assert.equal(M.size (1,3,4,5),4) -+ assert.equal(M.size (nil,1,3,4,5),4) -+ assert.equal(M.size (nil,1,3,4,nil,5),4) - end) - -- test('handles nil', function() -- assert_equal(_.size(),0) -- assert_equal(_.size(nil),0) -+ it('handles nil', function() -+ assert.equal(M.size(),0) -+ assert.equal(M.size(nil),0) - end) - - - end) - -- context('containsKeys', function() -+ describe('containsKeys', function() - -- test('returns whether a table has all the keys from a given list', function() -- assert_true(_.containsKeys({1,2,3},{1,2,3})) -- assert_true(_.containsKeys({x = 1, y = 2},{x = 1,y =2})) -+ it('returns whether a table has all the keys from a given list', function() -+ assert.is_true(M.containsKeys({1,2,3},{1,2,3})) -+ assert.is_true(M.containsKeys({x = 1, y = 2},{x = 1,y =2})) - end) - -- test('does not compare values', function() -- assert_true(_.containsKeys({1,2,3},{4,5,6})) -- assert_true(_.containsKeys({x = 1, y = 2},{x = 4,y = -1})) -+ it('does not compare values', function() -+ assert.is_true(M.containsKeys({1,2,3},{4,5,6})) -+ assert.is_true(M.containsKeys({x = 1, y = 2},{x = 4,y = -1})) - end) - -- test('is not commutative', function() -- assert_true(_.containsKeys({1,2,3,4},{4,5,6})) -- assert_true(_.containsKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) -- assert_false(_.containsKeys({1,2,3},{4,5,6,7})) -- assert_false(_.containsKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) -+ it('is not commutative', function() -+ assert.is_true(M.containsKeys({1,2,3,4},{4,5,6})) -+ assert.is_true(M.containsKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) -+ assert.is_false(M.containsKeys({1,2,3},{4,5,6,7})) -+ assert.is_false(M.containsKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) - end) - - end) - -- context('sameKeys', function() -+ describe('sameKeys', function() - -- test('returns whether both tables features the same keys', function() -- assert_true(_.sameKeys({1,2,3},{1,2,3})) -- assert_true(_.sameKeys({x = 1, y = 2},{x = 1,y =2})) -+ it('returns whether both tables features the same keys', function() -+ assert.is_true(M.sameKeys({1,2,3},{1,2,3})) -+ assert.is_true(M.sameKeys({x = 1, y = 2},{x = 1,y =2})) - end) - -- test('does not compare values', function() -- assert_true(_.sameKeys({1,2,3},{4,5,6})) -- assert_true(_.sameKeys({x = 1, y = 2},{x = 4,y = -1})) -+ it('does not compare values', function() -+ assert.is_true(M.sameKeys({1,2,3},{4,5,6})) -+ assert.is_true(M.sameKeys({x = 1, y = 2},{x = 4,y = -1})) - end) - -- test('is commutative', function() -- assert_false(_.sameKeys({1,2,3,4},{4,5,6})) -- assert_false(_.sameKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) -- assert_false(_.sameKeys({1,2,3},{4,5,6,7})) -- assert_false(_.sameKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) -+ it('is commutative', function() -+ assert.is_false(M.sameKeys({1,2,3,4},{4,5,6})) -+ assert.is_false(M.sameKeys({x = 1, y = 2,z = 5},{x = 4,y = -1})) -+ assert.is_false(M.sameKeys({1,2,3},{4,5,6,7})) -+ assert.is_false(M.sameKeys({x = 1, y = 2},{x = 4,y = -1,z = 0})) - end) - - end) -Submodule extra/penlight 6d108e6..bd26cb9: -diff --git a/extra/penlight/.busted b/extra/penlight/.busted -new file mode 100644 -index 0000000..065847b ---- /dev/null -+++ b/extra/penlight/.busted -@@ -0,0 +1,8 @@ -+return { -+ default = { -+ verbose = true, -+ output = "gtest", -+ lpath = "./lua/?.lua;./lua/?/init.lua", -+ } -+} -+-- vim: ft=lua -diff --git a/extra/penlight/.editorconfig b/extra/penlight/.editorconfig -new file mode 100644 -index 0000000..3434e8a ---- /dev/null -+++ b/extra/penlight/.editorconfig -@@ -0,0 +1,22 @@ -+root = true -+ -+[*] -+end_of_line = lf -+insert_final_newline = true -+trim_trailing_whitespace = true -+charset = utf-8 -+ -+[*.lua] -+indent_style = space -+indent_size = 2 -+ -+[kong/templates/nginx*] -+indent_style = space -+indent_size = 4 -+ -+[*.template] -+indent_style = space -+indent_size = 4 -+ -+[Makefile] -+indent_style = tab -diff --git a/extra/penlight/.github/workflows/luacheck.yml b/extra/penlight/.github/workflows/luacheck.yml -new file mode 100644 -index 0000000..f6d7b93 ---- /dev/null -+++ b/extra/penlight/.github/workflows/luacheck.yml -@@ -0,0 +1,13 @@ -+name: Luacheck -+ -+on: [push, pull_request] -+ -+jobs: -+ -+ luacheck: -+ runs-on: ubuntu-latest -+ steps: -+ - name: Checkout -+ uses: actions/checkout@v4 -+ - name: Luacheck -+ uses: lunarmodules/luacheck@v1 -diff --git a/extra/penlight/.github/workflows/unix_build.yml b/extra/penlight/.github/workflows/unix_build.yml -new file mode 100644 -index 0000000..0a60c05 ---- /dev/null -+++ b/extra/penlight/.github/workflows/unix_build.yml -@@ -0,0 +1,52 @@ -+name: "Unix build" -+ -+on: [push, pull_request] -+ -+jobs: -+ test: -+ runs-on: ubuntu-latest -+ -+ strategy: -+ fail-fast: false -+ matrix: -+ luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3", "luajit-openresty"] -+ -+ steps: -+ - uses: actions/checkout@v4 -+ -+ - uses: hishamhm/gh-actions-lua@master -+ with: -+ luaVersion: ${{ matrix.luaVersion }} -+ -+ - uses: hishamhm/gh-actions-luarocks@master -+ with: -+ luaRocksVersion: "3.12.0" -+ -+ - name: dependencies -+ run: | -+ luarocks install busted -+ luarocks install luacov-coveralls -+ -+ - name: build -+ run: | -+ luarocks remove penlight --force -+ luarocks make -+ -+ - name: Busted tests -+ run: | -+ busted --coverage --Xoutput "--color" -+ -+ - name: Old test suite -+ run: | -+ lua run.lua tests --luacov -+ -+ - name: Examples -+ run: | -+ lua run.lua examples -+ -+ - name: Report test coverage -+ if: success() -+ continue-on-error: true -+ run: luacov-coveralls -+ env: -+ COVERALLS_REPO_TOKEN: ${{ github.token }} -diff --git a/extra/penlight/.gitignore b/extra/penlight/.gitignore -new file mode 100644 -index 0000000..1237f8f ---- /dev/null -+++ b/extra/penlight/.gitignore -@@ -0,0 +1,2 @@ -+luacov.stats.out -+*.rock -diff --git a/extra/penlight/.luacheckrc b/extra/penlight/.luacheckrc -new file mode 100644 -index 0000000..82dab88 ---- /dev/null -+++ b/extra/penlight/.luacheckrc -@@ -0,0 +1,40 @@ -+unused_args = false -+redefined = false -+max_line_length = false -+ -+globals = { -+ "ngx", -+} -+ -+not_globals = { -+ "string.len", -+ "table.getn", -+} -+ -+include_files = { -+ "**/*.lua", -+ "*.rockspec", -+ ".busted", -+ ".luacheckrc", -+} -+ -+files["spec/**/*.lua"] = { -+ std = "+busted", -+} -+ -+exclude_files = { -+ "tests/*.lua", -+ "tests/**/*.lua", -+ -- Travis Lua environment -+ "here/*.lua", -+ "here/**/*.lua", -+ -- GH Actions Lua Environment -+ ".lua", -+ ".luarocks", -+ ".install", -+ -+ -- TODO: fix these files -+ "examples/symbols.lua", -+ "examples/test-symbols.lua", -+} -+ -diff --git a/extra/penlight/.travis.yml b/extra/penlight/.travis.yml -deleted file mode 100644 -index f0dd90f..0000000 ---- a/extra/penlight/.travis.yml -+++ /dev/null -@@ -1,26 +0,0 @@ --language: python --sudo: false -- --env: -- - LUA="lua 5.1" -- - LUA="lua 5.2" -- - LUA="lua 5.3" -- - LUA="luajit 2.0" -- - LUA="luajit 2.0 --compat 5.2" -- - LUA="luajit 2.1" -- - LUA="luajit 2.1 --compat 5.2" -- --before_install: -- - pip install hererocks -- - hererocks here -r^ --$LUA -- - source here/bin/activate -- --install: -- - luarocks make -- - luarocks install luacov-coveralls -- --script: -- - lua run.lua tests --luacov -- --after_success: -- - luacov-coveralls -diff --git a/extra/penlight/CHANGELOG.md b/extra/penlight/CHANGELOG.md -new file mode 100644 -index 0000000..a96fd10 ---- /dev/null -+++ b/extra/penlight/CHANGELOG.md -@@ -0,0 +1,687 @@ -+# Changelog -+ -+Versioning is strictly according to [Semantic Versioning](https://semver.org/), -+see the [README.md](README.md#versioning) for details on version scoping and -+deprecation policy. -+ -+see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions -+ -+## unreleased -+ - fix(types): callable would return false positive if `__call` was nested -+ [#489](https://github.com/lunarmodules/Penlight/pull/489) -+ -+## 1.14.0 (2024-Apr-15) -+ - fix(path): make `path.expanduser` more sturdy -+ [#469](https://github.com/lunarmodules/Penlight/pull/469) -+ - feat(func): extend `compose` to support N functions -+ [#448](https://github.com/lunarmodules/Penlight/pull/448) -+ - fix(utils) `nil` values in `utils.choose(cond, val1, val2)` -+ [#447](https://github.com/lunarmodules/Penlight/pull/447) -+ - fix(template) using `%` as an escape character caused the expression to not be recognized -+ [#452](https://github.com/lunarmodules/Penlight/pull/452) -+ - enhance(template): Preserve line numbers -+ [#468](https://github.com/lunarmodules/Penlight/pull/468) -+ - fix(pretty) integers for Lua 5.4 -+ [#456](https://github.com/lunarmodules/Penlight/pull/456) -+ -+## 1.13.1 (2022-Jul-22) -+ - fix: `warn` unquoted argument -+ [#439](https://github.com/lunarmodules/Penlight/pull/439) -+ -+## 1.13.0 (2022-Jul-22) -+ - fix: `xml.parse` returned nonsense when given a file name -+ [#431](https://github.com/lunarmodules/Penlight/pull/431) -+ - feat: `app.require_here` now follows symlink'd main modules to their directory -+ [#423](https://github.com/lunarmodules/Penlight/pull/423) -+ - fix: `pretty.write` invalid order function for sorting -+ [#430](https://github.com/lunarmodules/Penlight/pull/430) -+ - fix: `compat.warn` raised write guard warning in OpenResty -+ [#414](https://github.com/lunarmodules/Penlight/pull/414) -+ - feat: `utils.enum` now accepts hash tables, to enable better error handling -+ [#413](https://github.com/lunarmodules/Penlight/pull/413) -+ - feat: `utils.kpairs` new iterator over all non-integer keys -+ [#413](https://github.com/lunarmodules/Penlight/pull/413) -+ - fix: `warn` use rawget to not trigger strict-checkers -+ [#437](https://github.com/lunarmodules/Penlight/pull/437) -+ - fix: `lapp` provides the file name when using the default argument -+ [#427](https://github.com/lunarmodules/Penlight/pull/427) -+ - fix: `lapp` positional arguments now allow digits after the first character -+ [#428](https://github.com/lunarmodules/Penlight/pull/428) -+ - fix: `path.isdir` windows root directories (including drive letter) were not considered valid -+ [#436](https://github.com/lunarmodules/Penlight/pull/436) -+ -+ -+## 1.12.0 (2022-Jan-10) -+ - deprecate: module `pl.text` the contents have moved to `pl.stringx` (removal later) -+ [#407](https://github.com/lunarmodules/Penlight/pull/407) -+ - deprecate: module `pl.xml`, please switch to a more specialized library (removal later) -+ [#409](https://github.com/lunarmodules/Penlight/pull/409) -+ - feat: `utils.npairs` added. An iterator with a range that honours the `n` field -+ [#387](https://github.com/lunarmodules/Penlight/pull/387) -+ - fix: `xml.maptags` would hang if it encountered text-nodes -+ [#396](https://github.com/lunarmodules/Penlight/pull/396) -+ - fix: `text.dedent` didn't handle declining indents nor empty lines -+ [#402](https://github.com/lunarmodules/Penlight/pull/402) -+ - fix: `dir.getfiles`, `dir.getdirectories`, and `dir.getallfiles` now have the -+ directory optional, as was already documented -+ [#405](https://github.com/lunarmodules/Penlight/pull/405) -+ - feat: `array2d.default_range` now also takes a spreadsheet range, which means -+ also other functions now take a range. [#404](https://github.com/lunarmodules/Penlight/pull/404) -+ - fix: `lapp` enums allow [patterns magic characters](https://www.lua.org/pil/20.2.html) -+ [#393](https://github.com/lunarmodules/Penlight/pull/393) -+ - fix: `text.wrap` and `text.fill` numerous fixes for handling whitespace, -+ accented characters, honouring width, etc. -+ [#400](https://github.com/lunarmodules/Penlight/pull/400) -+ - feat: `text.wrap` and `text.fill` have a new parameter to forcefully break words -+ longer than the width given. -+ [#400](https://github.com/lunarmodules/Penlight/pull/400) -+ - fix: `stringx.expandtabs` could error out on Lua 5.3+ -+ [#406](https://github.com/lunarmodules/Penlight/pull/406) -+ - fix: `pl` the module would not properly forward the `newindex` metamethod -+ on the global table. -+ [#395](https://github.com/lunarmodules/Penlight/pull/395) -+ - feat: `utils.enum` added to create enums and prevent magic strings -+ [#408](https://github.com/lunarmodules/Penlight/pull/408) -+ - change: `xml.new` added some sanity checks on input -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - added: `xml.xml_escape` and `xml.xml_unescape` functions (previously private) -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - feat: `xml.tostring` now also takes numeric indents (previously only strings) -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.walk` now detects recursion (errors out) -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.clone` now detects recursion (errors out) -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.compare` now detects recursion (errors out) -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.compare` text compares now work -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.compare` attribute order compares now only compare if both inputs provide an order -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ - fix: `xml.compare` child comparisons failing now report proper error -+ [#397](https://github.com/lunarmodules/Penlight/pull/397) -+ -+ -+## 1.11.0 (2021-Aug-18) -+ -+ - fix: `stringx.strip` behaved badly with string lengths > 200 -+ [#382](https://github.com/lunarmodules/Penlight/pull/382) -+ - fix: `path.currentdir` now takes no arguments and calls `lfs.currentdir` without argument -+ [#383](https://github.com/lunarmodules/Penlight/pull/383) -+ - feat: `utils.raise_deprecation` now has an option to NOT include a -+ stack-trace [#385](https://github.com/lunarmodules/Penlight/pull/385) -+ -+ -+## 1.10.0 (2021-Apr-27) -+ -+ - deprecate: `permute.iter`, renamed to `permute.order_iter` (removal later) -+ [#360](https://github.com/lunarmodules/Penlight/pull/360) -+ - deprecate: `permute.table`, renamed to `permute.order_table` (removal later) -+ [#360](https://github.com/lunarmodules/Penlight/pull/360) -+ - deprecate: `Date` module (removal later) -+ [#367](https://github.com/lunarmodules/Penlight/pull/367) -+ - feat: `permute.list_iter` to iterate over different sets of values -+ [#360](https://github.com/lunarmodules/Penlight/pull/360) -+ - feat: `permute.list_table` generate table with different sets of values -+ [#360](https://github.com/lunarmodules/Penlight/pull/360) -+ - feat: Lua 5.4 'warn' compatibility function -+ [#366](https://github.com/lunarmodules/Penlight/pull/366) -+ - feat: deprecation functionality `utils.raise_deprecation` -+ [#361](https://github.com/lunarmodules/Penlight/pull/361) -+ - feat: `utils.splitv` now takes same args as `split` -+ [#373](https://github.com/lunarmodules/Penlight/pull/373) -+ - fix: `dir.rmtree` failed to remove symlinks to directories -+ [#365](https://github.com/lunarmodules/Penlight/pull/365) -+ - fix: `pretty.write` could error out on failing metamethods (Lua 5.3+) -+ [#368](https://github.com/lunarmodules/Penlight/pull/368) -+ - fix: `app.parse` now correctly parses values containing '=' or ':' -+ [#373](https://github.com/lunarmodules/Penlight/pull/373) -+ - fix: `dir.makepath` failed to create top-level directories -+ [#372](https://github.com/lunarmodules/Penlight/pull/372) -+ - overhaul: `array2d` module was updated, got additional tests and several -+ documentation updates -+ [#377](https://github.com/lunarmodules/Penlight/pull/377) -+ - feat: `array2d` now accepts negative indices -+ - feat: `array2d.row` added to align with `column` -+ - fix: bad error message in `array2d.map` -+ - fix: `array2d.flatten` now ensures to deliver a 'square' result if `nil` is -+ encountered -+ - feat: `array2d.transpose` added -+ - feat: `array2d.swap_rows` and `array2d.swap_cols` now return the array -+ - fix: `array2d.range` correctly recognizes `R` column in spreadsheet format, was -+ mistaken for `R1C1` format. -+ - fix: `array2d.range` correctly recognizes 2 char column in spreadsheet format -+ - feat: `array2d.default_range` added (previously private) -+ - feat: `array2d.set` if used with a function now passes `i,j` to the function -+ in line with the `new` implementation. -+ - fix: `array2d.iter` didn't properly iterate the indices -+ [#376](https://github.com/lunarmodules/Penlight/issues/376) -+ - feat: `array2d.columns` now returns a second value; the column index -+ - feat: `array2d.rows` added to be in line with `columns` -+ -+ -+## 1.9.2 (2020-Sep-27) -+ -+ - fix: dir.walk [#350](https://github.com/lunarmodules/Penlight/pull/350) -+ -+ -+## 1.9.1 (2020-Sep-24) -+ -+ - released to superseed the 1.9.0 version which was retagged in git after some -+ distro's already had picked it up. This version is identical to 1.8.1. -+ -+## 1.8.1 (2020-Sep-24) (replacing a briefly released but broken 1.9.0 version) -+ -+## Fixes -+ -+ - In `pl.class`, `_init` can now be inherited from grandparent (or older ancestor) classes. [#289](https://github.com/lunarmodules/Penlight/pull/289) -+ - Fixes `dir`, `lexer`, and `permute` to no longer use coroutines. [#344](https://github.com/lunarmodules/Penlight/pull/344) -+ -+## 1.8.0 (2020-Aug-05) -+ -+### New features -+ -+ - `pretty.debug` quickly dumps a set of values to stdout for debug purposes -+ -+### Changes -+ -+ - `pretty.write`: now also sorts non-string keys [#319](https://github.com/lunarmodules/Penlight/pull/319) -+ - `stringx.count` has an extra option to allow overlapping matches -+ [#326](https://github.com/lunarmodules/Penlight/pull/326) -+ - added an extra changelog entry for `types.is_empty` on the 1.6.0 changelog, due -+ to additional fixed behaviour not called out appropriately [#313](https://github.com/lunarmodules/Penlight/pull/313) -+ - `path.packagepath` now returns a proper error message with names tried if -+ it fails -+ -+### Fixes -+ -+ - Fix: `stringx.rfind` now properly works with overlapping matches -+ [#314](https://github.com/lunarmodules/Penlight/pull/314) -+ - Fix: `package.searchpath` (in module `pl.compat`) -+ [#328](https://github.com/lunarmodules/Penlight/pull/328) -+ - Fix: `path.isabs` now reports drive + relative-path as `false`, eg. "c:some/path" (Windows only) -+ - Fix: OpenResty coroutines, used by `dir.dirtree`, `pl.lexer`, `pl.permute`. If -+ available the original coroutine functions are now used [#329](https://github.com/lunarmodules/Penlight/pull/329) -+ - Fix: in `pl.strict` also predefine global `_PROMPT2` -+ - Fix: in `pl.strict` apply `tostring` to the given name, in case it is not a string. -+ - Fix: the lexer would not recognize numbers without leading zero; "-.123". -+ See [#315](https://github.com/lunarmodules/Penlight/issues/315) -+ -+## 1.7.0 (2019-Oct-14) -+ -+### New features -+ -+ - `utils.quote_arg` will now optionally take an array of arguments and escape -+ them all into a single string. -+ - `app.parse_args` now accepts a 3rd parameter with a list of valid flags and aliases -+ - `app.script_name` returns the name of the current script (previously a private function) -+ -+### Changes -+ -+ - Documentation updates -+ - `utils.quit`: exit message is no longer required, and closes the Lua state (on 5.2+). -+ - `utils.assert_arg` and `utils.assert_string`: now return the validated value -+ - `pl.compat`: now exports the `jit` and `jit52` flags -+ - `pretty.write`: now sorts the output for easier diffs [#293](https://github.com/lunarmodules/Penlight/pull/293) -+ -+### Fixes -+ -+ - `utils.raise` changed the global `on_error`-level when passing in bad arguments -+ - `utils.writefile` now checks and returns errors when writing -+ - `compat.execute` now handles the Windows exitcode -1 properly -+ - `types.is_empty` would return true on spaces always, independent of the parameter -+ - `types.to_bool` will now compare case-insensitive for the extra passed strings -+ - `app.require_here` will now properly handle an absolute base path -+ - `stringx.split` will no longer append an empty match if the number of requested -+ elements has already been reached [#295](https://github.com/lunarmodules/Penlight/pull/295) -+ - `path.common_prefix` and `path.relpath` return the result in the original casing -+ (only impacted Windows) [#297](https://github.com/lunarmodules/Penlight/pull/297) -+ - `dir.copyfile`, `dir.movefile`, and `dir.makepath` create the new file/path with -+ the requested casing, and no longer force lowercase (only impacted Windows) -+ [#297](https://github.com/lunarmodules/Penlight/pull/297) -+ - added a missing assertion on `path.getmtime` [#291](https://github.com/lunarmodules/Penlight/pull/291) -+ - `stringx.rpartition` returned bad results on a not-found [#299](https://github.com/lunarmodules/Penlight/pull/299) -+ -+## 1.6.0 (2018-Nov-23) -+ -+### New features -+ -+ - `pl.compat` now provides `unpack` as `table.unpack` on Lua 5.1 -+ -+### Changes -+ -+ - `utils.unpack` is now documented and respects `.n` field of its argument. -+ - `tablex.deepcopy` and `tablex.deepcompare` are now cycle aware (#262) -+ - Installing through LuaRocks will now include the full rendered documentation -+ -+### Fixes -+ -+ - Fixed `seq.last` returning `nil` instead of an empty list when given an empty iterator (#253). -+ - `pl.template` now applies `tostring` when substituting values in templates, avoiding errors when they are not strings or numbers (#256). -+ - Fixed `pl.import_into` not importing some Penlight modules (#268). -+ - Fixed version number stuck at 1.5.2 (#260). -+ - Fixed `types.is_empty` returning `true` on tables containing `false` key (#267). -+ - Fixed `types.is_empty` returning `false` if not a nil/table/string -+ - Fixed `test.assertraise` throwing an error when passed an array with a function to call plus its arguments (#272). -+ - Fixed `test.assertraise` not throwing an error when given function does not error but instead returns a string matching given error pattern. -+ - Fixed placeholder expressions being evaluated with wrong precedence of binary and unary negation. -+ - Fixed placeholder expressions being evaluated assuming wrong binary operator associativity (e.g. `_1-(_2+_3)` was evaluated as `(_1-_2)+_3`. -+ - Fixed placeholder expressions being evaluated as if unary operators take precedence over power operator (e.g. `(-_1)^_2`) was evaluated as `-(_1^2)`). -+ - Fixed vulnerable backtracking pattern in `pl.stringx.strip` (#275) -+ -+## 1.5.4 (2017-07-17) -+ -+### Fixes -+ -+ - Fixed `compat.execute` behaving differently on Lua 5.1 and 5.1+. -+ - Fixed `lapp.process_options_string` setting global `success` variable. -+ -+## 1.5.3 (2017-07-16) -+ -+### Changes -+ -+ - Added `template.compile` function that allows caching compiled template and rendering it multiple times. -+ - Added special `_debug` field to environment table argument in `template.substitute` for printing generated template code upon render error. -+ -+### Fixes -+ -+ - Fixed error (`attempt to concatenate a nil value (local 'vtype')`) in `lapp.process_options_string`. -+ -+## 1.5.2 (2017-04-08) -+ -+### Fixes -+ -+ - Removed leftover debug pring in `lapp.process_options_string`. -+ -+## 1.5.1 (2017-04-02) -+ -+### Fixes -+ -+ - Fixed `dir.getfiles` matching given pattern against full paths from base directory instead of file names. -+ -+## 1.5.0 (2017-04-01) -+ -+### Changes -+ -+ - `stringx.splitlines` considers `\r\n` a single line ending. -+ - `stringx.splitlines` returns an empty list for an empty string. -+ -+### Fixes -+ -+ - `tablex.count_map` no longer raises an error. -+ - `strict.module` correctly handles existing `__index` metamethod returning `false`. -+ - `app.parse_args` accepts colon as a separator between option name and value, as advertised. -+ - `pretty.load` handles case where a C hook is present. -+ ' `os.execute` had issue with LuaJIT in 5.2 compat mode. -+ -+### Features -+ -+ - `template` supports customizing inline escape character and chunk name. -+ - `seq` constructor supports iterators with a state object as the second argument. -+ - `stringx.splitlines` has `keep_ends` argument. -+ - `tablex.reduce` can take an optional initial value. -+ -+## 1.4.1 (2016-08-16) -+ -+### Changes -+ -+ - All functions that return instances of `pl.List`, `pl.Map` and `pl.Set` now require corresponding modules, -+ so that their methods always work right away. -+ -+### Fixes -+ -+ - Fixed `dir.getallfiles` returning an empty array when called without `pattern` argument. -+ -+### Features -+ -+## 1.4.0 (2016-08-14) -+ -+### Changes -+ -+### Fixes -+ -+ - `pl.path` covers edge cases better (e.g `path.normpath` was broken) -+ - `p.dir` shell patterns fixed -+ - `os.tmpname` broken on modern Windows/MSVC14 -+ - (likewise for `utils.executeex` which depends on it) -+ - `pretty.write` more robust and does not lose floating-point precision; -+ saves and restores debug hooks when loading. -+ - `pl.lexer` fixes: `cpp` lexer now filters space by default -+ - `tablex.sortv` no longer assumes that the values are all unique -+ - `stringx.center` is now consistent with Python; `stringx.rfind` and -+ `string.quote_string` fixed. -+ - `data.write` had a problem with default delimiter, properly returns error now. -+ - `pl.Set` `+` and `-` now have correct semantics -+ -+### Features -+ -+ - `pl.tablex` has `union` and `merge` convenience functions -+ - `pl.lapp` understands '--' meaning end of parsed arguments -+ - `utils.quote_arg` quotes command arguments for `os.execute`, -+ correctly handling all special characters. -+ - `utils.writefile` has optional `is_bin` argument -+ - 'pl.lexer' supports line numbers with string argument -+ - `stringx.endswith` may be passed an array of possible suffixes. -+ - `data.read` - in CSV mode, assume empty fields are numerical zero -+ -+ -+## 1.3.2 (2015-05-10) -+ -+### Changes -+ -+ - now works and passes tests with Lua 5.3 -+ - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) -+ - Updated pl.dir.file_op to return true on success and false on failure... -+ - workaround for issues with pl.lapp with amalg.lua - will look at global LAPP_SCRIPT if arg[0] is nil -+ -+### Fixes -+ -+ - func was broken: do NOT use ipairs to iterate if __index is overridden! -+ - issue #133 pretty.read (naively) confused by unbalanced brackets -+ - xml attribute underscore fix for simple parser -+ - Fix path.normpath -+ - lexer: fix parsing block comments/string. fix hang on empty string. -+ - Fixed utils.execute returning different values for Lua 5.1 and Lua 5.2 -+ - Issue #97; fixed attempt to put a month into a day -+ - problem with tablex.count_map with custom comparison -+ - tablex.pairmap overwrites result if key already exists; instead, upon detection that key already exists -+ for a returned value, we modify the key's value to be a table and insert values into that table -+ -+### Features -+ -+ - Add Python style url module for quote and unquote. -+ - stringx.quote_string, which scans for embedded long-string quote matches and escapes them by creating a long-string quote. -+ - issue #117: tablex.range now works with decreasing numbers, consistent with numerical for loop -+ - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) -+ - issue #125: DOCTYPE ignored in xml documents as well -+ - Allow XML tostring() function to customize the default prefacing with `` -+ - More Robust Quoted Strings -+ - lapp: improved detection of unsupported short flags -+ -+## 1.3.1 (2013-09-24) -+ -+## 1.3.0 (2013-09-14) -+ -+### Changes -+ -+ - class: RIP base method - not possible to implement correctly -+ - lapp: short flags can now always be followed directly by their value, for instance, -+`-I/usr/include/lua/5.1` -+ - Date: new explicit `Date.Interval` class; `toUTC/toLocal` return new object; `Date.__tostring` -+always returns ISO 8601 times for exact serialization. `+/-` explicit operators. Date objects -+are explicitly flagged as being UTC or not. -+ -+### Fixes -+ -+ - class: super method fixed. -+ - Date: DST is now accounted for properly. -+ - Date: weekday calculation borked. -+ -+### Features -+ -+ - All tests pass with no-5.1-compatible Lua 5.2; now always uses `utils.load` and -+`utils.unpack` is always available. -+ - types: new module containing `utils.is_xxx` methods plus new `to_bool`. -+ - class: can be passed methods in a table (see `test=klass.lua`). This is -+particularly convenient for using from Moonscript. -+ - general documentation improvements, e.g `class` -+ -+## 1.2.1 (2013-06-21) -+ -+### Changes -+ -+ - utils.set(get)fenv always defined (_not_ set as globals for 5.2 anymore!). -+ These are defined in new module pl.compat, but still available through utils. -+ - class.Frodo now puts 'Frodo' in _current environment_ -+ -+### Fixes -+ -+ - lapp.add_type was broken (Pete Kazmier) -+ - class broke with classes that redefined __newindex -+ - Set.isdisjoint was broken because of misspelling; default ctor Set() now works as expected -+ - tablex.transform was broken; result now has same keys as original (CoolistheName007) -+ - xml match not handling empty matches (royalbee) -+ - pl.strict: assigning nil to global declares it, as God intended. (Pierre Chapuis) -+ - tests all work with pl.strict -+ - 5.2 compatible load now respects mode -+ - tablex.difference thought that a value of `false` meant 'not present' (Andrew Starke) -+ -+### Features -+ -+ - tablex.sort(t) iterates over sorted keys, tablex.sortv(t) iterates over sorted values (Pete Kazmier) -+ - tablex.readonly(t) creates a read-only proxy for a table (John Schember) -+ - utils.is_empty(o) true if o==nil, o is an empty table, or o is an empty string (John Schember) -+ - utils.executeex(cmd,bin) returns true if successful, return code, plus stdout and stderr output as strings. (tieske) -+ - class method base for calling inherited methods (theypsilon) -+ - class supports pre-constructor _create for making a custom self (used in pl.List) -+ - xml HTML mode improvements - can parse non-trivial well-formed HTML documents. -+ xml.parsehtml is a parse function, no longer a flag -+ - if a LOM document has ordered attributes, use these when stringifying -+ - xml.tostring has yet another extra parm to force prefacing with `` -+ - lapp boolean flags may have `true` default -+ - lapp slack mode where 'short' flags can be multi-char -+ - test.asserteq etc take extra arg, which is extra level where error must be reported at -+ - path.currentdir,chdir,rmdir,mkdir and dir as alias to lfs are exported; no dependencies on luafilesystem outside pl.path, making it easier to plug in different implementations. -+ -+## 1.2.0 (2013-05-28) -+ -+## 1.1.1 (2013-05-14) -+ -+## 1.1.0 (2013-03-18) -+ -+## 1.0.3 (2012-12-07) -+ -+## 1.0.2 (2012-05-12) -+ -+## 1.0.1 (2012-05-26) -+ -+## 1.0.0 (2012-04-26) -+ -+## 0.9.8 (2011-11-27) -+ -+## 0.9.7 (2011-11-27) -+ -+### Lua 5.2 compatibility -+ -+(These are all now defined in pl.utils) -+ -+- setfenv, getfenv defined for Lua 5.2 (by Sergey Rozhenko) -+ -+### Changes -+ -+- array2d.flatten is new -+- OrderedMap:insert is new -+ -+### Fixes -+ -+- seq.reduce re-implemented to give correct order (Carl Ådahl) -+- seq.unique was broken: new test -+- tablex.icopy broken for last argument; new test -+- utils.function_arg last parm 'msg' was missing -+- array2d.product was broken; more sensible implementation -+- array2d.range, .slice, .write were broken -+- text optional operator % overload broken for 'fmt % fun'; new tests -+- a few occurrences of non-existent function utils.error removed -+ -+ -+## 0.9.6 (2011-09-11) -+ -+### Lua 5.2 compatibility -+ -+- Bad string escape in tests fixed -+ -+### Changes -+ -+- LuaJIT FFI used on Windows for Copy/MoveFile functionality -+ -+### Fixes -+ -+- Issue 13 seq.sort now calls seq.copy -+- issue 14 bad pattern to escape trailing separators in path.abspath -+- lexer: string tokens broken with some combinations -+- lexer: long comments broken for Lua and C -+- stringx.split behaves according to Python spec; extra parm meaning 'max splits' -+- stringx.title behaves according to Python spec -+- stringx.endswith broken for 2nd arg being table of postfixes -+- OrderedMap.set broken when value was nil and key did not exist in map; ctor throws -+ error if unhappy -+ -+## 0.9.5 (2011-07-05) -+ -+### Lua 5.2 compatibility -+ -+ - defines Lua 5.2 beta compatible load() -+ - defines table.pack() -+ -+### New functions -+ -+ - stringx.title(): translates "a dog's day" to "A Dog's Day" -+ - path.normpath(): translates 'A//B','A/./B' and 'A/C/../B' to 'A/B' -+ - utils.execute(): returns ok,return-code: compatible with 5.1 and 5.2 -+ -+### Fixes -+ -+ - pretty.write() _always_ returns a string, but will return also an error string -+if the argument is not a table. Non-integer indices between 1 and #t are no longer falsely considered part of the array -+ - stringx.expandtabs() now works like the Python string method; it will expand each field up to the next tab stop -+ - path.normcase() was broken, because of a misguided attempt to normalize the path. -+ - UNC specific fix to path.abspath() -+ - UNC paths recognized as absolute; dir.makedir() works here -+ - utils.quit() varargs broken, e.g. utils.quit("answer was %d",42) -+ - some stray globals caused trouble with 'strict' -+ -+## 0.9.4 (2011-04-08) -+ -+## 0.9.3 (2011-03-05) -+ -+## 0.9.2 (2011-02-16) -+ -+## 0.9.1 (2011-02-12) -+ -+## 0.9.0 (2010-12-20) -+ -+## 0.8.5 (2010-12-16) -+ -+### What's new with 0.8b ? -+ -+#### Features: -+ -+pl.app provides useful stuff like simple command-line argument parsing and require_here(), which -+makes subsequent require() calls look in the local directory by preference. -+ -+p.file provides useful functions like copy(),move(), read() and write(). (These are aliases to -+dir.copyfile(),movefile(),utils.readfile(),writefile()) -+ -+Custom error trace will only show the functions in user code. -+ -+More robust argument checking. -+ -+In function arguments, now supports 'string lambdas', e.g. `'|x| 2*x'` -+ -+utils.readfile,writefile now insist on being given filenames. This will cause less confusion. -+ -+tablex.search() is new: will look recursively in an arbitrary table; can specify tables not to follow. -+tablex.move() will work with source and destination tables the same, with overlapping ranges. -+ -+#### Bug Fixes: -+ -+dir.copyfile() now works fine without Alien on Windows -+ -+dir.makepath() and rmtree() had problems. -+ -+tablex.compare_no_order() is now O(NlogN), as expected. -+tablex.move() had a problem with source size -+ -+### What's New with 0.7.0b? -+ -+#### Features: -+ -+utils.is_type(v,tp) can say is_type(s,'string') and is_type(l,List). -+utils.is_callable(v) either a function, or has a `__call` metamethod. -+ -+Sequence wrappers: can write things like this: -+ -+seq(s):last():filter('<'):copy() -+ -+seq:mapmethod(s,name) - map using a named method over a sequence. -+ -+seq:enum(s) If s is a simple sequence, then -+ for i,v in seq.enum(s) do print(i,v) end -+ -+seq:take(s,n) Grab the next n values from a (possibly infinite) -+sequence. -+ -+In a related change suggested by Flemming Madsden, the in-place List -+methods like reverse() and sort() return the list, allowing for -+method chaining. -+ -+list.join() explicitly converts using tostring first. -+ -+tablex.count_map() like seq.count_map(), but takes an equality function. -+ -+tablex.difference() set difference -+tablex.set() explicit set generator given a list of values -+ -+Template.indent_substitute() is a new Template method which adjusts -+for indentation and can also substitute templates themselves. -+ -+pretty.read(). This reads a Lua table (as dumped by pretty.write) -+and attempts to be paranoid about its contents. -+ -+sip.match_at_start(). Convenience function for anchored SIP matches. -+ -+#### Bug Fixes: -+ -+tablex.deepcompare() was confused by false boolean values, which -+it thought were synonymous with being nil. -+ -+pretty.write() did not handle cycles, and could not display tables -+with 'holes' properly (Flemming Madsden) -+ -+The SIP pattern '$(' was not escaped properly. -+sip.match() did not pass on options table. -+ -+seq.map() was broken for double-valued sequences. -+seq.copy_tuples() did not use default_iter(), so did not e.g. like -+table arguments. -+ -+dir.copyfile() returns the wrong result for \*nix operations. -+dir.makepath() was broken for non-Windows paths. -+ -+### What's New with 0.6.3? -+ -+The map and reduce functions now take the function first, as Nature intended. -+ -+The Python-like overloading of '\*' for strings has been dropped, since it -+is silly. Also, strings are no longer callable; use 's:at(1)' instead of -+'s(1)' - this tended to cause Obscure Error messages. -+ -+Wherever a function argument is expected, you can use the operator strings -+like '+','==',etc as well as pl.operator.add, pl.operator.eq, etc. -+(see end of pl/operator.lua for the full list.) -+ -+tablex now has compare() and compare_no_order(). An explicit set() -+function has been added which constructs a table with the specified -+keys, all set to a value of true. -+ -+List has reduce() and partition() (This is a cool function which -+separates out elements of a list depending on a classifier function.) -+ -+There is a new array module which generalizes tablex operations like -+map and reduce for two-dimensional arrays. -+ -+The famous iterator over permutations from PiL 9.3 has been included. -+ -+David Manura's list comprehension library has been included. -+ -+Also, utils now contains his memoize function, plus a useful function -+args which captures the case where varargs contains nils. -+ -+There was a bug with dir.copyfile() where the flag was the wrong way round. -+ -+config.lines() had a problem with continued lines. -+ -+Some operators were missing in pl.operator; have renamed them to be -+consistent with the Lua metamethod names. -+ -+ -diff --git a/extra/penlight/CHANGES.md b/extra/penlight/CHANGES.md -deleted file mode 100644 -index bf5a157..0000000 ---- a/extra/penlight/CHANGES.md -+++ /dev/null -@@ -1,358 +0,0 @@ --## 1.5.0 [in progress] -- --### Changes -- -- - `stringx.splitlines` considers `\r\n` a single line ending. -- - `stringx.splitlines` returns an empty list for an empty string. -- --### Fixes -- -- - `tablex.count_map` no longer raises an error. -- - `strict.module` correctly handles existing `__index` metamethod returning `false`. -- - `app.parse_args` accepts colon as a separator between option name and value, as advertised. -- - `pretty.load` handles case where a C hook is present. -- ' `os.execute` had issue with LuaJIT in 5.2 compat mode. -- --### Features -- -- - `template` supports customizing inline escape character and chunk name. -- - `seq` constructor supports iterators with a state object as the second argument. -- - `stringx.splitlines` has `keep_ends` argument. -- - `tablex.reduce` can take an optional initial value. -- --## 1.4.1 -- --### Changes -- -- - All functions that return instances of `pl.List`, `pl.Map` and `pl.Set` now require corresponding modules, -- so that their methods always work right away. -- --### Fixes -- -- - Fixed `dir.getallfiles` returning an empty array when called without `pattern` argument. -- --### Features -- --## 1.4.0 -- --### Changes -- --### Fixes -- -- - `pl.path` covers edge cases better (e.g 'path.normpath` was broken) -- - `p.dir` shell patterns fixed -- - `os.tmpname` broken on modern Windows/MSVC14 -- - (likewise for `utils.executeex` which depends on it) -- - `pretty.write` more robust and does not lose floating-point precision; -- saves and restores debug hooks when loading. -- - `pl.lexer` fixes: `cpp` lexer now filters space by default -- - `tablex.sortv` no longer assumes that the values are all unique -- - `stringx.center` is now consistent with Python; `stringx.rfind` and -- `string.quote_string` fixed. -- - `data.write` had a problem with default delimiter, properly returns error now. -- - `pl.Set` `+` and `-` now have correct semantics -- --### Features -- -- - `pl.tablex` has `union` and `merge` convenience functions -- - `pl.lapp` understands '--' meaning end of parsed arguments -- - `utils.quote_arg` quotes command arguments for `os.execute`, -- correctly handling all special characters. -- - `utils.writefile` has optional `is_bin` argument -- - 'pl.lexer' supports line numbers with string argument -- - `stringx.endswith` may be passed an array of possible suffixes. -- - `data.read` - in CSV mode, assume empty fields are numerical zero -- -- --## 1.3.2 -- --### Changes -- -- - now works and passes tests with Lua 5.3 -- - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) -- - Updated pl.dir.file_op to return true on success and false on failure... -- - workaround for issues with pl.lapp with amalg.lua - will look at global LAPP_SCRIPT if arg[0] is nil -- --### Fixes -- -- - func was broken: do NOT use ipairs to iterate if __index is overriden! -- - issue #133 pretty.read (naively) confused by unbalanced brackets -- - xml attribute underscore fix for simple parser -- - Fix path.normpath -- - lexer: fix parsing block comments/string. fix hang on empty string. -- - Fixed utils.execute returning different values for Lua 5.1 and Lua 5.2 -- - Issue #97; fixed attempt to put a month into a day -- - problem with tablex.count_map with custom comparison -- - tablex.pairmap overwrites result if key already exists; instead, upon detection that key already exists -- for a returned value, we modify the key's value to be a table and insert values into that table -- --### Features -- -- - Add Python style url module for quote and unquote. -- - stringx.quote_string, which scans for embedded long-string quote matches and escapes them by creating a long-string quote. -- - issue #117: tablex.range now works with decreasing numbers, consistent with numerical for loop -- - utils.import will NOT override global symbols (import 'math' caused global type() to be clobbered) -- - issue #125: DOCTYPE ignored in xml documents as well -- - Allow XML tostring() function to customize the default prefacing with -- - More Robust Quoted Strings -- - lapp: improved detection of unsupported short flags -- --## 1.3.0 -- --### Changes -- -- - class: RIP base method - not possible to implement correctly -- - lapp: short flags can now always be followed directly by their value, for instance, --`-I/usr/include/lua/5.1` -- - Date: new explicit `Date.Interval` class; `toUTC/toLocal` return new object; `Date.__tostring` --always returns ISO 8601 times for exact serialization. `+/-` explicit operators. Date objects --are explicitly flagged as being UTC or not. -- --### Fixes -- -- - class: super method fixed. -- - Date: DST is now accounted for properly. -- - Date: weekday calculation borked. -- --### Features -- -- - All tests pass with no-5.1-compatible Lua 5.2; now always uses `utils.load` and --`utils.unpack` is always available. -- - types: new module containing `utils.is_xxx` methods plus new `to_bool`. -- - class: can be passed methods in a table (see `test=klass.lua`). This is --particularly convenient for using from Moonscript. -- - general documentation improvements, e.g `class` -- --## 1.2.1 -- --### Changes -- -- - utils.set(get)fenv always defined (_not_ set as globals for 5.2 anymore!). -- These are defined in new module pl.compat, but still available through utils. -- - class.Frodo now puts 'Frodo' in _current environment_ -- --### Fixes -- -- - lapp.add_type was broken (Pete Kazmier) -- - class broke with classes that redefined __newindex -- - Set.isdisjoint was broken because of misspelling; default ctor Set() now works as expected -- - tablex.transform was broken; result now has same keys as original (CoolistheName007) -- - xml match not handling empty matches (royalbee) -- - pl.strict: assigning nil to global declares it, as God intended. (Pierre Chapuis) -- - tests all work with pl.strict -- - 5.2 compatible load now respects mode -- - tablex.difference thought that a value of `false` meant 'not present' (Andrew Starke) -- --### Features -- -- - tablex.sort(t) iterates over sorted keys, tablex.sortv(t) iterates over sorted values (Pete Kazmier) -- - tablex.readonly(t) creates a read-only proxy for a table (John Schember) -- - utils.is_empty(o) true if o==nil, o is an empty table, or o is an empty string (John Schember) -- - utils.executeex(cmd,bin) returns true if successful, return code, plus stdout and stderr output as strings. (tieske) -- - class method base for calling inherited methods (theypsilon) -- - class supports pre-constructor _create for making a custom self (used in pl.List) -- - xml HTML mode improvements - can parse non-trivial well-formed HTML documents. -- xml.parsehtml is a parse function, no longer a flag -- - if a LOM document has ordered attributes, use these when stringifying -- - xml.tostring has yet another extra parm to force prefacing with -- - lapp boolean flags may have `true` default -- - lapp slack mode where 'short' flags can be multi-char -- - test.asserteq etc take extra arg, which is extra level where error must be reported at -- - path.currentdir,chdir,rmdir,mkdir and dir as alias to lfs are exported; no dependencies on luafilesystem outside pl.path, making it easier to plug in different implementations. -- -- -- --## 0.9.7 -- --### Lua 5.2 compatibility -- --(These are all now defined in pl.utils) -- --- setfenv, getfenv defined for Lua 5.2 (by Sergey Rozhenko) -- --### Changes -- --- array2d.flatten is new --- OrderedMap:insert is new -- --### Fixes -- --- seq.reduce re-implemented to give correct order (Carl Ådahl) --- seq.unique was broken: new test --- tablex.icopy broken for last argument; new test --- utils.function_arg last parm 'msg' was missing --- array2d.product was broken; more sensible implementation --- array2d.range, .slice, .write were broken --- text optional operator % overload broken for 'fmt % fun'; new tests --- a few occurances of non-existent function utils.error removed -- -- --## 0.9.6 -- --### Lua 5.2 compatibility -- --- Bad string escape in tests fixed -- --### Changes -- --- LuaJIT FFI used on Windows for Copy/MoveFile functionality -- --### Fixes -- --- Issue 13 seq.sort now calls seq.copy --- issue 14 bad pattern to escape trailing separators in path.abspath --- lexer: string tokens broken with some combinations --- lexer: long comments broken for Lua and C --- stringx.split behaves according to Python spec; extra parm meaning 'max splits' --- stringx.title behaves according to Python spec --- stringx.endswith broken for 2nd arg being table of postfixes --- OrderedMap.set broken when value was nil and key did not exist in map; ctor throws -- error if unhappy -- --## 0.9.5 -- --### Lua 5.2 compatibility -- -- - defines Lua 5.2 beta compatible load() -- - defines table.pack() -- --### New functions -- -- - stringx.title(): translates "a dog's day" to "A Dog's Day" -- - path.normpath(): translates 'A//B','A/./B' and 'A/C/../B' to 'A/B' -- - utils.execute(): returns ok,return-code: compatible with 5.1 and 5.2 -- --### Fixes -- -- - pretty.write() _always_ returns a string, but will return also an error string --if the argument is not a table. Non-integer indices between 1 and #t are no longer falsely considered part of the array -- - stringx.expandtabs() now works like the Python string method; it will expand each field up to the next tab stop -- - path.normcase() was broken, because of a misguided attempt to normalize the path. -- - UNC specific fix to path.abspath() -- - UNC paths recognized as absolute; dir.makedir() works here -- - utils.quit() varargs broken, e.g. utils.quit("answer was %d",42) -- - some stray globals caused trouble with 'strict' -- --###What's new with 0.8b ? -- --####Features: -- --pl.app provides useful stuff like simple command-line argument parsing and require_here(), which --makes subsequent require() calls look in the local directory by preference. -- --p.file provides useful functions like copy(),move(), read() and write(). (These are aliases to --dir.copyfile(),movefile(),utils.readfile(),writefile()) -- --Custom error trace will only show the functions in user code. -- --More robust argument checking. -- --In function arguments, now supports 'string lambdas', e.g. '|x| 2*x' -- --utils.readfile,writefile now insist on being given filenames. This will cause less confusion. -- --tablex.search() is new: will look recursively in an arbitrary table; can specify tables not to follow. --tablex.move() will work with source and destination tables the same, with overlapping ranges. -- --####Bug Fixes: -- --dir.copyfile() now works fine without Alien on Windows -- --dir.makepath() and rmtree() had problems. -- --tablex.compare_no_order() is now O(NlogN), as expected. --tablex.move() had a problem with source size -- --###What's New with 0.7.0b? -- --####Features: -- --utils.is_type(v,tp) can say is_type(s,'string') and is_type(l,List). --utils.is_callable(v) either a function, or has a __call metamethod. -- --Sequence wrappers: can write things like this: -- --seq(s):last():filter('<'):copy() -- --seq:mapmethod(s,name) - map using a named method over a sequence. -- --seq:enum(s) If s is a simple sequence, then -- for i,v in seq.enum(s) do print(i,v) end -- --seq:take(s,n) Grab the next n values from a (possibly infinite) --sequence. -- --In a related change suggested by Flemming Madsden, the in-place List --methods like reverse() and sort() return the list, allowing for --method chaining. -- --list.join() explicitly converts using tostring first. -- --tablex.count_map() like seq.count_map(), but takes an equality function. -- --tablex.difference() set difference --tablex.set() explicit set generator given a list of values -- --Template.indent_substitute() is a new Template method which adjusts --for indentation and can also substitute templates themselves. -- --pretty.read(). This reads a Lua table (as dumped by pretty.write) --and attempts to be paranoid about its contents. -- --sip.match_at_start(). Convenience function for anchored SIP matches. -- --####Bug Fixes: -- --tablex.deepcompare() was confused by false boolean values, which --it thought were synonymous with being nil. -- --pretty.write() did not handle cycles, and could not display tables --with 'holes' properly (Flemming Madsden) -- --The SIP pattern '$(' was not escaped properly. --sip.match() did not pass on options table. -- --seq.map() was broken for double-valued sequences. --seq.copy_tuples() did not use default_iter(), so did not e.g. like --table arguments. -- --dir.copyfile() returns the wrong result for *nix operations. --dir.makepath() was broken for non-Windows paths. -- --###What's New with 0.6.3? -- --The map and reduce functions now take the function first, as Nature intended. -- --The Python-like overloading of '*' for strings has been dropped, since it --is silly. Also, strings are no longer callable; use 's:at(1)' instead of --'s(1)' - this tended to cause Obscure Error messages. -- --Wherever a function argument is expected, you can use the operator strings --like '+','==',etc as well as pl.operator.add, pl.operator.eq, etc. --(see end of pl/operator.lua for the full list.) -- --tablex now has compare() and compare_no_order(). An explicit set() --function has been added which constructs a table with the specified --keys, all set to a value of true. -- --List has reduce() and partition() (This is a cool function which --separates out elements of a list depending on a classifier function.) -- --There is a new array module which generalizes tablex operations like --map and reduce for two-dimensional arrays. -- --The famous iterator over permutations from PiL 9.3 has been included. -- --David Manura's list comprehension library has been included. -- --Also, utils now contains his memoize function, plus a useful function --args which captures the case where varargs contains nils. -- --There was a bug with dir.copyfile() where the flag was the wrong way round. -- --config.lines() had a problem with continued lines. -- --Some operators were missing in pl.operator; have renamed them to be --consistent with the Lua metamethod names. -- -- -diff --git a/extra/penlight/CONTRIBUTING.md b/extra/penlight/CONTRIBUTING.md -index 90ea9e4..69c75cd 100644 ---- a/extra/penlight/CONTRIBUTING.md -+++ b/extra/penlight/CONTRIBUTING.md -@@ -14,7 +14,7 @@ Here's some examples of things you might want to make a pull request for: - - If you have a more deeply-rooted problem with how the library is built or some - of the stylistic decisions made in the code, it's best to --[create an issue](https://github.com/stevedonovan/Penlight/issues) before putting -+[create an issue](https://github.com/lunarmodules/Penlight/issues) before putting - the effort into a pull request. The same goes for new features - it might be - best to check the project's direction, existing pull requests, and currently open - and closed issues first. -@@ -23,9 +23,9 @@ and closed issues first. - - Here's how to go about contributing to Penlight: - --1. [Fork the repository](https://github.com/stevedonovan/Penlight/fork) to -+1. [Fork the repository](https://github.com/lunarmodules/Penlight/fork) to - your Github account. --2. Create a *topical branch* - a branch whose name is succint but explains what -+2. Create a *topical branch* - a branch whose name is succinct but explains what - you're doing, such as _"added-klingon-cloacking-device"_ - from `master` branch. - 3. Make your changes, committing at logical breaks. - 4. Push your branch to your personal account -@@ -34,9 +34,27 @@ you're doing, such as _"added-klingon-cloacking-device"_ - from `master` branch. - - If you wanna be a rockstar; - --1. Update the [CHANGES.md](https://github.com/stevedonovan/Penlight/blob/master/CHANGES.md) file --2. [Add tests](https://github.com/stevedonovan/Penlight/tree/master/tests) that show the defect your fix repairs, or that tests your new feature -+1. Update the [CHANGELOG.md](https://github.com/lunarmodules/Penlight/blob/master/CHANGELOG.md) file -+2. [Add tests](https://github.com/lunarmodules/Penlight/tree/master/tests) that show the defect your fix repairs, or that tests your new feature - - Please note - if you want to change multiple things that don't depend on each - other, make sure you check out the `master` branch again and create a different topical branch - before making more changes - that way we can take in each change separately. -+ -+## Release instructions for a new version -+ -+ - create a new release branch -+ - update `./lua/pl/utils.lua` (the `_VERSION` constant) -+ - update `./config.ld` with the new version number -+ - create a new rockspec file for the version in `./rockspecs` -+ - check the `./CHANGELOG.md` files for completeness -+ - commit the release related changes with `release x.y.z` -+ - render the documentation using `ldoc .` -+ - commit the documentation as a separate commit with `release x.y.z docs` -+ - push the release branch and create a PR -+ - merge the PR -+ - tag the release as `x.y.z` and push the tag to the github repo -+ - upload the rockspec, and source rock files to LuaRocks -+ - test installing through LuaRocks -+ - announce the release on the Lua mailing list -+ -diff --git a/extra/penlight/README.md b/extra/penlight/README.md -index ef9095a..f6e822f 100644 ---- a/extra/penlight/README.md -+++ b/extra/penlight/README.md -@@ -1,8 +1,10 @@ - # Penlight Lua Libraries - --[![Build Status](https://travis-ci.org/stevedonovan/Penlight.svg)](https://travis-ci.org/stevedonovan/Penlight) --[![Coverage Status](https://coveralls.io/repos/mpeterv/Penlight/badge.svg?branch=master&service=github)](https://coveralls.io/github/stevedonovan/Penlight?branch=master) --[![AppVeyor status](https://ci.appveyor.com/api/projects/status/2ypffmsatb5rh9yw/branch/master?svg=true)](https://ci.appveyor.com/project/stevedonovan/penlight/branch/master) -+[![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/penlight/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/Penlight/actions) -+[![AppVeyor build status](https://img.shields.io/appveyor/build/Tieske/penlight-ta1gi/master?label=Windows%20build&logo=windows)](https://ci.appveyor.com/project/Tieske/penlight-ta1gi/branch/master) -+[![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/Penlight?logo=coveralls)](https://coveralls.io/github/lunarmodules/Penlight) -+[![Luacheck](https://github.com/lunarmodules/Penlight/workflows/Luacheck/badge.svg)](https://github.com/lunarmodules/Penlight/actions) -+[![SemVer](https://img.shields.io/github/v/tag/lunarmodules/Penlight?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) - - ## Why a new set of libraries? - -@@ -57,9 +59,44 @@ Python standard libraries. - * `utils`: `utils.string_lambda` converts short strings like '|x| x^2' into functions - * `comprehension`: list comprehensions: `C'x for x=1,4'()=={1,2,3,4}` - -+## Versioning -+ -+Penlight is strictly versioned according to [Semantic Versioning](https://semver.org/). -+ -+In scope of the version: -+ * functionality provided by Penlight modules/classes -+ * based on stock Lua PuC-Rio or LuaJIT -+ -+Not in scope of the version: -+ * Documentation -+ * Error messages (textual changes) -+ * Deprecation warnings (by default to `stderr`) -+ -+### Deprecating functionality -+ -+Any version may deprecate functionality. So new deprecation notices may appear -+in major, minor, and patch releases. Final removal of functionality (assuming it -+is a breaking change) will only be done in a major version. -+ -+It is strongly suggested to use the deprecation warning mechanism to test usage -+of deprecated functionalities when upgrading. This is done by enabling the -+warning system (in Lua 5.4, or the Penlight compatibility function for earlier -+versions): -+ -+```lua -+require "pl.compat" -+warn "@on" -+``` -+ -+See `pl.utils.raise_deprecation` for more info. -+ -+## License -+ -+Penlight is distributed under the [MIT license](LICENSE.md). -+ - ## Installation - --Using [LuaRocks](https://luarocks.org): simply run `luarocks install penlight`. -+Using [LuaRocks](https://luarocks.org/modules/tieske/penlight): simply run `luarocks install penlight`. - - Manually: copy `lua/pl` directory into your Lua module path. It's typically - `/usr/local/share/lua/5.x` on a Linux system and `C:\Program Files\Lua\5.x\lua` -@@ -67,7 +104,7 @@ for Lua for Windows. - - ## Dependencies - --The file and directory functions depend on [LuaFileSystem](https://keplerproject.github.io/luafilesystem/), -+The file and directory functions depend on [LuaFileSystem](https://lunarmodules.github.io/luafilesystem/), - which is installed automatically if you are using LuaRocks. Additionally, if you want `dir.copyfile` to work - elegantly on Windows, then you need [Alien](http://mascarenhas.github.io/alien/). Both libraries are present - in Lua for Windows. -@@ -75,12 +112,16 @@ in Lua for Windows. - ## Building the Documentation - - Requires [ldoc](https://github.com/stevedonovan/LDoc), which is available --through LuaRocks. Then it's a simple matter of running `ldoc` in the docs folder. -+through LuaRocks. Then it's a simple matter of running `ldoc .` from the repo. - --``` --Penlight/docs$ ldoc . --``` -+## Contributing -+ -+Contributions are most welcome, please check the [contribution guidelines](CONTRIBUTING.md). - - ## Running tests - - Execute `lua run.lua tests` to run the tests. Execute `lua run.lua examples` to run examples. -+ -+## History -+ -+For a complete history of the development of Penlight, please check the [changelog](CHANGELOG.md). -diff --git a/extra/penlight/appveyor.yml b/extra/penlight/appveyor.yml -index 5083e10..31e1202 100644 ---- a/extra/penlight/appveyor.yml -+++ b/extra/penlight/appveyor.yml -@@ -1,10 +1,13 @@ - shallow_clone: true - - environment: -+ COVERALLS_REPO_TOKEN: -+ secure: Ot23JDCk/sDpsIa8DkjX6u1ym09mVht+aFyIOmY09Ro6VFs/+VzQzqcldP0haP7r - matrix: - - LUA: "lua 5.1" - - LUA: "lua 5.2" - - LUA: "lua 5.3" -+ - LUA: "lua 5.4" - - LUA: "luajit 2.0" - - LUA: "luajit 2.0 --compat 5.2" - - LUA: "luajit 2.1" -@@ -12,12 +15,24 @@ environment: - - before_build: - - set PATH=C:\Python27\Scripts;%PATH% -+ - pip install --upgrade certifi -+ - FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "import certifi;print(certifi.where())"`) DO ( SET SSL_CERT_FILE=%%F ) - - pip install hererocks - - hererocks here --%LUA% -rlatest - - call here\bin\activate -+ - luarocks install luacov-coveralls -+ - luarocks install busted - - build_script: - - luarocks make - - test_script: -- - lua run.lua tests -+ - busted --coverage -+ - lua run.lua tests --luacov -+ - lua run.lua examples -+ -+on_success: -+ # secure coveralls token not available on PR builds, only BRANCH builds -+ - "if not \"%COVERALLS_REPO_TOKEN%\"==\"\" ( -+ luacov-coveralls -+ )" -diff --git a/extra/penlight/config.ld b/extra/penlight/config.ld -new file mode 100644 -index 0000000..0c901e8 ---- /dev/null -+++ b/extra/penlight/config.ld -@@ -0,0 +1,18 @@ -+project = 'Penlight' -+description = 'Penlight Lua Libraries 1.14.0' -+full_description = 'Penlight is a set of pure Lua libraries for making it easier to work with common tasks like iterating over directories, reading configuration files and the like. Provides functional operations on tables and sequences. Visit the GitHub project to review the code or file issues. Skip to the @{01-introduction.md|introduction}.' -+title = 'Penlight Documentation' -+dir = 'docs' -+style = '!fixed' -+template = true -+use_markdown_titles = true -+topics = 'docs_topics' -+examples = {'./examples','./tests/test-data.lua'} -+package = 'pl' -+format = 'discount' -+sort_modules=true -+file = './lua/pl' -+kind_names={topic='Manual',module='Libraries'} -+tparam_alias('array','array') -+tparam_alias('array2d','array') -+alias('ret',{'return',modifiers={type="$1"}}) -diff --git a/extra/penlight/doc/config.ld b/extra/penlight/doc/config.ld -deleted file mode 100644 -index 25c46f2..0000000 ---- a/extra/penlight/doc/config.ld -+++ /dev/null -@@ -1,20 +0,0 @@ --project = 'Penlight' --description = 'Penlight Lua Libraries 1.5.3' --full_description = 'The documentation is available @{01-introduction.md|here}.' --title = 'Penlight Documentation' --dir = 'api' --style = '!fixed' --use_markdown_titles = true --topics = 'manual' --examples = {'../examples','../tests/test-data.lua'} --package = 'pl' --format = 'discount' --sort_modules=true --file = '../lua/pl' --kind_names={topic='Manual',module='Libraries'} --tparam_alias('array','array') --tparam_alias('array2d','array') --alias('ret',{'return',modifiers={type="$1"}}) -- -- -- -diff --git a/extra/penlight/docs/classes/pl.Date.html b/extra/penlight/docs/classes/pl.Date.html -new file mode 100644 -index 0000000..ab131b3 ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.Date.html -@@ -0,0 +1,1049 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.Date

    -+

    Date and Date Format classes.

    -+

    See the Guide.

    -+ -+

    NOTE: the date module is deprecated! see -+ https://github.com/lunarmodules/Penlight/issues/285

    -+ -+

    Dependencies: pl.class, pl.stringx, pl.utils

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Date:set (t)set the current time of this Date object.
    Date.tzone (ts)get the time zone offset from UTC.
    Date:toUTC ()convert this date to UTC.
    Date:toLocal ()convert this UTC date to local.
    Date:year (y)set the year.
    Date:month (m)set the month.
    Date:day (d)set the day.
    Date:hour (h)set the hour.
    Date:min (min)set the minutes.
    Date:sec (sec)set the seconds.
    Date:yday (yday)set the day of year.
    Date:year (y)get the year.
    Date:month ()get the month.
    Date:day ()get the day.
    Date:hour ()get the hour.
    Date:min ()get the minutes.
    Date:sec ()get the seconds.
    Date:yday ()get the day of year.
    Date:weekday_name (full)name of day of week.
    Date:month_name (full)name of month.
    Date:is_weekend ()is this day on a weekend?.
    Date:add (t)add to a date object.
    Date:last_day ()last day of the month.
    Date:diff (other)difference between two Date objects.
    Date:__tostring ()long numerical ISO data format version of this date.
    Date:__eq (other)equality between Date objects.
    Date:__lt (other)ordering between Date objects.
    Date:__sub ()difference between Date objects.
    Date:__add (other)add a date and an interval.
    Date.Interval (t)Date.Interval constructor
    Date.Interval:__tostring ()If it's an interval then the format is '2 hours 29 sec' etc.
    Date.Format (fmt.)Date.Format constructor.
    Date.Format:parse (str)parse a string into a Date object.
    Date.Format:tostring (d)convert a Date object into a string.
    Date.Format:US_order (yesno)force US order in dates like 9/11/2001
    -+

    Methods

    -+ -+ -+ -+ -+ -+
    pl.date:Date (t, ...)Date constructor.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ Date:set (t) -+
    -+
    -+ set the current time of this Date object. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ integer -+ seconds since epoch -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date.tzone (ts) -+
    -+
    -+ get the time zone offset from UTC. -+ -+ -+

    Parameters:

    -+
      -+
    • ts -+ integer -+ seconds ahead of UTC -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:toUTC () -+
    -+
    -+ convert this date to UTC. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:toLocal () -+
    -+
    -+ convert this UTC date to local. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:year (y) -+
    -+
    -+ set the year. -+ -+ -+

    Parameters:

    -+
      -+
    • y -+ integer -+ Four-digit year -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:month (m) -+
    -+
    -+ set the month. -+ -+ -+

    Parameters:

    -+
      -+
    • m -+ integer -+ month -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:day (d) -+
    -+
    -+ set the day. -+ -+ -+

    Parameters:

    -+
      -+
    • d -+ integer -+ day -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:hour (h) -+
    -+
    -+ set the hour. -+ -+ -+

    Parameters:

    -+
      -+
    • h -+ integer -+ hour -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:min (min) -+
    -+
    -+ set the minutes. -+ -+ -+

    Parameters:

    -+
      -+
    • min -+ integer -+ minutes -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:sec (sec) -+
    -+
    -+ set the seconds. -+ -+ -+

    Parameters:

    -+
      -+
    • sec -+ integer -+ seconds -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:yday (yday) -+
    -+
    -+ set the day of year. -+ -+ -+

    Parameters:

    -+
      -+
    • yday -+ integer -+ day of year -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:year (y) -+
    -+
    -+ get the year. -+ -+ -+

    Parameters:

    -+
      -+
    • y -+ integer -+ Four-digit year -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:month () -+
    -+
    -+ get the month. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:day () -+
    -+
    -+ get the day. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:hour () -+
    -+
    -+ get the hour. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:min () -+
    -+
    -+ get the minutes. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:sec () -+
    -+
    -+ get the seconds. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:yday () -+
    -+
    -+ get the day of year. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:weekday_name (full) -+
    -+
    -+ name of day of week. -+ -+ -+

    Parameters:

    -+
      -+
    • full -+ boolean -+ abbreviated if true, full otherwise. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ name -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date:month_name (full) -+
    -+
    -+ name of month. -+ -+ -+

    Parameters:

    -+
      -+
    • full -+ integer -+ abbreviated if true, full otherwise. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ name -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date:is_weekend () -+
    -+
    -+ is this day on a weekend?. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:add (t) -+
    -+
    -+ add to a date object. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table containing one of the following keys and a value: -+ one of year,month,day,hour,min,sec -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ this date -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date:last_day () -+
    -+
    -+ last day of the month. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ int day -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date:diff (other) -+
    -+
    -+ difference between two Date objects. -+ -+ -+

    Parameters:

    -+
      -+
    • other -+ Date -+ Date object -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ Date.Interval -+ object -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date:__tostring () -+
    -+
    -+ long numerical ISO data format version of this date. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:__eq (other) -+
    -+
    -+ equality between Date objects. -+ -+ -+

    Parameters:

    -+
      -+
    • other -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:__lt (other) -+
    -+
    -+ ordering between Date objects. -+ -+ -+

    Parameters:

    -+
      -+
    • other -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:__sub () -+
    -+
    -+ difference between Date objects. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date:__add (other) -+
    -+
    -+ add a date and an interval. -+ -+ -+

    Parameters:

    -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date.Interval (t) -+
    -+
    -+ Date.Interval constructor -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ integer -+ an interval in seconds -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date.Interval:__tostring () -+
    -+
    -+ If it's an interval then the format is '2 hours 29 sec' etc. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Date.Format (fmt.) -+
    -+
    -+ Date.Format constructor. -+ -+ -+

    Parameters:

    -+
      -+
    • fmt. -+ string -+ A string where the following fields are significant:

      -+ -+
        -+
      • d day (either d or dd)
      • -+
      • y year (either yy or yyy)
      • -+
      • m month (either m or mm)
      • -+
      • H hour (either H or HH)
      • -+
      • M minute (either M or MM)
      • -+
      • S second (either S or SS)
      • -+
      -+ -+

      Alternatively, if fmt is nil then this returns a flexible date parser -+ that tries various date/time schemes in turn:

      -+ -+
        -+
      • ISO 8601, like 2010-05-10 12:35:23Z or 2008-10-03T14:30+02
      • -+
      • times like 15:30 or 8.05pm (assumed to be today's date)
      • -+
      • dates like 28/10/02 (European order!) or 5 Feb 2012
      • -+
      • month name like march or Mar (case-insensitive, first 3 letters); here the -+ day will be 1 and the year this current year
      • -+
      -+ -+

      A date in format 3 can be optionally followed by a time in format 2. -+ Please see test-date.lua in the tests folder for more examples. -+

    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      df = Date.Format("yyyy-mm-dd HH:MM:SS")
      -+
    -+ -+
    -+
    -+ -+ Date.Format:parse (str) -+
    -+
    -+ parse a string into a Date object. -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ a date string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ date object -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date.Format:tostring (d) -+
    -+
    -+ convert a Date object into a string. -+ -+ -+

    Parameters:

    -+
      -+
    • d -+ a date object, or a time value as returned by os.time -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Date.Format:US_order (yesno) -+
    -+
    -+ force US order in dates like 9/11/2001 -+ -+ -+

    Parameters:

    -+
      -+
    • yesno -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Methods

    -+ -+
    -+
    -+ -+ pl.date:Date (t, ...) -+
    -+
    -+ Date constructor. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ -+

      this can be either

      -+ -+
        -+
      • nil or empty - use current date and time
      • -+
      • number - seconds since epoch (as returned by os.time). Resulting time is UTC
      • -+
      • Date - make a copy of this date
      • -+
      • table - table containing year, month, etc as for os.time. You may leave out year, month or day, -+ in which case current values will be used.
      • -+
      • year (will be followed by month, day etc)
      • -+
      -+ -+ -+
    • -+
    • ... -+ true if Universal Coordinated Time, or two to five numbers: month,day,hour,min,sec -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/classes/pl.List.html b/extra/penlight/docs/classes/pl.List.html -new file mode 100644 -index 0000000..33e3d84 ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.List.html -@@ -0,0 +1,1443 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.List

    -+

    Python-style list class.

    -+

    Please Note: methods that change the list will return the list. -+ This is to allow for method chaining, but please note that ls = ls:sort() -+ does not mean that a new copy of the list is made. In-place (mutable) methods -+ are marked as returning 'the list' in this documentation.

    -+ -+

    See the Guide for further discussion

    -+ -+

    See http://www.python.org/doc/current/tut/tut.html, section 5.1

    -+ -+

    Note: The comments before some of the functions are from the Python docs -+ and contain Python code.

    -+ -+

    Written for Lua version Nick Trout 4.0; Redone for Lua 5.1, Steve Donovan.

    -+ -+

    Dependencies: pl.utils, pl.tablex, pl.class

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    List.new ([t])Create a new list.
    List:clone ()Make a copy of an existing list.
    List:append (i)Add an item to the end of the list.
    List:extend (L)Extend the list by appending all the items in the given list.
    List:insert (i, x)Insert an item at a given position.
    List:put (x)Insert an item at the beginning of the list.
    List:remove (i)Remove an element given its index.
    List:remove_value (x)Remove the first item from the list whose value is given.
    List:pop ([i])Remove the item at the given position in the list, and return it.
    List:index (x[, idx=1])Return the index in the list of the first item whose value is given.
    List:contains (x)Does this list contain the value?
    List:count (x)Return the number of times value appears in the list.
    List:sort ([cmp='<'])Sort the items of the list, in place.
    List:sorted ([cmp='<'])Return a sorted copy of this list.
    List:reverse ()Reverse the elements of the list, in place.
    List:minmax ()Return the minimum and the maximum value of the list.
    List:slice (first, last)Emulate list slicing.
    List:clear ()Empty the list.
    List.range (start[, finish[, incr=1]])Emulate Python's range(x) function.
    List:len ()list:len() is the same as #list.
    List:chop (i1, i2)Remove a subrange of elements.
    List:splice (idx, list)Insert a sublist into a list -+ equivalent to 's[idx:idx] = list' in Python
    List:slice_assign (i1, i2, seq)General slice assignment s[i1:i2] = seq.
    List:join ([delim=''])Join the elements of a list using a delimiter.
    List:concat ([delim=''])Join a list of strings.
    List:foreach (fun, ...)Call the function on each element of the list.
    List:foreachm (name, ...)Call the named method on each element of the list.
    List:filter (fun[, arg])Create a list of all elements which match a function.
    List.split (s[, delim])Split a string using a delimiter.
    List:map (fun, ...)Apply a function to all elements.
    List:transform (fun, ...)Apply a function to all elements, in-place.
    List:map2 (fun, ls, ...)Apply a function to elements of two lists.
    List:mapm (name, ...)apply a named method to all elements.
    List:reduce (fun)'reduce' a list using a binary function.
    List:partition (fun, ...)Partition a list using a classifier function.
    List:iter ()return an iterator over all values.
    List.iterate (seq)Create an iterator over a sequence.
    -+

    metamethods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    List:__concat (L)Concatenation operator.
    List:__eq (L)Equality operator ==.
    List:__tostring ()How our list should be rendered as a string.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ List.new ([t]) -+
    -+
    -+ Create a new list. Can optionally pass a table; -+ passing another instance of List will cause a copy to be created; -+ this will return a plain table with an appropriate metatable. -+ we pass anything which isn't a simple table to iterate() to work out -+ an appropriate iterator -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ An optional list-like table -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new List -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      ls = List();  ls = List {1,2,3,4}
      -+
    -+ -+
    -+
    -+ -+ List:clone () -+
    -+
    -+ Make a copy of an existing list. -+ The difference from a plain 'copy constructor' is that this returns -+ the actual List subtype. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ List:append (i) -+
    -+
    -+ Add an item to the end of the list. -+ -+ -+

    Parameters:

    -+
      -+
    • i -+ An item -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:extend (L) -+
    -+
    -+ Extend the list by appending all the items in the given list. -+ equivalent to 'a[len(a):] = L'. -+ -+ -+

    Parameters:

    -+
      -+
    • L -+ List -+ Another List -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:insert (i, x) -+
    -+
    -+ Insert an item at a given position. i is the index of the -+ element before which to insert. -+ -+ -+

    Parameters:

    -+
      -+
    • i -+ integer -+ index of element before whichh to insert -+
    • -+
    • x -+ A data item -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:put (x) -+
    -+
    -+ Insert an item at the beginning of the list. -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ a data item -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:remove (i) -+
    -+
    -+ Remove an element given its index. -+ (equivalent of Python's del s[i]) -+ -+ -+

    Parameters:

    -+
      -+
    • i -+ integer -+ the index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:remove_value (x) -+
    -+
    -+ Remove the first item from the list whose value is given. -+ (This is called 'remove' in Python; renamed to avoid confusion -+ with table.remove) -+ Return nil if there is no such item. -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ A data value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:pop ([i]) -+
    -+
    -+ Remove the item at the given position in the list, and return it. -+ If no index is specified, a:pop() returns the last item in the list. -+ The item is also removed from the list. -+ -+ -+

    Parameters:

    -+
      -+
    • i -+ integer -+ An index -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the item -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:index (x[, idx=1]) -+
    -+
    -+ Return the index in the list of the first item whose value is given. -+ Return nil if there is no such item. -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ A data value -+
    • -+
    • idx -+ integer -+ where to start search -+ (default 1) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the index, or nil if not found. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:contains (x) -+
    -+
    -+ Does this list contain the value? -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ A data value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:count (x) -+
    -+
    -+ Return the number of times value appears in the list. -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ A data value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ number of times x appears -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:sort ([cmp='<']) -+
    -+
    -+ Sort the items of the list, in place. -+ -+ -+

    Parameters:

    -+
      -+
    • cmp -+ function -+ an optional comparison function -+ (default '<') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:sorted ([cmp='<']) -+
    -+
    -+ Return a sorted copy of this list. -+ -+ -+

    Parameters:

    -+
      -+
    • cmp -+ function -+ an optional comparison function -+ (default '<') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:reverse () -+
    -+
    -+ Reverse the elements of the list, in place. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:minmax () -+
    -+
    -+ Return the minimum and the maximum value of the list. -+ -+ -+ -+

    Returns:

    -+
      -+
    1. -+ minimum value
    2. -+
    3. -+ maximum value
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:slice (first, last) -+
    -+
    -+ Emulate list slicing. like 'list[first:last]' in Python. -+ If first or last are negative then they are relative to the end of the list -+ eg. slice(-2) gives last 2 entries in a list, and -+ slice(-4,-2) gives from -4th to -2nd -+ -+ -+

    Parameters:

    -+
      -+
    • first -+ An index -+
    • -+
    • last -+ An index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new List -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:clear () -+
    -+
    -+ Empty the list. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List.range (start[, finish[, incr=1]]) -+
    -+
    -+ Emulate Python's range(x) function. -+ Include it in List table for tidiness -+ -+ -+

    Parameters:

    -+
      -+
    • start -+ integer -+ A number -+
    • -+
    • finish -+ integer -+ A number greater than start; if absent, -+ then start is 1 and finish is start -+ (optional) -+
    • -+
    • incr -+ integer -+ an increment (may be less than 1) -+ (default 1) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a List from start .. finish -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • List.range(0,3) == List{0,1,2,3}
    • -+
    • List.range(4) = List{1,2,3,4}
    • -+
    • List.range(5,1,-1) == List{5,4,3,2,1}
    • -+
    -+ -+
    -+
    -+ -+ List:len () -+
    -+
    -+ list:len() is the same as #list. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ List:chop (i1, i2) -+
    -+
    -+ Remove a subrange of elements. -+ equivalent to 'del s[i1:i2]' in Python. -+ -+ -+

    Parameters:

    -+
      -+
    • i1 -+ integer -+ start of range -+
    • -+
    • i2 -+ integer -+ end of range -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:splice (idx, list) -+
    -+
    -+ Insert a sublist into a list -+ equivalent to 's[idx:idx] = list' in Python -+ -+ -+

    Parameters:

    -+
      -+
    • idx -+ integer -+ index -+
    • -+
    • list -+ List -+ list to insert -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      l = List{10,20}; l:splice(2,{21,22});  assert(l == List{10,21,22,20})
      -+
    -+ -+
    -+
    -+ -+ List:slice_assign (i1, i2, seq) -+
    -+
    -+ General slice assignment s[i1:i2] = seq. -+ -+ -+

    Parameters:

    -+
      -+
    • i1 -+ integer -+ start index -+
    • -+
    • i2 -+ integer -+ end index -+
    • -+
    • seq -+ List -+ a list -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:join ([delim='']) -+
    -+
    -+ Join the elements of a list using a delimiter. -+ This method uses tostring on all elements. -+ -+ -+

    Parameters:

    -+
      -+
    • delim -+ string -+ a delimiter string, can be empty. -+ (default '') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:concat ([delim='']) -+
    -+
    -+ Join a list of strings.
    -+ Uses table.concat directly. -+ -+ -+

    Parameters:

    -+
      -+
    • delim -+ string -+ a delimiter -+ (default '') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:foreach (fun, ...) -+
    -+
    -+ Call the function on each element of the list. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function or callable object -+
    • -+
    • ... -+ optional values to pass to function -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ List:foreachm (name, ...) -+
    -+
    -+ Call the named method on each element of the list. -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ the method name -+
    • -+
    • ... -+ optional values to pass to function -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ List:filter (fun[, arg]) -+
    -+
    -+ Create a list of all elements which match a function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a boolean function -+
    • -+
    • arg -+ optional argument to be passed as second argument of the predicate -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new filtered list. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List.split (s[, delim]) -+
    -+
    -+ Split a string using a delimiter. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • delim -+ string -+ the delimiter (default spaces) -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a List of strings -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ List:map (fun, ...) -+
    -+
    -+ Apply a function to all elements. -+ Any extra arguments will be passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of at least one argument -+
    • -+
    • ... -+ arbitrary extra arguments. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list: {f(x) for x in self} -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      List{'one','two'}:map(string.upper) == {'ONE','TWO'}
      -+
    -+ -+
    -+
    -+ -+ List:transform (fun, ...) -+
    -+
    -+ Apply a function to all elements, in-place. -+ Any extra arguments are passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ A function that takes at least one argument -+
    • -+
    • ... -+ arbitrary extra arguments. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the list. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:map2 (fun, ls, ...) -+
    -+
    -+ Apply a function to elements of two lists. -+ Any extra arguments will be passed to the function -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of at least two arguments -+
    • -+
    • ls -+ List -+ another list -+
    • -+
    • ... -+ arbitrary extra arguments. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list: {f(x,y) for x in self, for x in arg1} -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ List:mapm (name, ...) -+
    -+
    -+ apply a named method to all elements. -+ Any extra arguments will be passed to the method. -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ name of method -+
    • -+
    • ... -+ extra arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list of the results -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ List:reduce (fun) -+
    -+
    -+ 'reduce' a list using a binary function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of two arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ result of the function -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ List:partition (fun, ...) -+
    -+
    -+ Partition a list using a classifier function. -+ The function may return nil, but this will be converted to the string key ''. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of at least one argument -+
    • -+
    • ... -+ will also be passed to the function -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ MultiMap -+ a table where the keys are the returned values, and the values are Lists -+ of values where the function returned that key. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ List:iter () -+
    -+
    -+ return an iterator over all values. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ List.iterate (seq) -+
    -+
    -+ Create an iterator over a sequence. -+ This captures the Python concept of 'sequence'. -+ For tables, iterates over all values with integer indices. -+ -+ -+

    Parameters:

    -+
      -+
    • seq -+ a sequence; a string (over characters), a table, a file object (over lines) or an iterator function -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • for x in iterate {1,10,22,55} do io.write(x,',') end ==> 1,10,22,55
    • -+
    • for ch in iterate 'help' do do io.write(ch,' ') end ==> h e l p
    • -+
    -+ -+
    -+
    -+

    metamethods

    -+ -+
    -+
    -+ -+ List:__concat (L) -+
    -+
    -+ Concatenation operator. -+ -+ -+

    Parameters:

    -+
      -+
    • L -+ List -+ another List -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list consisting of the list with the elements of the new list appended -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:__eq (L) -+
    -+
    -+ Equality operator ==. True iff all elements of two lists are equal. -+ -+ -+

    Parameters:

    -+
      -+
    • L -+ List -+ another List -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ List:__tostring () -+
    -+
    -+ How our list should be rendered as a string. Uses join(). -+ -+ -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/classes/pl.Map.html b/extra/penlight/docs/classes/pl.Map.html -new file mode 100644 -index 0000000..9ef1741 ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.Map.html -@@ -0,0 +1,454 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.Map

    -+

    A Map class.

    -+

    -+ -+ -+ -+

    -+> Map = require 'pl.Map'
    -+> m = Map{one=1,two=2}
    -+> m:update {three=3,four=4,two=20}
    -+> = m == M{one=1,two=20,three=3,four=4}
    -+true
    -+
    -+ -+

    Dependencies: pl.utils, pl.class, pl.tablex, pl.pretty

    -+

    -+ -+ -+

    Fields

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.map.keyslist of keys.
    pl.map.valueslist of values.
    -+

    Methods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.map:iter ()return an iterator over all key-value pairs.
    pl.map:items ()return a List of all key-value pairs, sorted by the keys.
    pl.map:setdefault (key, default)set a value in the map if it doesn't exist yet.
    pl.map:len ()size of map.
    pl.map:set (key, val)put a value into the map.
    pl.map:get (key)get a value from the map.
    pl.map:getvalues (keys)get a list of values indexed by a list of keys.
    pl.map:update (table)update the map using key/value pairs from another table.
    -+

    Metamethods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.map:__eq (m)equality between maps.
    pl.map:__tostring ()string representation of a map.
    -+ -+
    -+
    -+ -+ -+

    Fields

    -+ -+
    -+
    -+ -+ pl.map.keys -+
    -+
    -+ list of keys. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map.values -+
    -+
    -+ list of values. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+

    Methods

    -+ -+
    -+
    -+ -+ pl.map:iter () -+
    -+
    -+ return an iterator over all key-value pairs. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:items () -+
    -+
    -+ return a List of all key-value pairs, sorted by the keys. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:setdefault (key, default) -+
    -+
    -+ set a value in the map if it doesn't exist yet. -+ -+ -+

    Parameters:

    -+
      -+
    • key -+ the key -+
    • -+
    • default -+ value to set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the value stored in the map (existing value, or the new value) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:len () -+
    -+
    -+ size of map. -+ note: this is a relatively expensive operation! -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:set (key, val) -+
    -+
    -+ put a value into the map. -+ This will remove the key if the value is nil -+ -+ -+

    Parameters:

    -+
      -+
    • key -+ the key -+
    • -+
    • val -+ the value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:get (key) -+
    -+
    -+ get a value from the map. -+ -+ -+

    Parameters:

    -+
      -+
    • key -+ the key -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the value, or nil if not found. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:getvalues (keys) -+
    -+
    -+ get a list of values indexed by a list of keys. -+ -+ -+

    Parameters:

    -+
      -+
    • keys -+ a list-like table of keys -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:update (table) -+
    -+
    -+ update the map using key/value pairs from another table. -+ -+ -+

    Parameters:

    -+
      -+
    • table -+ table -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Metamethods

    -+ -+
    -+
    -+ -+ pl.map:__eq (m) -+
    -+
    -+ equality between maps. -+ -+ -+

    Parameters:

    -+
      -+
    • m -+ Map -+ another map. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.map:__tostring () -+
    -+
    -+ string representation of a map. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/classes/pl.MultiMap.html b/extra/penlight/docs/classes/pl.MultiMap.html -new file mode 100644 -index 0000000..be7fe39 ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.MultiMap.html -@@ -0,0 +1,207 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.MultiMap

    -+

    MultiMap, a Map which has multiple values per key.

    -+

    Dependencies: pl.utils, pl.class, pl.List, pl.Map

    -+ -+ -+

    Methods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.multimap:update (t)update a MultiMap using a table.
    pl.multimap:set (key, val)add a new value to a key.
    -+ -+
    -+
    -+ -+ -+

    Methods

    -+ -+
    -+
    -+ -+ pl.multimap:update (t) -+
    -+
    -+ update a MultiMap using a table. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ either a Multimap or a map-like table. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the map -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.multimap:set (key, val) -+
    -+
    -+ add a new value to a key. Setting a nil value removes the key. -+ -+ -+

    Parameters:

    -+
      -+
    • key -+ the key -+
    • -+
    • val -+ the value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the map -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/classes/pl.OrderedMap.html b/extra/penlight/docs/classes/pl.OrderedMap.html -new file mode 100644 -index 0000000..efc1947 ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.OrderedMap.html -@@ -0,0 +1,417 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.OrderedMap

    -+

    OrderedMap, a map which preserves ordering.

    -+

    Derived from pl.Map.

    -+ -+

    Dependencies: pl.utils, pl.tablex, pl.class, pl.List, pl.Map

    -+ -+ -+

    Methods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.orderedmap:_init (t)construct an OrderedMap.
    pl.orderedmap:update (t)update an OrderedMap using a table.
    pl.orderedmap:set (key, val)set the key's value.
    pl.orderedmap:insert (pos, key, val)insert a key/value pair before a given position.
    pl.orderedmap:keys ()return the keys in order.
    pl.orderedmap:values ()return the values in order.
    pl.orderedmap:sort (cmp)sort the keys.
    pl.orderedmap:iter ()iterate over key-value pairs in order.
    -+

    Metamethods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.orderedmap:__pairs ()iterate over an ordered map (5.2).
    pl.orderedmap:__tostring ()string representation of an ordered map.
    -+ -+
    -+
    -+ -+ -+

    Methods

    -+ -+
    -+
    -+ -+ pl.orderedmap:_init (t) -+
    -+
    -+ construct an OrderedMap. -+ Will throw an error if the argument is bad. -+ -+ -+

    Parameters:

    -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:update (t) -+
    -+
    -+ update an OrderedMap using a table. -+ If the table is itself an OrderedMap, then its entries will be appended. -+ if it s a table of the form {{key1=val1},{key2=val2},...} these will be appended.

    -+ -+

    Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ a table. -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ the map, or nil in case of error
    2. -+
    3. -+ the error message
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:set (key, val) -+
    -+
    -+ set the key's value. This key will be appended at the end of the map.

    -+ -+

    If the value is nil, then the key is removed. -+ -+ -+

    Parameters:

    -+
      -+
    • key -+ the key -+
    • -+
    • val -+ the value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the map -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:insert (pos, key, val) -+
    -+
    -+ insert a key/value pair before a given position. -+ Note: if the map already contains the key, then this effectively -+ moves the item to the new position by first removing at the old position. -+ Has no effect if the key does not exist and val is nil -+ -+ -+

    Parameters:

    -+
      -+
    • pos -+ integer -+ a position starting at 1 -+
    • -+
    • key -+ the key -+
    • -+
    • val -+ the value; if nil use the old value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:keys () -+
    -+
    -+ return the keys in order. -+ (Not a copy!) -+ -+ -+ -+

    Returns:

    -+
      -+ -+ List -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:values () -+
    -+
    -+ return the values in order. -+ this is relatively expensive. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ List -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:sort (cmp) -+
    -+
    -+ sort the keys. -+ -+ -+

    Parameters:

    -+
      -+
    • cmp -+ function -+ a comparison function as for table.sort -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the map -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:iter () -+
    -+
    -+ iterate over key-value pairs in order. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+

    Metamethods

    -+ -+
    -+
    -+ -+ pl.orderedmap:__pairs () -+
    -+
    -+ iterate over an ordered map (5.2). -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.orderedmap:__tostring () -+
    -+
    -+ string representation of an ordered map. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/classes/pl.Set.html b/extra/penlight/docs/classes/pl.Set.html -new file mode 100644 -index 0000000..f351e4d ---- /dev/null -+++ b/extra/penlight/docs/classes/pl.Set.html -@@ -0,0 +1,655 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Class pl.Set

    -+

    A Set class.

    -+

    -+ -+ -+ -+

    -+> Set = require 'pl.Set'
    -+> = Set{'one','two'} == Set{'two','one'}
    -+true
    -+> fruit = Set{'apple','banana','orange'}
    -+> = fruit['banana']
    -+true
    -+> = fruit['hazelnut']
    -+nil
    -+> colours = Set{'red','orange','green','blue'}
    -+> = fruit,colours
    -+[apple,orange,banana]   [blue,green,orange,red]
    -+> = fruit+colours
    -+[blue,green,apple,red,orange,banana]
    -+[orange]
    -+> more_fruits = fruit + 'apricot'
    -+> = fruit*colours
    -+ =  more_fruits, fruit
    -+banana,apricot,apple,orange]    [banana,apple,orange]
    -+
    -+ -+

    Dependencies: pl.utils, pl.tablex, pl.class, pl.Map, (pl.List if __tostring is used)

    -+

    -+ -+ -+

    Methods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.set:Set (t)create a set.
    pl.set:values (self)get a list of the values in a set.
    pl.set:map (self, fn, ...)map a function over the values of a set.
    pl.set:union (self, set)union of two sets (also +).
    pl.set:intersection (self, set)intersection of two sets (also *).
    pl.set:difference (self, set)new set with elements in the set that are not in the other (also -).
    pl.set:issubset (self, set)is the first set a subset of the second (also <)?.
    pl.set:isempty (self)is the set empty?.
    pl.set:isdisjoint (s1, s2)are the sets disjoint?
    pl.set:len (s)size of this set (also # for 5.2).
    -+

    Metamethods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.set:__tostring ()string representation of a set.
    pl.set:__add ()union of sets.
    pl.set:__mul ()intersection of sets.
    pl.set:__sub ()difference of sets.
    pl.set:__pow ()symmetric difference of sets.
    pl.set:__lt ()first set subset of second?
    pl.set:__len ()cardinality of set (5.2).
    pl.set:__eq (s1, s2)equality between sets.
    -+ -+
    -+
    -+ -+ -+

    Methods

    -+ -+
    -+
    -+ -+ pl.set:Set (t) -+
    -+
    -+ create a set.
    -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ may be a Set, Map or list-like table. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:values (self) -+
    -+
    -+ get a list of the values in a set. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:map (self, fn, ...) -+
    -+
    -+ map a function over the values of a set. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • fn -+ a function -+
    • -+
    • ... -+ extra arguments to pass to the function. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:union (self, set) -+
    -+
    -+ union of two sets (also +). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:intersection (self, set) -+
    -+
    -+ intersection of two sets (also *). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      > s = Set{10,20,30}
      -+> t = Set{20,30,40}
      -+> = t
      -+[20,30,40]
      -+> = Set.intersection(s,t)
      -+[30,20]
      -+> = s*t
      -+[30,20]
      -+
    -+ -+
    -+
    -+ -+ pl.set:difference (self, set) -+
    -+
    -+ new set with elements in the set that are not in the other (also -). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:issubset (self, set) -+
    -+
    -+ is the first set a subset of the second (also <)?. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:isempty (self) -+
    -+
    -+ is the set empty?. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:isdisjoint (s1, s2) -+
    -+
    -+ are the sets disjoint? (no elements in common). -+ Uses naive definition, i.e. that intersection is empty -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ a Set -+
    • -+
    • s2 -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:len (s) -+
    -+
    -+ size of this set (also # for 5.2). -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ size -+
    -+ -+ -+ -+ -+
    -+
    -+

    Metamethods

    -+ -+
    -+
    -+ -+ pl.set:__tostring () -+
    -+
    -+ string representation of a set. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__add () -+
    -+
    -+ union of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__mul () -+
    -+
    -+ intersection of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__sub () -+
    -+
    -+ difference of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__pow () -+
    -+
    -+ symmetric difference of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__lt () -+
    -+
    -+ first set subset of second? -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__len () -+
    -+
    -+ cardinality of set (5.2). -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pl.set:__eq (s1, s2) -+
    -+
    -+ equality between sets. -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ -+ -+ -+
    • -+
    • s2 -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/seesubst.lua.html b/extra/penlight/docs/examples/seesubst.lua.html -new file mode 100644 -index 0000000..1679fd7 ---- /dev/null -+++ b/extra/penlight/docs/examples/seesubst.lua.html -@@ -0,0 +1,174 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    seesubst.lua

    -+
    -+-- shows how replacing '@see module' in the Markdown documentation
    -+-- can be done more elegantly using PL.
    -+-- We either have something like 'pl.config' (a module reference)
    -+-- or 'pl.seq.map' (a function reference); these cases must be distinguished
    -+-- and a Markdown link generated pointing to the LuaDoc file.
    -+
    -+local sip = require 'pl.sip'
    -+local stringx = require 'pl.stringx'
    -+
    -+local res = {}
    -+local s = [[
    -+(@see pl.bonzo.dog)
    -+remember about @see pl.bonzo
    -+
    -+]]
    -+
    -+local _gsub_patterns = {}
    -+
    -+local function gsub (s,pat,subst,start)
    -+    local fpat = _gsub_patterns[pat]
    -+    if not fpat then
    -+        -- use SIP to generate a proper string pattern.
    -+        -- the _whole thing_ is a capture, to get the whole match
    -+        -- and the unnamed capture.
    -+        fpat = '('..sip.create_pattern(pat)..')'
    -+        _gsub_patterns[pat] = fpat
    -+    end
    -+    return s:gsub(fpat,subst,start)
    -+end
    -+
    -+
    -+local mod = sip.compile '$v.$v'
    -+local fun = sip.compile '$v.$v.$v'
    -+
    -+for line in stringx.lines(s) do
    -+    line = gsub(line,'@see $p',function(see,path)
    -+        if fun(path,res) or mod(path,res) then
    -+            local ret = ('[see %s](%s.%s.html'):format(path,res[1],res[2])
    -+            if res[3] then
    -+                return ret..'#'..res[3]..')'
    -+            else
    -+                return ret..')'
    -+            end
    -+        end
    -+    end)
    -+    print(line)
    -+end
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/sipscan.lua.html b/extra/penlight/docs/examples/sipscan.lua.html -new file mode 100644 -index 0000000..b881358 ---- /dev/null -+++ b/extra/penlight/docs/examples/sipscan.lua.html -@@ -0,0 +1,161 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    sipscan.lua

    -+
    -+-- another SIP example, shows how an awkward log file format
    -+-- can be parsed. It also prints out the actual Lua string
    -+-- pattern generated:
    -+-- SYNC%s*%[([+%-%d]%d*)%]%s*([+%-%d]%d*)%s*([+%-%d]%d*)
    -+
    -+local sip = require 'pl.sip'
    -+local stringx = require 'pl.stringx'
    -+
    -+local s = [[
    -+SYNC [1] 0 547 (14679 sec)
    -+SYNC [2] 0 555 (14679 sec)
    -+SYNC [3] 0 563 (14679 sec)
    -+SYNC [4] 0 571 (14679 sec)
    -+SYNC [5] -1 580 (14679 sec)
    -+SYNC [6] 0 587 (14679 sec)
    -+]]
    -+
    -+
    -+local first = true
    -+local expected
    -+local res = {}
    -+local pat = 'SYNC [$i{seq}] $i{diff} $i{val}'
    -+print(sip.create_pattern(pat))
    -+local match = sip.compile(pat)
    -+for line in stringx.lines(s) do
    -+  if match(line,res) then
    -+    if first then
    -+      expected = res.val
    -+      first = false
    -+    end
    -+    print(res.val,expected - res.val)
    -+    expected = expected + 8
    -+  end
    -+end
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/symbols.lua.html b/extra/penlight/docs/examples/symbols.lua.html -new file mode 100644 -index 0000000..3a3b4a3 ---- /dev/null -+++ b/extra/penlight/docs/examples/symbols.lua.html -@@ -0,0 +1,347 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    symbols.lua

    -+
    -+require 'pl'
    -+utils.import 'pl.func'
    -+local ops = require 'pl.operator'
    -+local List = require 'pl.List'
    -+local append,concat = table.insert,table.concat
    -+local compare,find_if,compare_no_order,imap,reduce,count_map = tablex.compare,tablex.find_if,tablex.compare_no_order,tablex.imap,tablex.reduce,tablex.count_map
    -+local unpack = table.unpack
    -+
    -+function bindval (self,val)
    -+    rawset(self,'value',val)
    -+end
    -+
    -+local optable = ops.optable
    -+
    -+function sexpr (e)
    -+	if isPE(e) then
    -+		if e.op ~= 'X' then
    -+			local args = tablex.imap(sexpr,e)
    -+			return '('..e.op..' '..table.concat(args,' ')..')'
    -+		else
    -+			return e.repr
    -+		end
    -+	else
    -+		return tostring(e)
    -+	end
    -+end
    -+
    -+
    -+psexpr = compose(print,sexpr)
    -+
    -+
    -+
    -+function equals (e1,e2)
    -+    local p1,p2 = isPE(e1),isPE(e2)
    -+    if p1 ~= p2 then return false end  -- different kinds of animals!
    -+    if p1 and p2 then -- both PEs
    -+        -- operators must be the same
    -+        if e1.op ~= e2.op then return false end
    -+        -- PHs are equal if their representations are equal
    -+        if e1.op == 'X' then return e1.repr == e2.repr
    -+        -- commutative operators
    -+        elseif e1.op == '+' or e1.op == '*' then
    -+            return compare_no_order(e1,e2,equals)
    -+        else
    -+            -- arguments must be the same
    -+            return compare(e1,e2,equals)
    -+        end
    -+    else -- fall back on simple equality for non PEs
    -+        return e1 == e2
    -+    end
    -+end
    -+
    -+-- run down an unbalanced operator chain (like a+b+c) and return the arguments {a,b,c}
    -+function tcollect (op,e,ls)
    -+    if isPE(e) and e.op == op then
    -+        for i = 1,#e do
    -+            tcollect(op,e[i],ls)
    -+        end
    -+    else
    -+        ls:append(e)
    -+        return
    -+    end
    -+end
    -+
    -+function rcollect (e)
    -+    local res = List()
    -+    tcollect(e.op,e,res)
    -+    return res
    -+end
    -+
    -+
    -+-- balance ensures that +/* chains are collected together, operates in-place.
    -+-- thus (+(+ a b) c) or (+ a (+ b c)) becomes (+ a b c), order immaterial
    -+function balance (e)
    -+    if isPE(e) and e.op ~= 'X' then
    -+        local op,args = e.op
    -+        if op == '+' or op == '*' then
    -+            args = rcollect(e)
    -+        else
    -+            args = imap(balance,e)
    -+        end
    -+        for i = 1,#args do
    -+            e[i] = args[i]
    -+        end
    -+    end
    -+    return e
    -+end
    -+
    -+-- fold constants in an expression
    -+function fold (e)
    -+    if isPE(e) then
    -+        if e.op == 'X' then
    -+            -- there could be _bound values_!
    -+            local val = rawget(e,'value')
    -+            return val and val or e
    -+        else
    -+            local op = e.op
    -+            local addmul = op == '*' or op == '+'
    -+            -- first fold all arguments
    -+            local args = imap(fold,e)
    -+            if not addmul and not find_if(args,isPE) then
    -+                -- no placeholders in these args, we can fold the expression.
    -+                local opfn = optable[op]
    -+                if opfn then
    -+                    return opfn(unpack(args))
    -+                else
    -+                    return '?'
    -+                end
    -+            elseif addmul then
    -+                -- enforce a few rules for + and *
    -+                -- split the args into two classes, PE args and non-PE args.
    -+                local classes = List.partition(args,isPE)
    -+                local pe,npe = classes[true],classes[false]
    -+                if npe then -- there's at least one non PE argument
    -+                    -- so fold them
    -+                    if #npe == 1 then npe = npe[1]
    -+                    else npe = npe:reduce(optable[op])
    -+                    end
    -+                    -- if the result is a constant, return it
    -+                    if not pe then return npe end
    -+
    -+                    -- either (* 1 x) => x or (* 1 x y ...) => (* x y ...)
    -+                    if op == '*' then
    -+                        if npe == 0 then return 0
    -+                        elseif npe == 1 then -- identity
    -+                            if #pe == 1 then return pe[1] else npe = nil end
    -+                        end
    -+                    else -- special cases for +
    -+                        if npe == 0 then -- identity
    -+                            if #pe == 1 then return pe[1] else npe = nil end
    -+                        end
    -+                    end
    -+                end
    -+                -- build up the final arguments
    -+                local res = {}
    -+                if npe then append(res,npe) end
    -+                for val,count in pairs(count_map(pe,equals)) do
    -+                    if count > 1 then
    -+                        if op == '*' then val = val ^ count
    -+                        else val = val * count
    -+                        end
    -+                    end
    -+                    append(res,val)
    -+                end
    -+                if #res == 1 then return res[1] end
    -+                return PE{op=op,unpack(res)}
    -+            elseif op == '^' then
    -+                if args[2] == 1 then return args[1] end -- identity
    -+                if args[2] == 0 then return 1 end
    -+            end
    -+            return PE{op=op,unpack(args)}
    -+        end
    -+    else
    -+        return e
    -+    end
    -+end
    -+
    -+function expand (e)
    -+    if isPE(e) and e.op == '*' and isPE(e[2]) and e[2].op == '+' then
    -+        local a,b = e[1],e[2]
    -+        return expand(b[1]*a) + expand(b[2]*a)
    -+    else
    -+        return e
    -+    end
    -+end
    -+
    -+function isnumber (x)
    -+    return type(x) == 'number'
    -+end
    -+
    -+-- does this PE contain a reference to x?
    -+function references (e,x)
    -+    if isPE(e) then
    -+        if e.op == 'X' then return x.repr == e.repr
    -+        else
    -+            return find_if(e,references,x)
    -+        end
    -+    else
    -+        return false
    -+    end
    -+end
    -+
    -+local function muli (args)
    -+    return PE{op='*',unpack(args)}
    -+end
    -+
    -+local function addi (args)
    -+    return PE{op='+',unpack(args)}
    -+end
    -+
    -+function diff (e,x)
    -+    if isPE(e) and references(e,x) then
    -+        local op = e.op
    -+        if op == 'X' then
    -+            return 1
    -+        else
    -+            local a,b = e[1],e[2]
    -+            if op == '+' then -- differentiation is linear
    -+                local args = imap(diff,e,x)
    -+                return balance(addi(args))
    -+            elseif op == '*' then -- product rule
    -+                local res,d,ee = {}
    -+                for i = 1,#e do
    -+                    d = fold(diff(e[i],x))
    -+                    if d ~= 0 then
    -+                        ee = {unpack(e)}
    -+                        ee[i] = d
    -+                        append(res,balance(muli(ee)))
    -+                    end
    -+                end
    -+                if #res > 1 then return addi(res)
    -+                else return res[1] end
    -+            elseif op == '^' and isnumber(b) then -- power rule
    -+                return b*x^(b-1)
    -+            end
    -+        end
    -+    else
    -+        return 0
    -+    end
    -+end
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/test-cmp.lua.html b/extra/penlight/docs/examples/test-cmp.lua.html -new file mode 100644 -index 0000000..f755fd1 ---- /dev/null -+++ b/extra/penlight/docs/examples/test-cmp.lua.html -@@ -0,0 +1,130 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    test-cmp.lua

    -+
    -+local A = require 'pl.tablex'
    -+print(A.compare_no_order({1,2,3},{2,1,3}))
    -+print(A.compare_no_order({1,2,3},{2,1,3},'=='))
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/test-data.lua.html b/extra/penlight/docs/examples/test-data.lua.html -new file mode 100644 -index 0000000..bef712c ---- /dev/null -+++ b/extra/penlight/docs/examples/test-data.lua.html -@@ -0,0 +1,372 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    test-data.lua

    -+
    -+local data = require 'pl.data'
    -+local List = require 'pl.List'
    -+local array = require 'pl.array2d'
    -+local func = require 'pl.func'
    -+local seq = require 'pl.seq'
    -+local stringio = require 'pl.stringio'
    -+local open = stringio. open
    -+local asserteq = require 'pl.test' . asserteq
    -+local T = require 'pl.test'. tuple
    -+
    -+--[=[
    -+dat,err = data.read(open [[
    -+1.0 0.1
    -+0.2 1.3
    -+]])
    -+
    -+if err then print(err) end
    -+
    -+require 'pl.pretty'.dump(dat)
    -+os.exit(0)
    -+--]=]
    -+
    -+-- tab-separated data, explicit column names
    -+local t1f = open [[
    -+EventID	Magnitude	LocationX	LocationY	LocationZ	LocationError	EventDate	DataFile
    -+981124001	2.0	18988.4	10047.1	4149.7	33.8	24/11/1998 11:18:05	981124DF.AAB
    -+981125001	0.8	19104.0	9970.4	5088.7	3.0	25/11/1998 05:44:54	981125DF.AAB
    -+981127003	0.5	19012.5	9946.9	3831.2	46.0	27/11/1998 17:15:17	981127DF.AAD
    -+981127005	0.6	18676.4	10606.2	3761.9	4.4	27/11/1998 17:46:36	981127DF.AAF
    -+981127006	0.2	19109.9	9716.5	3612.0	11.8	27/11/1998 19:29:51	981127DF.AAG
    -+]]
    -+
    -+local t1 = data.read (t1f)
    -+-- column_by_name returns a List
    -+asserteq(t1:column_by_name 'Magnitude',List{2,0.8,0.5,0.6,0.2})
    -+-- can use array.column as well
    -+asserteq(array.column(t1,2),{2,0.8,0.5,0.6,0.2})
    -+
    -+-- only numerical columns (deduced from first data row) are converted by default
    -+-- can look up indices in the list fieldnames.
    -+local EDI = t1.fieldnames:index 'EventDate'
    -+assert(type(t1[1][EDI]) == 'string')
    -+
    -+-- select method returns a sequence, in this case single-valued.
    -+-- (Note that seq.copy returns a List)
    -+asserteq(seq(t1:select 'LocationX where Magnitude > 0.5'):copy(),List{18988.4,19104,18676.4})
    -+
    -+--[[
    -+--a common select usage pattern:
    -+for event,mag in t1:select 'EventID,Magnitude sort by Magnitude desc' do
    -+    print(event,mag)
    -+end
    -+--]]
    -+
    -+-- space-separated, but with last field containing spaces.
    -+local t2f = open [[
    -+USER PID %MEM %CPU COMMAND
    -+sdonovan 2333  0.3 0.1 background --n=2
    -+root 2332  0.4  0.2 fred --start=yes
    -+root 2338  0.2  0.1 backyard-process
    -+]]
    -+
    -+local t2,err = data.read(t2f,{last_field_collect=true})
    -+if not t2 then return print (err) end
    -+
    -+-- the last_field_collect option is useful with space-delimited data where the last
    -+-- field may contain spaces. Otherwise, a record count mismatch should be an error!
    -+local lt2 = List(t2[2])
    -+asserteq(lt2:join ',','root,2332,0.4,0.2,fred --start=yes')
    -+
    -+-- fieldnames are converted into valid identifiers by substituting _
    -+-- (we do this to make select queries parseable by Lua)
    -+asserteq(t2.fieldnames,List{'USER','PID','_MEM','_CPU','COMMAND'})
    -+
    -+-- select queries are NOT SQL so remember to use == ! (and no 'between' operator, sorry)
    -+--s,err = t2:select('_MEM where USER="root"')
    -+--assert(err == [[[string "tmp"]:9: unexpected symbol near '=']])
    -+
    -+local s = t2:select('_MEM where USER=="root"')
    -+assert(s() == 0.4)
    -+assert(s() == 0.2)
    -+assert(s() == nil)
    -+
    -+-- CSV, Excel style. Double-quoted fields are allowed, and they may contain commas!
    -+local t3f = open [[
    -+"Department Name","Employee ID",Project,"Hours Booked"
    -+sales,1231,overhead,4
    -+sales,1255,overhead,3
    -+engineering,1501,development,5
    -+engineering,1501,maintenance,3
    -+engineering,1433,maintenance,10
    -+]]
    -+
    -+local t3 = data.read(t3f,{csv=true})
    -+
    -+-- although fieldnames are turned in valid Lua identifiers, there is always original_fieldnames
    -+asserteq(t3.fieldnames,List{'Department_Name','Employee_ID','Project','Hours_Booked'})
    -+asserteq(t3.original_fieldnames,List{'Department Name','Employee ID','Project','Hours Booked'})
    -+
    -+-- a common operation is to select using a given list of columns, and each row
    -+-- on some explicit condition. The select() method can take a table with these
    -+-- parameters
    -+local keepcols = {'Employee_ID','Hours_Booked'}
    -+
    -+local q = t3:select { fields = keepcols,
    -+    where = function(row) return row[1]=='engineering' end
    -+    }
    -+
    -+asserteq(seq.copy2(q),{{1501,5},{1501,3},{1433,10}})
    -+
    -+-- another pattern is doing a select to restrict rows & columns, process some
    -+-- fields and write out the modified rows.
    -+
    -+local outf = stringio.create()
    -+
    -+local names = {[1501]='don',[1433]='dilbert'}
    -+
    -+t3:write_row (outf,{'Employee','Hours_Booked'})
    -+q = t3:select_row {fields=keepcols,where=func.Eq(func._1[1],'engineering')}
    -+for row in q do
    -+    row[1] = names[row[1]]
    -+    t3:write_row(outf,row)
    -+end
    -+
    -+asserteq(outf:value(),
    -+[[
    -+Employee,Hours_Booked
    -+don,5
    -+don,3
    -+dilbert,10
    -+]])
    -+
    -+-- data may not always have column headers. When creating a data object
    -+-- from a two-dimensional array, may specify the fieldnames, as a list or a string.
    -+-- The delimiter is deduced from the fieldname string, so a string just containing
    -+-- the delimiter will set it,  and the fieldnames will be empty.
    -+local dat = List()
    -+local row = List.range(1,10)
    -+for i = 1,10 do
    -+    dat:append(row:map('*',i))
    -+end
    -+dat = data.new(dat,',')
    -+local out = stringio.create()
    -+dat:write(out,',')
    -+asserteq(out:value(), [[
    -+1,2,3,4,5,6,7,8,9,10
    -+2,4,6,8,10,12,14,16,18,20
    -+3,6,9,12,15,18,21,24,27,30
    -+4,8,12,16,20,24,28,32,36,40
    -+5,10,15,20,25,30,35,40,45,50
    -+6,12,18,24,30,36,42,48,54,60
    -+7,14,21,28,35,42,49,56,63,70
    -+8,16,24,32,40,48,56,64,72,80
    -+9,18,27,36,45,54,63,72,81,90
    -+10,20,30,40,50,60,70,80,90,100
    -+]])
    -+
    -+-- you can always use numerical field indices, AWK-style;
    -+-- note how the copy_select method gives you a data object instead of an
    -+-- iterator over the fields
    -+local res = dat:copy_select '$1,$3 where $1 > 5'
    -+local L = List
    -+asserteq(L(res),L{
    -+    L{6, 18},
    -+    L{7,21},
    -+    L{8,24},
    -+    L{9,27},
    -+    L{10,30},
    -+})
    -+
    -+-- the column_by_name method may take a fieldname or an index
    -+asserteq(dat:column_by_name(2), L{2,4,6,8,10,12,14,16,18,20})
    -+
    -+-- the field list may contain expressions or even constants
    -+local q = dat:select '$3,2*$4 where $1 == 8'
    -+asserteq(T(q()),T(24,64))
    -+
    -+dat,err = data.read(open [[
    -+1.0 0.1
    -+0.2 1.3
    -+]])
    -+
    -+if err then print(err) end
    -+
    -+-- if a method cannot be found, then we look up in array2d
    -+-- array2d.flatten(t) makes a 1D list out of a 2D array,
    -+-- and then List.minmax() gets the extrema.
    -+
    -+asserteq(T(dat:flatten():minmax()),T(0.1,1.3))
    -+
    -+local f = open [[
    -+Time Message
    -+1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00
    -+1266840760 closure data 0.000000 1972 1972 0
    -+1266840760 ++ 1266840760 EE 1
    -+1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00
    -+1266840764 closure data 0.000000 1972 1972 0
    -+1266840764 ++ 1266840764 EE 1
    -+1266840764 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00
    -+1266840768 duplicate?
    -+1266840768 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00
    -+1266840768 closure data 0.000000 1972 1972 0
    -+]]
    -+
    -+-- the convert option provides custom converters for each specified column.
    -+-- Here we convert the timestamps into Date objects and collect everything
    -+-- else into one field
    -+local Date = require 'pl.Date'
    -+
    -+local function date_convert (ds)
    -+    return Date(tonumber(ds))
    -+end
    -+
    -+local d = data.read(f,{convert={[1]=date_convert},last_field_collect=true})
    -+
    -+asserteq(#d[1],2)
    -+asserteq(d[2][1]:year(),2010)
    -+
    -+d = {{1,2,3},{10,20,30}}
    -+out = stringio.create()
    -+data.write(d,out,{'A','B','C'},',')
    -+asserteq(out:value(),
    -+[[
    -+A,B,C
    -+1,2,3
    -+10,20,30
    -+]])
    -+
    -+out = stringio.create()
    -+d.fieldnames = {'A','B','C'}
    -+data.write(d,out)
    -+
    -+asserteq(out:value(),
    -+[[
    -+A	B	C
    -+1	2	3
    -+10	20	30
    -+]])
    -+
    -+
    -+d = data.read(stringio.open 'One,Two\n1,\n,20\n',{csv=true})
    -+asserteq(d,{
    -+    {1,0},{0,20},
    -+    original_fieldnames={"One","Two"},fieldnames={"One","Two"},delim=","
    -+})
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/test-listcallbacks.lua.html b/extra/penlight/docs/examples/test-listcallbacks.lua.html -new file mode 100644 -index 0000000..b565bf2 ---- /dev/null -+++ b/extra/penlight/docs/examples/test-listcallbacks.lua.html -@@ -0,0 +1,138 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    test-listcallbacks.lua

    -+
    -+-- demonstrates how to use a list of callbacks
    -+local List = require 'pl.List'
    -+local utils = require 'pl.utils'
    -+local actions = List()
    -+local L = utils.string_lambda
    -+
    -+actions:append(function() print 'hello' end)
    -+actions:append(L '|| print "yay"')
    -+
    -+-- '()' is a shortcut for operator.call or function(x) return x() end
    -+actions:foreach '()'
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/test-pretty.lua.html b/extra/penlight/docs/examples/test-pretty.lua.html -new file mode 100644 -index 0000000..ff0a776 ---- /dev/null -+++ b/extra/penlight/docs/examples/test-pretty.lua.html -@@ -0,0 +1,140 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    test-pretty.lua

    -+
    -+local pretty = require 'pl.pretty'
    -+
    -+local tb = {
    -+    'one','two','three',{1,2,3},
    -+    alpha=1,beta=2,gamma=3,['&']=true,[0]=false,
    -+    _fred = {true,true},
    -+    s = [[
    -+hello dolly
    -+you're so fine
    -+]]
    -+}
    -+
    -+print(pretty.write(tb))
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/test-symbols.lua.html b/extra/penlight/docs/examples/test-symbols.lua.html -new file mode 100644 -index 0000000..fb4b7c1 ---- /dev/null -+++ b/extra/penlight/docs/examples/test-symbols.lua.html -@@ -0,0 +1,209 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    test-symbols.lua

    -+
    -+require 'pl'
    -+-- force us to look in the script's directory when requiring...
    -+app.require_here()
    -+require 'symbols'
    -+
    -+local MT = getmetatable(_1)
    -+
    -+add = MT.__add
    -+mul = MT.__mul
    -+pow = MT.__pow
    -+
    -+
    -+function testeq (e1,e2)
    -+    if not equals(e1,e2) then
    -+        print ('Not equal',repr(e1),repr(e2))
    -+    end
    -+end
    -+
    -+sin = register(math.sin,'sin')
    -+
    -+f = register(function(x,y,z) end)
    -+
    -+--[[
    -+testeq (_1,_1)
    -+testeq (_1+_2,_1+_2)
    -+testeq (_1 + 3*_2,_1 + 3*_2)
    -+testeq (_2+_1,_1+_2)
    -+testeq (sin(_1),sin(_1))
    -+testeq (1+f(10,20,'ok'),f(10,20,'ok')+1)
    -+--]]
    -+
    -+
    -+function testexpand (e)
    -+    print(repr(fold(expand(e)))) --fold
    -+end
    -+
    -+--[[
    -+testexpand (a*(a+1))
    -+
    -+testexpand ((x+2)*(b+1))
    -+]]--
    -+
    -+function testfold (e)
    -+    print(repr(fold(e)))
    -+end
    -+
    -+a,b,c,x,y = Var 'a,b,c,x,y'
    -+
    -+--~ testfold(_1 + _2)
    -+--~ testfold(add(10,20))
    -+--~ testfold(add(mul(2,_1),mul(3,_2)))
    -+--[[
    -+testfold(sin(a))
    -+e = a^(b+2)
    -+testfold(e)
    -+bindval(b,1)
    -+testfold(e)
    -+bindval(a,2)
    -+testfold(e)
    -+
    -+bindval(a)
    -+bindval(b)
    -+]]
    -+
    -+
    -+
    -+function testdiff (e)
    -+    balance(e)
    -+    e = diff(e,x)
    -+    balance(e)
    -+    print('+ ',e)
    -+    e = fold(e)
    -+    print('- ',e)
    -+end
    -+
    -+
    -+testdiff(x^2+1)
    -+testdiff(3*x^2)
    -+testdiff(x^2 + 2*x^3)
    -+testdiff(x^2 + 2*a*x^3 + x^4)
    -+testdiff(2*a*x^3)
    -+testdiff(x*x*x)
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testapp.lua.html b/extra/penlight/docs/examples/testapp.lua.html -new file mode 100644 -index 0000000..56ae5a3 ---- /dev/null -+++ b/extra/penlight/docs/examples/testapp.lua.html -@@ -0,0 +1,133 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testapp.lua

    -+
    -+-- shows how a script can get a private file path
    -+-- the output on my Windows machine is:
    -+-- C:\Documents and Settings\steve\.testapp\test.txt
    -+local app = require 'pl.app'
    -+print(app.appfile 'test.txt')
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.4.6 -+Last updated 2018-11-23 21:07:42 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testclone.lua.html b/extra/penlight/docs/examples/testclone.lua.html -new file mode 100644 -index 0000000..6f17069 ---- /dev/null -+++ b/extra/penlight/docs/examples/testclone.lua.html -@@ -0,0 +1,165 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testclone.lua

    -+
    -+--cloning a directory tree.
    -+local lfs = require 'lfs'
    -+local path = require 'pl.path'
    -+local dir = require 'pl.dir'
    -+
    -+local p1 = [[examples]]
    -+local p2 = [[copy/of/examples]]
    -+
    -+if not path.isfile 'examples/testclone.lua' then
    -+	return print 'please run this in the penlight folder (below examples)'
    -+end
    -+
    -+-- make a copy of the examples folder
    -+dir.clonetree(p1,p2,dir.copyfile)
    -+
    -+assert(path.isdir 'copy')
    -+
    -+print '---'
    -+local t = os.time()
    -+print(lfs.touch('examples/testclone.lua',t,t+10))
    -+
    -+-- this should only update this file
    -+dir.clonetree(p1,p2,
    -+function(f1,f2)
    -+  local t1 = path.getmtime(f1)
    -+  local t2 = path.getmtime(f2)
    -+  --print(f1,t1,f2,t2)
    -+  if t1 > t2 then
    -+	dir.copyfile(f1,f2)
    -+	print(f1,f2,t1,t2)
    -+  end
    -+  return true
    -+end)
    -+
    -+-- and get rid of the whole copy directory, with subdirs
    -+dir.rmtree 'copy'
    -+
    -+assert(not path.exists 'copy')
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testconfig.lua.html b/extra/penlight/docs/examples/testconfig.lua.html -new file mode 100644 -index 0000000..ca9a336 ---- /dev/null -+++ b/extra/penlight/docs/examples/testconfig.lua.html -@@ -0,0 +1,176 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testconfig.lua

    -+
    -+local stringio = require 'pl.stringio'
    -+local config = require 'pl.config'
    -+
    -+local function dump(t,indent)
    -+    if type(t) == 'table' then
    -+        io.write(indent,'{\n')
    -+        local newindent = indent..'  '
    -+        for k,v in pairs(t) do
    -+            io.write(newindent,k,'=')
    -+            dump(v,indent)
    -+            io.write('\n')
    -+        end
    -+        io.write(newindent,'},\n')
    -+    else
    -+        io.write(indent,t,'(',type(t),')')
    -+    end
    -+end
    -+
    -+
    -+local function testconfig(test)
    -+    local f = stringio.open(test)
    -+    local c = config.read(f)
    -+    f:close()
    -+    dump(c,'  ')
    -+    print '-----'
    -+end
    -+
    -+testconfig [[
    -+ ; comment 2 (an ini file)
    -+[section!]
    -+bonzo.dog=20,30
    -+config_parm=here we go again
    -+depth = 2
    -+[another]
    -+felix="cat"
    -+]]
    -+
    -+testconfig [[
    -+# this is a more Unix-y config file
    -+fred = 1
    -+alice = 2
    -+home = /bonzo/dog/etc
    -+]]
    -+
    -+testconfig [[
    -+# this is just a set of comma-separated values
    -+1000,444,222
    -+44,555,224
    -+]]
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testglobal.lua.html b/extra/penlight/docs/examples/testglobal.lua.html -new file mode 100644 -index 0000000..0f8c191 ---- /dev/null -+++ b/extra/penlight/docs/examples/testglobal.lua.html -@@ -0,0 +1,153 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testglobal.lua

    -+
    -+-- very simple lexer program which looks at all identifiers in a Lua
    -+-- file and checks whether they're in the global namespace.
    -+-- At the end, we dump out the result of count_map, which will give us
    -+-- unique identifiers with their usage count.
    -+-- (an example of a program which itself needs to be careful about what
    -+-- goes into the global namespace)
    -+
    -+local utils = require 'pl.utils'
    -+local file = require 'pl.file'
    -+local lexer = require 'pl.lexer'
    -+local List = require 'pl.List'
    -+local pretty = require 'pl.pretty'
    -+local seq = require 'pl.seq'
    -+local path = require 'pl.path'
    -+
    -+utils.on_error 'quit'
    -+
    -+local txt = file.read(arg[1] or path.normpath('examples/testglobal.lua'))
    -+local globals = List()
    -+for t,v in lexer.lua(txt) do
    -+	if t == 'iden' and rawget(_G,v) then
    -+		globals:append(v)
    -+	end
    -+end
    -+
    -+pretty.dump(seq.count_map(globals))
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testinputfields.lua.html b/extra/penlight/docs/examples/testinputfields.lua.html -new file mode 100644 -index 0000000..99158d2 ---- /dev/null -+++ b/extra/penlight/docs/examples/testinputfields.lua.html -@@ -0,0 +1,140 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testinputfields.lua

    -+
    -+local input = require 'pl.input'
    -+local sum = 0.0
    -+local count = 0
    -+local text = [[
    -+    981124001	2.0	18988.4	10047.1	4149.7
    -+    981125001	0.8	19104.0	9970.4	5088.7
    -+    981127003	0.5	19012.5	9946.9	3831.2
    -+]]
    -+for id,magn,x in input.fields(3,' ',text) do
    -+  sum = sum + x
    -+  count = count + 1
    -+end
    -+print('average x coord is ',sum/count)
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testinputfields2.lua.html b/extra/penlight/docs/examples/testinputfields2.lua.html -new file mode 100644 -index 0000000..192dae4 ---- /dev/null -+++ b/extra/penlight/docs/examples/testinputfields2.lua.html -@@ -0,0 +1,136 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testinputfields2.lua

    -+
    -+local input = require 'pl.input'
    -+local seq = require 'pl.seq'
    -+local text = [[
    -+    981124001	2.0	18988.4	10047.1	4149.7
    -+    981125001	0.8	19104.0	9970.4	5088.7
    -+    981127003	0.5	19012.5	9946.9	3831.2
    -+]]
    -+local sum,count = seq.sum(input.fields ({3},' ',text))
    -+print(sum/count)
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/testxml.lua.html b/extra/penlight/docs/examples/testxml.lua.html -new file mode 100644 -index 0000000..58e7bcf ---- /dev/null -+++ b/extra/penlight/docs/examples/testxml.lua.html -@@ -0,0 +1,209 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    testxml.lua

    -+
    -+-- an example showing 'pl.lexer' doing some serious work.
    -+-- The resulting Lua table is in the same LOM format used by luaexpat.
    -+-- This is (clearly) not a professional XML parser, so don't use it
    -+-- on your homework!
    -+
    -+local lexer = require 'pl.lexer'
    -+local pretty = require 'pl.pretty'
    -+
    -+local append = table.insert
    -+local skipws,expecting = lexer.skipws,lexer.expecting
    -+
    -+local function parse_element (tok,tag)
    -+	local tbl,t,v,attrib
    -+	tbl = {}
    -+	tbl.tag = tag  -- LOM 'tag' is the element tag
    -+	t,v = skipws(tok)
    -+	while v ~= '/' and v ~= '>' do
    -+		if t ~= 'iden' then error('expecting attribute identifier') end
    -+		attrib = v
    -+		expecting(tok,'=')
    -+		v = expecting(tok,'string')
    -+		-- LOM: 'attr' subtable contains attrib/value pairs and an ordered list of attribs
    -+		if not tbl.attr then tbl.attr = {} end
    -+		tbl.attr[attrib] = v
    -+		append(tbl.attr,attrib)
    -+		t,v = skipws(tok)
    -+	end
    -+	if v == '/' then
    -+		expecting(tok,'>')
    -+		return tbl
    -+	end
    -+	-- pick up element data
    -+	t,v = tok()
    -+	while true do
    -+		if t == '<' then
    -+			t,v = skipws(tok)
    -+			if t == '/' then -- element end tag
    -+				t,v = tok()
    -+				if t == '>' then return tbl end
    -+				if t == 'iden' and v == tag then
    -+					if tok() == '>' then return tbl end
    -+				end
    -+				error('expecting end tag '..tag)
    -+			else
    -+				append(tbl,parse_element(tok,v)) -- LOM: child elements added to table
    -+				t,v = skipws(tok)
    -+			end
    -+		else
    -+			append(tbl,v) -- LOM: text added to table
    -+			t,v = skipws(tok)
    -+		end
    -+	end
    -+end
    -+
    -+local function parse_xml (tok)
    -+	local t = skipws(tok)
    -+	local v
    -+	while t == '<' do
    -+		t,v = tok()
    -+		if t == '?' or t == '!' then
    -+			-- skip meta stuff and commentary
    -+			repeat t = tok() until t == '>'
    -+			t = expecting(tok,'<')
    -+		else
    -+			return parse_element(tok,v)
    -+		end
    -+	end
    -+end
    -+
    -+local s = [[
    -+<?xml version="1.0" encoding="UTF-8"?>
    -+<sensor name="closure-meter-2" id="7D7D0600006F0D00" loc="100,100,0" device="closure-meter" init="true">
    -+<detector name="closure-meter" phenomenon="closure" units="mm" id="1"
    -+    vmin="0" vmax="5000" device="closure-meter" calib="0,0;5000,5000"
    -+    sampling_interval="25000" measurement_interval="600000"
    -+/>
    -+</sensor>
    -+]]
    -+
    -+local tok = lexer.scan(s,nil,{space=false},{string=true})
    -+local res = parse_xml(tok)
    -+print(pretty.write(res))
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/examples/which.lua.html b/extra/penlight/docs/examples/which.lua.html -new file mode 100644 -index 0000000..6d07770 ---- /dev/null -+++ b/extra/penlight/docs/examples/which.lua.html -@@ -0,0 +1,155 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    which.lua

    -+
    -+-- a simple implementation of the which command. This looks for
    -+-- the given file on the path. On windows, it will assume an extension
    -+-- of .exe if no extension is given.
    -+local List = require 'pl.List'
    -+local path = require 'pl.path'
    -+local app = require 'pl.app'
    -+
    -+local pathl = List.split(os.getenv 'PATH',path.dirsep)
    -+
    -+local function which (file)
    -+    local res = pathl:map(path.join,file)
    -+    res = res:filter(path.exists)
    -+    if res then return res[1] end
    -+end
    -+
    -+local _,lua = app.lua()
    -+local file = arg[1] or lua -- i.e. location of lua executable
    -+local try
    -+
    -+if not file then return print 'must provide a filename' end
    -+
    -+if path.extension(file) == '' and path.is_windows then
    -+    try = which(file..'.exe')
    -+else
    -+    try = which(file)
    -+end
    -+
    -+if try then print(try) else print 'cannot find on path' end
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/index.html b/extra/penlight/docs/index.html -new file mode 100644 -index 0000000..2a11195 ---- /dev/null -+++ b/extra/penlight/docs/index.html -@@ -0,0 +1,392 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Penlight Lua Libraries 1.14.0

    -+

    Penlight is a set of pure Lua libraries for making it easier to work with common tasks like iterating over directories, reading configuration files and the like. Provides functional operations on tables and sequences. Visit the GitHub project to review the code or file issues. Skip to the introduction.

    -+ -+

    Libraries

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    plEntry point for loading all PL libraries only on demand, into the global space.
    pl.appApplication support functions.
    pl.array2dOperations on two-dimensional arrays.
    pl.classProvides a reusable and convenient framework for creating classes in Lua.
    pl.compatLua 5.1/5.2/5.3 compatibility.
    pl.comprehensionList comprehensions implemented in Lua.
    pl.configReads configuration files into a Lua table.
    pl.dataReading and querying simple tabular data.
    pl.dirListing files in directories and creating/removing directory paths.
    pl.fileFile manipulation functions: reading, writing, moving and copying.
    pl.funcFunctional helpers like composition, binding and placeholder expressions.
    pl.import_intoPL loader, for loading all PL libraries, only on demand.
    pl.inputIterators for extracting words or numbers from an input source.
    pl.lappSimple command-line parsing using human-readable specification.
    pl.lexerLexical scanner for creating a sequence of tokens from text.
    pl.luabalancedExtract delimited Lua sequences from strings.
    pl.operatorLua operators available as functions.
    pl.pathPath manipulation and file queries.
    pl.permutePermutation operations.
    pl.prettyPretty-printing Lua tables.
    pl.seqManipulating iterators as sequences.
    pl.sipSimple Input Patterns (SIP).
    pl.strictChecks uses of undeclared global variables.
    pl.stringioReading and writing strings using file-like objects.
    pl.stringxPython-style extended string library.
    pl.tablexExtended operations on Lua tables.
    pl.templateA template preprocessor.
    pl.testUseful test utilities.
    pl.textText processing utilities.
    pl.typesDealing with Detailed Type Information
    pl.urlPython-style URL quoting library.
    pl.utilsGenerally useful routines.
    pl.xmlXML LOM Utilities.
    -+

    Classes

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pl.DateDate and Date Format classes.
    pl.ListPython-style list class.
    pl.MapA Map class.
    pl.MultiMapMultiMap, a Map which has multiple values per key.
    pl.OrderedMapOrderedMap, a map which preserves ordering.
    pl.SetA Set class.
    -+

    Manual

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    01-introduction.md
    02-arrays.md
    03-strings.md
    04-paths.md
    05-dates.md
    06-data.md
    07-functional.md
    08-additional.md
    09-discussion.md
    -+

    Examples

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    seesubst.lua
    sipscan.lua
    symbols.lua
    test-cmp.lua
    test-data.lua
    test-listcallbacks.lua
    test-pretty.lua
    test-symbols.lua
    testclone.lua
    testconfig.lua
    testglobal.lua
    testinputfields.lua
    testinputfields2.lua
    testxml.lua
    which.lua
    -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/ldoc_fixed.css b/extra/penlight/docs/ldoc_fixed.css -new file mode 100644 -index 0000000..d7c7a6e ---- /dev/null -+++ b/extra/penlight/docs/ldoc_fixed.css -@@ -0,0 +1,312 @@ -+/* BEGIN RESET -+ -+Copyright (c) 2010, Yahoo! Inc. All rights reserved. -+Code licensed under the BSD License: -+http://developer.yahoo.com/yui/license.html -+version: 2.8.2r1 -+*/ -+html { -+ color: #000; -+ background: #FFF; -+} -+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { -+ margin: 0; -+ padding: 0; -+} -+table { -+ border-collapse: collapse; -+ border-spacing: 0; -+} -+fieldset,img { -+ border: 0; -+} -+address,caption,cite,code,dfn,em,strong,th,var,optgroup { -+ font-style: inherit; -+ font-weight: inherit; -+} -+del,ins { -+ text-decoration: none; -+} -+li { -+ margin-left: 20px; -+} -+caption,th { -+ text-align: left; -+} -+h1,h2,h3,h4,h5,h6 { -+ font-size: 100%; -+ font-weight: bold; -+} -+q:before,q:after { -+ content: ''; -+} -+abbr,acronym { -+ border: 0; -+ font-variant: normal; -+} -+sup { -+ vertical-align: baseline; -+} -+sub { -+ vertical-align: baseline; -+} -+legend { -+ color: #000; -+} -+input,button,textarea,select,optgroup,option { -+ font-family: inherit; -+ font-size: inherit; -+ font-style: inherit; -+ font-weight: inherit; -+} -+input,button,textarea,select {*font-size:100%; -+} -+/* END RESET */ -+ -+body { -+ margin-left: 1em; -+ margin-right: 1em; -+ font-family: arial, helvetica, geneva, sans-serif; -+ background-color: #ffffff; margin: 0px; -+} -+ -+code, tt { font-family: monospace; font-size: 1.1em; } -+span.parameter { font-family:monospace; } -+span.parameter:after { content:":"; } -+span.types:before { content:"("; } -+span.types:after { content:")"; } -+.type { font-weight: bold; font-style:italic } -+ -+body, p, td, th { font-size: .95em; line-height: 1.2em;} -+ -+p, ul { margin: 10px 0 0 0px;} -+ -+strong { font-weight: bold;} -+ -+em { font-style: italic;} -+ -+h1 { -+ font-size: 1.5em; -+ margin: 0 0 20px 0; -+} -+h2, h3, h4 { margin: 15px 0 10px 0; } -+h2 { font-size: 1.25em; } -+h3 { font-size: 1.15em; } -+h4 { font-size: 1.06em; } -+ -+a:link { font-weight: bold; color: #004080; text-decoration: none; } -+a:visited { font-weight: bold; color: #006699; text-decoration: none; } -+a:link:hover { text-decoration: underline; } -+ -+hr { -+ color:#cccccc; -+ background: #00007f; -+ height: 1px; -+} -+ -+blockquote { margin-left: 3em; } -+ -+ul { list-style-type: disc; } -+ -+p.name { -+ font-family: "Andale Mono", monospace; -+ padding-top: 1em; -+} -+ -+pre { -+ background-color: rgb(245, 245, 245); -+ border: 1px solid #C0C0C0; /* silver */ -+ padding: 10px; -+ margin: 10px 0 10px 0; -+ overflow: auto; -+ font-family: "Andale Mono", monospace; -+} -+ -+pre.example { -+ font-size: .85em; -+} -+ -+table.index { border: 1px #00007f; } -+table.index td { text-align: left; vertical-align: top; } -+ -+#container { -+ margin-left: 1em; -+ margin-right: 1em; -+ background-color: #ffffff; -+} -+ -+#product { -+ text-align: center; -+ border-bottom: 1px solid #cccccc; -+ background-color: #ffffff; -+} -+ -+#product big { -+ font-size: 2em; -+} -+ -+#main { -+ background-color:#FFFFFF; // #f0f0f0; -+ border-left: 1px solid #cccccc; -+} -+ -+#navigation { -+ position: fixed; -+ top: 0; -+ left: 0; -+ float: left; -+ width: 14em; -+ vertical-align: top; -+ background-color:#FFFFFF; // #f0f0f0; -+ border-right: 2px solid #cccccc; -+ overflow: visible; -+ overflow-y: scroll; -+ height: 100%; -+ padding-left: 1em; -+} -+ -+#navigation h2 { -+ background-color:#FFFFFF;//:#e7e7e7; -+ font-size:1.1em; -+ color:#000000; -+ text-align: left; -+ padding:0.2em; -+ border-bottom:1px solid #dddddd; -+} -+ -+#navigation ul -+{ -+ font-size:1em; -+ list-style-type: none; -+ margin: 1px 1px 10px 1px; -+} -+ -+#navigation li { -+ text-indent: -1em; -+ display: block; -+ margin: 3px 0px 0px 22px; -+} -+ -+#navigation li li a { -+ margin: 0px 3px 0px -1em; -+} -+ -+#content { -+ margin-left: 14em; -+ padding: 1em; -+ padding-left: 2em; -+ width: 700px; -+ border-left: 2px solid #cccccc; -+ // border-right: 2px solid #cccccc; -+ background-color: #ffffff; -+} -+ -+#about { -+ clear: both; -+ padding-left: 1em; -+ margin-left: 14em; // avoid the damn sidebar! -+ border-top: 2px solid #cccccc; -+ border-left: 2px solid #cccccc; -+ background-color: #ffffff; -+} -+ -+@media print { -+ body { -+ font: 12pt "Times New Roman", "TimeNR", Times, serif; -+ } -+ a { font-weight: bold; color: #004080; text-decoration: underline; } -+ -+ #main { -+ background-color: #ffffff; -+ border-left: 0px; -+ } -+ -+ #container { -+ margin-left: 2%; -+ margin-right: 2%; -+ background-color: #ffffff; -+ } -+ -+ #content { -+ padding: 1em; -+ background-color: #ffffff; -+ } -+ -+ #navigation { -+ display: none; -+ } -+ pre.example { -+ font-family: "Andale Mono", monospace; -+ font-size: 10pt; -+ page-break-inside: avoid; -+ } -+} -+ -+table.module_list { -+ border-width: 1px; -+ border-style: solid; -+ border-color: #cccccc; -+ border-collapse: collapse; -+} -+table.module_list td { -+ border-width: 1px; -+ padding: 3px; -+ border-style: solid; -+ border-color: #cccccc; -+} -+table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } -+table.module_list td.summary { width: 100%; } -+ -+table.function_list { -+ border-width: 1px; -+ border-style: solid; -+ border-color: #cccccc; -+ border-collapse: collapse; -+} -+table.function_list td { -+ border-width: 1px; -+ padding: 3px; -+ border-style: solid; -+ border-color: #cccccc; -+} -+table.function_list td.name { background-color: #f6f6ff; ; min-width: 200px; } -+table.function_list td.summary { width: 100%; } -+ -+dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} -+dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} -+dl.table h3, dl.function h3 {font-size: .95em;} -+ -+ul.nowrap { -+ overflow:auto; -+ whitespace:nowrap; -+} -+ -+/* stop sublists from having initial vertical space */ -+ul ul { margin-top: 0px; } -+ol ul { margin-top: 0px; } -+ol ol { margin-top: 0px; } -+ul ol { margin-top: 0px; } -+ -+/* make the target distinct; helps when we're navigating to a function */ -+a:target + * { -+ background-color: #FF9; -+} -+ -+ -+/* styles for prettification of source */ -+pre .comment { color: #558817; } -+pre .constant { color: #a8660d; } -+pre .escape { color: #844631; } -+pre .keyword { color: #aa5050; font-weight: bold; } -+pre .library { color: #0e7c6b; } -+pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } -+pre .string { color: #8080ff; } -+pre .number { color: #f8660d; } -+pre .function-name { color: #60447f; } -+pre .operator { color: #2239a8; font-weight: bold; } -+pre .preprocessor, pre .prepro { color: #a33243; } -+pre .global { color: #800080; } -+pre .user-keyword { color: #800080; } -+pre .prompt { color: #558817; } -+pre .url { color: #272fc2; text-decoration: underline; } -+ -diff --git a/extra/penlight/docs/libraries/pl.Set.html b/extra/penlight/docs/libraries/pl.Set.html -new file mode 100644 -index 0000000..8eda41e ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.Set.html -@@ -0,0 +1,650 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.Set

    -+

    A Set class.

    -+

    -+ -+

    -+> Set = require 'pl.Set'
    -+> = Set{'one','two'} == Set{'two','one'}
    -+true
    -+> fruit = Set{'apple','banana','orange'}
    -+> = fruit['banana']
    -+true
    -+> = fruit['hazelnut']
    -+nil
    -+> colours = Set{'red','orange','green','blue'}
    -+> = fruit,colours
    -+[apple,orange,banana]   [blue,green,orange,red]
    -+> = fruit+colours
    -+[blue,green,apple,red,orange,banana]
    -+[orange]
    -+> more_fruits = fruit + 'apricot'
    -+> = fruit*colours
    -+ =  more_fruits, fruit
    -+banana,apricot,apple,orange]    [banana,apple,orange]
    -+
    -+ -+ -+

    Dependencies: pl.utils, pl.tablex, pl.class, pl.Map, (pl.List if __tostring is used)

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Set (t)create a set.
    values (self)get a list of the values in a set.
    map (self, fn, ...)map a function over the values of a set.
    union (self, set)union of two sets (also +).
    intersection (self, set)intersection of two sets (also *).
    difference (self, set)new set with elements in the set that are not in the other (also -).
    issubset (self, set)is the first set a subset of the second (also <)?.
    isempty (self)is the set empty?.
    isdisjoint (s1, s2)are the sets disjoint?
    len (s)size of this set (also # for 5.2).
    -+

    metamethods

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    __tostring ()string representation of a set.
    __add ()union of sets.
    __mul ()intersection of sets.
    __sub ()difference of sets.
    __pow ()symmetric difference of sets.
    __lt ()first set subset of second?
    __len ()cardinality of set (5.2).
    __eq (s1, s2)equality between sets.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ Set (t) -+
    -+
    -+ create a set.
    -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ may be a Set, Map or list-like table. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ values (self) -+
    -+
    -+ get a list of the values in a set. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ map (self, fn, ...) -+
    -+
    -+ map a function over the values of a set. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • fn -+ a function -+
    • -+
    • ... -+ extra arguments to pass to the function. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ union (self, set) -+
    -+
    -+ union of two sets (also +). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ intersection (self, set) -+
    -+
    -+ intersection of two sets (also *). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      > s = Set{10,20,30}
      -+> t = Set{20,30,40}
      -+> = t
      -+[20,30,40]
      -+> = Set.intersection(s,t)
      -+[30,20]
      -+> = s*t
      -+[30,20]
      -+
    -+ -+
    -+
    -+ -+ difference (self, set) -+
    -+
    -+ new set with elements in the set that are not in the other (also -). -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ issubset (self, set) -+
    -+
    -+ is the first set a subset of the second (also <)?. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    • set -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ isempty (self) -+
    -+
    -+ is the set empty?. -+ -+ -+

    Parameters:

    -+
      -+
    • self -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ isdisjoint (s1, s2) -+
    -+
    -+ are the sets disjoint? (no elements in common). -+ Uses naive definition, i.e. that intersection is empty -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ a Set -+
    • -+
    • s2 -+ another set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ len (s) -+
    -+
    -+ size of this set (also # for 5.2). -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ a Set -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ size -+
    -+ -+ -+ -+ -+
    -+
    -+

    metamethods

    -+ -+
    -+
    -+ -+ __tostring () -+
    -+
    -+ string representation of a set. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __add () -+
    -+
    -+ union of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __mul () -+
    -+
    -+ intersection of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __sub () -+
    -+
    -+ difference of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __pow () -+
    -+
    -+ symmetric difference of sets. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __lt () -+
    -+
    -+ first set subset of second? -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __len () -+
    -+
    -+ cardinality of set (5.2). -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ __eq (s1, s2) -+
    -+
    -+ equality between sets. -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ -+
    • -+
    • s2 -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.4.6 -+Last updated 2018-11-23 21:07:42 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.app.html b/extra/penlight/docs/libraries/pl.app.html -new file mode 100644 -index 0000000..4201d44 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.app.html -@@ -0,0 +1,401 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.app

    -+

    Application support functions.

    -+

    See the Guide

    -+ -+

    Dependencies: pl.utils, pl.path

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    script_name ()return the name of the current script running.
    require_here (base, nofollow)prefixes the current script's path to the Lua module path.
    appfile (file)return a suitable path for files private to this application.
    platform ()return string indicating operating system.
    lua ()return the full command-line used to invoke this script.
    parse_args (args, flags_with_values, flags_valid)parse command-line arguments into flags and parameters.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ script_name () -+
    -+
    -+ return the name of the current script running. -+ The name will be the name as passed on the command line -+ -+ -+ -+

    Returns:

    -+
      -+ -+ string filename -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ require_here (base, nofollow) -+
    -+
    -+ prefixes the current script's path to the Lua module path. -+ Applies to both the source and the binary module paths. It makes it easy for -+ the main file of a multi-file program to access its modules in the same directory. -+ base allows these modules to be put in a specified subdirectory, to allow for -+ cleaner deployment and resolve potential conflicts between a script name and its -+ library directory.

    -+ -+

    Note: the path is prefixed, so it is searched first when requiring modules. -+ -+ -+

    Parameters:

    -+
      -+
    • base -+ string -+ optional base directory (absolute, or relative path). -+
    • -+
    • nofollow -+ boolean -+ always use the invocation's directory, even if the invoked file is a symlink -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ the current script's path with a trailing slash -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ appfile (file) -+
    -+
    -+ return a suitable path for files private to this application. -+ These will look like '~/.SNAME/file', with '~' as with expanduser and -+ SNAME is the name of the script without .lua extension. -+ If the directory does not exist, it will be created. -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ string -+ a filename (w/out path) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a full pathname, or nil
    2. -+
    3. -+ cannot create directory error
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      -- when run from a script called 'testapp' (on Windows):
      -+local app = require 'pl.app'
      -+print(app.appfile 'test.txt')
      -+-- C:\Documents and Settings\steve\.testapp\test.txt
      -+
    -+ -+
    -+
    -+ -+ platform () -+
    -+
    -+ return string indicating operating system. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ 'Windows','OSX' or whatever uname returns (e.g. 'Linux') -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ lua () -+
    -+
    -+ return the full command-line used to invoke this script. -+ It will not include the scriptname itself, see app.script_name. -+ -+ -+ -+

    Returns:

    -+
      -+
    1. -+ command-line
    2. -+
    3. -+ name of Lua program used
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      -- execute:  lua -lluacov -e 'print(_VERSION)' myscript.lua
      -+
      -+-- myscript.lua
      -+print(require("pl.app").lua())  --> "lua -lluacov -e 'print(_VERSION)'", "lua"
      -+
    -+ -+
    -+
    -+ -+ parse_args (args, flags_with_values, flags_valid) -+
    -+
    -+ parse command-line arguments into flags and parameters. -+ Understands GNU-style command-line flags; short (-f) and long (--flag).

    -+ -+

    These may be given a value with either '=' or ':' (-k:2,--alpha=3.2,-n2), -+ a number value can be given without a space. If the flag is marked -+ as having a value, then a space-separated value is also accepted (-i hello), -+ see the flags_with_values argument).

    -+ -+

    Multiple short args can be combined like so: ( -abcd).

    -+ -+

    When specifying the flags_valid parameter, its contents can also contain -+ aliases, to convert short/long flags to the same output name. See the -+ example below.

    -+ -+

    Note: if a flag is repeated, the last value wins. -+ -+ -+

    Parameters:

    -+
      -+
    • args -+ {string} -+ an array of strings (default is the global arg) -+
    • -+
    • flags_with_values -+ table -+ any flags that take values, either list or hash -+ table e.g. { out=true } or { "out" }. -+
    • -+
    • flags_valid -+ table -+ (optional) flags that are valid, either list or hashtable. -+ If not given, everything -+ will be accepted(everything in flags_with_values will automatically be allowed) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a table of flags (flag=value pairs)
    2. -+
    3. -+ an array of parameters
    4. -+
    -+ -+

    Raises:

    -+ if args is nil, then the global args must be available! -+ -+ -+

    Usage:

    -+
      -+
      -- Simple form:
      -+local flags, params = app.parse_args(nil,
      -+     { "hello", "world" },  -- list of flags taking values
      -+     { "l", "a", "b"})      -- list of allowed flags (value ones will be added)
      -+
      -+-- More complex example using aliases:
      -+local valid = {
      -+    long = "l",           -- if 'l' is specified, it is reported as 'long'
      -+    new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new'
      -+}
      -+local values = {
      -+    "value",   -- will automatically be added to the allowed set of flags
      -+    "new",     -- will mark 'n' and 'old' as requiring a value as well
      -+}
      -+local flags, params = app.parse_args(nil, values, valid)
      -+
      -+-- command:  myapp.lua -l --old:hello --value world param1 param2
      -+-- will yield:
      -+flags = {
      -+    long = true,     -- input from 'l'
      -+    new = "hello",   -- input from 'old'
      -+    value = "world", -- allowed because it was in 'values', note: space separated!
      -+}
      -+params = {
      -+    [1] = "param1"
      -+    [2] = "param2"
      -+}
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.array2d.html b/extra/penlight/docs/libraries/pl.array2d.html -new file mode 100644 -index 0000000..c798f4d ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.array2d.html -@@ -0,0 +1,1368 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.array2d

    -+

    Operations on two-dimensional arrays.

    -+

    See The Guide

    -+ -+

    The size of the arrays is determined by using the length operator # hence -+ the module is not nil safe, and the usual precautions apply.

    -+ -+

    Note: all functions taking i1,j1,i2,j2 as arguments will normalize the -+ arguments using default_range.

    -+ -+

    Dependencies: pl.utils, pl.tablex, pl.types

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    size (a)return the row and column size.
    column (a, j)extract a column from the 2D array.
    row (a, i)extract a row from the 2D array.
    map (f, a, arg)map a function over a 2D array
    reduce_rows (f, a)reduce the rows using a function.
    reduce_cols (f, a)reduce the columns using a function.
    reduce2 (opc, opr, a)reduce a 2D array into a scalar, using two operations.
    map2 (f, ad, bd, a, b, arg)map a function over two arrays.
    product (f, t1, t2)cartesian product of two 1d arrays.
    flatten (t)flatten a 2D array.
    reshape (t, nrows, co)reshape a 2D array.
    transpose (t)transpose a 2D array.
    swap_rows (t, i1, i2)swap two rows of an array.
    swap_cols (t, j1, j2)swap two columns of an array.
    extract_rows (t, ridx)extract the specified rows.
    extract_cols (t, cidx)extract the specified columns.
    remove_row (t, i)remove a row from an array.
    remove_col (t, j)remove a column from an array.
    parse_range (s)parse a spreadsheet range or cell.
    range (...)get a slice of a 2D array.
    default_range (t[, i1=1[, j1=1[, i2=N[, j2=M]]]])normalizes coordinates to valid positive entries and defaults.
    slice (t[, i1=1[, j1=1[, i2=N[, j2=M]]]])get a slice of a 2D array.
    set (t, value[, i1=1[, j1=1[, i2=N[, j2=M]]]])set a specified range of an array to a value.
    write (t, f, fmt[, i1=1[, j1=1[, i2=N[, j2=M]]]])write a 2D array to a file.
    forall (t, row_op, end_row_op[, i1=1[, j1=1[, i2=N[, j2=M]]]])perform an operation for all values in a 2D array.
    move (dest, di, dj, src[, i1=1[, j1=1[, i2=N[, j2=M]]]])move a block from the destination to the source.
    iter (a, indices[, i1=1[, j1=1[, i2=N[, j2=M]]]])iterate over all elements in a 2D array, with optional indices.
    columns (a)iterate over all columns.
    rows (a)iterate over all rows.
    new (rows, cols, val)new array of specified dimensions
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ size (a) -+
    -+
    -+ return the row and column size. -+ Size is calculated using the Lua length operator #, so usual precautions -+ regarding nil values apply. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ a 2d array -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ int -+ number of rows (#a)
    2. -+
    3. -+ int -+ number of cols (#a[1])
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ column (a, j) -+
    -+
    -+ extract a column from the 2D array. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ 2d array -+
    • -+
    • j -+ column index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 1d array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ row (a, i) -+
    -+
    -+ extract a row from the 2D array. -+ Added in line with column, for read-only purposes directly -+ accessing a[i] is more performant. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ 2d array -+
    • -+
    • i -+ row index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 1d array (copy of the row) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ map (f, a, arg) -+
    -+
    -+ map a function over a 2D array -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ a function of at least one argument -+
    • -+
    • a -+ array -+ 2d array -+
    • -+
    • arg -+ an optional extra argument to be passed to the function. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 2d array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ reduce_rows (f, a) -+
    -+
    -+ reduce the rows using a function. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ a binary function -+
    • -+
    • a -+ array -+ 2d array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 1d array -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ reduce_cols (f, a) -+
    -+
    -+ reduce the columns using a function. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ a binary function -+
    • -+
    • a -+ array -+ 2d array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 1d array -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ reduce2 (opc, opr, a) -+
    -+
    -+ reduce a 2D array into a scalar, using two operations. -+ -+ -+

    Parameters:

    -+
      -+
    • opc -+ function -+ operation to reduce the final result -+
    • -+
    • opr -+ function -+ operation to reduce the rows -+
    • -+
    • a -+ 2D array -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ map2 (f, ad, bd, a, b, arg) -+
    -+
    -+ map a function over two arrays. -+ They can be both or either 2D arrays -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ function of at least two arguments -+
    • -+
    • ad -+ integer -+ order of first array (1 if a is a list/array, 2 if it is a 2d array) -+
    • -+
    • bd -+ integer -+ order of second array (1 if b is a list/array, 2 if it is a 2d array) -+
    • -+
    • a -+ table -+ 1d or 2d array -+
    • -+
    • b -+ table -+ 1d or 2d array -+
    • -+
    • arg -+ optional extra argument to pass to function -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 2D array, unless both arrays are 1D -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ product (f, t1, t2) -+
    -+
    -+ cartesian product of two 1d arrays. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ a function of 2 arguments -+
    • -+
    • t1 -+ array -+ a 1d table -+
    • -+
    • t2 -+ array -+ a 1d table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ 2d table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
      -+
    -+ -+
    -+
    -+ -+ flatten (t) -+
    -+
    -+ flatten a 2D array. -+ (this goes over columns first.) -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2d table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a 1d table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
      -+
    -+ -+
    -+
    -+ -+ reshape (t, nrows, co) -+
    -+
    -+ reshape a 2D array. Reshape the array by specifying a new nr of rows. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2d array -+
    • -+
    • nrows -+ integer -+ new number of rows -+
    • -+
    • co -+ boolean -+ use column-order (Fortran-style) (default false) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new 2d array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ transpose (t) -+
    -+
    -+ transpose a 2D array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2d array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new 2d array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ swap_rows (t, i1, i2) -+
    -+
    -+ swap two rows of an array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2d array -+
    • -+
    • i1 -+ integer -+ a row index -+
    • -+
    • i2 -+ integer -+ a row index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ t (same, modified 2d array) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ swap_cols (t, j1, j2) -+
    -+
    -+ swap two columns of an array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2d array -+
    • -+
    • j1 -+ integer -+ a column index -+
    • -+
    • j2 -+ integer -+ a column index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ t (same, modified 2d array) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ extract_rows (t, ridx) -+
    -+
    -+ extract the specified rows. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2d array -+
    • -+
    • ridx -+ {int} -+ a table of row indices -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new 2d array with the extracted rows -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ extract_cols (t, cidx) -+
    -+
    -+ extract the specified columns. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2d array -+
    • -+
    • cidx -+ {int} -+ a table of column indices -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new 2d array with the extracted columns -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ remove_row (t, i) -+
    -+
    -+ remove a row from an array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2d array -+
    • -+
    • i -+ integer -+ a row index -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ remove_col (t, j) -+
    -+
    -+ remove a column from an array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2d array -+
    • -+
    • j -+ integer -+ a column index -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ parse_range (s) -+
    -+
    -+ parse a spreadsheet range or cell. -+ The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for -+ single cells as 'A1' or 'R1C1'. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a range (case insensitive). -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ int -+ start row
    2. -+
    3. -+ int -+ start col
    4. -+
    5. -+ int -+ end row (or nil if the range was a single cell)
    6. -+
    7. -+ int -+ end col (or nil if the range was a single cell)
    8. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ range (...) -+
    -+
    -+ get a slice of a 2D array. -+ Same as slice. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ -+ -+ -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ default_range (t[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ normalizes coordinates to valid positive entries and defaults. -+ Negative indices will be counted from the end, too low, or too high -+ will be limited by the array sizes. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2D array -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ i1, j1, i2, j2 -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ slice (t[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ get a slice of a 2D array. Note that if the specified range has -+ a 1D result, the rank of the result will be 1. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2D array -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an array, 2D in general but 1D in special cases. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ set (t, value[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ set a specified range of an array to a value. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2D array -+
    • -+
    • value -+ the value (may be a function, called as val(i,j)) -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ write (t, f, fmt[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ write a 2D array to a file. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a 2D array -+
    • -+
    • f -+ a file object (default stdout) -+
    • -+
    • fmt -+ string -+ a format string (default is just to use tostring) -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ forall (t, row_op, end_row_op[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ perform an operation for all values in a 2D array. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ 2D array -+
    • -+
    • row_op -+ function -+ function to call on each value; row_op(row,j) -+
    • -+
    • end_row_op -+ function -+ function to call at end of each row; end_row_op(i) -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ move (dest, di, dj, src[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ move a block from the destination to the source. -+ -+ -+

    Parameters:

    -+
      -+
    • dest -+ array -+ a 2D array -+
    • -+
    • di -+ integer -+ start row in dest -+
    • -+
    • dj -+ integer -+ start col in dest -+
    • -+
    • src -+ array -+ a 2D array -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ iter (a, indices[, i1=1[, j1=1[, i2=N[, j2=M]]]]) -+
    -+
    -+ iterate over all elements in a 2D array, with optional indices. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ 2D array -+
    • -+
    • indices -+ boolean -+ with indices (default false) -+
    • -+
    • i1 -+ int or string -+ start row or spreadsheet range passed to parse_range -+ (default 1) -+
    • -+
    • j1 -+ int -+ start col -+ (default 1) -+
    • -+
    • i2 -+ int -+ end row -+ (default N) -+
    • -+
    • j2 -+ int -+ end col -+ (default M) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ either value or i,j,value depending on the value of indices -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ columns (a) -+
    -+
    -+ iterate over all columns. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ a 2D array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ column, column-index -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ rows (a) -+
    -+
    -+ iterate over all rows. -+ Returns a copy of the row, for read-only purposes directly iterating -+ is more performant; ipairs(a) -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ array -+ a 2D array -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ row, row-index -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ new (rows, cols, val) -+
    -+
    -+ new array of specified dimensions -+ -+ -+

    Parameters:

    -+
      -+
    • rows -+ integer -+ number of rows -+
    • -+
    • cols -+ integer -+ number of cols -+
    • -+
    • val -+ initial value; if it's a function then use val(i,j) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ new 2d array -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.class.html b/extra/penlight/docs/libraries/pl.class.html -new file mode 100644 -index 0000000..cd0f014 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.class.html -@@ -0,0 +1,332 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.class

    -+

    Provides a reusable and convenient framework for creating classes in Lua.

    -+

    Two possible notations:

    -+ -+ -+
    -+B = class(A)
    -+class.B(A)
    -+
    -+ -+

    The latter form creates a named class within the current environment. Note -+ that this implicitly brings in pl.utils as a dependency.

    -+ -+

    See the Guide for further discussion

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    _init (...)initializes an instance upon creation.
    instance:is_a (some_class)checks whether an instance is derived from some class.
    some_class:class_of (some_instance)checks whether an instance is derived from some class.
    some_class:cast (some_instance)cast an object to another class.
    class (base, c_arg, c)create a new class, derived from a given base class.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ _init (...) -+
    -+
    -+ initializes an instance upon creation. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ parameters passed to the constructor -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      local Cat = class()
      -+function Cat:_init(name)
      -+  --self:super(name)   -- call the ancestor initializer if needed
      -+  self.name = name
      -+end
      -+
      -+local pussycat = Cat("pussycat")
      -+print(pussycat.name)  --> pussycat
      -+
    -+ -+
    -+
    -+ -+ instance:is_a (some_class) -+
    -+
    -+ checks whether an instance is derived from some class. -+ Works the other way around as class_of. It has two ways of using; -+ 1) call with a class to check against, 2) call without params. -+ -+ -+

    Parameters:

    -+
      -+
    • some_class -+ class to check against, or nil to return the class -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if instance is derived from some_class, or if some_class == nil then -+ it returns the class table of the instance -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local pussycat = Lion()  -- assuming Lion derives from Cat
      -+if pussycat:is_a(Cat) then
      -+  -- it's true, it is a Lion, but also a Cat
      -+end
      -+
      -+if pussycat:is_a() == Lion then
      -+  -- It's true
      -+end
      -+
    -+ -+
    -+
    -+ -+ some_class:class_of (some_instance) -+
    -+
    -+ checks whether an instance is derived from some class. -+ Works the other way around as is_a. -+ -+ -+

    Parameters:

    -+
      -+
    • some_instance -+ instance to check against -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if some_instance is derived from some_class -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local pussycat = Lion()  -- assuming Lion derives from Cat
      -+if Cat:class_of(pussycat) then
      -+  -- it's true
      -+end
      -+
    -+ -+
    -+
    -+ -+ some_class:cast (some_instance) -+
    -+
    -+ cast an object to another class. -+ It is not clever (or safe!) so use carefully. -+ -+ -+

    Parameters:

    -+
      -+
    • some_instance -+ the object to be changed -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ class (base, c_arg, c) -+
    -+
    -+ create a new class, derived from a given base class. -+ Supporting two class creation syntaxes: -+ either Name = class(base) or class.Name(base). -+ The first form returns the class directly and does not set its _name. -+ The second form creates a variable Name in the current environment set -+ to the class, and also sets _name. -+ -+ -+

    Parameters:

    -+
      -+
    • base -+ optional base class -+
    • -+
    • c_arg -+ optional parameter to class constructor -+
    • -+
    • c -+ optional table to be used as class -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.compat.html b/extra/penlight/docs/libraries/pl.compat.html -new file mode 100644 -index 0000000..fc83841 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.compat.html -@@ -0,0 +1,579 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.compat

    -+

    Lua 5.1/5.2/5.3 compatibility.

    -+

    Injects table.pack, table.unpack, and package.searchpath in the global -+ environment, to make sure they are available for Lua 5.1 and LuaJIT.

    -+ -+

    All other functions are exported as usual in the returned module table.

    -+ -+

    NOTE: everything in this module is also available in pl.utils.

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    execute (cmd)execute a shell command, in a compatible and platform independent way.
    load (ld[, source[, mode[, env]]])Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way).
    getfenv (f)Get environment of a function (in a Lua 5.1 compatible way).
    setfenv (f, env)Set environment of a function (in a Lua 5.1 compatible way).
    -+

    Fields

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    lua51boolean flag this is Lua 5.1 (or LuaJIT).
    jitboolean flag this is LuaJIT.
    jit52boolean flag this is LuaJIT with 5.2 compatibility compiled in.
    dir_separatorthe directory separator character for the current platform.
    is_windowsboolean flag this is a Windows platform.
    -+

    Global exported functions (for Lua 5.1 & LuaJIT)

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    table.pack (...)pack an argument list into a table.
    table.unpack (t[, i[, j]])unpack a table and return the elements.
    package.searchpath (name, path[, sep[, rep]])return the full path where a file name would be matched.
    -+

    Global exported functions (for Lua < 5.4)

    -+ -+ -+ -+ -+ -+
    warn (...)raise a warning message.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ execute (cmd) -+
    -+
    -+ execute a shell command, in a compatible and platform independent way. -+ This is a compatibility function that returns the same for Lua 5.1 and -+ Lua 5.2+.

    -+ -+

    NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems -+ only use exitcodes 0-255, anything else is undefined.

    -+ -+

    NOTE2: In Lua 5.2 and 5.3 a Windows exitcode of -1 would not properly be -+ returned, this function will return it properly for all versions. -+ -+ -+

    Parameters:

    -+
      -+
    • cmd -+ a shell command -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ true if successful
    2. -+
    3. -+ actual return code
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ load (ld[, source[, mode[, env]]]) -+
    -+
    -+ Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way). -+ -+ -+

    Parameters:

    -+
      -+
    • ld -+ code string or loader -+
    • -+
    • source -+ name of chunk for errors -+ (optional) -+
    • -+
    • mode -+ 'b', 't' or 'bt' -+ (optional) -+
    • -+
    • env -+ environment to load the chunk in -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ getfenv (f) -+
    -+
    -+ Get environment of a function (in a Lua 5.1 compatible way). -+ Not 100% compatible, so with Lua 5.2 it may return nil for a function with no -+ global references! -+ Based on code by Sergey Rozhenko -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ a function or a call stack reference -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ setfenv (f, env) -+
    -+
    -+ Set environment of a function (in a Lua 5.1 compatible way). -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ a function or a call stack reference -+
    • -+
    • env -+ a table that becomes the new environment of f -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Fields

    -+ -+
    -+
    -+ -+ lua51 -+
    -+
    -+ boolean flag this is Lua 5.1 (or LuaJIT). -+ -+ -+
      -+
    • lua51 -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ jit -+
    -+
    -+ boolean flag this is LuaJIT. -+ -+ -+
      -+
    • jit -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ jit52 -+
    -+
    -+ boolean flag this is LuaJIT with 5.2 compatibility compiled in. -+ -+ -+
      -+
    • jit52 -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ dir_separator -+
    -+
    -+ the directory separator character for the current platform. -+ -+ -+
      -+
    • dir_separator -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ is_windows -+
    -+
    -+ boolean flag this is a Windows platform. -+ -+ -+
      -+
    • is_windows -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Global exported functions (for Lua 5.1 & LuaJIT)

    -+ -+
    -+
    -+ -+ table.pack (...) -+
    -+
    -+ pack an argument list into a table. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ any arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table with field n set to the length -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ table.unpack (t[, i[, j]]) -+
    -+
    -+ unpack a table and return the elements.

    -+ -+

    NOTE: this version does NOT honor the n field, and hence it is not nil-safe. -+ See utils.unpack for a version that is nil-safe. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table to unpack -+
    • -+
    • i -+ index from which to start unpacking, defaults to 1 -+ (optional) -+
    • -+
    • j -+ index of the last element to unpack, defaults to #t -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ multiple return values from the table -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ package.searchpath (name, path[, sep[, rep]]) -+
    -+
    -+ return the full path where a file name would be matched. -+ This function was introduced in Lua 5.2, so this compatibility version -+ will be injected in Lua 5.1 engines. -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ file name, possibly dotted -+
    • -+
    • path -+ string -+ a path-template in the same form as package.path or package.cpath -+
    • -+
    • sep -+ string -+ template separate character to be replaced by path separator. Default: "." -+ (optional) -+
    • -+
    • rep -+ string -+ the path separator to use, defaults to system separator. Default; "/" on Unixes, "\" on Windows. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ on success: path of the file
    2. -+
    3. -+ on failure: nil, error string listing paths tried
    4. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+

    Global exported functions (for Lua < 5.4)

    -+ -+
    -+
    -+ -+ warn (...) -+
    -+
    -+ raise a warning message. -+ This functions mimics the warn function added in Lua 5.4. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ any arguments -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.comprehension.html b/extra/penlight/docs/libraries/pl.comprehension.html -new file mode 100644 -index 0000000..4733ba8 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.comprehension.html -@@ -0,0 +1,165 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.comprehension

    -+

    List comprehensions implemented in Lua.

    -+

    See the wiki page

    -+ -+ -+
    -+local C= require 'pl.comprehension' . new()
    -+
    -+C ('x for x=1,10') ()
    -+==> {1,2,3,4,5,6,7,8,9,10}
    -+C 'x^2 for x=1,4' ()
    -+==> {1,4,9,16}
    -+C '{x,x^2} for x=1,4' ()
    -+==> {{1,1},{2,4},{3,9},{4,16}}
    -+C '2*x for x' {1,2,3}
    -+==> {2,4,6}
    -+dbl = C '2*x for x'
    -+dbl {10,20,30}
    -+==> {20,40,60}
    -+C 'x for x if x % 2 == 0' {1,2,3,4,5}
    -+==> {2,4}
    -+C '{x,y} for x = 1,2 for y = 1,2' ()
    -+==> {{1,1},{1,2},{2,1},{2,2}}
    -+C '{x,y} for x for y' ({1,2},{10,20})
    -+==> {{1,10},{1,20},{2,10},{2,20}}
    -+assert(C 'sum(x^2 for x)' {2,3,4} == 2^2+3^2+4^2)
    -+
    -+ -+

    (c) 2008 David Manura. Licensed under the same terms as Lua (MIT license).

    -+ -+

    Dependencies: pl.utils, pl.luabalanced

    -+ -+

    See the Guide

    -+ -+ -+ -+
    -+
    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.config.html b/extra/penlight/docs/libraries/pl.config.html -new file mode 100644 -index 0000000..70f4622 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.config.html -@@ -0,0 +1,259 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.config

    -+

    Reads configuration files into a Lua table.

    -+

    -+ -+

    Understands INI files, classic Unix config files, and simple -+ delimited columns of values. See the Guide

    -+ -+ -+
    -+# test.config
    -+# Read timeout in seconds
    -+read.timeout=10
    -+# Write timeout in seconds
    -+write.timeout=5
    -+#acceptable ports
    -+ports = 1002,1003,1004
    -+
    -+-- readconfig.lua
    -+local config = require 'config'
    -+local t = config.read 'test.config'
    -+print(pretty.write(t))
    -+
    -+### output #####
    -+{
    -+  ports = {
    -+    1002,
    -+    1003,
    -+    1004
    -+  },
    -+  write_timeout = 5,
    -+  read_timeout = 10
    -+}
    -+
    -+ -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    lines (file)like io.lines, but allows for lines to be continued with '\'.
    read (file[, cnfg])read a configuration file into a table
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ lines (file) -+
    -+
    -+ like io.lines, but allows for lines to be continued with '\'. -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ a file-like object (anything where read() returns the next line) or a filename. -+ Defaults to standard input. -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ an iterator over the lines, or nil
    2. -+
    3. -+ error 'not a file-like object' or 'file is nil'
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ read (file[, cnfg]) -+
    -+
    -+ read a configuration file into a table -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ either a file-like object or a string, which must be a filename -+
    • -+
    • cnfg -+ table -+ -+

      a configuration table that may contain these fields:

      -+ -+
        -+
      • smart try to deduce what kind of config file we have (default false)
      • -+
      • variabilize make names into valid Lua identifiers (default true)
      • -+
      • convert_numbers try to convert values into numbers (default true)
      • -+
      • trim_space ensure that there is no starting or trailing whitespace with values (default true)
      • -+
      • trim_quotes remove quotes from strings (default false)
      • -+
      • list_delim delimiter to use when separating columns (default ',')
      • -+
      • keysep separator between key and value pairs (default '=')
      • -+
      -+ -+ -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a table containing items, or nil
    2. -+
    3. -+ error message (same as config.lines
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.data.html b/extra/penlight/docs/libraries/pl.data.html -new file mode 100644 -index 0000000..3c770ac ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.data.html -@@ -0,0 +1,571 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.data

    -+

    Reading and querying simple tabular data.

    -+

    -+ -+ -+ -+

    -+data.read 'test.txt'
    -+==> {{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
    -+
    -+ -+

    Provides a way of creating basic SQL-like queries.

    -+ -+ -+
    -+require 'pl'
    -+local d = data.read('xyz.txt')
    -+local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
    -+for x,y,z in q do
    -+    print(x,y,z)
    -+end
    -+
    -+ -+

    See the Guide

    -+ -+

    Dependencies: pl.utils, pl.array2d (fallback methods)

    -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Data.column_by_name (name)return a particular column as a list of values (method).
    Data.select (condn)return a query iterator on this data (method).
    Data.select_row (condn)return a row iterator on this data (method).
    Data.copy_select (condn)return a new data object based on this query (method).
    Data.column_names ()return the field names of this data object (method).
    Data.write_row (f)write out a row (method).
    Data.write (f)write data out to file (method).
    read (file, cnfg)read a delimited file in a Lua table.
    write (data, file[, fieldnames[, delim='\t']])write 2D data to a file.
    new (d[, fieldnames])create a new dataset from a table of rows.
    query (data, condn, context, return_row)create a query iterator from a select string.
    filter (Q, infile, outfile, dont_fail)Filter input using a query.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ Data.column_by_name (name) -+
    -+
    -+ return a particular column as a list of values (method). -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ either name of column, or numerical index. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Data.select (condn) -+
    -+
    -+ return a query iterator on this data (method). -+ -+ -+

    Parameters:

    -+
      -+
    • condn -+ string -+ the query expression -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ Data.select_row (condn) -+
    -+
    -+ return a row iterator on this data (method). -+ -+ -+

    Parameters:

    -+
      -+
    • condn -+ string -+ the query expression -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Data.copy_select (condn) -+
    -+
    -+ return a new data object based on this query (method). -+ -+ -+

    Parameters:

    -+
      -+
    • condn -+ string -+ the query expression -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Data.column_names () -+
    -+
    -+ return the field names of this data object (method). -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Data.write_row (f) -+
    -+
    -+ write out a row (method). -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ file-like object -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Data.write (f) -+
    -+
    -+ write data out to file (method). -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ file-like object -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ read (file, cnfg) -+
    -+
    -+ read a delimited file in a Lua table. -+ By default, attempts to treat first line as separated list of fieldnames. -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ a filename or a file-like object -+
    • -+
    • cnfg parsing options -+
        -+
      • delim -+ string -+ a string pattern to split fields -+
      • -+
      • fieldnames -+ array -+ (i.e. don't read from first line) -+
      • -+
      • no_convert -+ boolean -+ (default is to try conversion on first data line) -+
      • -+
      • convert -+ table -+ table of custom conversion functions with column keys -+
      • -+
      • numfields -+ integer -+ indices of columns known to be numbers -+
      • -+
      • last_field_collect -+ boolean -+ only split as many fields as fieldnames. -+
      • -+
      • thousands_dot -+ integer -+ thousands separator in Excel CSV is '.' -+
      • -+
      • csv -+ boolean -+ fields may be double-quoted and contain commas; -+ Also, empty fields are considered to be equivalent to zero. -+
      • -+
      -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ data object, or nil
    2. -+
    3. -+ error message. May be a file error, 'not a file-like object' -+ or a conversion error
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ write (data, file[, fieldnames[, delim='\t']]) -+
    -+
    -+ write 2D data to a file. -+ Does not assume that the data has actually been -+ generated with new or read. -+ -+ -+

    Parameters:

    -+
      -+
    • data -+ 2D array -+
    • -+
    • file -+ filename or file-like object -+
    • -+
    • fieldnames -+ {string} -+ list of fields (optional) -+ (optional) -+
    • -+
    • delim -+ string -+ delimiter (default tab) -+ (default '\t') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or nil, error -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ new (d[, fieldnames]) -+
    -+
    -+ create a new dataset from a table of rows. -+ Can specify the fieldnames, else the table must have a field called -+ 'fieldnames', which is either a string of delimiter-separated names, -+ or a table of names.
    -+ If the table does not have a field called 'delim', then an attempt will be -+ made to guess it from the fieldnames string, defaults otherwise to tab. -+ -+ -+

    Parameters:

    -+
      -+
    • d -+ the table. -+
    • -+
    • fieldnames -+ {string} -+ optional fieldnames -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the table. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ query (data, condn, context, return_row) -+
    -+
    -+ create a query iterator from a select string. -+ Select string has this format:
    -+ FIELDLIST [ where LUA-CONDN [ sort by FIELD] ]
    -+ FIELDLIST is a comma-separated list of valid fields, or '*'.

    -+ The condition can also be a table, with fields 'fields' (comma-sep string or -+ table), 'sort_by' (string) and 'where' (Lua expression string or function) -+ -+ -+

    Parameters:

    -+
      -+
    • data -+ table produced by read -+
    • -+
    • condn -+ select string or table -+
    • -+
    • context -+ a list of tables to be searched when resolving functions -+
    • -+
    • return_row -+ if true, wrap the results in a row table -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ an iterator over the specified fields, or nil
    2. -+
    3. -+ an error message
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ filter (Q, infile, outfile, dont_fail) -+
    -+
    -+ Filter input using a query. -+ -+ -+

    Parameters:

    -+
      -+
    • Q -+ string -+ a query string -+
    • -+
    • infile -+ filename or file-like object -+
    • -+
    • outfile -+ filename or file-like object -+
    • -+
    • dont_fail -+ boolean -+ true if you want to return an error, not just fail -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.dir.html b/extra/penlight/docs/libraries/pl.dir.html -new file mode 100644 -index 0000000..66371e3 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.dir.html -@@ -0,0 +1,620 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.dir

    -+

    Listing files in directories and creating/removing directory paths.

    -+

    Dependencies: pl.utils, pl.path

    -+ -+

    Soft Dependencies: alien, ffi (either are used on Windows for copying/moving files)

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    fnmatch (filename, pattern)Test whether a file name matches a shell pattern.
    filter (filenames, pattern)Return a list of all file names within an array which match a pattern.
    getfiles ([dirname='.'[, mask]])return a list of all files in a directory which match a shell pattern.
    getdirectories ([dirname='.'])return a list of all subdirectories of the directory.
    copyfile (src, dest, flag)copy a file.
    movefile (src, dest)move a file.
    walk (root, bottom_up, follow_links)return an iterator which walks through a directory tree starting at root.
    rmtree (fullpath)remove a whole directory tree.
    makepath (p)create a directory path.
    clonetree (path1, path2, file_fun, verbose)clone a directory tree.
    dirtree (d)return an iterator over all entries in a directory tree
    getallfiles ([start_path='.'[, shell_pattern='*']])Recursively returns all the file starting at 'path'.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ fnmatch (filename, pattern) -+
    -+
    -+ Test whether a file name matches a shell pattern. -+ Both parameters are case-normalized if operating system is -+ case-insensitive. -+ -+ -+

    Parameters:

    -+
      -+
    • filename -+ string -+ A file name. -+
    • -+
    • pattern -+ string -+ A shell pattern. The only special characters are -+ '*' and '?': '*' matches any sequence of characters and -+ '?' matches any single character. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ bool -+ -+ -+ -+
    -+ -+

    Raises:

    -+ dir and mask must be strings -+ -+ -+ -+
    -+
    -+ -+ filter (filenames, pattern) -+
    -+
    -+ Return a list of all file names within an array which match a pattern. -+ -+ -+

    Parameters:

    -+
      -+
    • filenames -+ table -+ An array containing file names. -+
    • -+
    • pattern -+ string -+ A shell pattern (see fnmatch). -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ List(string) -+ List of matching file names. -+
    -+ -+

    Raises:

    -+ dir and mask must be strings -+ -+ -+ -+
    -+
    -+ -+ getfiles ([dirname='.'[, mask]]) -+
    -+
    -+ return a list of all files in a directory which match a shell pattern. -+ -+ -+

    Parameters:

    -+
      -+
    • dirname -+ string -+ A directory. -+ (default '.') -+
    • -+
    • mask -+ string -+ A shell pattern (see fnmatch). If not given, all files are returned. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ {string} -+ list of files -+
    -+ -+

    Raises:

    -+ dirname and mask must be strings -+ -+ -+ -+
    -+
    -+ -+ getdirectories ([dirname='.']) -+
    -+
    -+ return a list of all subdirectories of the directory. -+ -+ -+

    Parameters:

    -+
      -+
    • dirname -+ string -+ A directory. -+ (default '.') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ {string} -+ a list of directories -+
    -+ -+

    Raises:

    -+ dir must be a valid directory -+ -+ -+ -+
    -+
    -+ -+ copyfile (src, dest, flag) -+
    -+
    -+ copy a file. -+ -+ -+

    Parameters:

    -+
      -+
    • src -+ string -+ source file -+
    • -+
    • dest -+ string -+ destination file or directory -+
    • -+
    • flag -+ boolean -+ true if you want to force the copy (default) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ bool -+ operation succeeded -+
    -+ -+

    Raises:

    -+ src and dest must be strings -+ -+ -+ -+
    -+
    -+ -+ movefile (src, dest) -+
    -+
    -+ move a file. -+ -+ -+

    Parameters:

    -+
      -+
    • src -+ string -+ source file -+
    • -+
    • dest -+ string -+ destination file or directory -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ bool -+ operation succeeded -+
    -+ -+

    Raises:

    -+ src and dest must be strings -+ -+ -+ -+
    -+
    -+ -+ walk (root, bottom_up, follow_links) -+
    -+
    -+ return an iterator which walks through a directory tree starting at root. -+ The iterator returns (root,dirs,files) -+ Note that dirs and files are lists of names (i.e. you must say path.join(root,d) -+ to get the actual full path) -+ If bottom_up is false (or not present), then the entries at the current level are returned -+ before we go deeper. This means that you can modify the returned list of directories before -+ continuing. -+ This is a clone of os.walk from the Python libraries. -+ -+ -+

    Parameters:

    -+
      -+
    • root -+ string -+ A starting directory -+
    • -+
    • bottom_up -+ boolean -+ False if we start listing entries immediately. -+
    • -+
    • follow_links -+ boolean -+ follow symbolic links -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator returning root,dirs,files -+
    -+ -+

    Raises:

    -+ root must be a directory -+ -+ -+ -+
    -+
    -+ -+ rmtree (fullpath) -+
    -+
    -+ remove a whole directory tree. -+ Symlinks in the tree will be deleted without following them. -+ -+ -+

    Parameters:

    -+
      -+
    • fullpath -+ string -+ A directory path (must be an actual directory, not a symlink) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ true or nil
    2. -+
    3. -+ error if failed
    4. -+
    -+ -+

    Raises:

    -+ fullpath must be a string -+ -+ -+ -+
    -+
    -+ -+ makepath (p) -+
    -+
    -+ create a directory path. -+ This will create subdirectories as necessary! -+ -+ -+

    Parameters:

    -+
      -+
    • p -+ string -+ A directory path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true on success, nil + errormsg on failure -+
    -+ -+

    Raises:

    -+ failure to create -+ -+ -+ -+
    -+
    -+ -+ clonetree (path1, path2, file_fun, verbose) -+
    -+
    -+ clone a directory tree. Will always try to create a new directory structure -+ if necessary. -+ -+ -+

    Parameters:

    -+
      -+
    • path1 -+ string -+ the base path of the source tree -+
    • -+
    • path2 -+ string -+ the new base path for the destination -+
    • -+
    • file_fun -+ function -+ an optional function to apply on all files -+
    • -+
    • verbose -+ boolean -+ an optional boolean to control the verbosity of the output. -+ It can also be a logging function that behaves like print() -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ true, or nil
    2. -+
    3. -+ error message, or list of failed directory creations
    4. -+
    5. -+ list of failed file operations
    6. -+
    -+ -+

    Raises:

    -+ path1 and path2 must be strings -+ -+ -+

    Usage:

    -+
      -+
      clonetree('.','../backup',copyfile)
      -+
    -+ -+
    -+
    -+ -+ dirtree (d) -+
    -+
    -+ return an iterator over all entries in a directory tree -+ -+ -+

    Parameters:

    -+
      -+
    • d -+ string -+ a directory -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator giving pathname and mode (true for dir, false otherwise) -+
    -+ -+

    Raises:

    -+ d must be a non-empty string -+ -+ -+ -+
    -+
    -+ -+ getallfiles ([start_path='.'[, shell_pattern='*']]) -+
    -+
    -+ Recursively returns all the file starting at 'path'. It can optionally take a shell pattern and -+ only returns files that match 'shell_pattern'. If a pattern is given it will do a case insensitive search. -+ -+ -+

    Parameters:

    -+
      -+
    • start_path -+ string -+ A directory. -+ (default '.') -+
    • -+
    • shell_pattern -+ string -+ A shell pattern (see fnmatch). -+ (default '*') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ List(string) -+ containing all the files found recursively starting at 'path' and filtered by 'shell_pattern'. -+
    -+ -+

    Raises:

    -+ start_path must be a directory -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.file.html b/extra/penlight/docs/libraries/pl.file.html -new file mode 100644 -index 0000000..391ece4 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.file.html -@@ -0,0 +1,301 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.file

    -+

    File manipulation functions: reading, writing, moving and copying.

    -+

    This module wraps a number of functions from other modules into a -+ file related module for convenience.

    -+ -+

    Dependencies: pl.utils, pl.dir, pl.path

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    read ()return the contents of a file as a string.
    write ()write a string to a file.
    copy ()copy a file.
    move ()move a file.
    access_time ()Return the time of last access as the number of seconds since the epoch.
    creation_time ()Return when the file was created.
    modified_time ()Return the time of last modification.
    delete ()Delete a file.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ read () -+
    -+
    -+ return the contents of a file as a string. -+ This function is a copy of utils.readfile. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ write () -+
    -+
    -+ write a string to a file. -+ This function is a copy of utils.writefile. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ copy () -+
    -+
    -+ copy a file. -+ This function is a copy of dir.copyfile. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ move () -+
    -+
    -+ move a file. -+ This function is a copy of dir.movefile. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ access_time () -+
    -+
    -+ Return the time of last access as the number of seconds since the epoch. -+ This function is a copy of path.getatime. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ creation_time () -+
    -+
    -+ Return when the file was created. -+ This function is a copy of path.getctime. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ modified_time () -+
    -+
    -+ Return the time of last modification. -+ This function is a copy of path.getmtime. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ delete () -+
    -+
    -+ Delete a file. -+ This function is a copy of os.remove. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.func.html b/extra/penlight/docs/libraries/pl.func.html -new file mode 100644 -index 0000000..5ee0ade ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.func.html -@@ -0,0 +1,464 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.func

    -+

    Functional helpers like composition, binding and placeholder expressions.

    -+

    Placeholder expressions are useful for short anonymous functions, and were -+ inspired by the Boost Lambda library.

    -+ -+ -+
    -+> utils.import 'pl.func'
    -+> ls = List{10,20,30}
    -+> = ls:map(_1+1)
    -+{11,21,31}
    -+
    -+ -+

    They can also be used to bind particular arguments of a function.

    -+ -+ -+
    -+> p = bind(print,'start>',_0)
    -+> p(10,20,30)
    -+> start>   10   20  30
    -+
    -+ -+

    See the Guide

    -+ -+

    Dependencies: pl.utils, pl.tablex

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    import (tname, context)wrap a table of functions.
    register (fun[, name])register a function for use in placeholder expressions.
    tail (ls)all elements of a table except the first.
    repr (e, lastpred)create a string representation of a placeholder expression.
    instantiate (e)instantiate a PE into an actual function.
    I (e)instantiate a PE unless it has already been done.
    bind1 (fn, p)bind the first parameter of the function to a value.
    compose (f, g, ...)create a function which chains multiple functions.
    bind (fn, ...)bind the arguments of a function to given values.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ import (tname, context) -+
    -+
    -+ wrap a table of functions. This makes them available for use in -+ placeholder expressions. -+ -+ -+

    Parameters:

    -+
      -+
    • tname -+ string -+ a table name -+
    • -+
    • context -+ table -+ context to put results, defaults to environment of caller -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ register (fun[, name]) -+
    -+
    -+ register a function for use in placeholder expressions. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function -+
    • -+
    • name -+ string -+ an optional name -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a placeholder functiond -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ tail (ls) -+
    -+
    -+ all elements of a table except the first. -+ -+ -+

    Parameters:

    -+
      -+
    • ls -+ table -+ a list-like table. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ repr (e, lastpred) -+
    -+
    -+ create a string representation of a placeholder expression. -+ -+ -+

    Parameters:

    -+
      -+
    • e -+ a placeholder expression -+
    • -+
    • lastpred -+ not used -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ instantiate (e) -+
    -+
    -+ instantiate a PE into an actual function. First we find the largest placeholder used, -+ e.g. 2; from this a list of the formal parameters can be build. Then we collect and replace -+ any non-PE values from the PE, and build up a constant binding list. -+ Finally, the expression can be compiled, and e.PEfunction is set. -+ -+ -+

    Parameters:

    -+
      -+
    • e -+ a placeholder expression -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ I (e) -+
    -+
    -+ instantiate a PE unless it has already been done. -+ -+ -+

    Parameters:

    -+
      -+
    • e -+ a placeholder expression -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the function -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ bind1 (fn, p) -+
    -+
    -+ bind the first parameter of the function to a value. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ function -+ a function of one or more arguments -+
    • -+
    • p -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function of one less argument -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      (bind1(math.max,10))(20) == math.max(10,20)
      -+
    -+ -+
    -+
    -+ -+ compose (f, g, ...) -+
    -+
    -+ create a function which chains multiple functions. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ function -+ a function of at least one argument -+
    • -+
    • g -+ function -+ a function of at least one argument -+
    • -+
    • ... -+ additional functions to compose -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • printf = compose(io.write, string.format)
    • -+
    • printf = compose(io.write, string.lower, string.format)
    • -+
    -+ -+
    -+
    -+ -+ bind (fn, ...) -+
    -+
    -+ bind the arguments of a function to given values. -+ bind(fn,v,_2) is equivalent to bind1(fn,v). -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ function -+ a function of at least one argument -+
    • -+
    • ... -+ values or placeholder variables -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • (bind(f,_1,a))(b) == f(a,b)
    • -+
    • (bind(f,_2,_1))(a,b) == f(b,a)
    • -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.html b/extra/penlight/docs/libraries/pl.html -new file mode 100644 -index 0000000..24644ec ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.html -@@ -0,0 +1,139 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl

    -+

    Entry point for loading all PL libraries only on demand, into the global space.

    -+

    Requiring 'pl' means that whenever a module is implicitly accessed -+ (e.g. utils.split) -+ then that module is dynamically loaded. The submodules are all brought into -+ the global space. -+Updated to use pl.import_into

    -+ -+ -+ -+
    -+
    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.import_into.html b/extra/penlight/docs/libraries/pl.import_into.html -new file mode 100644 -index 0000000..d3b329e ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.import_into.html -@@ -0,0 +1,142 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.import_into

    -+

    PL loader, for loading all PL libraries, only on demand.

    -+

    Whenever a module is implicitly accessed, the table will have the module automatically injected. -+ (e.g. _ENV.tablex) -+ then that module is dynamically loaded. The submodules are all brought into -+ the table that is provided as the argument, or returned in a new table. -+ If a table is provided, that table's metatable is clobbered, but the values are not. -+ This module returns a single function, which is passed the environment. -+ If this is true, then return a 'shadow table' as the module -+ See the Guide

    -+ -+ -+ -+
    -+
    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.input.html b/extra/penlight/docs/libraries/pl.input.html -new file mode 100644 -index 0000000..2f55157 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.input.html -@@ -0,0 +1,336 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.input

    -+

    Iterators for extracting words or numbers from an input source.

    -+

    -+ -+ -+ -+

    -+require 'pl'
    -+local total,n = seq.sum(input.numbers())
    -+print('average',total/n)
    -+
    -+ -+

    source is defined as a string or a file-like object (i.e. has a read() method which returns the next line)

    -+ -+

    See here

    -+ -+

    Dependencies: pl.utils

    -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    alltokens (getter, pattern[, fn])create an iterator over all tokens.
    create_getter (f)create a function which grabs the next value from a source.
    numbers (f)generate a sequence of numbers from a source.
    words (f)generate a sequence of words from a source.
    fields (ids, delim, f, opts)parse an input source into fields.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ alltokens (getter, pattern[, fn]) -+
    -+
    -+ create an iterator over all tokens. -+ based on allwords from PiL, 7.1 -+ -+ -+

    Parameters:

    -+
      -+
    • getter -+ function -+ any function that returns a line of text -+
    • -+
    • pattern -+ string -+ -+ -+ -+
    • -+
    • fn -+ string -+ Optionally can pass a function to process each token as it's found. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ create_getter (f) -+
    -+
    -+ create a function which grabs the next value from a source. If the source is a string, then the getter -+ will return the string and thereafter return nil. If not specified then the source is assumed to be stdin. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ a string or a file-like object (i.e. has a read() method which returns the next line) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a getter function -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ numbers (f) -+
    -+
    -+ generate a sequence of numbers from a source. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ A source -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ An iterator -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ words (f) -+
    -+
    -+ generate a sequence of words from a source. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ A source -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ An iterator -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ fields (ids, delim, f, opts) -+
    -+
    -+ parse an input source into fields. -+ By default, will fail if it cannot convert a field to a number. -+ -+ -+

    Parameters:

    -+
      -+
    • ids -+ a list of field indices, or a maximum field index -+
    • -+
    • delim -+ string -+ delimiter to parse fields (default space) -+
    • -+
    • f -+ a source @see create_getter -+
    • -+
    • opts -+ table -+ option table, {no_fail=true} -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator with the field values -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      for x,y in fields {2,3} do print(x,y) end -- 2nd and 3rd fields from stdin
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.lapp.html b/extra/penlight/docs/libraries/pl.lapp.html -new file mode 100644 -index 0000000..af5cee5 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.lapp.html -@@ -0,0 +1,382 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.lapp

    -+

    Simple command-line parsing using human-readable specification.

    -+

    Supports GNU-style parameters.

    -+ -+ -+
    -+lapp = require 'pl.lapp'
    -+local args = lapp [[
    -+Does some calculations
    -+  -o,--offset (default 0.0)  Offset to add to scaled number
    -+  -s,--scale  (number)  Scaling factor
    -+  <number> (number) Number to be scaled
    -+]]
    -+
    -+print(args.offset + args.scale * args.number)
    -+
    -+ -+

    Lines beginning with '-' are flags; there may be a short and a long name; -+ lines beginning with '<var>' are arguments. Anything in parens after -+ the flag/argument is either a default, a type name or a range constraint.

    -+ -+

    See the Guide

    -+ -+

    Dependencies: pl.sip

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    quit (msg, no_usage)quit this script immediately.
    error (msg, no_usage)print an error to stderr and quit.
    open (file[, opt])open a file.
    assert (condn, msg)quit if the condition is false.
    add_type (name, converter[, constraint])add a new type to Lapp.
    process_options_string (str, args)process a Lapp options string.
    -+

    Fields

    -+ -+ -+ -+ -+ -+
    show_usage_errorcontrols whether to dump usage on error.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ quit (msg, no_usage) -+
    -+
    -+ quit this script immediately. -+ -+ -+

    Parameters:

    -+
      -+
    • msg -+ string -+ optional message -+
    • -+
    • no_usage -+ boolean -+ suppress 'usage' display -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ error (msg, no_usage) -+
    -+
    -+ print an error to stderr and quit. -+ -+ -+

    Parameters:

    -+
      -+
    • msg -+ string -+ a message -+
    • -+
    • no_usage -+ boolean -+ suppress 'usage' display -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ open (file[, opt]) -+
    -+
    -+ open a file. -+ This will quit on error, and keep a list of file objects for later cleanup. -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ string -+ filename -+
    • -+
    • opt -+ string -+ same as second parameter of io.open -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ assert (condn, msg) -+
    -+
    -+ quit if the condition is false. -+ -+ -+

    Parameters:

    -+
      -+
    • condn -+ boolean -+ a condition -+
    • -+
    • msg -+ string -+ message text -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ add_type (name, converter[, constraint]) -+
    -+
    -+ add a new type to Lapp. These appear in parens after the value like -+ a range constraint, e.g. ' (integer) Process PID' -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ name of type -+
    • -+
    • converter -+ either a function to convert values, or a Lua type name. -+
    • -+
    • constraint -+ function -+ optional function to verify values, should use lapp.error -+ if failed. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ process_options_string (str, args) -+
    -+
    -+ process a Lapp options string. -+ Usually called as lapp(). -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ the options text -+
    • -+
    • args -+ {string} -+ a table of arguments (default is _G.arg) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table with parameter-value pairs -+
    -+ -+ -+ -+ -+
    -+
    -+

    Fields

    -+ -+
    -+
    -+ -+ show_usage_error -+
    -+
    -+ controls whether to dump usage on error. -+ Defaults to true -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.lexer.html b/extra/penlight/docs/libraries/pl.lexer.html -new file mode 100644 -index 0000000..dc3102a ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.lexer.html -@@ -0,0 +1,524 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.lexer

    -+

    Lexical scanner for creating a sequence of tokens from text.

    -+

    lexer.scan(s) returns an iterator over all tokens found in the -+ string s. This iterator returns two values, a token type string -+ (such as 'string' for quoted string, 'iden' for identifier) and the value of the -+ token.

    -+ -+

    Versions specialized for Lua and C are available; these also handle block comments -+ and classify keywords as 'keyword' tokens. For example:

    -+ -+ -+
    -+> s = 'for i=1,n do'
    -+> for t,v in lexer.lua(s)  do print(t,v) end
    -+keyword for
    -+iden    i
    -+=       =
    -+number  1
    -+,       ,
    -+iden    n
    -+keyword do
    -+
    -+ -+

    See the Guide for further discussion

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    scan (s, matches[, filter[, options]])create a plain token iterator from a string or file-like object.
    insert (tok, a1, a2)insert tokens into a stream.
    getline (tok)get everything in a stream upto a newline.
    lineno (tok)get current line number.
    getrest (tok)get the rest of the stream.
    get_keywords ()get the Lua keywords as a set-like table.
    lua (s[, filter[, options]])create a Lua token iterator from a string or file-like object.
    cpp (s[, filter[, options]])create a C/C++ token iterator from a string or file-like object.
    get_separated_list (tok[, endtoken=')'[, delim=']])get a list of parameters separated by a delimiter from a stream.
    skipws (tok)get the next non-space token from the stream.
    expecting (tok, expected_type, no_skip_ws)get the next token, which must be of the expected type.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ scan (s, matches[, filter[, options]]) -+
    -+
    -+ create a plain token iterator from a string or file-like object. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string or file -+ a string or a file-like object with :read() method returning lines. -+
    • -+
    • matches -+ table -+ an optional match table - array of token descriptions. -+ A token is described by a {pattern, action} pair, where pattern should match -+ token body and action is a function called when a token of described type is found. -+
    • -+
    • filter -+ table -+ a table of token types to exclude, by default {space=true} -+ (optional) -+
    • -+
    • options -+ table -+ a table of options; by default, {number=true,string=true}, -+ which means convert numbers and strip string quotes. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ insert (tok, a1, a2) -+
    -+
    -+ insert tokens into a stream. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ a token stream -+
    • -+
    • a1 -+ a string is the type, a table is a token list and -+ a function is assumed to be a token-like iterator (returns type & value) -+
    • -+
    • a2 -+ string -+ a string is the value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ getline (tok) -+
    -+
    -+ get everything in a stream upto a newline. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ a token stream -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ lineno (tok) -+
    -+
    -+ get current line number. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ a token stream -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the line number. -+ if the input source is a file-like object, -+ also return the column. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ getrest (tok) -+
    -+
    -+ get the rest of the stream. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ a token stream -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ get_keywords () -+
    -+
    -+ get the Lua keywords as a set-like table. -+ So res["and"] etc would be true. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ a table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ lua (s[, filter[, options]]) -+
    -+
    -+ create a Lua token iterator from a string or file-like object. -+ Will return the token type and value. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • filter -+ table -+ a table of token types to exclude, by default {space=true,comments=true} -+ (optional) -+
    • -+
    • options -+ table -+ a table of options; by default, {number=true,string=true}, -+ which means convert numbers and strip string quotes. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ cpp (s[, filter[, options]]) -+
    -+
    -+ create a C/C++ token iterator from a string or file-like object. -+ Will return the token type type and value. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • filter -+ table -+ a table of token types to exclude, by default {space=true,comments=true} -+ (optional) -+
    • -+
    • options -+ table -+ a table of options; by default, {number=true,string=true}, -+ which means convert numbers and strip string quotes. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ get_separated_list (tok[, endtoken=')'[, delim=']]) -+
    -+
    -+ get a list of parameters separated by a delimiter from a stream. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ the token stream -+
    • -+
    • endtoken -+ string -+ end of list. Can be '\n' -+ (default ')') -+
    • -+
    • delim -+ string -+ separator -+ (default ') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list of token lists. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ skipws (tok) -+
    -+
    -+ get the next non-space token from the stream. -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ the token stream. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ expecting (tok, expected_type, no_skip_ws) -+
    -+
    -+ get the next token, which must be of the expected type. -+ Throws an error if this type does not match! -+ -+ -+

    Parameters:

    -+
      -+
    • tok -+ the token stream -+
    • -+
    • expected_type -+ string -+ the token type -+
    • -+
    • no_skip_ws -+ boolean -+ whether we should skip whitespace -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.luabalanced.html b/extra/penlight/docs/libraries/pl.luabalanced.html -new file mode 100644 -index 0000000..ab475c9 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.luabalanced.html -@@ -0,0 +1,149 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.luabalanced

    -+

    Extract delimited Lua sequences from strings.

    -+

    Inspired by Damian Conway's Text::Balanced in Perl.
    -+

      -+
    • [1] Lua Wiki Page
    • -+
    • [2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm
    • -+

    -+
    -+ local lb = require "pl.luabalanced"
    -+ --Extract Lua expression starting at position 4.
    -+  print(lb.match_expression("if x^2 + x > 5 then print(x) end", 4))
    -+  --> x^2 + x > 5     16
    -+ --Extract Lua string starting at (default) position 1.
    -+ print(lb.match_string([["test\"123" .. "more"]]))
    -+ --> "test\"123"     12
    -+ 
    -+ (c) 2008, David Manura, Licensed under the same terms as Lua (MIT license).

    -+ -+ -+ -+
    -+
    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.operator.html b/extra/penlight/docs/libraries/pl.operator.html -new file mode 100644 -index 0000000..958678e ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.operator.html -@@ -0,0 +1,819 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.operator

    -+

    Lua operators available as functions.

    -+

    (similar to the Python module of the same name)

    -+ -+

    There is a module field optable which maps the operator strings -+ onto these functions, e.g. operator.optable['()']==operator.call

    -+ -+

    Operator strings like '>' and '{}' can be passed to most Penlight functions -+ expecting a function argument.

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    call (fn, ...)apply function to some arguments ()
    index (t, k)get the indexed value from a table []
    eq (a, b)returns true if arguments are equal ==
    neq (a, b)returns true if arguments are not equal ~=
    lt (a, b)returns true if a is less than b <
    le (a, b)returns true if a is less or equal to b <=
    gt (a, b)returns true if a is greater than b >
    ge (a, b)returns true if a is greater or equal to b >=
    len (a)returns length of string or table #
    add (a, b)add two values +
    sub (a, b)subtract b from a -
    mul (a, b)multiply two values *
    div (a, b)divide first value by second /
    pow (a, b)raise first to the power of second ^
    mod (a, b)modulo; remainder of a divided by b %
    concat (a, b)concatenate two values (either strings or __concat defined) ..
    unm (a)return the negative of a value -
    lnot (a)false if value evaluates as true not
    land (a, b)true if both values evaluate as true and
    lor (a, b)true if either value evaluate as true or
    table (...)make a table from the arguments {}
    match (a, b)match two strings ~.
    nop (...)the null operation.
    -+

    Tables

    -+ -+ -+ -+ -+ -+
    optableMap from operator symbol to function.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ call (fn, ...) -+
    -+
    -+ apply function to some arguments () -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ a function or callable object -+
    • -+
    • ... -+ arguments -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ index (t, k) -+
    -+
    -+ get the indexed value from a table [] -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a table or any indexable object -+
    • -+
    • k -+ the key -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ eq (a, b) -+
    -+
    -+ returns true if arguments are equal == -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ neq (a, b) -+
    -+
    -+ returns true if arguments are not equal ~= -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ lt (a, b) -+
    -+
    -+ returns true if a is less than b < -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ le (a, b) -+
    -+
    -+ returns true if a is less or equal to b <= -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ gt (a, b) -+
    -+
    -+ returns true if a is greater than b > -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ ge (a, b) -+
    -+
    -+ returns true if a is greater or equal to b >= -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ len (a) -+
    -+
    -+ returns length of string or table # -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ a string or a table -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ add (a, b) -+
    -+
    -+ add two values + -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ sub (a, b) -+
    -+
    -+ subtract b from a - -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ mul (a, b) -+
    -+
    -+ multiply two values * -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ div (a, b) -+
    -+
    -+ divide first value by second / -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pow (a, b) -+
    -+
    -+ raise first to the power of second ^ -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ mod (a, b) -+
    -+
    -+ modulo; remainder of a divided by b % -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ concat (a, b) -+
    -+
    -+ concatenate two values (either strings or __concat defined) .. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ unm (a) -+
    -+
    -+ return the negative of a value - -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ lnot (a) -+
    -+
    -+ false if value evaluates as true not -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ land (a, b) -+
    -+
    -+ true if both values evaluate as true and -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ lor (a, b) -+
    -+
    -+ true if either value evaluate as true or -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ value -+
    • -+
    • b -+ value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ table (...) -+
    -+
    -+ make a table from the arguments {} -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ non-nil arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ match (a, b) -+
    -+
    -+ match two strings ~. -+ uses string.find -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ -+ -+ -+
    • -+
    • b -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ nop (...) -+
    -+
    -+ the null operation. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the arguments -+
    -+ -+ -+ -+ -+
    -+
    -+

    Tables

    -+ -+
    -+
    -+ -+ optable -+
    -+
    -+ -+

    Map from operator symbol to function. -+ Most of these map directly from operators; -+ But note these extras

    -+ -+ -+ -+ -+ -+ -+

    Fields:

    -+
      -+
    • operator -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.path.html b/extra/penlight/docs/libraries/pl.path.html -new file mode 100644 -index 0000000..74fea5e ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.path.html -@@ -0,0 +1,1087 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.path

    -+

    Path manipulation and file queries.

    -+

    This is modelled after Python's os.path library (10.1); see the Guide.

    -+ -+

    NOTE: the functions assume the paths being dealt with to originate -+ from the OS the application is running on. Windows drive letters are not -+ to be used when running on a Unix system for example. The one exception -+ is Windows paths to allow both forward and backward slashes (since Lua -+ also accepts those)

    -+ -+

    Dependencies: pl.utils, lfs

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    dir ()Lua iterator over the entries of a given directory.
    mkdir ()Creates a directory.
    rmdir ()Removes a directory.
    attrib ()Gets attributes.
    currentdir ()Get the working directory.
    link_attrib ()Gets symlink attributes.
    chdir ()Changes the working directory.
    isdir (P)is this a directory?
    isfile (P)is this a file?
    getsize (P)return size of a file.
    exists (P)does a path exist?
    getatime (P)Return the time of last access as the number of seconds since the epoch.
    getmtime (P)Return the time of last modification as the number of seconds since the epoch.
    getctime (P)Return the system's ctime as the number of seconds since the epoch.
    splitpath (P)given a path, return the directory part and a file part.
    abspath (P[, pwd])return an absolute path.
    splitext (P)given a path, return the root part and the extension part.
    dirname (P)return the directory part of a path
    basename (P)return the file part of a path
    extension (P)get the extension part of a path.
    isabs (P)is this an absolute path?
    join (p1, p2, ...)return the path resulting from combining the individual paths.
    normcase (P)normalize the case of a pathname.
    normpath (P)normalize a path name.
    relpath (P[, start])relative path from current directory or optional start point
    expanduser (P)Replace a starting '~' with the user's home directory.
    tmpname ()Return a suitable full path to a new temporary file name.
    common_prefix (path1, path2)return the largest common prefix path of two paths.
    package_path (mod)return the full path where a particular Lua module would be found.
    -+

    Fields

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    is_windowsare we running Windows?
    seppath separator for this platform.
    dirsepseparator for PATH for this platform
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ dir () -+
    -+
    -+ Lua iterator over the entries of a given directory. -+ Implicit link to luafilesystem.dir -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ mkdir () -+
    -+
    -+ Creates a directory. -+ Implicit link to luafilesystem.mkdir -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ rmdir () -+
    -+
    -+ Removes a directory. -+ Implicit link to luafilesystem.rmdir -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ attrib () -+
    -+
    -+ Gets attributes. -+ Implicit link to luafilesystem.attributes -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ currentdir () -+
    -+
    -+ Get the working directory. -+ Implicit link to luafilesystem.currentdir -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ link_attrib () -+
    -+
    -+ Gets symlink attributes. -+ Implicit link to luafilesystem.symlinkattributes -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ chdir () -+
    -+
    -+ Changes the working directory. -+ On Windows, if a drive is specified, it also changes the current drive. If -+ only specifying the drive, it will only switch drive, but not modify the path. -+ Implicit link to luafilesystem.chdir -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isdir (P) -+
    -+
    -+ is this a directory? -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isfile (P) -+
    -+
    -+ is this a file? -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ getsize (P) -+
    -+
    -+ return size of a file. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ exists (P) -+
    -+
    -+ does a path exist? -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the file path if it exists (either as file, directory, socket, etc), nil otherwise -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ getatime (P) -+
    -+
    -+ Return the time of last access as the number of seconds since the epoch. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ getmtime (P) -+
    -+
    -+ Return the time of last modification as the number of seconds since the epoch. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ getctime (P) -+
    -+
    -+ Return the system's ctime as the number of seconds since the epoch. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ splitpath (P) -+
    -+
    -+ given a path, return the directory part and a file part. -+ if there's no directory part, the first value will be empty -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ directory part
    2. -+
    3. -+ file part
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local dir, file = path.splitpath("some/dir/myfile.txt")
      -+assert(dir == "some/dir")
      -+assert(file == "myfile.txt")
      -+
      -+local dir, file = path.splitpath("some/dir/")
      -+assert(dir == "some/dir")
      -+assert(file == "")
      -+
      -+local dir, file = path.splitpath("some_dir")
      -+assert(dir == "")
      -+assert(file == "some_dir")
      -+
    -+ -+
    -+
    -+ -+ abspath (P[, pwd]) -+
    -+
    -+ return an absolute path. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    • pwd -+ string -+ optional start path to use (default is current dir) -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ splitext (P) -+
    -+
    -+ given a path, return the root part and the extension part. -+ if there's no extension part, the second value will be empty -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ string -+ root part (everything upto the "."", maybe empty)
    2. -+
    3. -+ string -+ extension part (including the ".", maybe empty)
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local file_path, ext = path.splitext("/bonzo/dog_stuff/cat.txt")
      -+assert(file_path == "/bonzo/dog_stuff/cat")
      -+assert(ext == ".txt")
      -+
      -+local file_path, ext = path.splitext("")
      -+assert(file_path == "")
      -+assert(ext == "")
      -+
    -+ -+
    -+
    -+ -+ dirname (P) -+
    -+
    -+ return the directory part of a path -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ everything before the last dir-separator -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      path.dirname("/some/path/file.txt")   -- "/some/path"
      -+path.dirname("file.txt")              -- "" (empty string)
      -+
    -+ -+
    -+
    -+ -+ basename (P) -+
    -+
    -+ return the file part of a path -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ -+ -+ -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      path.basename("/some/path/file.txt")  -- "file.txt"
      -+path.basename("/some/path/file/")     -- "" (empty string)
      -+
    -+ -+
    -+
    -+ -+ extension (P) -+
    -+
    -+ get the extension part of a path. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ -+ -+ -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      path.extension("/some/path/file.txt") -- ".txt"
      -+path.extension("/some/path/file_txt") -- "" (empty string)
      -+
    -+ -+
    -+
    -+ -+ isabs (P) -+
    -+
    -+ is this an absolute path? -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      path.isabs("hello/path")    -- false
      -+path.isabs("/hello/path")   -- true
      -+-- Windows;
      -+path.isabs("hello\path")    -- false
      -+path.isabs("\hello\path")   -- true
      -+path.isabs("C:\hello\path") -- true
      -+path.isabs("C:hello\path")  -- false
      -+
    -+ -+
    -+
    -+ -+ join (p1, p2, ...) -+
    -+
    -+ return the path resulting from combining the individual paths. -+ if the second (or later) path is absolute, we return the last absolute path (joined with any non-absolute paths following). -+ empty elements (except the last) will be ignored. -+ -+ -+

    Parameters:

    -+
      -+
    • p1 -+ string -+ A file path -+
    • -+
    • p2 -+ string -+ A file path -+
    • -+
    • ... -+ string -+ more file paths -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ the combined path -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      path.join("/first","second","third")   -- "/first/second/third"
      -+path.join("first","second/third")      -- "first/second/third"
      -+path.join("/first","/second","third")  -- "/second/third"
      -+
    -+ -+
    -+
    -+ -+ normcase (P) -+
    -+
    -+ -+

    normalize the case of a pathname. On Unix, this returns the path unchanged, -+ for Windows it converts;

    -+ -+
      -+
    • the path to lowercase
    • -+
    • forward slashes to backward slashes
    • -+
    -+ -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      path.normcase("/Some/Path/File.txt")
      -+-- Windows: "\some\path\file.txt"
      -+-- Others : "/Some/Path/File.txt"
      -+
    -+ -+
    -+
    -+ -+ normpath (P) -+
    -+
    -+ normalize a path name. -+ A//B, A/./B, and A/foo/../B all become A/B.

    -+ -+

    An empty path results in '.'. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ a file path -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ relpath (P[, start]) -+
    -+
    -+ relative path from current directory or optional start point -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ a path -+
    • -+
    • start -+ string -+ optional start point (default current directory) -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ expanduser (P) -+
    -+
    -+ Replace a starting '~' with the user's home directory. -+ In windows, if HOME isn't set, then USERPROFILE is used in preference to -+ HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. -+ -+ -+

    Parameters:

    -+
      -+
    • P -+ string -+ A file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string -+ The file path with the ~ prefix substituted, or the input path if it had no prefix. -+
    -+

    Or

    -+
      -+
    1. -+ nil -+ -+ -+
    2. -+
    3. -+ string -+ Error message if the environment variables were unavailable.
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ tmpname () -+
    -+
    -+ Return a suitable full path to a new temporary file name. -+ unlike os.tmpname(), it always gives you a writeable path (uses TEMP environment variable on Windows) -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ common_prefix (path1, path2) -+
    -+
    -+ return the largest common prefix path of two paths. -+ -+ -+

    Parameters:

    -+
      -+
    • path1 -+ string -+ a file path -+
    • -+
    • path2 -+ string -+ a file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the common prefix (Windows: separators will be normalized, casing will be original) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ package_path (mod) -+
    -+
    -+ return the full path where a particular Lua module would be found. -+ Both package.path and package.cpath is searched, so the result may -+ either be a Lua file or a shared library. -+ -+ -+

    Parameters:

    -+
      -+
    • mod -+ string -+ name of the module -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ on success: path of module, lua or binary
    2. -+
    3. -+ on error: nil, error string listing paths tried
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+

    Fields

    -+ -+
    -+
    -+ -+ is_windows -+
    -+
    -+ are we running Windows? -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ sep -+
    -+
    -+ path separator for this platform. -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ dirsep -+
    -+
    -+ separator for PATH for this platform -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.permute.html b/extra/penlight/docs/libraries/pl.permute.html -new file mode 100644 -index 0000000..4f6fb4b ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.permute.html -@@ -0,0 +1,354 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.permute

    -+

    Permutation operations.

    -+

    Dependencies: pl.utils, pl.tablex

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    order_iter (a)an iterator over all order-permutations of the elements of a list.
    order_table (a)construct a table containing all the order-permutations of a list.
    list_iter (...)an iterator over all permutations of the elements of the given lists.
    list_table (...)construct a table containing all the permutations of a set of lists.
    iter (...)deprecated.
    table (...)deprecated.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ order_iter (a) -+
    -+
    -+ an iterator over all order-permutations of the elements of a list. -+ Please note that the same list is returned each time, so do not keep references! -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ list-like table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator which provides the next permutation as a list -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ order_table (a) -+
    -+
    -+ construct a table containing all the order-permutations of a list. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ list-like table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table of tables -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      permute.order_table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}}
      -+
    -+ -+
    -+
    -+ -+ list_iter (...) -+
    -+
    -+ an iterator over all permutations of the elements of the given lists. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ list-like tables, they are nil-safe if a length-field n is provided (see utils.pack) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator which provides the next permutation as return values in the same order as the provided lists, preceded by an index -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local strs = utils.pack("one", nil, "three")  -- adds an 'n' field for nil-safety
      -+local bools = utils.pack(true, false)
      -+local iter = permute.list_iter(strs, bools)
      -+
      -+print(iter())    --> 1, one, true
      -+print(iter())    --> 2, nil, true
      -+print(iter())    --> 3, three, true
      -+print(iter())    --> 4, one, false
      -+print(iter())    --> 5, nil, false
      -+print(iter())    --> 6, three, false
      -+
    -+ -+
    -+
    -+ -+ list_table (...) -+
    -+
    -+ construct a table containing all the permutations of a set of lists. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ list-like tables, they are nil-safe if a length-field n is provided -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list of lists, the sub-lists have an 'n' field for nil-safety -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local strs = utils.pack("one", nil, "three")  -- adds an 'n' field for nil-safety
      -+local bools = utils.pack(true, false)
      -+local results = permute.list_table(strs, bools)
      -+-- results = {
      -+--   { "one, true, n = 2 }
      -+--   { nil, true, n = 2 },
      -+--   { "three, true, n = 2 },
      -+--   { "one, false, n = 2 },
      -+--   { nil, false, n = 2 },
      -+--   { "three", false, n = 2 },
      -+-- }
      -+
    -+ -+
    -+
    -+ -+ iter (...) -+
    -+
    -+ deprecated. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ -+ -+ -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ table (...) -+
    -+
    -+ deprecated. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ -+ -+ -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.pretty.html b/extra/penlight/docs/libraries/pl.pretty.html -new file mode 100644 -index 0000000..35a5fdd ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.pretty.html -@@ -0,0 +1,402 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.pretty

    -+

    Pretty-printing Lua tables.

    -+

    Also provides a sandboxed Lua table reader and -+ a function to present large numbers in human-friendly format.

    -+ -+

    Dependencies: pl.utils, pl.lexer, pl.stringx, debug

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    read (s)Read a string representation of a Lua table.
    load (s[, env[, paranoid]])Read a Lua chunk.
    write (tbl[, space[, not_clever]])Create a string representation of a Lua table.
    dump (t[, filename])Dump a Lua table out to a file or stdout.
    debug (...)Dump a series of arguments to stdout for debug purposes.
    number (num[, kind[, prec]])Format large numbers nicely for human consumption.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ read (s) -+
    -+
    -+ Read a string representation of a Lua table. -+ This function loads and runs the string as Lua code, but bails out -+ if it contains a function definition. -+ Loaded string is executed in an empty environment. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ string to read in {...} format, possibly with some whitespace -+ before or after the curly braces. A single line comment may be present -+ at the beginning. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table in case of success. -+ If loading the string failed, return nil and error message. -+ If executing loaded string failed, return nil and the error it raised. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ load (s[, env[, paranoid]]) -+
    -+
    -+ Read a Lua chunk. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ Lua code. -+
    • -+
    • env -+ table -+ environment used to run the code, empty by default. -+ (optional) -+
    • -+
    • paranoid -+ boolean -+ abort loading if any looping constructs a found in the code -+ and disable string methods. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the environment in case of success or nil and syntax or runtime error -+ if something went wrong. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ write (tbl[, space[, not_clever]]) -+
    -+
    -+ Create a string representation of a Lua table. -+ This function never fails, but may complain by returning an -+ extra value. Normally puts out one item per line, using -+ the provided indent; set the second parameter to an empty string -+ if you want output on one line.

    -+ -+

    NOTE: this is NOT a serialization function, not a full blown -+ debug function. Checkout out respectively the -+ serpent -+ or inspect -+ Lua modules for that if you need them. -+ -+ -+

    Parameters:

    -+
      -+
    • tbl -+ table -+ Table to serialize to a string. -+
    • -+
    • space -+ string -+ The indent to use. -+ Defaults to two spaces; pass an empty string for no indentation. -+ (optional) -+
    • -+
    • not_clever -+ boolean -+ Pass true for plain output, e.g {['key']=1}. -+ Defaults to false. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a string
    2. -+
    3. -+ an optional error message
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ dump (t[, filename]) -+
    -+
    -+ Dump a Lua table out to a file or stdout. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ The table to write to a file or stdout. -+
    • -+
    • filename -+ string -+ File name to write too. Defaults to writing -+ to stdout. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ debug (...) -+
    -+
    -+ Dump a series of arguments to stdout for debug purposes. -+ This function is attached to the module table __call method, to make it -+ extra easy to access. So the full:

    -+ -+
     print(require("pl.pretty").write({...}))
    -+
    -+ -+

    Can be shortened to:

    -+ -+
     require"pl.pretty" (...)
    -+
    -+ -+

    Any nil entries will be printed as "<nil>" to make them explicit. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ the parameters to dump to stdout. -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      -- example debug output
      -+require"pl.pretty" ("hello", nil, "world", { bye = "world", true} )
      -+
      -+-- output:
      -+{
      -+  ["arg 1"] = "hello",
      -+  ["arg 2"] = "<nil>",
      -+  ["arg 3"] = "world",
      -+  ["arg 4"] = {
      -+    true,
      -+    bye = "world"
      -+  }
      -+}
      -+
    -+ -+
    -+
    -+ -+ number (num[, kind[, prec]]) -+
    -+
    -+ Format large numbers nicely for human consumption. -+ -+ -+

    Parameters:

    -+
      -+
    • num -+ number -+ a number. -+
    • -+
    • kind -+ string -+ one of 'M' (memory in KiB, MiB, etc.), -+ 'N' (postfixes are 'K', 'M' and 'B'), -+ or 'T' (use commas as thousands separator), 'N' by default. -+ (optional) -+
    • -+
    • prec -+ integer -+ number of digits to use for 'M' and 'N', 1 by default. -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.seq.html b/extra/penlight/docs/libraries/pl.seq.html -new file mode 100644 -index 0000000..48e8a2c ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.seq.html -@@ -0,0 +1,888 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.seq

    -+

    Manipulating iterators as sequences.

    -+

    See The Guide

    -+ -+

    Dependencies: pl.utils, pl.types, debug

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    matching (s)given a string, return a function(y) which matches y against the string.
    list (t)sequence adaptor for a table.
    keys (t)return the keys of the table.
    range (start, finish)create an iterator over a numerical range.
    minmax (iter)return the minimum and the maximum value of the sequence.
    sum (iter, fn)return the sum and element count of the sequence.
    copy (iter)create a table from the sequence.
    copy2 (iter, i1, i2)create a table of pairs from the double-valued sequence.
    copy_tuples (iter)create a table of 'tuples' from a multi-valued sequence.
    random (n, l, u)return an iterator of random numbers.
    sort (iter, comp)return an iterator to the sorted elements of a sequence.
    zip (iter1, iter2)return an iterator which returns elements of two sequences.
    count_map (iter)Makes a table where the key/values are the values and value counts of the sequence.
    printall (iter, sep, nfields, fmt)print out a sequence iter with a separator.
    map (fn, iter, arg)return a sequence where every element of a sequence has been transformed -+ by a function.
    filter (iter, pred, arg)filter a sequence using a predicate function.
    reduce (fn, iter, initval)'reduce' a sequence using a binary function.
    take (iter, n)take the first n values from the sequence.
    skip (iter, n)skip the first n values of a sequence
    enum (iter)a sequence with a sequence count and the original value.
    mapmethod (iter, name, arg1, arg2)map using a named method over a sequence.
    last (iter)a sequence of (last,current) values from another sequence.
    foreach (iter, fn)call the function on each element of the sequence.
    lines (f, ...)create a wrapped iterator over all lines in the file.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ matching (s) -+
    -+
    -+ given a string, return a function(y) which matches y against the string. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ list (t) -+
    -+
    -+ sequence adaptor for a table. Note that if any generic function is -+ passed a table, it will automatically use seq.list() -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a list-like table -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • sum(list(t)) is the sum of all elements of t
    • -+
    • for x in list(t) do...end
    • -+
    -+ -+
    -+
    -+ -+ keys (t) -+
    -+
    -+ return the keys of the table. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ an arbitrary table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ iterator over keys -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ range (start, finish) -+
    -+
    -+ create an iterator over a numerical range. Like the standard Python function xrange. -+ -+ -+

    Parameters:

    -+
      -+
    • start -+ a number -+
    • -+
    • finish -+ a number greater than start -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ minmax (iter) -+
    -+
    -+ return the minimum and the maximum value of the sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ minimum value
    2. -+
    3. -+ maximum value
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ sum (iter, fn) -+
    -+
    -+ return the sum and element count of the sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    • fn -+ an optional function to apply to the values -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ copy (iter) -+
    -+
    -+ create a table from the sequence. (This will make the result a List.) -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a List -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • copy(list(ls)) is equal to ls
    • -+
    • copy(list {1,2,3}) == List{1,2,3}
    • -+
    -+ -+
    -+
    -+ -+ copy2 (iter, i1, i2) -+
    -+
    -+ create a table of pairs from the double-valued sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a double-valued sequence -+
    • -+
    • i1 -+ used to capture extra iterator values -+
    • -+
    • i2 -+ as with pairs & ipairs -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list-like table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      copy2(ipairs{10,20,30}) == {{1,10},{2,20},{3,30}}
      -+
    -+ -+
    -+
    -+ -+ copy_tuples (iter) -+
    -+
    -+ create a table of 'tuples' from a multi-valued sequence. -+ A generalization of copy2 above -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a multiple-valued sequence -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list-like table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ random (n, l, u) -+
    -+
    -+ return an iterator of random numbers. -+ -+ -+

    Parameters:

    -+
      -+
    • n -+ the length of the sequence -+
    • -+
    • l -+ same as the first optional argument to math.random -+
    • -+
    • u -+ same as the second optional argument to math.random -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a sequence -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ sort (iter, comp) -+
    -+
    -+ return an iterator to the sorted elements of a sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    • comp -+ an optional comparison function (comp(x,y) is true if x < y) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ zip (iter1, iter2) -+
    -+
    -+ return an iterator which returns elements of two sequences. -+ -+ -+

    Parameters:

    -+
      -+
    • iter1 -+ a sequence -+
    • -+
    • iter2 -+ a sequence -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      for x,y in seq.zip(ls1,ls2) do....end
      -+
    -+ -+
    -+
    -+ -+ count_map (iter) -+
    -+
    -+ Makes a table where the key/values are the values and value counts of the sequence. -+ This version works with 'hashable' values like strings and numbers. -+ pl.tablex.count_map is more general. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a map-like table
    2. -+
    3. -+ a table
    4. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ printall (iter, sep, nfields, fmt) -+
    -+
    -+ print out a sequence iter with a separator. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    • sep -+ the separator (default space) -+
    • -+
    • nfields -+ maximum number of values per line (default 7) -+
    • -+
    • fmt -+ optional format function for each value -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ map (fn, iter, arg) -+
    -+
    -+ return a sequence where every element of a sequence has been transformed -+ by a function. If you don't supply an argument, then the function will -+ receive both values of a double-valued sequence, otherwise behaves rather like -+ tablex.map. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ a function to apply to elements; may take two arguments -+
    • -+
    • iter -+ a sequence of one or two values -+
    • -+
    • arg -+ optional argument to pass to function. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ filter (iter, pred, arg) -+
    -+
    -+ filter a sequence using a predicate function. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence of one or two values -+
    • -+
    • pred -+ a boolean function; may take two arguments -+
    • -+
    • arg -+ optional argument to pass to function. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ reduce (fn, iter, initval) -+
    -+
    -+ 'reduce' a sequence using a binary function. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ function -+ a function of two arguments -+
    • -+
    • iter -+ a sequence -+
    • -+
    • initval -+ optional initial value -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • seq.reduce(operator.add,seq.list{1,2,3,4}) == 10
    • -+
    • seq.reduce('-',{1,2,3,4,5}) == -13
    • -+
    -+ -+
    -+
    -+ -+ take (iter, n) -+
    -+
    -+ take the first n values from the sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence of one or two values -+
    • -+
    • n -+ number of items to take -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a sequence of at most n items -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ skip (iter, n) -+
    -+
    -+ skip the first n values of a sequence -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence of one or more values -+
    • -+
    • n -+ number of items to skip -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ enum (iter) -+
    -+
    -+ a sequence with a sequence count and the original value. -+ enum(copy(ls)) is a roundabout way of saying ipairs(ls). -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a single or double valued sequence -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ sequence of (i,v), i = 1..n and v is from iter. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ mapmethod (iter, name, arg1, arg2) -+
    -+
    -+ map using a named method over a sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    • name -+ the method name -+
    • -+
    • arg1 -+ optional first extra argument -+
    • -+
    • arg2 -+ optional second extra argument -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ last (iter) -+
    -+
    -+ a sequence of (last,current) values from another sequence. -+ This will return S(i-1),S(i) if given S(i) -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ foreach (iter, fn) -+
    -+
    -+ call the function on each element of the sequence. -+ -+ -+

    Parameters:

    -+
      -+
    • iter -+ a sequence with up to 3 values -+
    • -+
    • fn -+ a function -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ lines (f, ...) -+
    -+
    -+ create a wrapped iterator over all lines in the file. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ either a filename, file-like object, or 'STDIN' (for standard input) -+
    • -+
    • ... -+ for Lua 5.2 only, optional format specifiers, as in io.read. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a sequence wrapper -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.sip.html b/extra/penlight/docs/libraries/pl.sip.html -new file mode 100644 -index 0000000..c1812ea ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.sip.html -@@ -0,0 +1,399 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.sip

    -+

    Simple Input Patterns (SIP).

    -+

    SIP patterns start with '$', then a -+ one-letter type, and then an optional variable in curly braces.

    -+ -+ -+
    -+sip.match('$v=$q','name="dolly"',res)
    -+==> res=={'name','dolly'}
    -+sip.match('($q{first},$q{second})','("john","smith")',res)
    -+==> res=={second='smith',first='john'}
    -+
    -+ -+

    Type names:

    -+ -+ -+
    -+v     identifier
    -+i     integer
    -+f     floating-point
    -+q     quoted string
    -+([{<  match up to closing bracket
    -+
    -+ -+

    See the Guide

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    create_pattern (spec, options)convert a SIP pattern into the equivalent Lua string pattern.
    compile (spec, options)convert a SIP pattern into a matching function.
    match (spec, line, res, options)match a SIP pattern against a string.
    match_at_start (spec, line, res)match a SIP pattern against the start of a string.
    fields (spec, f)given a pattern and a file object, return an iterator over the results
    pattern (spec, fun)register a match which will be used in the read function.
    read (f, matches)enter a loop which applies all registered matches to the input file.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ create_pattern (spec, options) -+
    -+
    -+ convert a SIP pattern into the equivalent Lua string pattern. -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ a SIP pattern -+
    • -+
    • options -+ a table; only the at_start field is -+ currently meaningful and ensures that the pattern is anchored -+ at the start of the string. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a Lua string pattern. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ compile (spec, options) -+
    -+
    -+ convert a SIP pattern into a matching function. -+ The returned function takes two arguments, the line and an empty table. -+ If the line matched the pattern, then this function returns true -+ and the table is filled with field-value pairs. -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ a SIP pattern -+
    • -+
    • options -+ optional table; {at_start=true} ensures that the pattern -+ is anchored at the start of the string. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function if successful, or nil,error -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ match (spec, line, res, options) -+
    -+
    -+ match a SIP pattern against a string. -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ a SIP pattern -+
    • -+
    • line -+ a string -+
    • -+
    • res -+ a table to receive values -+
    • -+
    • options -+ (optional) option table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ match_at_start (spec, line, res) -+
    -+
    -+ match a SIP pattern against the start of a string. -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ a SIP pattern -+
    • -+
    • line -+ a string -+
    • -+
    • res -+ a table to receive values -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ fields (spec, f) -+
    -+
    -+ given a pattern and a file object, return an iterator over the results -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ a SIP pattern -+
    • -+
    • f -+ a file-like object. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ pattern (spec, fun) -+
    -+
    -+ register a match which will be used in the read function. -+ -+ -+

    Parameters:

    -+
      -+
    • spec -+ string -+ a SIP pattern -+
    • -+
    • fun -+ function -+ a function to be called with the results of the match -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ read (f, matches) -+
    -+
    -+ enter a loop which applies all registered matches to the input file. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ a file-like object -+
    • -+
    • matches -+ array -+ optional list of {spec,fun} pairs, as for pattern above. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.strict.html b/extra/penlight/docs/libraries/pl.strict.html -new file mode 100644 -index 0000000..33a2c11 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.strict.html -@@ -0,0 +1,270 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.strict

    -+

    Checks uses of undeclared global variables.

    -+

    All global variables must be 'declared' through a regular assignment -+ (even assigning nil will do) in a main chunk before being used -+ anywhere or assigned to inside a function. Existing metatables __newindex and __index -+ metamethods are respected.

    -+ -+

    You can set any table to have strict behaviour using strict.module. Creating a new -+ module with strict.closed_module makes the module immune to monkey-patching, if -+ you don't wish to encourage monkey business.

    -+ -+

    If the global PENLIGHT_NO_GLOBAL_STRICT is defined, then this module won't make the -+ global environment strict - if you just want to explicitly set table strictness.

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    module ([name[, mod[, predeclared]]])make an existing table strict.
    make_all_strict (T)make all tables in a table strict.
    closed_module (mod, name)make a new module table which is closed to further changes.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ module ([name[, mod[, predeclared]]]) -+
    -+
    -+ make an existing table strict. -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ name of table -+ (optional) -+
    • -+
    • mod -+ table -+ the table to protect - if nil then we'll return a new table -+ (optional) -+
    • -+
    • predeclared -+ table -+ -+
        -+
      • table of variables that are to be considered predeclared.
      • -+
      -+ -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the given table, or a new table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local M = { hello = "world" }
      -+strict.module ("Awesome_Module", M, {
      -+  Lua = true,  -- defines allowed keys
      -+})
      -+
      -+assert(M.hello == "world")
      -+assert(M.Lua == nil)       -- access allowed, but has no value yet
      -+M.Lua = "Rocks"
      -+assert(M.Lua == "Rocks")
      -+M.not_allowed = "bad boy"  -- throws an error
      -+
    -+ -+
    -+
    -+ -+ make_all_strict (T) -+
    -+
    -+ make all tables in a table strict. -+ So strict.make_all_strict(_G) prevents monkey-patching -+ of any global table -+ -+ -+

    Parameters:

    -+
      -+
    • T -+ table -+ the table containing the tables to protect. Table T itself will NOT be protected. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ closed_module (mod, name) -+
    -+
    -+ make a new module table which is closed to further changes. -+ -+ -+

    Parameters:

    -+
      -+
    • mod -+ table -+ module table -+
    • -+
    • name -+ string -+ module name -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.stringio.html b/extra/penlight/docs/libraries/pl.stringio.html -new file mode 100644 -index 0000000..84e3011 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.stringio.html -@@ -0,0 +1,215 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.stringio

    -+

    Reading and writing strings using file-like objects.

    -+


    -+ -+ -+
    -+f = stringio.open(text)
    -+l1 = f:read()  -- read first line
    -+n,m = f:read ('*n','*n') -- read two numbers
    -+for line in f:lines() do print(line) end -- iterate over all lines
    -+f = stringio.create()
    -+f:write('hello')
    -+f:write('dolly')
    -+assert(f:value(),'hellodolly')
    -+
    -+ -+

    See the Guide.

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    create ()create a file-like object which can be used to construct a string.
    open (s)create a file-like object for reading from a given string.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ create () -+
    -+
    -+ create a file-like object which can be used to construct a string. -+ The resulting object has an extra value() method for -+ retrieving the string value. Implements file:write, file:seek, file:lines, -+ plus an extra writef method which works like utils.printf. -+ -+ -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      f = create(); f:write('hello, dolly\n'); print(f:value())
      -+
    -+ -+
    -+
    -+ -+ open (s) -+
    -+
    -+ create a file-like object for reading from a given string. -+ Implements file:read. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ The input string. -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      fs = open '20 10'; x,y = f:read ('*n','*n'); assert(x == 20 and y == 10)
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.stringx.html b/extra/penlight/docs/libraries/pl.stringx.html -new file mode 100644 -index 0000000..b00666f ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.stringx.html -@@ -0,0 +1,1630 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.stringx

    -+

    Python-style extended string library.

    -+

    see 3.6.1 of the Python reference. -+ If you want to make these available as string methods, then say -+ stringx.import() to bring them into the standard string table.

    -+ -+

    See the Guide

    -+ -+

    Dependencies: pl.utils, pl.types

    -+ -+ -+

    String Predicates

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    isalpha (s)does s only contain alphabetic characters?
    isdigit (s)does s only contain digits?
    isalnum (s)does s only contain alphanumeric characters?
    isspace (s)does s only contain whitespace?
    islower (s)does s only contain lower case characters?
    isupper (s)does s only contain upper case characters?
    startswith (s, prefix)does s start with prefix or one of prefixes?
    endswith (s, suffix)does s end with suffix or one of suffixes?
    -+

    Strings and Lists

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    join (s, seq)concatenate the strings using this string as a delimiter.
    splitlines (s[, keep_ends])Split a string into a list of lines.
    split (s[, re[, n]])split a string into a list of strings using a delimiter.
    expandtabs (s, tabsize)replace all tabs in s with tabsize spaces.
    -+

    Finding and Replacing

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    lfind (s, sub[, first[, last]])find index of first instance of sub in s from the left.
    rfind (s, sub[, first[, last]])find index of first instance of sub in s from the right.
    replace (s, old, new[, n])replace up to n instances of old by new in the string s.
    count (s, sub[, allow_overlap])count all instances of substring in string.
    -+

    Stripping and Justifying

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    ljust (s, w[, ch=' '])left-justify s with width w.
    rjust (s, w[, ch=' '])right-justify s with width w.
    center (s, w[, ch=' '])center-justify s with width w.
    lstrip (s[, chrs='%s'])trim any characters on the left of s.
    rstrip (s[, chrs='%s'])trim any characters on the right of s.
    strip (s[, chrs='%s'])trim any characters on both left and right of s.
    -+

    Partitioning Strings

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    splitv (s[, re='%s'])split a string using a pattern.
    partition (s, ch)partition the string using first occurrence of a delimiter
    rpartition (s, ch)partition the string p using last occurrence of a delimiter
    at (s, idx)return the 'character' at the index.
    -+

    Text handling

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    indent (s, n[, ch=' '])indent a multiline string.
    dedent (s)dedent a multiline string by removing any initial indent.
    wrap (s[, width=70[, breaklong=false]])format a paragraph into lines so that they fit into a line width.
    fill (s[, width=70[, breaklong=false]])format a paragraph so that it fits into a line width.
    -+

    Template

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Template (tmpl)Creates a new Template class.
    Template:substitute (tbl)substitute values into a template, throwing an error.
    Template:safe_substitute (tbl)substitute values into a template.
    Template:indent_substitute (tbl)substitute values into a template, preserving indentation.
    -+

    Miscellaneous

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    lines (s)return an iterator over all lines in a string
    title (s)initial word letters uppercase ('title case').
    shorten (s, w, tail)Return a shortened version of a string.
    quote_string (s)Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
    format_operator ()Python-style formatting operator.
    import ()import the stringx functions into the global string (meta)table
    -+ -+
    -+
    -+ -+ -+

    String Predicates

    -+ -+
    -+
    -+ -+ isalpha (s) -+
    -+
    -+ does s only contain alphabetic characters? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isdigit (s) -+
    -+
    -+ does s only contain digits? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isalnum (s) -+
    -+
    -+ does s only contain alphanumeric characters? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isspace (s) -+
    -+
    -+ does s only contain whitespace? -+ Matches on pattern '%s' so matches space, newline, tabs, etc. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ islower (s) -+
    -+
    -+ does s only contain lower case characters? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ isupper (s) -+
    -+
    -+ does s only contain upper case characters? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ startswith (s, prefix) -+
    -+
    -+ does s start with prefix or one of prefixes? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    • prefix -+ a string or an array of strings -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ endswith (s, suffix) -+
    -+
    -+ does s end with suffix or one of suffixes? -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ a string -+
    • -+
    • suffix -+ a string or an array of strings -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Strings and Lists

    -+ -+
    -+
    -+ -+ join (s, seq) -+
    -+
    -+ concatenate the strings using this string as a delimiter. -+ Note that the arguments are reversed from string.concat. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • seq -+ a table of strings or numbers -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.join(' ', {1,2,3}) == '1 2 3'
      -+
    -+ -+
    -+
    -+ -+ splitlines (s[, keep_ends]) -+
    -+
    -+ Split a string into a list of lines. -+ "\r", "\n", and "\r\n" are considered line ends. -+ They are not included in the lines unless keepends is passed. -+ Terminal line end does not produce an extra line. -+ Splitting an empty string results in an empty list. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string. -+
    • -+
    • keep_ends -+ boolean -+ include line ends. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ List of lines -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ split (s[, re[, n]]) -+
    -+
    -+ split a string into a list of strings using a delimiter. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • re -+ string -+ a delimiter (defaults to whitespace) -+ (optional) -+
    • -+
    • n -+ integer -+ maximum number of results -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ List -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • #(stringx.split('one two')) == 2
    • -+
    • stringx.split('one,two,three', ',') == List{'one','two','three'}
    • -+
    • stringx.split('one,two,three', ',', 2) == List{'one','two,three'}
    • -+
    -+ -+
    -+
    -+ -+ expandtabs (s, tabsize) -+
    -+
    -+ replace all tabs in s with tabsize spaces. If not specified, tabsize defaults to 8. -+ Tab stops will be honored. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • tabsize -+ integer -+ [opt=8] number of spaces to expand each tab -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ expanded string -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • stringx.expandtabs('\tone,two,three', 4)   == '    one,two,three'
    • -+
    • stringx.expandtabs('  \tone,two,three', 4) == '    one,two,three'
    • -+
    -+ -+
    -+
    -+

    Finding and Replacing

    -+ -+
    -+
    -+ -+ lfind (s, sub[, first[, last]]) -+
    -+
    -+ find index of first instance of sub in s from the left. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • sub -+ string -+ substring -+
    • -+
    • first -+ integer -+ first index -+ (optional) -+
    • -+
    • last -+ integer -+ last index -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ start index, or nil if not found -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ rfind (s, sub[, first[, last]]) -+
    -+
    -+ find index of first instance of sub in s from the right. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • sub -+ string -+ substring -+
    • -+
    • first -+ integer -+ first index -+ (optional) -+
    • -+
    • last -+ integer -+ last index -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ start index, or nil if not found -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ replace (s, old, new[, n]) -+
    -+
    -+ replace up to n instances of old by new in the string s. -+ If n is not present, replace all instances. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • old -+ string -+ the target substring -+
    • -+
    • new -+ string -+ the substitution -+
    • -+
    • n -+ integer -+ optional maximum number of substitutions -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ result string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ count (s, sub[, allow_overlap]) -+
    -+
    -+ count all instances of substring in string. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • sub -+ string -+ substring -+
    • -+
    • allow_overlap -+ boolean -+ allow matches to overlap -+ (optional) -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      assert(stringx.count('banana', 'ana') == 1)
      -+assert(stringx.count('banana', 'ana', true) == 2)
      -+
    -+ -+
    -+
    -+

    Stripping and Justifying

    -+ -+
    -+
    -+ -+ ljust (s, w[, ch=' ']) -+
    -+
    -+ left-justify s with width w. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • w -+ integer -+ width of justification -+
    • -+
    • ch -+ string -+ padding character -+ (default ' ') -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.ljust('hello', 10, '*') == '*****hello'
      -+
    -+ -+
    -+
    -+ -+ rjust (s, w[, ch=' ']) -+
    -+
    -+ right-justify s with width w. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • w -+ integer -+ width of justification -+
    • -+
    • ch -+ string -+ padding character -+ (default ' ') -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.rjust('hello', 10, '*') == 'hello*****'
      -+
    -+ -+
    -+
    -+ -+ center (s, w[, ch=' ']) -+
    -+
    -+ center-justify s with width w. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • w -+ integer -+ width of justification -+
    • -+
    • ch -+ string -+ padding character -+ (default ' ') -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.center('hello', 10, '*') == '**hello***'
      -+
    -+ -+
    -+
    -+ -+ lstrip (s[, chrs='%s']) -+
    -+
    -+ trim any characters on the left of s. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • chrs -+ string -+ default any whitespace character, -+ but can be a string of characters to be trimmed -+ (default '%s') -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ rstrip (s[, chrs='%s']) -+
    -+
    -+ trim any characters on the right of s. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • chrs -+ string -+ default any whitespace character, -+ but can be a string of characters to be trimmed -+ (default '%s') -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ strip (s[, chrs='%s']) -+
    -+
    -+ trim any characters on both left and right of s. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • chrs -+ string -+ default any whitespace character, -+ but can be a string of characters to be trimmed -+ (default '%s') -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.strip('  --== Hello ==--  ', "- =")  --> 'Hello'
      -+
    -+ -+
    -+
    -+

    Partitioning Strings

    -+ -+
    -+
    -+ -+ splitv (s[, re='%s']) -+
    -+
    -+ split a string using a pattern. Note that at least one value will be returned! -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • re -+ string -+ a Lua string pattern (defaults to whitespace) -+ (default '%s') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the parts of the string -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      a,b = line:splitv('=')
      -+
    -+ -+
    -+
    -+ -+ partition (s, ch) -+
    -+
    -+ partition the string using first occurrence of a delimiter -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • ch -+ string -+ delimiter (match as plain string, no patterns) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ part before ch
    2. -+
    3. -+ ch
    4. -+
    5. -+ part after ch
    6. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • {stringx.partition('a,b,c', ','))} == {'a', ',', 'b,c'}
    • -+
    • {stringx.partition('abc', 'x'))} == {'abc', '', ''}
    • -+
    -+ -+
    -+
    -+ -+ rpartition (s, ch) -+
    -+
    -+ partition the string p using last occurrence of a delimiter -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • ch -+ string -+ delimiter (match as plain string, no patterns) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ part before ch
    2. -+
    3. -+ ch
    4. -+
    5. -+ part after ch
    6. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • {stringx.rpartition('a,b,c', ','))} == {'a,b', ',', 'c'}
    • -+
    • {stringx.rpartition('abc', 'x'))} == {'', '', 'abc'}
    • -+
    -+ -+
    -+
    -+ -+ at (s, idx) -+
    -+
    -+ return the 'character' at the index. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • idx -+ integer -+ an index (can be negative) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a substring of length 1 if successful, empty string otherwise. -+
    -+ -+ -+ -+ -+
    -+
    -+

    Text handling

    -+ -+
    -+
    -+ -+ indent (s, n[, ch=' ']) -+
    -+
    -+ indent a multiline string. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the (multiline) string -+
    • -+
    • n -+ integer -+ the size of the indent -+
    • -+
    • ch -+ string -+ the character to use when indenting -+ (default ' ') -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ indented string -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ dedent (s) -+
    -+
    -+ dedent a multiline string by removing any initial indent. -+ useful when working with [[..]] strings. -+ Empty lines are ignored. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the (multiline) string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string with initial indent zero. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local s = dedent [[
      -+         One
      -+
      -+       Two
      -+
      -+     Three
      -+]]
      -+assert(s == [[
      -+    One
      -+
      -+  Two
      -+
      -+Three
      -+]])
      -+
    -+ -+
    -+
    -+ -+ wrap (s[, width=70[, breaklong=false]]) -+
    -+
    -+ format a paragraph into lines so that they fit into a line width. -+ It will not break long words by default, so lines can be over the length -+ to that extent. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string to format -+
    • -+
    • width -+ integer -+ the margin width -+ (default 70) -+
    • -+
    • breaklong -+ boolean -+ if truthy, words longer than the width given will be forced split. -+ (default false) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list of lines (List object), use fill to return a string instead of a List. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ fill (s[, width=70[, breaklong=false]]) -+
    -+
    -+ format a paragraph so that it fits into a line width. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string to format -+
    • -+
    • width -+ integer -+ the margin width -+ (default 70) -+
    • -+
    • breaklong -+ boolean -+ if truthy, words longer than the width given will be forced split. -+ (default false) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string, use wrap to return a list of lines instead of a string. -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+

    Template

    -+ -+
    -+
    -+ -+ Template (tmpl) -+
    -+
    -+ Creates a new Template class. -+ This is a shortcut to Template.new(tmpl). -+ -+ -+

    Parameters:

    -+
      -+
    • tmpl -+ string -+ the template string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ Template -+ -+ -+ -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Template:substitute (tbl) -+
    -+
    -+ substitute values into a template, throwing an error. -+ This will throw an error if no name is found. -+ -+ -+

    Parameters:

    -+
      -+
    • tbl -+ table -+ a table of name-value pairs. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string with place holders substituted -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Template:safe_substitute (tbl) -+
    -+
    -+ substitute values into a template. -+ This version just passes unknown names through. -+ -+ -+

    Parameters:

    -+
      -+
    • tbl -+ table -+ a table of name-value pairs. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string with place holders substituted -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Template:indent_substitute (tbl) -+
    -+
    -+ substitute values into a template, preserving indentation.
    -+ If the value is a multiline string or a template, it will insert -+ the lines at the correct indentation.
    -+ Furthermore, if a template, then that template will be substituted -+ using the same table. -+ -+ -+

    Parameters:

    -+
      -+
    • tbl -+ table -+ a table of name-value pairs. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ string with place holders substituted -+
    -+ -+ -+ -+ -+
    -+
    -+

    Miscellaneous

    -+ -+
    -+
    -+ -+ lines (s) -+
    -+
    -+ return an iterator over all lines in a string -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local line_no = 1
      -+for line in stringx.lines(some_text) do
      -+  print(line_no, line)
      -+  line_no = line_no + 1
      -+end
      -+
    -+ -+
    -+
    -+ -+ title (s) -+
    -+
    -+ initial word letters uppercase ('title case'). -+ Here 'words' mean chunks of non-space characters. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string with each word's first letter uppercase -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      stringx.title("hello world") == "Hello World")
      -+
    -+ -+
    -+
    -+ -+ shorten (s, w, tail) -+
    -+
    -+ Return a shortened version of a string. -+ Fits string within w characters. Removed characters are marked with ellipsis. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • w -+ integer -+ the maximum size allowed -+
    • -+
    • tail -+ boolean -+ true if we want to show the end of the string (head otherwise) -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • ('1234567890'):shorten(8) == '12345...'
    • -+
    • ('1234567890'):shorten(8, true) == '...67890'
    • -+
    • ('1234567890'):shorten(20) == '1234567890'
    • -+
    -+ -+
    -+
    -+ -+ quote_string (s) -+
    -+
    -+ Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ The string to be quoted. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ The quoted string. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ format_operator () -+
    -+
    -+ Python-style formatting operator. -+ Calling text.format_operator() overloads the % operator for strings to give -+ Python/Ruby style formatted output. -+ This is extended to also do template-like substitution for map-like data.

    -+ -+

    Note this goes further than the original, and will allow these cases:

    -+ -+
      -+
    1. a single value
    2. -+
    3. a list of values
    4. -+
    5. a map of var=value pairs
    6. -+
    7. a function, as in gsub
    8. -+
    -+ -+

    For the second two cases, it uses $-variable substitution.

    -+ -+

    When called, this function will monkey-patch the global string metatable by -+ adding a __mod method.

    -+ -+

    See the lua-users wiki -+ -+ -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      require 'pl.text'.format_operator()
      -+local out1 = '%s = %5.3f' % {'PI',math.pi}                   --> 'PI = 3.142'
      -+local out2 = '$name = $value' % {name='dog',value='Pluto'}   --> 'dog = Pluto'
      -+
    -+ -+
    -+
    -+ -+ import () -+
    -+
    -+ import the stringx functions into the global string (meta)table -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.tablex.html b/extra/penlight/docs/libraries/pl.tablex.html -new file mode 100644 -index 0000000..48eea54 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.tablex.html -@@ -0,0 +1,1980 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.tablex

    -+

    Extended operations on Lua tables.

    -+

    See the Guide

    -+ -+

    Dependencies: pl.utils, pl.types

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    size (t)total number of elements in this table.
    index_by (tbl, idx)return a list of all values in a table indexed by another list.
    transform (fun, t, ...)apply a function to all values of a table, in-place.
    range (start, finish[, step=1])generate a table of all numbers in a range.
    reduce (fun, t, memo)'reduce' a list using a binary function.
    index_map (t)create an index map from a list-like table.
    makeset (t)create a set from a list-like table.
    union (t1, t2)the union of two map-like tables.
    intersection (t1, t2)the intersection of two map-like tables.
    count_map (t, cmp)A table where the key/values are the values and value counts of the table.
    set (t, val[, i1=1[, i2=#t]])set an array range to a value.
    new (n, val)create a new array of specified size with initial value.
    clear (t, istart)clear out the contents of a table.
    removevalues (t, i1, i2)remove a range of values from a table.
    readonly (t)modifies a table to be read only.
    -+

    Copying

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    update (t1, t2)copy a table into another, in-place.
    copy (t)make a shallow copy of a table
    deepcopy (t)make a deep copy of a table, recursively copying all the keys and fields.
    icopy (dest, src[, idest=1[, isrc=1[, nsrc=#src]]])copy an array into another one, clearing dest after idest+nsrc, if necessary.
    move (dest, src[, idest=1[, isrc=1[, nsrc=#src]]])copy an array into another one.
    insertvalues (t[, position], values)insert values into a table.
    -+

    Comparing

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    deepcompare (t1, t2[, ignore_mt[, eps]])compare two values.
    compare (t1, t2, cmp)compare two arrays using a predicate.
    compare_no_order (t1, t2, cmp)compare two list-like tables using an optional predicate, without regard for element order.
    -+

    Finding

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    find (t, val, idx)return the index of a value in a list.
    rfind (t, val, idx)return the index of a value in a list, searching from the end.
    find_if (t, cmp, arg)return the index (or key) of a value in a table using a comparison function.
    search (t, value[, exclude])find a value in a table by recursive search.
    -+

    MappingAndFiltering

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    map (fun, t, ...)apply a function to all values of a table.
    imap (fun, t, ...)apply a function to all values of a list.
    map_named_method (name, t, ...)apply a named method to values from a table.
    map2 (fun, t1, t2, ...)apply a function to values from two tables.
    imap2 (fun, t1, t2, ...)apply a function to values from two arrays.
    mapn (fun, ..., fun)Apply a function to a number of tables.
    pairmap (fun, t, ...)call the function with the key and value pairs from a table.
    filter (t, pred, arg)filter an array's values using a predicate function
    -+

    Iterating

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    foreach (t, fun, ...)apply a function to all elements of a table.
    foreachi (t, fun, ...)apply a function to all elements of a list-like table in order.
    sort (t, f)return an iterator to a table sorted by its keys
    sortv (t, f)return an iterator to a table sorted by its values
    -+

    Extraction

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    keys (t)return all the keys of a table in arbitrary order.
    values (t)return all the values of the table in arbitrary order
    sub (t, first, last)Extract a range from a table, like 'string.sub'.
    -+

    Merging

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    merge (t1, t2, dup)combine two tables, either as union or intersection.
    difference (s1, s2, symm)a new table which is the difference of two tables.
    zip (...)return a table where each element is a table of the ith values of an arbitrary -+ number of tables.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ size (t) -+
    -+
    -+ total number of elements in this table. -+ Note that this is distinct from #t, which is the number -+ of values in the array part; this value will always -+ be greater or equal. The difference gives the size of -+ the hash part, for practical purposes. Works for any -+ object with a __pairs metamethod. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ a table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the size -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ index_by (tbl, idx) -+
    -+
    -+ return a list of all values in a table indexed by another list. -+ -+ -+

    Parameters:

    -+
      -+
    • tbl -+ table -+ a table -+
    • -+
    • idx -+ array -+ an index table (a list of keys) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list-like table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • index_by({10,20,30,40},{2,4}) == {20,40}
    • -+
    • index_by({one=1,two=2,three=3},{'one','three'}) == {1,3}
    • -+
    -+ -+
    -+
    -+ -+ transform (fun, t, ...) -+
    -+
    -+ apply a function to all values of a table, in-place. -+ Any extra arguments are passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ A function that takes at least one argument -+
    • -+
    • t -+ table -+ a table -+
    • -+
    • ... -+ extra arguments passed to fun -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ range (start, finish[, step=1]) -+
    -+
    -+ generate a table of all numbers in a range. -+ This is consistent with a numerical for loop. -+ -+ -+

    Parameters:

    -+
      -+
    • start -+ integer -+ number -+
    • -+
    • finish -+ integer -+ number -+
    • -+
    • step -+ integer -+ make this negative for start < finish -+ (default 1) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ reduce (fun, t, memo) -+
    -+
    -+ 'reduce' a list using a binary function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of two arguments -+
    • -+
    • t -+ array -+ a list-like table -+
    • -+
    • memo -+ array -+ optional initial memo value. Defaults to first value in table. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the result of the function -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      reduce('+',{1,2,3,4}) == 10
      -+
    -+ -+
    -+
    -+ -+ index_map (t) -+
    -+
    -+ create an index map from a list-like table. The original values become keys, -+ and the associated values are the indices into the original list. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a map-like table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ makeset (t) -+
    -+
    -+ create a set from a list-like table. A set is a table where the original values -+ become keys, and the associated values are all true. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a set (a map-like table) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ union (t1, t2) -+
    -+
    -+ the union of two map-like tables. -+ If there are duplicate keys, the second table wins. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ table -+ a table -+
    • -+
    • t2 -+ table -+ a table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ tab -+ -+ -+ -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ intersection (t1, t2) -+
    -+
    -+ the intersection of two map-like tables. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ table -+ a table -+
    • -+
    • t2 -+ table -+ a table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ tab -+ -+ -+ -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ count_map (t, cmp) -+
    -+
    -+ A table where the key/values are the values and value counts of the table. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    • cmp -+ function -+ a function that defines equality (otherwise uses ==) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a map-like table -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ set (t, val[, i1=1[, i2=#t]]) -+
    -+
    -+ set an array range to a value. If it's a function we use the result -+ of applying it to the indices. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    • val -+ a value -+
    • -+
    • i1 -+ integer -+ start range -+ (default 1) -+
    • -+
    • i2 -+ integer -+ end range -+ (default #t) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ new (n, val) -+
    -+
    -+ create a new array of specified size with initial value. -+ -+ -+

    Parameters:

    -+
      -+
    • n -+ integer -+ size -+
    • -+
    • val -+ initial value (can be nil, but don't expect # to work!) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ clear (t, istart) -+
    -+
    -+ clear out the contents of a table. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list -+
    • -+
    • istart -+ optional start position -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ removevalues (t, i1, i2) -+
    -+
    -+ remove a range of values from a table. -+ End of range may be negative. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    • i1 -+ integer -+ start index -+
    • -+
    • i2 -+ integer -+ end index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ readonly (t) -+
    -+
    -+ modifies a table to be read only. -+ This only offers weak protection. Tables can still be modified with -+ table.insert and rawset.

    -+ -+

    NOTE: for Lua 5.1 length, pairs and ipairs will not work, since the -+ equivalent metamethods are only available in Lua 5.2 and newer. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the table read only (a proxy). -+
    -+ -+ -+ -+ -+
    -+
    -+

    Copying

    -+ -+
    -+
    -+ -+ update (t1, t2) -+
    -+
    -+ copy a table into another, in-place. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ table -+ destination table -+
    • -+
    • t2 -+ table -+ source (actually any iterable object) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ first table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ copy (t) -+
    -+
    -+ make a shallow copy of a table -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ an iterable source -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ new table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ deepcopy (t) -+
    -+
    -+ make a deep copy of a table, recursively copying all the keys and fields. -+ This supports cycles in tables; cycles will be reproduced in the copy. -+ This will also set the copied table's metatable to that of the original. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ A table -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ new table -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ icopy (dest, src[, idest=1[, isrc=1[, nsrc=#src]]]) -+
    -+
    -+ copy an array into another one, clearing dest after idest+nsrc, if necessary. -+ -+ -+

    Parameters:

    -+
      -+
    • dest -+ array -+ a list-like table -+
    • -+
    • src -+ array -+ a list-like table -+
    • -+
    • idest -+ integer -+ where to start copying values into destination -+ (default 1) -+
    • -+
    • isrc -+ integer -+ where to start copying values from source -+ (default 1) -+
    • -+
    • nsrc -+ integer -+ number of elements to copy from source -+ (default #src) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ move (dest, src[, idest=1[, isrc=1[, nsrc=#src]]]) -+
    -+
    -+ copy an array into another one. -+ -+ -+

    Parameters:

    -+
      -+
    • dest -+ array -+ a list-like table -+
    • -+
    • src -+ array -+ a list-like table -+
    • -+
    • idest -+ integer -+ where to start copying values into destination -+ (default 1) -+
    • -+
    • isrc -+ integer -+ where to start copying values from source -+ (default 1) -+
    • -+
    • nsrc -+ integer -+ number of elements to copy from source -+ (default #src) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ insertvalues (t[, position], values) -+
    -+
    -+ insert values into a table. -+ similar to table.insert but inserts values from given table values, -+ not the object itself, into table t at position pos. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ the list -+
    • -+
    • position -+ integer -+ (default is at end) -+ (optional) -+
    • -+
    • values -+ array -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Comparing

    -+ -+
    -+
    -+ -+ deepcompare (t1, t2[, ignore_mt[, eps]]) -+
    -+
    -+ compare two values. -+ if they are tables, then compare their keys and fields recursively. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ A value -+
    • -+
    • t2 -+ A value -+
    • -+
    • ignore_mt -+ boolean -+ if true, ignore __eq metamethod (default false) -+ (optional) -+
    • -+
    • eps -+ number -+ if defined, then used for any number comparisons -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ compare (t1, t2, cmp) -+
    -+
    -+ compare two arrays using a predicate. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ array -+ an array -+
    • -+
    • t2 -+ array -+ an array -+
    • -+
    • cmp -+ function -+ A comparison function; bool = cmp(t1_value, t2_value) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true or false -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      assert(tablex.compare({ 1, 2, 3 }, { 1, 2, 3 }, "=="))
      -+
      -+assert(tablex.compare(
      -+   {1,2,3, hello = "world"},  -- fields are not compared!
      -+   {1,2,3}, function(v1, v2) return v1 == v2 end)
      -+
    -+ -+
    -+
    -+ -+ compare_no_order (t1, t2, cmp) -+
    -+
    -+ compare two list-like tables using an optional predicate, without regard for element order. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ array -+ a list-like table -+
    • -+
    • t2 -+ array -+ a list-like table -+
    • -+
    • cmp -+ A comparison function (may be nil) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Finding

    -+ -+
    -+
    -+ -+ find (t, val, idx) -+
    -+
    -+ return the index of a value in a list. -+ Like string.find, there is an optional index to start searching, -+ which can be negative. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ A list-like table -+
    • -+
    • val -+ A value -+
    • -+
    • idx -+ integer -+ index to start; -1 means last element,etc (default 1) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ index of value or nil if not found -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • find({10,20,30},20) == 2
    • -+
    • find({'a','b','a','c'},'a',2) == 3
    • -+
    -+ -+
    -+
    -+ -+ rfind (t, val, idx) -+
    -+
    -+ return the index of a value in a list, searching from the end. -+ Like string.find, there is an optional index to start searching, -+ which can be negative. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ A list-like table -+
    • -+
    • val -+ A value -+
    • -+
    • idx -+ index to start; -1 means last element,etc (default #t) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ index of value or nil if not found -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      rfind({10,10,10},10) == 3
      -+
    -+ -+
    -+
    -+ -+ find_if (t, cmp, arg) -+
    -+
    -+ return the index (or key) of a value in a table using a comparison function.

    -+ -+

    NOTE: the 2nd return value of this function, the value returned -+ by the comparison function, has a limitation that it cannot be false. -+ Because if it is, then it indicates the comparison failed, and the -+ function will continue the search. See examples. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ A table -+
    • -+
    • cmp -+ function -+ A comparison function -+
    • -+
    • arg -+ an optional second argument to the function -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ index of value, or nil if not found
    2. -+
    3. -+ value returned by comparison function (cannot be false!)
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      -- using an operator
      -+local lst = { "Rudolph", true, false, 15 }
      -+local idx, cmp_result = tablex.rfind(lst, "==", "Rudolph")
      -+assert(idx == 1)
      -+assert(cmp_result == true)
      -+
      -+local idx, cmp_result = tablex.rfind(lst, "==", false)
      -+assert(idx == 3)
      -+assert(cmp_result == true)       -- looking up 'false' works!
      -+
      -+-- using a function returning the value looked up
      -+local cmp = function(v1, v2) return v1 == v2 and v2 end
      -+local idx, cmp_result = tablex.rfind(lst, cmp, "Rudolph")
      -+assert(idx == 1)
      -+assert(cmp_result == "Rudolph")  -- the value is returned
      -+
      -+-- NOTE: this fails, since 'false' cannot be returned!
      -+local idx, cmp_result = tablex.rfind(lst, cmp, false)
      -+assert(idx == nil)               -- looking up 'false' failed!
      -+assert(cmp_result == nil)
      -+
    -+ -+
    -+
    -+ -+ search (t, value[, exclude]) -+
    -+
    -+ find a value in a table by recursive search. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table -+
    • -+
    • value -+ the value -+
    • -+
    • exclude -+ array -+ any tables to avoid searching -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a fieldspec, e.g. 'a.b' or 'math.sin' -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      search(_G,math.sin,{package.path}) == 'math.sin'
      -+
    -+ -+
    -+
    -+

    MappingAndFiltering

    -+ -+
    -+
    -+ -+ map (fun, t, ...) -+
    -+
    -+ apply a function to all values of a table. -+ This returns a table of the results. -+ Any extra arguments are passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ A function that takes at least one argument -+
    • -+
    • t -+ table -+ A table -+
    • -+
    • ... -+ optional arguments -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4}
      -+
    -+ -+
    -+
    -+ -+ imap (fun, t, ...) -+
    -+
    -+ apply a function to all values of a list. -+ This returns a table of the results. -+ Any extra arguments are passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ A function that takes at least one argument -+
    • -+
    • t -+ array -+ a table (applies to array part) -+
    • -+
    • ... -+ optional arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list-like table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900}
      -+
    -+ -+
    -+
    -+ -+ map_named_method (name, t, ...) -+
    -+
    -+ apply a named method to values from a table. -+ -+ -+

    Parameters:

    -+
      -+
    • name -+ string -+ the method name -+
    • -+
    • t -+ array -+ a list-like table -+
    • -+
    • ... -+ any extra arguments to the method -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a List with the results of the method (1st result only) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local Car = {}
      -+Car.__index = Car
      -+function Car.new(car)
      -+  return setmetatable(car or {}, Car)
      -+end
      -+Car.speed = 0
      -+function Car:faster(increase)
      -+  self.speed = self.speed + increase
      -+  return self.speed
      -+end
      -+
      -+local ferrari = Car.new{ name = "Ferrari" }
      -+local lamborghini = Car.new{ name = "Lamborghini", speed = 50 }
      -+local cars = { ferrari, lamborghini }
      -+
      -+assert(ferrari.speed == 0)
      -+assert(lamborghini.speed == 50)
      -+tablex.map_named_method("faster", cars, 10)
      -+assert(ferrari.speed == 10)
      -+assert(lamborghini.speed == 60)
      -+
    -+ -+
    -+
    -+ -+ map2 (fun, t1, t2, ...) -+
    -+
    -+ apply a function to values from two tables. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of at least two arguments -+
    • -+
    • t1 -+ table -+ a table -+
    • -+
    • t2 -+ table -+ a table -+
    • -+
    • ... -+ extra arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44}
      -+
    -+ -+
    -+
    -+ -+ imap2 (fun, t1, t2, ...) -+
    -+
    -+ apply a function to values from two arrays. -+ The result will be the length of the shortest array. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ a function of at least two arguments -+
    • -+
    • t1 -+ array -+ a list-like table -+
    • -+
    • t2 -+ array -+ a list-like table -+
    • -+
    • ... -+ extra arguments -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23}
      -+
    -+ -+
    -+
    -+ -+ mapn (fun, ..., fun) -+
    -+
    -+ Apply a function to a number of tables. -+ A more general version of map -+ The result is a table containing the result of applying that function to the -+ ith value of each table. Length of output list is the minimum length of all the lists -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ A function that takes as many arguments as there are tables -+
    • -+
    • ... -+ table -+ n tables -+
    • -+
    • fun -+ A function that takes as many arguments as there are tables -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333}
    • -+
    • mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is    {100,200,300}
    • -+
    -+ -+
    -+
    -+ -+ pairmap (fun, t, ...) -+
    -+
    -+ call the function with the key and value pairs from a table. -+ The function can return a value and a key (note the order!). If both -+ are not nil, then this pair is inserted into the result: if the key already exists, we convert the value for that -+ key into a table and append into it. If only value is not nil, then it is appended to the result. -+ -+ -+

    Parameters:

    -+
      -+
    • fun -+ function -+ A function which will be passed each key and value as arguments, plus any extra arguments to pairmap. -+
    • -+
    • t -+ table -+ A table -+
    • -+
    • ... -+ optional arguments -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
    • pairmap(function(k,v) return v end,{fred=10,bonzo=20}) is {10,20} _or_ {20,10}
    • -+
    • pairmap(function(k,v) return {k,v},k end,{one=1,two=2}) is {one={'one',1},two={'two',2}}
    • -+
    -+ -+
    -+
    -+ -+ filter (t, pred, arg) -+
    -+
    -+ filter an array's values using a predicate function -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    • pred -+ function -+ a boolean function -+
    • -+
    • arg -+ optional argument to be passed as second argument of the predicate -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Iterating

    -+ -+
    -+
    -+ -+ foreach (t, fun, ...) -+
    -+
    -+ apply a function to all elements of a table. -+ The arguments to the function will be the value, -+ the key and finally any extra arguments passed to this function. -+ Note that the Lua 5.0 function table.foreach passed the key first. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ a table -+
    • -+
    • fun -+ function -+ a function on the elements; function(value, key, ...) -+
    • -+
    • ... -+ extra arguments passed to fun -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ foreachi (t, fun, ...) -+
    -+
    -+ apply a function to all elements of a list-like table in order. -+ The arguments to the function will be the value, -+ the index and finally any extra arguments passed to this function -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a table -+
    • -+
    • fun -+ function -+ a function with at least one argument -+
    • -+
    • ... -+ optional arguments -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ sort (t, f) -+
    -+
    -+ return an iterator to a table sorted by its keys -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table -+
    • -+
    • f -+ function -+ an optional comparison function (f(x,y) is true if x < y) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator to traverse elements sorted by the keys -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      for k,v in tablex.sort(t) do print(k,v) end
      -+
    -+ -+
    -+
    -+ -+ sortv (t, f) -+
    -+
    -+ return an iterator to a table sorted by its values -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table -+
    • -+
    • f -+ function -+ an optional comparison function (f(x,y) is true if x < y) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an iterator to traverse elements sorted by the values -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      for k,v in tablex.sortv(t) do print(k,v) end
      -+
    -+ -+
    -+
    -+

    Extraction

    -+ -+
    -+
    -+ -+ keys (t) -+
    -+
    -+ return all the keys of a table in arbitrary order. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ A list-like table where the values are the keys of the input table -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ values (t) -+
    -+
    -+ return all the values of the table in arbitrary order -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ A list-like table where the values are the values of the input table -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ sub (t, first, last) -+
    -+
    -+ Extract a range from a table, like 'string.sub'. -+ If first or last are negative then they are relative to the end of the list -+ eg. sub(t,-2) gives last 2 entries in a list, and -+ sub(t,-4,-2) gives from -4th to -2nd -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ array -+ a list-like table -+
    • -+
    • first -+ integer -+ An index -+
    • -+
    • last -+ integer -+ An index -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a new List -+
    -+ -+ -+ -+ -+
    -+
    -+

    Merging

    -+ -+
    -+
    -+ -+ merge (t1, t2, dup) -+
    -+
    -+ combine two tables, either as union or intersection. Corresponds to -+ set operations for sets () but more general. Not particularly -+ useful for list-like tables. -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ table -+ a table -+
    • -+
    • t2 -+ table -+ a table -+
    • -+
    • dup -+ boolean -+ true for a union, false for an intersection. -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
    • merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34}
    • -+
    • merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23}
    • -+
    -+ -+
    -+
    -+ -+ difference (s1, s2, symm) -+
    -+
    -+ a new table which is the difference of two tables. -+ With sets (where the values are all true) this is set difference and -+ symmetric difference depending on the third parameter. -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ table -+ a map-like table or set -+
    • -+
    • s2 -+ table -+ a map-like table or set -+
    • -+
    • symm -+ boolean -+ symmetric difference (default false) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a map-like table or set -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ zip (...) -+
    -+
    -+ return a table where each element is a table of the ith values of an arbitrary -+ number of tables. It is equivalent to a matrix transpose. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ array -+ arrays to be zipped -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}}
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.template.html b/extra/penlight/docs/libraries/pl.template.html -new file mode 100644 -index 0000000..73658aa ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.template.html -@@ -0,0 +1,427 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.template

    -+

    A template preprocessor.

    -+

    Originally by Ricki Lake

    -+ -+

    There are two rules:

    -+ -+
      -+
    • lines starting with # are Lua
    • -+
    • otherwise, $(expr) is the result of evaluating expr
    • -+
    -+ -+

    Example:

    -+ -+ -+
    -+#  for i = 1,3 do
    -+   $(i) Hello, Word!
    -+#  end
    -+===>
    -+1 Hello, Word!
    -+2 Hello, Word!
    -+3 Hello, Word!
    -+
    -+ -+

    Other escape characters can be used, when the defaults conflict -+ with the output language.

    -+ -+ -+
    -+> for _,n in pairs{'one','two','three'} do
    -+static int l_${n} (luaState *state);
    -+> end
    -+
    -+ -+

    See the Guide.

    -+ -+

    Dependencies: pl.utils

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    substitute (str[, env])expand the template using the specified environment.
    ct:render ([env[, parent[, db]]])executes the previously compiled template and renders it.
    compile (str[, opts])compiles the template.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ substitute (str[, env]) -+
    -+
    -+ expand the template using the specified environment. -+ This function will compile and render the template. For more performant -+ recurring usage use the two step approach by using compile and ct:render. -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ the template string -+
    • -+
    • env the environment. This table has the following special fields: -+
        -+
      • _parent -+ table -+ continue looking up in this table (e.g. _parent=_G). -+ (default nil) -+
      • -+
      • _brackets -+ string -+ bracket pair that wraps inline Lua expressions. -+ (default "()") -+
      • -+
      • _escape -+ string -+ character marking Lua lines. -+ (default "#") -+
      • -+
      • _inline_escape -+ string -+ character marking inline Lua expression. -+ (default "$") -+
      • -+
      • _chunk_name -+ string -+ chunk name for loaded templates, used if there -+ is an error in Lua code. -+ (default "TMP") -+
      • -+
      • _debug -+ boolean -+ if truthy, the generated code will be printed upon a render error. -+ (default false) -+
      • -+
      -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ string -+ render result
    2. -+
    3. -+ nil -+ -+ -+
    4. -+
    5. -+ string -+ source_code (only if 'env._debug' was truthy).
    6. -+
    -+

    Or

    -+
      -+
    1. -+ nil -+ -+ -+
    2. -+
    3. -+ string -+ error message
    4. -+
    5. -+ string -+ source_code (only if 'env._debug' was truthy).
    6. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ ct:render ([env[, parent[, db]]]) -+
    -+
    -+ executes the previously compiled template and renders it. -+ -+ -+

    Parameters:

    -+
      -+
    • env -+ table -+ the environment. -+ (optional) -+
    • -+
    • parent -+ table -+ continue looking up in this table (e.g. parent=_G). -+ (optional) -+
    • -+
    • db -+ boolean -+ if thruthy, it will print the code upon a render error -+ (provided the template was compiled with the debug option). -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ string -+ render result
    2. -+
    3. -+ nil -+ -+ -+
    4. -+
    5. -+ string -+ source_code (only if 'env._debug' was truthy).
    6. -+
    -+

    Or

    -+
      -+
    1. -+ nil -+ -+ -+
    2. -+
    3. -+ string -+ error message
    4. -+
    5. -+ string -+ source_code (only if 'env._debug' was truthy).
    6. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local ct, err = template.compile(my_template)
      -+local rendered , err = ct:render(my_env, parent)
      -+
    -+ -+
    -+
    -+ -+ compile (str[, opts]) -+
    -+
    -+ compiles the template. -+ Returns an object that can repeatedly be rendered without parsing/compiling -+ the template again. Preserves the line layout of the template so that line -+ numbers in error messages should point to the correct lines in the source -+ string. -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ the template string -+
    • -+
    • opts the compilation options to use. This table supports the following options: -+
        -+
      • chunk_name -+ string -+ chunk name for loaded templates, used if there -+ is an error in Lua code. -+ (default "TMP") -+
      • -+
      • escape -+ string -+ character marking Lua lines. -+ (default "#") -+
      • -+
      • inline_escape -+ string -+ character marking inline Lua expression. -+ (default "$") -+
      • -+
      • inline_brackets -+ string -+ bracket pair that wraps inline Lua expressions. -+ (default "()") -+
      • -+
      • newline -+ boolean -+ if truthy, newlines will be stripped from text in the template. -+ (default false) -+
      • -+
      • debug -+ boolean -+ if truthy, the generated code will be printed upon a render error. -+ (default false) -+
      • -+
      -+
    -+ -+

    Returns:

    -+
      -+ -+ ct -+ compiled template object -+
    -+

    Or

    -+
      -+
    1. -+ nil -+ -+ -+
    2. -+
    3. -+ string -+ error message
    4. -+
    5. -+ string -+ source_code
    6. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local ct, err = template.compile(my_template)
      -+local rendered , err = ct:render(my_env, parent)
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.test.html b/extra/penlight/docs/libraries/pl.test.html -new file mode 100644 -index 0000000..64497e6 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.test.html -@@ -0,0 +1,445 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.test

    -+

    Useful test utilities.

    -+

    -+ -+ -+ -+

    -+test.asserteq({1,2},{1,2}) -- can compare tables
    -+test.asserteq(1.2,1.19,0.02) -- compare FP numbers within precision
    -+T = test.tuple -- used for comparing multiple results
    -+test.asserteq(T(string.find(" me","me")),T(2,3))
    -+
    -+ -+

    Dependencies: pl.utils, pl.tablex, pl.pretty, pl.path, debug

    -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    error_handler (file, line, got_text, needed_text, msg)error handling for test results.
    complain (x, y, msg, where)general test complain message.
    asserteq (x, y, eps, where)like assert, except takes two arguments that must be equal and can be tables.
    assertmatch (s1, s2, where)assert that the first string matches the second.
    assertraise (fn, e, where)assert that the function raises a particular error.
    asserteq2 (x1, x2, y1, y2, where)a version of asserteq that takes two pairs of values.
    tuple (...)encode an arbitrary argument list as a tuple.
    timer (msg, n, fun, ...)Time a function.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ error_handler (file, line, got_text, needed_text, msg) -+
    -+
    -+ error handling for test results. -+ By default, this writes to stderr and exits the program. -+ Re-define this function to raise an error and/or redirect output -+ -+ -+

    Parameters:

    -+
      -+
    • file -+ -+ -+ -+
    • -+
    • line -+ -+ -+ -+
    • -+
    • got_text -+ -+ -+ -+
    • -+
    • needed_text -+ -+ -+ -+
    • -+
    • msg -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ complain (x, y, msg, where) -+
    -+
    -+ general test complain message. -+ Useful for composing new test functions (see tests/tablex.lua for an example) -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ a value -+
    • -+
    • y -+ value to compare first value against -+
    • -+
    • msg -+ message -+
    • -+
    • where -+ extra level offset for errors -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ asserteq (x, y, eps, where) -+
    -+
    -+ like assert, except takes two arguments that must be equal and can be tables. -+ If they are plain tables, it will use tablex.deepcompare. -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ any value -+
    • -+
    • y -+ a value equal to x -+
    • -+
    • eps -+ an optional tolerance for numerical comparisons -+
    • -+
    • where -+ extra level offset -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ assertmatch (s1, s2, where) -+
    -+
    -+ assert that the first string matches the second. -+ -+ -+

    Parameters:

    -+
      -+
    • s1 -+ a string -+
    • -+
    • s2 -+ a string -+
    • -+
    • where -+ extra level offset -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ assertraise (fn, e, where) -+
    -+
    -+ assert that the function raises a particular error. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ a function or a table of the form {function,arg1,...} -+
    • -+
    • e -+ a string to match the error against -+
    • -+
    • where -+ extra level offset -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ asserteq2 (x1, x2, y1, y2, where) -+
    -+
    -+ a version of asserteq that takes two pairs of values. -+ x1==y1 and x2==y2 must be true. Useful for functions that naturally -+ return two values. -+ -+ -+

    Parameters:

    -+
      -+
    • x1 -+ any value -+
    • -+
    • x2 -+ any value -+
    • -+
    • y1 -+ any value -+
    • -+
    • y2 -+ any value -+
    • -+
    • where -+ extra level offset -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ tuple (...) -+
    -+
    -+ encode an arbitrary argument list as a tuple. -+ This can be used to compare to other argument lists, which is -+ very useful for testing functions which return a number of values. -+ Unlike regular array-like tables ('sequences') they may contain nils. -+ Tuples understand equality and know how to print themselves out. -+ The # operator is defined to be the size, irrespecive of any nils, -+ and there is an unpack method. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+

    Usage:

    -+
      -+
      asserteq(tuple( ('ab'):find 'a'), tuple(1,1))
      -+
    -+ -+
    -+
    -+ -+ timer (msg, n, fun, ...) -+
    -+
    -+ Time a function. Call the function a given number of times, and report the number of seconds taken, -+ together with a message. Any extra arguments will be passed to the function. -+ -+ -+

    Parameters:

    -+
      -+
    • msg -+ string -+ a descriptive message -+
    • -+
    • n -+ integer -+ number of times to call the function -+
    • -+
    • fun -+ function -+ the function -+
    • -+
    • ... -+ optional arguments to fun -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.text.html b/extra/penlight/docs/libraries/pl.text.html -new file mode 100644 -index 0000000..a15af4c ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.text.html -@@ -0,0 +1,145 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.text

    -+

    Text processing utilities.

    -+

    This provides a Template class (modeled after the same from the Python -+ libraries, see string.Template). It also provides similar functions to those -+ found in the textwrap module.

    -+ -+

    IMPORTANT: this module has been deprecated and will be removed in a future -+ version (2.0). The contents of this module have moved to the pl.stringx -+ module.

    -+ -+

    See the Guide.

    -+ -+

    Dependencies: pl.stringx, pl.utils

    -+ -+ -+ -+
    -+
    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.types.html b/extra/penlight/docs/libraries/pl.types.html -new file mode 100644 -index 0000000..b880da3 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.types.html -@@ -0,0 +1,475 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.types

    -+

    Dealing with Detailed Type Information

    -+

    -+ -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    is_callable (obj)is the object either a function or a callable object?.
    is_type (obj, tp)is the object of the specified type?.
    type (obj)a string representation of a type.
    is_integer (x)is this number an integer?
    is_empty (o, ignore_spaces)Check if the object is "empty".
    is_indexable (val)is an object 'array-like'?
    is_iterable (val)can an object be iterated over with pairs?
    is_writeable (val)can an object accept new key/pair values?
    to_bool (o[, true_strs[, check_objs]])Convert to a boolean value.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ is_callable (obj) -+
    -+
    -+ is the object either a function or a callable object?. -+ -+ -+

    Parameters:

    -+
      -+
    • obj -+ Object to check. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ is_type (obj, tp) -+
    -+
    -+ is the object of the specified type?. -+ If the type is a string, then use type, otherwise compare with metatable.

    -+ -+

    NOTE: this function is imported from utils.is_type. -+ -+ -+

    Parameters:

    -+
      -+
    • obj -+ An object to check -+
    • -+
    • tp -+ The expected type -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ type (obj) -+
    -+
    -+ a string representation of a type. -+ For tables and userdata with metatables, we assume that the metatable has a _name -+ field. If the field is not present it will return 'unknown table' or -+ 'unknown userdata'. -+ Lua file objects return the type 'file'. -+ -+ -+

    Parameters:

    -+
      -+
    • obj -+ an object -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string like 'number', 'table', 'file' or 'List' -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_integer (x) -+
    -+
    -+ is this number an integer? -+ -+ -+

    Parameters:

    -+
      -+
    • x -+ a number -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ boolean -+
    -+ -+

    Raises:

    -+ error if x is not a number -+ -+ -+ -+
    -+
    -+ -+ is_empty (o, ignore_spaces) -+
    -+
    -+ -+

    Check if the object is "empty". -+ An object is considered empty if it is:

    -+ -+
      -+
    • nil
    • -+
    • a table without any items (key-value pairs or indexes)
    • -+
    • a string with no content ("")
    • -+
    • not a nil/table/string
    • -+
    -+ -+ -+ -+

    Parameters:

    -+
      -+
    • o -+ The object to check if it is empty. -+
    • -+
    • ignore_spaces -+ If the object is a string and this is true the string is -+ considered empty if it only contains spaces. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the object is empty, otherwise a falsy value. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_indexable (val) -+
    -+
    -+ is an object 'array-like'? -+ An object is array like if:

    -+ -+
      -+
    • it is a table, or
    • -+
    • it has a metatable with __len and __index methods
    • -+
    -+ -+

    NOTE: since __len is 5.2+, on 5.1 is usually returns false for userdata -+ -+ -+

    Parameters:

    -+
      -+
    • val -+ any value. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the object is array-like, otherwise a falsy value. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_iterable (val) -+
    -+
    -+ can an object be iterated over with pairs? -+ An object is iterable if:

    -+ -+
      -+
    • it is a table, or
    • -+
    • it has a metatable with a __pairs meta method
    • -+
    -+ -+

    NOTE: since __pairs is 5.2+, on 5.1 is usually returns false for userdata -+ -+ -+

    Parameters:

    -+
      -+
    • val -+ any value. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the object is iterable, otherwise a falsy value. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_writeable (val) -+
    -+
    -+ -+

    can an object accept new key/pair values? -+ An object is iterable if:

    -+ -+
      -+
    • it is a table, or
    • -+
    • it has a metatable with a __newindex meta method
    • -+
    -+ -+ -+ -+ -+

    Parameters:

    -+
      -+
    • val -+ any value. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the object is writeable, otherwise a falsy value. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ to_bool (o[, true_strs[, check_objs]]) -+
    -+
    -+ -+

    Convert to a boolean value. -+ True values are:

    -+ -+
      -+
    • boolean: true.
    • -+
    • string: 'yes', 'y', 'true', 't', '1' or additional strings specified by true_strs.
    • -+
    • number: Any non-zero value.
    • -+
    • table: Is not empty and check_objs is true.
    • -+
    • everything else: Is not nil and check_objs is true.
    • -+
    -+ -+ -+ -+ -+

    Parameters:

    -+
      -+
    • o -+ The object to evaluate. -+
    • -+
    • true_strs -+ optional Additional strings that when matched should evaluate to true. Comparison is case insensitive. -+ This should be a List of strings. E.g. "ja" to support German. -+ (optional) -+
    • -+
    • check_objs -+ True if objects should be evaluated. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ true if the input evaluates to true, otherwise false. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.url.html b/extra/penlight/docs/libraries/pl.url.html -new file mode 100644 -index 0000000..c102dc1 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.url.html -@@ -0,0 +1,212 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.url

    -+

    Python-style URL quoting library.

    -+

    -+ -+

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    quote (s, quote_plus)Quote the url, replacing special characters using the '%xx' escape.
    unquote (s)Unquote the url, replacing '%xx' escapes and plus signs.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ quote (s, quote_plus) -+
    -+
    -+ Quote the url, replacing special characters using the '%xx' escape. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    • quote_plus -+ boolean -+ Also escape slashes and replace spaces by plus signs. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ The quoted string, or if s wasn't a string, just plain unaltered s. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ unquote (s) -+
    -+
    -+ Unquote the url, replacing '%xx' escapes and plus signs. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ string -+ the string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ The unquoted string, or if s wasn't a string, just plain unaltered s. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.utils.html b/extra/penlight/docs/libraries/pl.utils.html -new file mode 100644 -index 0000000..7457815 ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.utils.html -@@ -0,0 +1,1604 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.utils

    -+

    Generally useful routines.

    -+

    See the Guide.

    -+ -+

    Dependencies: pl.compat, all exported fields and functions from -+ pl.compat are also available in this module.

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    pack (...)pack an argument list into a table.
    unpack (t[, i[, j]])unpack a table and return its contents.
    printf (fmt, ...)print an arbitrary number of arguments using a format.
    fprintf (f, fmt, ...)write an arbitrary number of arguments to a file using a format.
    import (t, T)take a table and 'inject' it into the local namespace.
    choose (cond, value1, value2)return either of two values, depending on a condition.
    array_tostring (t[, temp[, tostr]])convert an array of values to strings.
    is_type (obj, tp)is the object of the specified type?
    npairs (t[, i_start=1[, i_end=t.n or #t[, step=1]]])an iterator with indices, similar to ipairs, but with a range.
    kpairs (t)an iterator over all non-integer keys (inverse of ipairs).
    -+

    Tables

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    patternsSome standard patterns
    stdmtStandard meta-tables as used by other Penlight modules
    -+

    Error handling

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    assert_arg (n, val, tp, verify, msg, lev)assert that the given argument is in fact of the correct type.
    enum (...)creates an Enum or constants lookup table for improved error handling.
    function_arg (idx, f, msg)process a function argument.
    assert_string (n, val)assert the common case that the argument is a string.
    on_error (mode)control the error strategy used by Penlight.
    raise (err)used by Penlight functions to return errors.
    -+

    File handling

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    readfile (filename, is_bin)return the contents of a file as a string
    writefile (filename, str, is_bin)write a string to a file
    readlines (filename)return the contents of a file as a list of lines
    -+

    OS functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    executeex (cmd, bin)execute a shell command and return the output.
    quote_arg (argument)Quote and escape an argument of a command.
    quit ([code], msg, ...)error out of this program gracefully.
    -+

    String functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    escape (s)escape any Lua 'magic' characters in a string
    split (s, re, plain, n)split a string into a list of strings separated by a delimiter.
    splitv (s, re, plain, n)split a string into a number of return values.
    -+

    Functional

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    memoize (func)'memoize' a function (cache returned value for next call).
    add_function_factory (mt, fun)associate a function factory with a type.
    string_lambda (lf)an anonymous function as a string.
    bind1 (fn, p)bind the first argument of the function to a value.
    bind2 (fn, p)bind the second argument of the function to a value.
    -+

    Deprecation

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    set_deprecation_func (func)Sets a deprecation warning function.
    raise_deprecation (opts)raises a deprecation warning.
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ pack (...) -+
    -+
    -+ pack an argument list into a table. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ any arguments -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a table with field n set to the length -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ unpack (t[, i[, j]]) -+
    -+
    -+ unpack a table and return its contents.

    -+ -+

    NOTE: this implementation differs from the Lua implementation in the way -+ that this one DOES honor the n field in the table t, such that it is 'nil-safe'. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table to unpack -+
    • -+
    • i -+ index from which to start unpacking, defaults to 1 -+ (optional) -+
    • -+
    • j -+ index of the last element to unpack, defaults to t.n or else #t -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ multiple return values from the table -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local t = table.pack(nil, nil, nil, 4)
      -+local a, b, c, d = table.unpack(t)   -- this unpack is NOT nil-safe, so d == nil
      -+
      -+local a, b, c, d = utils.unpack(t)   -- this is nil-safe, so d == 4
      -+
    -+ -+
    -+
    -+ -+ printf (fmt, ...) -+
    -+
    -+ print an arbitrary number of arguments using a format. -+ Output will be sent to stdout. -+ -+ -+

    Parameters:

    -+
      -+
    • fmt -+ The format (see string.format) -+
    • -+
    • ... -+ Extra arguments for format -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ fprintf (f, fmt, ...) -+
    -+
    -+ write an arbitrary number of arguments to a file using a format. -+ -+ -+

    Parameters:

    -+
      -+
    • f -+ File handle to write to. -+
    • -+
    • fmt -+ The format (see string.format). -+
    • -+
    • ... -+ Extra arguments for format -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ import (t, T) -+
    -+
    -+ take a table and 'inject' it into the local namespace. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ The table (table), or module name (string), defaults to this utils module table -+
    • -+
    • T -+ An optional destination table (defaults to callers environment) -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ choose (cond, value1, value2) -+
    -+
    -+ return either of two values, depending on a condition. -+ -+ -+

    Parameters:

    -+
      -+
    • cond -+ A condition -+
    • -+
    • value1 -+ Value returned if cond is truthy -+
    • -+
    • value2 -+ Value returned if cond is falsy -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ array_tostring (t[, temp[, tostr]]) -+
    -+
    -+ convert an array of values to strings. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ a list-like table -+
    • -+
    • temp -+ (table) buffer to use, otherwise allocate -+ (optional) -+
    • -+
    • tostr -+ custom tostring function, called with (value,index). Defaults to tostring. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the converted buffer -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_type (obj, tp) -+
    -+
    -+ is the object of the specified type? -+ If the type is a string, then use type, otherwise compare with metatable -+ -+ -+

    Parameters:

    -+
      -+
    • obj -+ An object to check -+
    • -+
    • tp -+ String of what type it should be -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ boolean -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      utils.is_type("hello world", "string")   --> true
      -+-- or check metatable
      -+local my_mt = {}
      -+local my_obj = setmetatable(my_obj, my_mt)
      -+utils.is_type(my_obj, my_mt)  --> true
      -+
    -+ -+
    -+
    -+ -+ npairs (t[, i_start=1[, i_end=t.n or #t[, step=1]]]) -+
    -+
    -+ an iterator with indices, similar to ipairs, but with a range. -+ This is a nil-safe index based iterator that will return nil when there -+ is a hole in a list. To be safe ensure that table t.n contains the length. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table to iterate over -+
    • -+
    • i_start -+ integer -+ start index -+ (default 1) -+
    • -+
    • i_end -+ integer -+ end index -+ (default t.n or #t) -+
    • -+
    • step -+ integer -+ step size -+ (default 1) -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ integer -+ index
    2. -+
    3. -+ any -+ value at index (which can be nil!)
    4. -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local t = utils.pack(nil, 123, nil)  -- adds an n field when packing
      -+
      -+for i, v in utils.npairs(t, 2) do  -- start at index 2
      -+  t[i] = tostring(t[i])
      -+end
      -+
      -+-- t = { n = 3, [2] = "123", [3] = "nil" }
      -+
    -+ -+
    -+
    -+ -+ kpairs (t) -+
    -+
    -+ an iterator over all non-integer keys (inverse of ipairs). -+ It will skip any key that is an integer number, so negative indices or an -+ array with holes will not return those either (so it returns slightly less than -+ 'the inverse of ipairs').

    -+ -+

    This uses pairs under the hood, so any value that is iterable using pairs -+ will work with this function. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ the table to iterate over -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ key -+ -+ -+
    2. -+
    3. -+ value -+ -+ -+
    4. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local t = {
      -+  "hello",
      -+  "world",
      -+  hello = "hallo",
      -+  world = "Welt",
      -+}
      -+
      -+for k, v in utils.kpairs(t) do
      -+  print("German: ", v)
      -+end
      -+
      -+-- output;
      -+-- German: hallo
      -+-- German: Welt
      -+
    -+ -+
    -+
    -+

    Tables

    -+ -+
    -+
    -+ -+ patterns -+
    -+
    -+ Some standard patterns -+ -+ -+

    Fields:

    -+
      -+
    • FLOAT -+ floating point number -+
    • -+
    • INTEGER -+ integer number -+
    • -+
    • IDEN -+ identifier -+
    • -+
    • FILE -+ file -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ stdmt -+
    -+
    -+ Standard meta-tables as used by other Penlight modules -+ -+ -+

    Fields:

    -+
      -+
    • List -+ the List metatable -+
    • -+
    • Map -+ the Map metatable -+
    • -+
    • Set -+ the Set metatable -+
    • -+
    • MultiMap -+ the MultiMap metatable -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+

    Error handling

    -+ -+
    -+
    -+ -+ assert_arg (n, val, tp, verify, msg, lev) -+
    -+
    -+ assert that the given argument is in fact of the correct type. -+ -+ -+

    Parameters:

    -+
      -+
    • n -+ argument index -+
    • -+
    • val -+ the value -+
    • -+
    • tp -+ the type -+
    • -+
    • verify -+ an optional verification function -+
    • -+
    • msg -+ an optional custom message -+
    • -+
    • lev -+ optional stack position for trace, default 2 -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the validated value -+
    -+ -+

    Raises:

    -+ if val is not the correct type -+ -+ -+

    Usage:

    -+
      -+
      local param1 = assert_arg(1,"hello",'table')  --> error: argument 1 expected a 'table', got a 'string'
      -+local param4 = assert_arg(4,'!@#$%^&*','string',path.isdir,'not a directory')
      -+     --> error: argument 4: '!@#$%^&*' not a directory
      -+
    -+ -+
    -+
    -+ -+ enum (...) -+
    -+
    -+ creates an Enum or constants lookup table for improved error handling. -+ This helps prevent magic strings in code by throwing errors for accessing -+ non-existing values, and/or converting strings/identifiers to other values.

    -+ -+

    Calling on the object does the same, but returns a soft error; nil + err, if -+ the call is successful (the key exists), it will return the value.

    -+ -+

    When calling with varargs or an array the values will be equal to the keys. -+ The enum object is read-only. -+ -+ -+

    Parameters:

    -+
      -+
    • ... -+ table or vararg -+ the input for the Enum. If varargs or an array then the -+ values in the Enum will be equal to the names (must be strings), if a hash-table -+ then values remain (any type), and the keys must be strings. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ Enum object (read-only table/object) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
    • -- Enum access at runtime
      -+local obj = {}
      -+obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT")
      -+
      -+if current_movement == obj.MOVEMENT.FORWARD then
      -+  -- do something
      -+
      -+elseif current_movement == obj.MOVEMENT.REVERES then
      -+  -- throws error due to typo 'REVERES', so a silent mistake becomes a hard error
      -+  -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')"
      -+
      -+end
    • -+
    • -- standardized error codes
      -+local obj = {
      -+  ERR = utils.enum {
      -+    NOT_FOUND = "the item was not found",
      -+    OUT_OF_BOUNDS = "the index is outside the allowed range"
      -+  },
      -+
      -+  some_method = function(self)
      -+    return nil, self.ERR.OUT_OF_BOUNDS
      -+  end,
      -+}
      -+
      -+local result, err = obj:some_method()
      -+if not result then
      -+  if err == obj.ERR.NOT_FOUND then
      -+    -- check on error code, not magic strings
      -+
      -+  else
      -+    -- return the error description, contained in the constant
      -+    return nil, "error: "..err  -- "error: the index is outside the allowed range"
      -+  end
      -+end
    • -+
    • -- validating/converting user-input
      -+local color = "purple"
      -+local ansi_colors = utils.enum {
      -+  black     = 30,
      -+  red       = 31,
      -+  green     = 32,
      -+}
      -+local color_code, err = ansi_colors(color) -- calling on the object, returns the value from the enum
      -+if not color_code then
      -+  print("bad 'color', " .. err)
      -+  -- "bad 'color', 'purple' is not a valid value (expected one of: 'black', 'red', 'green')"
      -+  os.exit(1)
      -+end
    • -+
    -+ -+
    -+
    -+ -+ function_arg (idx, f, msg) -+
    -+
    -+ process a function argument. -+ This is used throughout Penlight and defines what is meant by a function: -+ Something that is callable, or an operator string as defined by pl.operator, -+ such as '>' or '#'. If a function factory has been registered for the type, it will -+ be called to get the function. -+ -+ -+

    Parameters:

    -+
      -+
    • idx -+ argument index -+
    • -+
    • f -+ a function, operator string, or callable object -+
    • -+
    • msg -+ optional error message -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a callable -+
    -+ -+

    Raises:

    -+ if idx is not a number or if f is not callable -+ -+ -+ -+
    -+
    -+ -+ assert_string (n, val) -+
    -+
    -+ assert the common case that the argument is a string. -+ -+ -+

    Parameters:

    -+
      -+
    • n -+ argument index -+
    • -+
    • val -+ a value that must be a string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the validated value -+
    -+ -+

    Raises:

    -+ val must be a string -+ -+ -+

    Usage:

    -+
      -+
      local val = 42
      -+local param2 = utils.assert_string(2, val) --> error: argument 2 expected a 'string', got a 'number'
      -+
    -+ -+
    -+
    -+ -+ on_error (mode) -+
    -+
    -+ -+

    control the error strategy used by Penlight. -+ This is a global setting that controls how utils.raise behaves:

    -+ -+
      -+
    • 'default': return nil + error (this is the default)
    • -+
    • 'error': throw a Lua error
    • -+
    • 'quit': exit the program
    • -+
    -+ -+ -+ -+ -+

    Parameters:

    -+
      -+
    • mode -+ either 'default', 'quit' or 'error' -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ raise (err) -+
    -+
    -+ used by Penlight functions to return errors. Its global behaviour is controlled -+ by utils.on_error. -+ To use this function you MUST use it in conjunction with return, since it might -+ return nil + error. -+ -+ -+

    Parameters:

    -+
      -+
    • err -+ the error string. -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      if some_condition then
      -+  return utils.raise("some condition was not met")  -- MUST use 'return'!
      -+end
      -+
    -+ -+
    -+
    -+

    File handling

    -+ -+
    -+
    -+ -+ readfile (filename, is_bin) -+
    -+
    -+ return the contents of a file as a string -+ -+ -+

    Parameters:

    -+
      -+
    • filename -+ The file path -+
    • -+
    • is_bin -+ open in binary mode -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ file contents -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ writefile (filename, str, is_bin) -+
    -+
    -+ write a string to a file -+ -+ -+

    Parameters:

    -+
      -+
    • filename -+ The file path -+
    • -+
    • str -+ The string -+
    • -+
    • is_bin -+ open in binary mode -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ true or nil
    2. -+
    3. -+ error message
    4. -+
    -+ -+

    Raises:

    -+ error if filename or str aren't strings -+ -+ -+ -+
    -+
    -+ -+ readlines (filename) -+
    -+
    -+ return the contents of a file as a list of lines -+ -+ -+

    Parameters:

    -+
      -+
    • filename -+ The file path -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ file contents as a table -+
    -+ -+

    Raises:

    -+ error if filename is not a string -+ -+ -+ -+
    -+
    -+

    OS functions

    -+ -+
    -+
    -+ -+ executeex (cmd, bin) -+
    -+
    -+ execute a shell command and return the output. -+ This function redirects the output to tempfiles and returns the content of those files. -+ -+ -+

    Parameters:

    -+
      -+
    • cmd -+ a shell command -+
    • -+
    • bin -+ boolean, if true, read output as binary file -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ true if successful
    2. -+
    3. -+ actual return code
    4. -+
    5. -+ stdout output (string)
    6. -+
    7. -+ errout output (string)
    8. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ quote_arg (argument) -+
    -+
    -+ Quote and escape an argument of a command. -+ Quotes a single (or list of) argument(s) of a command to be passed -+ to os.execute, pl.utils.execute or pl.utils.executeex. -+ -+ -+

    Parameters:

    -+
      -+
    • argument -+ (string or table/list) the argument to quote. If a list then -+ all arguments in the list will be returned as a single string quoted. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ quoted and escaped argument. -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local options = utils.quote_arg {
      -+    "-lluacov",
      -+    "-e",
      -+    "utils = print(require('pl.utils')._VERSION",
      -+}
      -+-- returns: -lluacov -e 'utils = print(require('\''pl.utils'\'')._VERSION'
      -+
    -+ -+
    -+
    -+ -+ quit ([code], msg, ...) -+
    -+
    -+ error out of this program gracefully. -+ -+ -+

    Parameters:

    -+
      -+
    • code -+ The exit code, defaults to -1 if omitted -+ (optional) -+
    • -+
    • msg -+ The exit message will be sent to stderr (will be formatted with the extra parameters) -+
    • -+
    • ... -+ extra arguments for message's format' -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      utils.quit(-1, "Error '%s' happened", "42")
      -+-- is equivalent to
      -+utils.quit("Error '%s' happened", "42")  --> Error '42' happened
      -+
    -+ -+
    -+
    -+

    String functions

    -+ -+
    -+
    -+ -+ escape (s) -+
    -+
    -+ escape any Lua 'magic' characters in a string -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ The input string -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ split (s, re, plain, n) -+
    -+
    -+ split a string into a list of strings separated by a delimiter. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ The input string -+
    • -+
    • re -+ optional A Lua string pattern; defaults to '%s+' -+
    • -+
    • plain -+ optional If truthy don't use Lua patterns -+
    • -+
    • n -+ optional maximum number of elements (if there are more, the last will remain un-split) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list-like table -+
    -+ -+

    Raises:

    -+ error if s is not a string -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ splitv (s, re, plain, n) -+
    -+
    -+ split a string into a number of return values. -+ Identical to split but returns multiple sub-strings instead of -+ a single list of sub-strings. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ the string -+
    • -+
    • re -+ A Lua string pattern; defaults to '%s+' -+
    • -+
    • plain -+ don't use Lua patterns -+
    • -+
    • n -+ optional maximum number of splits -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ n values -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      first,next = splitv('user=jane=doe','=', false, 2)
      -+assert(first == "user")
      -+assert(next == "jane=doe")
      -+
    -+ -+
    -+
    -+

    Functional

    -+ -+
    -+
    -+ -+ memoize (func) -+
    -+
    -+ 'memoize' a function (cache returned value for next call). -+ This is useful if you have a function which is relatively expensive, -+ but you don't know in advance what values will be required, so -+ building a table upfront is wasteful/impossible. -+ -+ -+

    Parameters:

    -+
      -+
    • func -+ a function that takes exactly one argument (which later serves as the cache key) and returns a single value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function taking one argument and returning a single value either from the cache or by running the original input function -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ add_function_factory (mt, fun) -+
    -+
    -+ associate a function factory with a type. -+ A function factory takes an object of the given type and -+ returns a function for evaluating it -+ -+ -+

    Parameters:

    -+
      -+
    • mt -+ table -+ metatable -+
    • -+
    • fun -+ function -+ a callable that returns a function -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ string_lambda (lf) -+
    -+
    -+ an anonymous function as a string. This string is either of the form -+ '|args| expression' or is a function of one argument, '_' -+ -+ -+

    Parameters:

    -+
      -+
    • lf -+ function as a string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      string_lambda '|x|x+1' (2) == 3
      -+string_lambda '_+1' (2) == 3
      -+
    -+ -+
    -+
    -+ -+ bind1 (fn, p) -+
    -+
    -+ bind the first argument of the function to a value. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ a function of at least two values (may be an operator string) -+
    • -+
    • p -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function such that f(x) is fn(p,x) -+
    -+ -+

    Raises:

    -+ same as function_arg -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local function f(msg, name)
      -+  print(msg .. " " .. name)
      -+end
      -+
      -+local hello = utils.bind1(f, "Hello")
      -+
      -+print(hello("world"))     --> "Hello world"
      -+print(hello("sunshine"))  --> "Hello sunshine"
      -+
    -+ -+
    -+
    -+ -+ bind2 (fn, p) -+
    -+
    -+ bind the second argument of the function to a value. -+ -+ -+

    Parameters:

    -+
      -+
    • fn -+ a function of at least two values (may be an operator string) -+
    • -+
    • p -+ a value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a function such that f(x) is fn(x,p) -+
    -+ -+

    Raises:

    -+ same as function_arg -+ -+ -+

    Usage:

    -+
      -+
      local function f(a, b, c)
      -+  print(a .. " " .. b .. " " .. c)
      -+end
      -+
      -+local hello = utils.bind1(f, "world")
      -+
      -+print(hello("Hello", "!"))  --> "Hello world !"
      -+print(hello("Bye", "?"))    --> "Bye world ?"
      -+
    -+ -+
    -+
    -+

    Deprecation

    -+ -+
    -+
    -+ -+ set_deprecation_func (func) -+
    -+
    -+ Sets a deprecation warning function. -+ An application can override this function to support proper output of -+ deprecation warnings. The warnings can be generated from libraries or -+ functions by calling utils.raise_deprecation. The default function -+ will write to the 'warn' system (introduced in Lua 5.4, or the compatibility -+ function from the compat module for earlier versions).

    -+ -+

    Note: only applications should set/change this function, libraries should not. -+ -+ -+

    Parameters:

    -+
      -+
    • func -+ a callback with signature: function(msg, trace) both arguments are strings, the latter being optional. -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      -- write to the Nginx logs with OpenResty
      -+utils.set_deprecation_func(function(msg, trace)
      -+  ngx.log(ngx.WARN, msg, (trace and (" " .. trace) or nil))
      -+end)
      -+
      -+-- disable deprecation warnings
      -+utils.set_deprecation_func()
      -+
    -+ -+
    -+
    -+ -+ raise_deprecation (opts) -+
    -+
    -+ raises a deprecation warning. -+ For options see the usage example below.

    -+ -+

    Note: the opts.deprecated_after field is the last version in which -+ a feature or option was NOT YET deprecated! Because when writing the code it -+ is quite often not known in what version the code will land. But the last -+ released version is usually known. -+ -+ -+

    Parameters:

    -+
      -+
    • opts -+ options table -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      warn("@on")   -- enable Lua warnings, they are usually off by default
      -+
      -+function stringx.islower(str)
      -+  raise_deprecation {
      -+    source = "Penlight " .. utils._VERSION,                   -- optional
      -+    message = "function 'islower' was renamed to 'is_lower'", -- required
      -+    version_removed = "2.0.0",                                -- optional
      -+    deprecated_after = "1.2.3",                               -- optional
      -+    no_trace = true,                                          -- optional
      -+  }
      -+  return stringx.is_lower(str)
      -+end
      -+-- output: "[Penlight 1.9.2] function 'islower' was renamed to 'is_lower' (deprecated after 1.2.3, scheduled for removal in 2.0.0)"
      -+
    -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/libraries/pl.xml.html b/extra/penlight/docs/libraries/pl.xml.html -new file mode 100644 -index 0000000..58c98ef ---- /dev/null -+++ b/extra/penlight/docs/libraries/pl.xml.html -@@ -0,0 +1,1356 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+

    Module pl.xml

    -+

    XML LOM Utilities.

    -+

    This implements some useful things on LOM documents, such as returned by lxp.lom.parse. -+ In particular, it can convert LOM back into XML text, with optional pretty-printing control. -+ It is based on stanza.lua from Prosody

    -+ -+ -+
    -+> d = xml.parse "<nodes><node id='1'>alice</node></nodes>"
    -+> = d
    -+<nodes><node id='1'>alice</node></nodes>
    -+> = xml.tostring(d,'','  ')
    -+<nodes>
    -+   <node id='1'>alice</node>
    -+</nodes>
    -+
    -+ -+

    Can be used as a lightweight one-stop-shop for simple XML processing; a simple XML parser is included -+ but the default is to use lxp.lom if it can be found. -+

    -+ Prosody IM
    -+ Copyright (C) 2008-2010 Matthew Wild
    -+ Copyright (C) 2008-2010 Waqas Hussain--
    -+ classic Lua XML parser by Roberto Ierusalimschy.
    -+ modified to output LOM format.
    -+ http://lua-users.org/wiki/LuaXml
    -+ 
    -+ See the Guide

    -+ -+

    Dependencies: pl.utils

    -+ -+

    Soft Dependencies: lxp.lom (fallback is to use basic Lua parser)

    -+ -+ -+

    Functions

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    new (tag[, attr={}])create a new document node.
    parse (text_or_filename, is_file, use_basic)parse an XML document.
    elem (tag, items)Create a Node with a set of children (text or Nodes) and attributes.
    tags (list)given a list of names, return a number of element constructors.
    Doc:addtag (tag[, attrs={}])Adds a document Node, at current position.
    Doc:text (text)Adds a text node, at current position.
    Doc:up ()Moves current position up one level.
    Doc:reset ()Resets current position to top level.
    Doc:add_direct_child (child)Append a child to the current Node (ignoring current position).
    Doc:add_child (child)Append a child at the current position (without changing position).
    Doc:set_attribs (t)Set attributes of a document node.
    Doc:set_attrib (a, v)Set a single attribute of a document node.
    Doc:get_attribs ()Gets the attributes of a document node.
    Doc.subst (template, data)create a substituted copy of a document,
    Doc:child_with_name (tag)Return the first child with a given tag name (non-recursive).
    Doc:get_elements_with_name (tag[, dont_recurse=false])Returns all elements in a document that have a given tag.
    Doc:children ()Iterator over all children of a document node, including text nodes.
    Doc:first_childtag ()Return the first child element of a node, if it exists.
    Doc:matching_tags ([tag=nil[, xmlns=nil]])Iterator that matches tag names, and a namespace (non-recursive).
    Doc:childtags ()Iterator over all child tags of a document node.
    Doc:maptags (callback)Visit child Nodes of a node and call a function, possibly modifying the document.
    xml_escape (str)Escapes a string for safe use in xml.
    xml_unescape (str)Unescapes a string from xml.
    tostring (doc[, b_ind[, t_ind[, a_ind[, xml_preface]]]])Function to pretty-print an XML document.
    Doc:tostring ([b_ind[, t_ind[, a_ind[, xml_preface="<?xml version='1.0'?>"]]]])Method to pretty-print an XML document.
    Doc:get_text ()get the full text value of an element.
    clone (doc[, strsubst])Returns a copy of a document.
    Doc:filter ([strsubst])Returns a copy of a document.
    compare (t1, t2)Compare two documents or elements.
    is_tag (d)is this value a document element?
    walk (doc, depth_first, operation)Calls a function recursively over Nodes in the document.
    parsehtml (s)Parse a well-formed HTML file as a string.
    basic_parse (s, all_text, html)Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version.
    Doc:match (pat)does something...
    -+ -+
    -+
    -+ -+ -+

    Functions

    -+ -+
    -+
    -+ -+ new (tag[, attr={}]) -+
    -+
    -+ create a new document node. -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ string -+ the tag name -+
    • -+
    • attr -+ table -+ attributes (table of name-value pairs) -+ (default {}) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the Node object -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main", { hello = "world", answer = "42" })
      -+print(doc)  -->  <main hello='world' answer='42'/>
      -+
    -+ -+
    -+
    -+ -+ parse (text_or_filename, is_file, use_basic) -+
    -+
    -+ parse an XML document. By default, this uses lxp.lom.parse, but -+ falls back to basic_parse, or if use_basic is truthy -+ -+ -+

    Parameters:

    -+
      -+
    • text_or_filename -+ file or string representation -+
    • -+
    • is_file -+ whether textorfile is a file name or not -+
    • -+
    • use_basic -+ do a basic parse -+
    • -+
    -+ -+

    Returns:

    -+
      -+
    1. -+ a parsed LOM document with the document metatatables set
    2. -+
    3. -+ nil, error the error can either be a file error or a parse error
    4. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ elem (tag, items) -+
    -+
    -+ Create a Node with a set of children (text or Nodes) and attributes. -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ string -+ a tag name -+
    • -+
    • items -+ table or string -+ either a single child (text or Node), or a table where the hash -+ part is the attributes and the list part is the children (text or Nodes). -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the new Node -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.elem("top", "hello world")                -- <top>hello world</top>
      -+local doc = xml.elem("main", xml.new("child"))            -- <main><child/></main>
      -+local doc = xml.elem("main", { "this ", "is ", "nice" })  -- <main>this is nice</main>
      -+local doc = xml.elem("main", { xml.new "this",
      -+                               xml.new "is",
      -+                               xml.new "nice" })          -- <main><this/><is/><nice/></main>
      -+local doc = xml.elem("main", { hello = "world" })         -- <main hello='world'/>
      -+local doc = xml.elem("main", {
      -+  "prefix",
      -+  xml.elem("child", { "this ", "is ", "nice"}),
      -+  "postfix",
      -+  attrib = "value"
      -+})   -- <main attrib='value'>prefix<child>this is nice</child>postfix</main>"
      -+
    -+ -+
    -+
    -+ -+ tags (list) -+
    -+
    -+ given a list of names, return a number of element constructors. -+ If passing a comma-separated string, then whitespace surrounding the values -+ will be stripped.

    -+ -+

    The returned constructor functions are a shortcut to xml.elem where you -+ no longer provide the tag-name, but only the items table. -+ -+ -+

    Parameters:

    -+
      -+
    • list -+ string or table -+ a list of names, or a comma-separated string. -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ (multiple) constructor functions; function(items). For the items -+ parameter see xml.elem. -+
    -+ -+ -+

    See also:

    -+ -+ -+

    Usage:

    -+
      -+
      local new_parent, new_child = xml.tags 'mom, kid'
      -+doc = new_parent {new_child 'Bob', new_child 'Annie'}
      -+-- <mom><kid>Bob</kid><kid>Annie</kid></mom>
      -+
    -+ -+
    -+
    -+ -+ Doc:addtag (tag[, attrs={}]) -+
    -+
    -+ Adds a document Node, at current position. -+ This updates the last inserted position to the new Node. -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ string -+ the tag name -+
    • -+
    • attrs -+ table -+ attributes (table of name-value pairs) -+ (default {}) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main")
      -+doc:addtag("penlight", { hello = "world"})
      -+doc:addtag("expat")  -- added to 'penlight' since position moved
      -+print(doc)  -->  <main><penlight hello='world'><expat/></penlight></main>
      -+
    -+ -+
    -+
    -+ -+ Doc:text (text) -+
    -+
    -+ Adds a text node, at current position. -+ -+ -+

    Parameters:

    -+
      -+
    • text -+ string -+ a string -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main")
      -+doc:text("penlight")
      -+doc:text("expat")
      -+print(doc)  -->  <main><penlightexpat</main>
      -+
    -+ -+
    -+
    -+ -+ Doc:up () -+
    -+
    -+ Moves current position up one level. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:reset () -+
    -+
    -+ Resets current position to top level. -+ Resets to the self node. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:add_direct_child (child) -+
    -+
    -+ Append a child to the current Node (ignoring current position). -+ -+ -+

    Parameters:

    -+
      -+
    • child -+ a child node (either text or a document) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main")
      -+doc:add_direct_child("dog")
      -+doc:add_direct_child(xml.new("child"))
      -+doc:add_direct_child("cat")
      -+print(doc)  -->  <main>dog<child/>cat</main>
      -+
    -+ -+
    -+
    -+ -+ Doc:add_child (child) -+
    -+
    -+ Append a child at the current position (without changing position). -+ -+ -+

    Parameters:

    -+
      -+
    • child -+ a child node (either text or a document) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main")
      -+doc:addtag("one")
      -+doc:add_child(xml.new("item1"))
      -+doc:add_child(xml.new("item2"))
      -+doc:add_child(xml.new("item3"))
      -+print(doc)  -->  <main><one><item1/><item2/><item3/></one></main>
      -+
    -+ -+
    -+
    -+ -+ Doc:set_attribs (t) -+
    -+
    -+ Set attributes of a document node. -+ Will add/overwrite values, but will not remove existing ones. -+ Operates on the Node itself, will not take position into account. -+ -+ -+

    Parameters:

    -+
      -+
    • t -+ table -+ a table containing attribute/value pairs -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:set_attrib (a, v) -+
    -+
    -+ Set a single attribute of a document node. -+ Operates on the Node itself, will not take position into account. -+ -+ -+

    Parameters:

    -+
      -+
    • a -+ attribute -+
    • -+
    • v -+ its value, pass in nil to delete the attribute -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the current node (self) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:get_attribs () -+
    -+
    -+ Gets the attributes of a document node. -+ Operates on the Node itself, will not take position into account. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ table with attributes (attribute/value pairs) -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc.subst (template, data) -+
    -+
    -+ create a substituted copy of a document, -+ -+ -+

    Parameters:

    -+
      -+
    • template -+ may be a document or a string representation which will be parsed and cached -+
    • -+
    • data -+ a table of name-value pairs or a list of such tables -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ an XML document -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:child_with_name (tag) -+
    -+
    -+ Return the first child with a given tag name (non-recursive). -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ the tag name -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ the child Node found or nil if not found -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:get_elements_with_name (tag[, dont_recurse=false]) -+
    -+
    -+ Returns all elements in a document that have a given tag. -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ string -+ a tag name -+
    • -+
    • dont_recurse -+ boolean -+ optionally only return the immediate children with this tag name -+ (default false) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a list of elements found, list will be empty if none was found. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:children () -+
    -+
    -+ Iterator over all children of a document node, including text nodes. -+ This function is not recursive, so returns only direct child nodes. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ iterator that returns a single Node per iteration. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:first_childtag () -+
    -+
    -+ Return the first child element of a node, if it exists. -+ This will skip text nodes. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ first child Node or nil if there is none. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:matching_tags ([tag=nil[, xmlns=nil]]) -+
    -+
    -+ Iterator that matches tag names, and a namespace (non-recursive). -+ -+ -+

    Parameters:

    -+
      -+
    • tag -+ string -+ tag names to return. Returns all tags if not provided. -+ (default nil) -+
    • -+
    • xmlns -+ string -+ the namespace value ('xmlns' attribute) to return. If not -+ provided will match all namespaces. -+ (default nil) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ iterator that returns a single Node per iteration. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:childtags () -+
    -+
    -+ Iterator over all child tags of a document node. This will skip over -+ text nodes. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ iterator that returns a single Node per iteration. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:maptags (callback) -+
    -+
    -+ Visit child Nodes of a node and call a function, possibly modifying the document. -+ Text elements will be skipped. -+ This is not recursive, so only direct children will be passed. -+ -+ -+

    Parameters:

    -+
      -+
    • callback -+ function -+ a function with signature function(node), passed the node. -+ The element will be updated with the returned value, or deleted if it returns nil. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ xml_escape (str) -+
    -+
    -+ Escapes a string for safe use in xml. -+ Handles quotes(single+double), less-than, greater-than, and ampersand. -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ string value to escape -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ escaped string -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local esc = xml.xml_escape([["'<>&]])  --> "&quot;&apos;&lt;&gt;&amp;"
      -+
    -+ -+
    -+
    -+ -+ xml_unescape (str) -+
    -+
    -+ Unescapes a string from xml. -+ Handles quotes(single+double), less-than, greater-than, and ampersand. -+ -+ -+

    Parameters:

    -+
      -+
    • str -+ string -+ string value to unescape -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ unescaped string -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local unesc = xml.xml_escape("&quot;&apos;&lt;&gt;&amp;")  --> [["'<>&]]
      -+
    -+ -+
    -+
    -+ -+ tostring (doc[, b_ind[, t_ind[, a_ind[, xml_preface]]]]) -+
    -+
    -+ Function to pretty-print an XML document. -+ -+ -+

    Parameters:

    -+
      -+
    • doc -+ an XML document -+
    • -+
    • b_ind -+ string or int -+ an initial block-indent (required when t_ind is set) -+ (optional) -+
    • -+
    • t_ind -+ string or int -+ an tag-indent for each level (required when a_ind is set) -+ (optional) -+
    • -+
    • a_ind -+ string or int -+ if given, indent each attribute pair and put on a separate line -+ (optional) -+
    • -+
    • xml_preface -+ string or bool -+ force prefacing with default or custom , if truthy then &lt;?xml version='1.0'?&gt; will be used as default. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string representation -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ Doc:tostring ([b_ind[, t_ind[, a_ind[, xml_preface="<?xml version='1.0'?>"]]]]) -+
    -+
    -+ Method to pretty-print an XML document. -+ Invokes xml.tostring. -+ -+ -+

    Parameters:

    -+
      -+
    • b_ind -+ string or int -+ an initial indent (required when t_ind is set) -+ (optional) -+
    • -+
    • t_ind -+ string or int -+ an indent for each level (required when a_ind is set) -+ (optional) -+
    • -+
    • a_ind -+ string or int -+ if given, indent each attribute pair and put on a separate line -+ (optional) -+
    • -+
    • xml_preface -+ string -+ force prefacing with default or custom -+ (default "<?xml version='1.0'?>") -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ a string representation -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ Doc:get_text () -+
    -+
    -+ get the full text value of an element. -+ -+ -+ -+

    Returns:

    -+
      -+ -+ a single string with all text elements concatenated -+
    -+ -+ -+ -+

    Usage:

    -+
      -+
      local doc = xml.new("main")
      -+doc:text("one")
      -+doc:add_child(xml.elem "two")
      -+doc:text("three")
      -+
      -+local t = doc:get_text()    -->  "onethree"
      -+
    -+ -+
    -+
    -+ -+ clone (doc[, strsubst]) -+
    -+
    -+ -+

    Returns a copy of a document. -+ The strsubst parameter is a callback with signature function(object, kind, parent).

    -+ -+

    Param kind has the following values, and parameters:

    -+ -+
      -+
    • "*TAG": object is the tag-name, parent is the Node object. Returns the new tag name.

    • -+
    • "*TEXT": object is the text-element, parent is the Node object. Returns the new text value.

    • -+
    • other strings not prefixed with *: kind is the attribute name, object is the -+ attribute value, parent is the Node object. Returns the new attribute value.

    • -+
    -+ -+ -+ -+ -+

    Parameters:

    -+
      -+
    • doc -+ Node or string -+ a Node object or string (text node) -+
    • -+
    • strsubst -+ function -+ an optional function for handling string copying -+ which could do substitution, etc. -+ (optional) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ copy of the document -+
    -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ Doc:filter ([strsubst]) -+
    -+
    -+ Returns a copy of a document. -+ This is the method version of xml.clone. -+ -+ -+

    Parameters:

    -+
      -+
    • strsubst -+ function -+ an optional function for handling string copying -+ (optional) -+
    • -+
    -+ -+ -+ -+

    See also:

    -+ -+ -+ -+
    -+
    -+ -+ compare (t1, t2) -+
    -+
    -+ Compare two documents or elements. -+ Equality is based on tag, child nodes (text and tags), attributes and order -+ of those (order only fails if both are given, and not equal). -+ -+ -+

    Parameters:

    -+
      -+
    • t1 -+ Node or string -+ a Node object or string (text node) -+
    • -+
    • t2 -+ Node or string -+ a Node object or string (text node) -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ boolean -+ true when the Nodes are equal. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ is_tag (d) -+
    -+
    -+ is this value a document element? -+ -+ -+

    Parameters:

    -+
      -+
    • d -+ any value -+
    • -+
    -+ -+

    Returns:

    -+
      -+ -+ boolean -+ true if it is a table with property tag being a string value. -+
    -+ -+ -+ -+ -+
    -+
    -+ -+ walk (doc, depth_first, operation) -+
    -+
    -+ Calls a function recursively over Nodes in the document. -+ Will only call on tags, it will skip text nodes. -+ The function signature for operation is function(tag_name, Node). -+ -+ -+

    Parameters:

    -+
      -+
    • doc -+ Node or string -+ a Node object or string (text node) -+
    • -+
    • depth_first -+ boolean -+ visit child nodes first, then the current node -+
    • -+
    • operation -+ function -+ a function which will receive the current tag name and current node. -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ parsehtml (s) -+
    -+
    -+ Parse a well-formed HTML file as a string. -+ Tags are case-insensitive, DOCTYPE is ignored, and empty elements can be .. empty. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ the HTML -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ basic_parse (s, all_text, html) -+
    -+
    -+ Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version. -+ -+ -+

    Parameters:

    -+
      -+
    • s -+ the XML document to be parsed. -+
    • -+
    • all_text -+ if true, preserves all whitespace. Otherwise only text containing non-whitespace is included. -+
    • -+
    • html -+ if true, uses relaxed HTML rules for parsing -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ Doc:match (pat) -+
    -+
    -+ does something... -+ -+ -+

    Parameters:

    -+
      -+
    • pat -+ -+ -+ -+
    • -+
    -+ -+ -+ -+ -+ -+
    -+
    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/01-introduction.md.html b/extra/penlight/docs/manual/01-introduction.md.html -new file mode 100644 -index 0000000..8ff64eb ---- /dev/null -+++ b/extra/penlight/docs/manual/01-introduction.md.html -@@ -0,0 +1,843 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Introduction

    -+ -+

    -+

    Purpose

    -+ -+

    It is often said of Lua that it does not include batteries. That is because the -+goal of Lua is to produce a lean expressive language that will be used on all -+sorts of machines, (some of which don't even have hierarchical filesystems). The -+Lua language is the equivalent of an operating system kernel; the creators of Lua -+do not see it as their responsibility to create a full software ecosystem around -+the language. That is the role of the community.

    -+ -+

    A principle of software design is to recognize common patterns and reuse them. If -+you find yourself writing things like `io.write(string.format('the answer is %d -+',42))` more than a number of times then it becomes useful just to define a -+function printf. This is good, not just because repeated code is harder to -+maintain, but because such code is easier to read, once people understand your -+libraries.

    -+ -+

    Penlight captures many such code patterns, so that the intent of your code -+becomes clearer. For instance, a Lua idiom to copy a table is {unpack(t)}, but -+this will only work for 'small' tables (for a given value of 'small') so it is -+not very robust. Also, the intent is not clear. So tablex.deepcopy is provided, -+which will also copy nested tables and and associated metatables, so it can be -+used to clone complex objects.

    -+ -+

    The default error handling policy follows that of the Lua standard libraries: if -+a argument is the wrong type, then an error will be thrown, but otherwise we -+return nil,message if there is a problem. There are some exceptions; functions -+like input.fields default to shutting down the program immediately with a -+useful message. This is more appropriate behaviour for a script than providing -+a stack trace. (However, this default can be changed.) The lexer functions always -+throw errors, to simplify coding, and so should be wrapped in pcall.

    -+ -+

    If you are used to Python conventions, please note that all indices consistently -+start at 1.

    -+ -+

    The Lua function table.foreach has been deprecated in favour of the for in -+statement, but such an operation becomes particularly useful with the -+higher-order function support in Penlight. Note that tablex.foreach reverses -+the order, so that the function is passed the value and then the key. Although -+perverse, this matches the intended use better.

    -+ -+

    The only important external dependence of Penlight is -+LuaFileSystem -+(lfs), and if you want dir.copyfile to work cleanly on Windows, you will need -+either alien or be using -+LuaJIT as well. (The fallback is to call the equivalent -+shell commands.)

    -+ -+

    -+

    To Inject or not to Inject?

    -+ -+

    It was realized a long time ago that large programs needed a way to keep names -+distinct by putting them into tables (Lua), namespaces (C++) or modules -+(Python). It is obviously impossible to run a company where everyone is called -+'Bruce', except in Monty Python skits. These 'namespace clashes' are more of a -+problem in a simple language like Lua than in C++, because C++ does more -+complicated lookup over 'injected namespaces'. However, in a small group of -+friends, 'Bruce' is usually unique, so in particular situations it's useful to -+drop the formality and not use last names. It depends entirely on what kind of -+program you are writing, whether it is a ten line script or a ten thousand line -+program.

    -+ -+

    So the Penlight library provides the formal way and the informal way, without -+imposing any preference. You can do it formally like:

    -+ -+ -+
    -+local utils = require 'pl.utils'
    -+utils.printf("%s\n","hello, world!")
    -+
    -+ -+

    or informally like:

    -+ -+ -+
    -+require 'pl'
    -+utils.printf("%s\n","That feels better")
    -+
    -+ -+

    require 'pl' makes all the separate Penlight modules available, without needing -+to require them each individually.

    -+ -+

    Generally, the formal way is better when writing modules, since then there are no -+global side-effects and the dependencies of your module are made explicit.

    -+ -+

    Andrew Starks has contributed another way, which balances nicely between the -+formal need to keep the global table uncluttered and the informal need for -+convenience. require'pl.import_into' returns a function, which accepts a table -+for injecting Penlight into, or if no table is given, it passes back a new one.

    -+ -+ -+
    -+local pl = require'pl.import_into'()
    -+
    -+ -+

    The table pl is a 'lazy table' which loads modules as needed, so we can then -+use pl.utils.printf and so forth, without an explicit `require' or harming any -+globals.

    -+ -+

    If you are using _ENV with Lua 5.2 to define modules, then here is a way to -+make Penlight available within a module:

    -+ -+ -+
    -+local _ENV,M = require 'pl.import_into' ()
    -+
    -+function answer ()
    -+    -- all the Penlight modules are available!
    -+    return pretty.write(utils.split '10 20  30', '')
    -+end
    -+
    -+return M
    -+
    -+ -+

    The default is to put Penlight into \_ENV, which has the unintended effect of -+making it available from the module (much as module(...,package.seeall) does). -+To satisfy both convenience and safety, you may pass true to this function, and -+then the module M is not the same as \_ENV, but only contains the exported -+functions.

    -+ -+

    Otherwise, Penlight will not bring in functions into the global table, or -+clobber standard tables like 'io'. require('pl') will bring tables like -+'utils','tablex',etc into the global table if they are used. This -+'load-on-demand' strategy ensures that the whole kitchen sink is not loaded up -+front, so this method is as efficient as explicitly loading required modules.

    -+ -+

    You have an option to bring the pl.stringx methods into the standard string -+table. All strings have a metatable that allows for automatic lookup in string, -+so we can say s:upper(). Importing stringx allows for its functions to also -+be called as methods: s:strip(),etc:

    -+ -+ -+
    -+require 'pl'
    -+stringx.import()
    -+
    -+ -+

    or, more explicitly:

    -+ -+ -+
    -+require('pl.stringx').import()
    -+
    -+ -+

    A more delicate operation is importing tables into the local environment. This is -+convenient when the context makes the meaning of a name very clear:

    -+ -+ -+
    -+> require 'pl'
    -+> utils.import(math)
    -+> = sin(1.2)
    -+0.93203908596723
    -+
    -+ -+

    utils.import can also be passed a module name as a string, which is first -+required and then imported. If used in a module, import will bring the symbols -+into the module context.

    -+ -+

    Keeping the global scope simple is very necessary with dynamic languages. Using -+global variables in a big program is always asking for trouble, especially since -+you do not have the spell-checking provided by a compiler. The pl.strict -+module enforces a simple rule: globals must be 'declared'. This means that they -+must be assigned before use; assigning to nil is sufficient.

    -+ -+ -+
    -+> require 'pl.strict'
    -+> print(x)
    -+stdin:1: variable 'x' is not declared
    -+> x = nil
    -+> print(x)
    -+nil
    -+
    -+ -+

    The strict module provided by Penlight is compatible with the 'load-on-demand' -+scheme used by require 'pl.

    -+ -+

    strict also disallows assignment to global variables, except in the main -+program. Generally, modules have no business messing with global scope; if you -+must do it, then use a call to rawset. Similarly, if you have to check for the -+existence of a global, use rawget.

    -+ -+

    If you wish to enforce strictness globally, then just add require 'pl.strict' -+at the end of pl/init.lua, otherwise call it from your main program.

    -+ -+

    As from 1.1.0, this module provides a strict.module function which creates (or -+modifies) modules so that accessing an unknown function or field causes an error.

    -+ -+

    For example,

    -+ -+ -+
    -+-- mymod.lua
    -+local strict = require 'pl.strict'
    -+local M = strict.module (...)
    -+
    -+function M.answer ()
    -+    return 42
    -+end
    -+
    -+return M
    -+
    -+ -+

    If you were to accidentally type mymod.Answer(), then you would get a runtime -+error: "variable 'Answer' is not declared in 'mymod'".

    -+ -+

    This can be applied to existing modules. You may desire to have the same level -+of checking for the Lua standard libraries:

    -+ -+ -+
    -+strict.make_all_strict(_G)
    -+
    -+ -+

    Thereafter a typo such as math.cosine will give you an explicit error, rather -+than merely returning a nil that will cause problems later.

    -+ -+

    -+

    What are function arguments in Penlight?

    -+ -+

    Many functions in Penlight themselves take function arguments, like map which -+applies a function to a list, element by element. You can use existing -+functions, like math.max, anonymous functions (like `function(x,y) return x > y -+end ), or operations by name (e.g '*' or '..'). The module pl.operator` exports -+all the standard Lua operations, like the Python module of the same name. -+Penlight allows these to be referred to by name, so operator.gt can be more -+concisely expressed as '>'.

    -+ -+

    Note that the map functions pass any extra arguments to the function, so we can -+have ls:filter('>',0), which is a shortcut for -+ls:filter(function(x) return x > 0 end).

    -+ -+

    Finally, pl.func supports placeholder expressions in the Boost lambda style, -+so that an anonymous function to multiply the two arguments can be expressed as -+\1*\2.

    -+ -+

    To use them directly, note that all function arguments in Penlight go through -+utils.function_arg. pl.func registers itself with this function, so that you -+can directly use placeholder expressions with standard methods:

    -+ -+ -+
    -+> _1 = func._1
    -+> = List{10,20,30}:map(_1+1)
    -+{11,21,31}
    -+
    -+ -+

    Another option for short anonymous functions is provided by -+utils.string_lambda; this is invoked automatically:

    -+ -+ -+
    -+> = List{10,20,30}:map '|x| x + 1'
    -+{11,21,31}
    -+
    -+ -+

    -+

    Pros and Cons of Loopless Programming

    -+ -+

    The standard loops-and-ifs 'imperative' style of programming is dominant, and -+often seems to be the 'natural' way of telling a machine what to do. It is in -+fact very much how the machine does things, but we need to take a step back and -+find ways of expressing solutions in a higher-level way. For instance, applying -+a function to all elements of a list is a common operation:

    -+ -+ -+
    -+local res = {}
    -+for i = 1,#ls do
    -+    res[i] = fun(ls[i])
    -+end
    -+
    -+ -+

    This can be efficiently and succinctly expressed as ls:map(fun). Not only is -+there less typing but the intention of the code is clearer. If readers of your -+code spend too much time trying to guess your intention by analyzing your loops, -+then you have failed to express yourself clearly. Similarly, ls:filter('>',0) -+will give you all the values in a list greater than zero. (Of course, if you -+don't feel like using List, or have non-list-like tables, then pl.tablex -+offers the same facilities. In fact, the List methods are implemented using -+tablex functions.)

    -+ -+

    A common observation is that loopless programming is less efficient, particularly -+in the way it uses memory. ls1:map2('*',ls2):reduce '+' will give you the dot -+product of two lists, but an unnecessary temporary list is created. But -+efficiency is relative to the actual situation, it may turn out to be fast -+enough, or may not appear in any crucial inner loops, etc.

    -+ -+

    Writing loops is 'error-prone and tedious', as Stroustrup says. But any -+half-decent editor can be taught to do much of that typing for you. The question -+should actually be: is it tedious to read loops? As with natural language, -+programmers tend to read chunks at a time. A for-loop causes no surprise, and -+probably little brain activity. One argument for loopless programming is the -+loops that you do write stand out more, and signal 'something different -+happening here'. It should not be an all-or-nothing thing, since most programs -+require a mixture of idioms that suit the problem. Some languages (like APL) do -+nearly everything with map and reduce operations on arrays, and so solutions can -+sometimes seem forced. Wisdom is knowing when a particular idiom makes a -+particular problem easy to solve and the solution easy to explain afterwards.

    -+ -+

    -+

    Generally useful functions.

    -+ -+

    The function printf discussed earlier is included in pl.utils because it -+makes properly formatted output easier. (There is an equivalent fprintf which -+also takes a file object parameter, just like the C function.)

    -+ -+

    Splitting a string using a delimiter is a fairly common operation, hence split.

    -+ -+

    Utility functions like is_type help with identifying what -+kind of animal you are dealing with. -+The Lua type function handles the basic types, but can't distinguish between -+different kinds of objects, which are all tables. So is_type handles both -+cases, like is_type(s,"string") and is_type(ls,List).

    -+ -+

    A common pattern when working with Lua varargs is capturing all the arguments in -+a table:

    -+ -+ -+
    -+function t(...)
    -+    local args = {...}
    -+    ...
    -+end
    -+
    -+ -+

    But this will bite you someday when nil is one of the arguments, since this -+will put a 'hole' in your table. In particular, #ls will only give you the size -+upto the nil value. Hence the need for table.pack - this is a new Lua 5.2 -+function which Penlight defines also for Lua 5.1.

    -+ -+ -+
    -+function t(...)
    -+    local args,n = table.pack(...)
    -+    for i = 1,n do
    -+      ...
    -+    end
    -+end
    -+
    -+ -+

    The 'memoize' pattern occurs when you have a function which is expensive to call, -+but will always return the same value subsequently. utils.memoize is given a -+function, and returns another function. This calls the function the first time, -+saves the value for that argument, and thereafter for that argument returns the -+saved value. This is a more flexible alternative to building a table of values -+upfront, since in general you won't know what values are needed.

    -+ -+ -+
    -+sum = utils.memoize(function(n)
    -+    local sum = 0
    -+    for i = 1,n do sum = sum + i end
    -+    return sum
    -+end)
    -+...
    -+s = sum(1e8) --takes time!
    -+...
    -+s = sum(1e8) --returned saved value!
    -+
    -+ -+

    Penlight is fully compatible with Lua 5.1, 5.2 and LuaJIT 2. To ensure this, -+utils also defines the global Lua 5.2 -+load function as utils.load

    -+ -+
      -+
    • the input (either a string or a function)
    • -+
    • the source name used in debug information
    • -+
    • the mode is a string that can have either or both of 'b' or 't', depending on -+ whether the source is a binary chunk or text code (default is 'bt')
    • -+
    • the environment for the compiled chunk
    • -+
    -+ -+

    Using utils.load should reduce the need to call the deprecated function setfenv, -+and make your Lua 5.1 code 5.2-friendly.

    -+ -+

    The utils module exports getfenv and setfenv for -+Lua 5.2 as well, based on code by Sergey Rozhenko. Note that these functions can fail -+for functions which don't access any globals.

    -+ -+

    -+

    Application Support

    -+ -+

    app.parse_args is a simple command-line argument parser. If called without any -+arguments, it tries to use the global arg array. It returns the flags -+(options beginning with '-') as a table of name/value pairs, and the arguments -+as an array. It knows about long GNU-style flag names, e.g. --value, and -+groups of short flags are understood, so that -ab is short for -a -b. The -+flags result would then look like {value=true,a=true,b=true}.

    -+ -+

    Flags may take values. The command-line --value=open -n10 would result in -+{value='open',n='10'}; generally you can use '=' or ':' to separate the flag -+from its value, except in the special case where a short flag is followed by an -+integer. Or you may specify upfront that some flags have associated values, and -+then the values will follow the flag.

    -+ -+ -+
    -+> require 'pl'
    -+> flags,args = app.parse_args({'-o','fred','-n10','fred.txt'},{o=true})
    -+> pretty.dump(flags)
    -+{o='fred',n='10'}
    -+
    -+ -+

    parse_args is not intelligent or psychic; it will not convert any flag values -+or arguments for you, or raise errors. For that, have a look at -+Lapp.

    -+ -+

    An application which consists of several files usually cannot use require to -+load files in the same directory as the main script. app.require_here() -+ensures that the Lua module path is modified so that files found locally are -+found first. In the examples directory, test-symbols.lua uses this function -+to ensure that it can find symbols.lua even if it is not run from this directory.

    -+ -+

    app.appfile will create a filename that your application can use to store its -+private data, based on the script name. For example, app.appfile "test.txt" -+from a script called testapp.lua produces the following file on my Windows -+machine:

    -+ -+
    C:\Documents and Settings\SJDonova\.testapp\test.txt
    -+
    -+ -+ -+

    and the equivalent on my Linux machine:

    -+ -+
    /home/sdonovan/.testapp/test.txt
    -+
    -+ -+ -+

    If .testapp does not exist, it will be created.

    -+ -+

    Penlight makes it convenient to save application data in Lua format. You can use -+pretty.dump(t,file) to write a Lua table in a human-readable form to a file, -+and pretty.read(file.read(file)) to generate the table again, using the -+pretty module.

    -+ -+ -+

    -+

    Simplifying Object-Oriented Programming in Lua

    -+ -+

    Lua is similar to JavaScript in that the concept of class is not directly -+supported by the language. In fact, Lua has a very general mechanism for -+extending the behaviour of tables which makes it straightforward to implement -+classes. A table's behaviour is controlled by its metatable. If that metatable -+has a \\index function or table, this will handle looking up anything which is -+not found in the original table. A class is just a table with an __index key -+pointing to itself. Creating an object involves making a table and setting its -+metatable to the class; then when handling obj.fun, Lua first looks up fun in -+the table obj, and if not found it looks it up in the class. obj:fun(a) is -+just short for obj.fun(obj,a). So with the metatable mechanism and this bit of -+syntactic sugar, it is straightforward to implement classic object orientation.

    -+ -+ -+
    -+-- animal.lua
    -+
    -+class = require 'pl.class'
    -+
    -+class.Animal()
    -+
    -+function Animal:_init(name)
    -+    self.name = name
    -+end
    -+
    -+function Animal:__tostring()
    -+  return self.name..': '..self:speak()
    -+end
    -+
    -+class.Dog(Animal)
    -+
    -+function Dog:speak()
    -+  return 'bark'
    -+end
    -+
    -+class.Cat(Animal)
    -+
    -+function Cat:_init(name,breed)
    -+    self:super(name)  -- must init base!
    -+    self.breed = breed
    -+end
    -+
    -+function Cat:speak()
    -+  return 'meow'
    -+end
    -+
    -+class.Lion(Cat)
    -+
    -+function Lion:speak()
    -+  return 'roar'
    -+end
    -+
    -+fido = Dog('Fido')
    -+felix = Cat('Felix','Tabby')
    -+leo = Lion('Leo','African')
    -+
    -+$ lua -i animal.lua
    -+> = fido,felix,leo
    -+Fido: bark      Felix: meow     Leo: roar
    -+> = leo:is_a(Animal)
    -+true
    -+> = leo:is_a(Dog)
    -+false
    -+> = leo:is_a(Cat)
    -+true
    -+
    -+ -+

    All Animal does is define \\tostring, which Lua will use whenever a string -+representation is needed of the object. In turn, this relies on speak, which is -+not defined. So it's what C++ people would call an abstract base class; the -+specific derived classes like Dog define speak. Please note that if derived -+classes have their own constructors, they must explicitly call the base -+constructor for their base class; this is conveniently available as the super -+method.

    -+ -+

    Note that (as always) there are multiple ways to implement OOP in Lua; this method -+uses the classic 'a class is the __index of its objects' but does 'fat inheritance'; -+methods from the base class are copied into the new class. The advantage of this is -+that you are not penalized for long inheritance chains, for the price of larger classes, -+but generally objects outnumber classes! (If not, something odd is going on with your design.)

    -+ -+

    All such objects will have a is_a method, which looks up the inheritance chain -+to find a match. Another form is class_of, which can be safely called on all -+objects, so instead of leo:is_a(Animal) one can say Animal:class_of(leo).

    -+ -+

    There are two ways to define a class, either class.Name() or Name = class(); -+both work identically, except that the first form will always put the class in -+the current environment (whether global or module); the second form provides more -+flexibility about where to store the class. The first form does name the class -+by setting the _name field, which can be useful in identifying the objects of -+this type later. This session illustrates the usefulness of having named classes, -+if no __tostring method is explicitly defined.

    -+ -+ -+
    -+> class.Fred()
    -+> a = Fred()
    -+> = a
    -+Fred: 00459330
    -+> Alice = class()
    -+> b = Alice()
    -+> = b
    -+table: 00459AE8
    -+> Alice._name = 'Alice'
    -+> = b
    -+Alice: 00459AE8
    -+
    -+ -+

    So Alice = class(); Alice._name = 'Alice' is exactly the same as class.Alice().

    -+ -+

    This useful notation is borrowed from Hugo Etchegoyen's -+classlib which further -+extends this concept to allow for multiple inheritance. Notice that the -+more convenient form puts the class name in the current environment! That is, -+you may use it safely within modules using the old-fashioned module() -+or the new _ENV mechanism.

    -+ -+

    There is always more than one way of doing things in Lua; some may prefer this -+style for creating classes:

    -+ -+ -+
    -+local class = require 'pl.class'
    -+
    -+class.Named {
    -+    _init = function(self,name)
    -+        self.name = name
    -+    end;
    -+
    -+    __tostring = function(self)
    -+        return 'boo '..self.name
    -+    end;
    -+}
    -+
    -+b = Named 'dog'
    -+print(b)
    -+--> boo dog
    -+
    -+ -+

    Note that you have to explicitly declare self and end each function definition -+with a semi-colon or comma, since this is a Lua table. To inherit from a base class, -+set the special field _base to the class in this table.

    -+ -+

    Penlight provides a number of useful classes; there is List, which is a Lua -+clone of the standard Python list object, and Set which represents sets. There -+are three kinds of map defined: Map, MultiMap (where a key may have -+multiple values) and OrderedMap (where the order of insertion is remembered.). -+There is nothing special about these classes and you may inherit from them.

    -+ -+

    A powerful thing about dynamic languages is that you can redefine existing classes -+and functions, which is often called 'monkey patching' It's entertaining and convenient, -+but ultimately anti-social; you may modify List but then any other modules using -+this shared resource can no longer be sure about its behaviour. (This is why you -+must say stringx.import() explicitly if you want the extended string methods - it -+would be a bad default.) Lua is particularly open to modification but the -+community is not as tolerant of monkey-patching as the Ruby community, say. You may -+wish to add some new methods to List? Cool, but that's what subclassing is for.

    -+ -+ -+
    -+class.Strings(List)
    -+
    -+function Strings:my_method()
    -+...
    -+end
    -+
    -+ -+

    It's definitely more useful to define exactly how your objects behave -+in unknown conditions. All classes have a catch method you can use to set -+a handler for unknown lookups; the function you pass looks exactly like the -+__index metamethod.

    -+ -+ -+
    -+Strings:catch(function(self,name)
    -+    return function() error("no such method "..name,2) end
    -+end)
    -+
    -+ -+

    In this case we're just customizing the error message, but -+creative things can be done. Consider this code from test-vector.lua:

    -+ -+ -+
    -+Strings:catch(List.default_map_with(string))
    -+
    -+ls = Strings{'one','two','three'}
    -+asserteq(ls:upper(),{'ONE','TWO','THREE'})
    -+asserteq(ls:sub(1,2),{'on','tw','th'})
    -+
    -+ -+

    So we've converted a unknown method invocation into a map using the function of -+that name found in string. So for a Vector (which is a specialization of List -+for numbers) it makes sense to make math the default map so that v:sin() makes -+sense.

    -+ -+

    Note that map operations return a object of the same type - this is often called -+covariance. So ls:upper() itself returns a Strings object.

    -+ -+

    This is not always what you want, but objects can always be cast to the desired type. -+(cast doesn't create a new object, but returns the object passed.)

    -+ -+ -+
    -+local sizes = ls:map '#'
    -+asserteq(sizes, {3,3,5})
    -+asserteq(utils.type(sizes),'Strings')
    -+asserteq(sizes:is_a(Strings),true)
    -+sizes = Vector:cast(sizes)
    -+asserteq(utils.type(sizes),'Vector')
    -+asserteq(sizes+1,{4,4,6})
    -+
    -+ -+

    About utils.type: it can only return a string for a class type if that class does -+in fact have a _name field.

    -+ -+ -+

    Properties are a useful object-oriented pattern. We wish to control access to a -+field, but don't wish to force the user of the class to say obj:get_field() -+etc. This excerpt from tests/test-class.lua shows how it is done:

    -+ -+ -+ -+
    -+local MyProps = class(class.properties)
    -+local setted_a, got_b
    -+
    -+function MyProps:_init ()
    -+    self._a = 1
    -+    self._b = 2
    -+end
    -+
    -+function MyProps:set_a (v)
    -+    setted_a = true
    -+    self._a = v
    -+end
    -+
    -+function MyProps:get_b ()
    -+    got_b = true
    -+    return self._b
    -+end
    -+
    -+local mp = MyProps()
    -+
    -+mp.a = 10
    -+
    -+asserteq(mp.a,10)
    -+asserteq(mp.b,2)
    -+asserteq(setted_a and got_b, true)
    -+
    -+ -+

    The convention is that the internal field name is prefixed with an underscore; -+when reading mp.a, first a check for an explicit getter get_a and then only -+look for _a. Simularly, writing mp.a causes the setter set_a to be used.

    -+ -+

    This is cool behaviour, but like much Lua metaprogramming, it is not free. Method -+lookup on such objects goes through \\index as before, but now \\index is a -+function which has to explicitly look up methods in the class, before doing any -+property indexing, which is not going to be as fast as field lookup. If however, -+your accessors actually do non-trivial things, then the extra overhead could be -+worth it.

    -+ -+

    This is not really intended for access control because external code can write -+to mp._a directly. It is possible to have this kind of control in Lua, but it -+again comes with run-time costs.

    -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/02-arrays.md.html b/extra/penlight/docs/manual/02-arrays.md.html -new file mode 100644 -index 0000000..daa493d ---- /dev/null -+++ b/extra/penlight/docs/manual/02-arrays.md.html -@@ -0,0 +1,914 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Tables and Arrays

    -+ -+

    -+ -+

    -+

    Python-style Lists

    -+ -+

    One of the elegant things about Lua is that tables do the job of both lists and -+dicts (as called in Python) or vectors and maps, (as called in C++), and they do -+it efficiently. However, if we are dealing with 'tables with numerical indices' -+we may as well call them lists and look for operations which particularly make -+sense for lists. The Penlight List class was originally written by Nick Trout -+for Lua 5.0, and translated to 5.1 and extended by myself. It seemed that -+borrowing from Python was a good idea, and this eventually grew into Penlight.

    -+ -+

    Here is an example showing List in action; it redefines __tostring, so that -+it can print itself out more sensibly:

    -+ -+ -+
    -+> List = require 'pl.List'  --> automatic with require 'pl' <---
    -+> l = List()
    -+> l:append(10)
    -+> l:append(20)
    -+> = l
    -+{10,20}
    -+> l:extend {30,40}
    -+{10,20,30,40}
    -+> l:insert(1,5)
    -+{5,10,20,30,40}
    -+> = l:pop()
    -+40
    -+> = l
    -+{5,10,20,30}
    -+> = l:index(30)
    -+4
    -+> = l:contains(30)
    -+true
    -+> = l:reverse()  ---> note: doesn't make a copy!
    -+{30,20,10,5}
    -+
    -+ -+

    Although methods like sort and reverse operate in-place and change the list, -+they do return the original list. This makes it possible to do method chaining, -+like ls = ls:append(10):append(20):reverse():append(1). But (and this is an -+important but) no extra copy is made, so ls does not change identity. List -+objects (like tables) are mutable, unlike strings. If you want a copy of a -+list, then List(ls) will do the job, i.e. it acts like a copy constructor. -+However, if passed any other table, List will just set the metatable of the -+table and not make a copy.

    -+ -+

    A particular feature of Python lists is slicing. This is fully supported in -+this version of List, except we use 1-based indexing. So List.slice works -+rather like string.sub:

    -+ -+ -+
    -+> l = List {10,20,30,40}
    -+> = l:slice(1,1)  ---> note: creates a new list!
    -+{10}
    -+> = l:slice(2,2)
    -+{20}
    -+> = l:slice(2,3)
    -+{20,30}
    -+> = l:slice(2,-2)
    -+{20,30}
    -+> = l:slice_assign(2,2,{21,22,23})
    -+{10,21,22,23,30,40}
    -+> = l:chop(1,1)
    -+{21,22,23,30,40}
    -+
    -+ -+

    Functions like slice_assign and chop modify the list; the first is equivalent -+to Pythonl[i1:i2] = seq and the second to del l[i1:i2].

    -+ -+

    List objects are ultimately just Lua 'list-like' tables, but they have extra -+operations defined on them, such as equality and concatention. For regular -+tables, equality is only true if the two tables are identical objects, whereas -+two lists are equal if they have the same contents, i.e. that l1[i]==l2[i] for -+all elements.

    -+ -+ -+
    -+> l1 = List {1,2,3}
    -+> l2 = List {1,2,3}
    -+> = l1 == l2
    -+true
    -+> = l1..l2
    -+{1,2,3,1,2,3}
    -+
    -+ -+

    The List constructor can be passed a function. If so, it's assumed that this is -+an iterator function that can be repeatedly called to generate a sequence. One -+such function is io.lines; the following short, intense little script counts -+the number of lines in standard input:

    -+ -+ -+
    -+-- linecount.lua
    -+require 'pl'
    -+ls = List(io.lines())
    -+print(#ls)
    -+
    -+ -+

    List.iterate captures what List considers a sequence. In particular, it can -+also iterate over all 'characters' in a string:

    -+ -+ -+
    -+> for ch in List.iterate 'help' do io.write(ch,' ') end
    -+h e l p >
    -+
    -+ -+

    Since the function iterate is used internally by the List constructor, -+strings can be made into lists of character strings very easily.

    -+ -+

    There are a number of operations that go beyond the standard Python methods. For -+instance, you can partition a list into a table of sublists using a function. -+In the simplest form, you use a predicate (a function returning a boolean value) -+to partition the list into two lists, one of elements matching and another of -+elements not matching. But you can use any function; if we use type then the -+keys will be the standard Lua type names.

    -+ -+ -+
    -+> ls = List{1,2,3,4}
    -+> ops = require 'pl.operator'
    -+> ls:partition(function(x) return x > 2 end)
    -+{false={1,2},true={3,4}}
    -+> ls = List{'one',math.sin,List{1},10,20,List{1,2}}
    -+> ls:partition(type)
    -+{function={function: 00369110},string={one},number={10,20},table={{1},{1,2}}}
    -+
    -+ -+

    This is one List method which returns a table which is not a List. Bear in -+mind that you can always call a List method on a plain table argument, so -+List.partition(t,type) works as expected. But these functions will only operate -+on the array part of the table.

    -+ -+

    The 'nominal' type of the returned table is pl.Multimap, which describes a mapping -+between keys and multiple values. This does not mean that pl.Multimap is automatically -+loaded whenever you use partition (or List for that matter); this is one of the -+standard metatables which are only filled out when the appropriate module is loaded. -+This allows tables to be tagged appropriately without causing excessive coupling.

    -+ -+

    Stacks occur everywhere in computing. List supports stack-like operations; -+there is already pop (remove and return last value) and append acts like -+push (add a value to the end). push is provided as an alias for append, and -+the other stack operation (size) is simply the size operator #. Queues can -+also be implemented; you use pop to take values out of the queue, and put to -+insert a value at the beginning.

    -+ -+

    You may derive classes from List, and since the list-returning methods -+are covariant, the result of slice etc will return lists of the derived type, -+not List. For instance, consider the specialization of a List type that contains -+numbers in tests/test-list.lua:

    -+ -+ -+
    -+n1 = NA{10,20,30}
    -+n2 = NA{1,2,3}
    -+ns = n1 + 2*n2
    -+asserteq(ns,{12,24,36})
    -+min,max = ns:slice(1,2):minmax()
    -+asserteq(T(min,max),T(12,24))
    -+asserteq(n1:normalize():sum(),1,1e-8)
    -+
    -+ -+

    -+

    Map and Set classes

    -+ -+

    The Map class exposes what Python would call a 'dict' interface, and accesses -+the hash part of the table. The name 'Map' is used to emphasize the interface, -+not the implementation; it is an object which maps keys onto values; m['alice'] -+or the equivalent m.alice is the access operation. This class also provides -+explicit set and get methods, which are trivial for regular maps but get -+interesting when Map is subclassed. The other operation is update, which -+extends a map by copying the keys and values from another table, perhaps -+overwriting existing keys:

    -+ -+ -+
    -+> Map = require 'pl.Map'
    -+> m = Map{one=1,two=2}
    -+> m:update {three=3,four=4,two=20}
    -+> = m == M{one=1,two=20,three=3,four=4}
    -+true
    -+
    -+ -+

    The method values returns a list of the values, and keys returns a list of -+the keys; there is no guarantee of order. getvalues is given a list of keys and -+returns a list of values associated with these keys:

    -+ -+ -+
    -+> m = Map{one=1,two=2,three=3}
    -+> = m:getvalues {'one','three'}
    -+{1,3}
    -+> = m:getvalues(m:keys()) == m:values()
    -+true
    -+
    -+ -+

    When querying the value of a Map, it is best to use the get method:

    -+ -+ -+
    -+> print(m:get 'one', m:get 'two')
    -+1     2
    -+
    -+ -+

    The reason is that m[key] can be ambiguous; due to the current implementation, -+m["get"] will always succeed, because if a value is not present in the map, it -+will be looked up in the Map metatable, which contains a method get. There is -+currently no simple solution to this annoying restriction.

    -+ -+

    There are some useful classes which inherit from Map. An OrderedMap behaves -+like a Map but keeps its keys in order if you use its set method to add keys -+and values. Like all the 'container' classes in Penlight, it defines an iter -+method for iterating over its values; this will return the keys and values in the -+order of insertion; the keys and values methods likewise.

    -+ -+

    A MultiMap allows multiple values to be associated with a given key. So set -+(as before) takes a key and a value, but calling it with the same key and a -+different value does not overwrite but adds a new value. get (or using []) -+will return a list of values.

    -+ -+

    A Set can be seen as a special kind of Map, where all the values are true, -+the keys are the values, and the order is not important. So in this case -+Set.values is defined to return a list of the keys. Sets can display -+themselves, and the basic operations like union (+) and intersection (*) -+are defined.

    -+ -+ -+
    -+> Set = require 'pl.Set'
    -+> = Set{'one','two'} == Set{'two','one'}
    -+true
    -+> fruit = Set{'apple','banana','orange'}
    -+> = fruit['banana']
    -+true
    -+> = fruit['hazelnut']
    -+nil
    -+> = fruit:values()
    -+{apple,orange,banana}
    -+> colours = Set{'red','orange','green','blue'}
    -+> = fruit,colours
    -+[apple,orange,banana]   [blue,green,orange,red]
    -+> = fruit+colours
    -+[blue,green,apple,red,orange,banana]
    -+> = fruit*colours
    -+[orange]
    -+
    -+ -+

    There are also the functions Set.difference and Set.symmetric_difference. The -+first answers the question 'what fruits are not colours?' and the second 'what -+are fruits and colours but not both?'

    -+ -+ -+
    -+> = fruit - colours
    -+[apple,banana]
    -+> = fruit ^ colours
    -+[blue,green,apple,red,banana]
    -+
    -+ -+

    Adding elements to a set is simply fruit['peach'] = true and removing is -+fruit['apple'] = nil . To make this simplicity work properly, the Set class has no -+methods - either you use the operator forms or explicitly use Set.intersect -+etc. In this way we avoid the ambiguity that plagues Map.

    -+ -+ -+

    (See pl.Map and pl.Set)

    -+ -+

    -+

    Useful Operations on Tables

    -+ -+ -+

    Some notes on terminology: Lua tables are usually list-like (like an array) or -+map-like (like an associative array or dict); they can of course have a -+list-like and a map-like part. Some of the table operations only make sense for -+list-like tables, and some only for map-like tables. (The usual Lua terminology -+is the array part and the hash part of the table, which reflects the actual -+implementation used; it is more accurate to say that a Lua table is an -+associative map which happens to be particularly efficient at acting like an -+array.)

    -+ -+

    The functions provided in table provide all the basic manipulations on Lua -+tables, but as we saw with the List class, it is useful to build higher-level -+operations on top of those functions. For instance, to copy a table involves this -+kind of loop:

    -+ -+ -+
    -+local res = {}
    -+for k,v in pairs(T) do
    -+    res[k] = v
    -+end
    -+return res
    -+
    -+ -+

    The tablex module provides this as copy, which does a shallow copy of a -+table. There is also deepcopy which goes further than a simple loop in two -+ways; first, it also gives the copy the same metatable as the original (so it can -+copy objects like List above) and any nested tables will also be copied, to -+arbitrary depth. There is also icopy which operates on list-like tables, where -+you can set optionally set the start index of the source and destination as well. -+It ensures that any left-over elements will be deleted:

    -+ -+ -+
    -+asserteq(icopy({1,2,3,4,5,6},{20,30}),{20,30})   -- start at 1
    -+asserteq(icopy({1,2,3,4,5,6},{20,30},2),{1,20,30}) -- start at 2
    -+asserteq(icopy({1,2,3,4,5,6},{20,30},2,2),{1,30}) -- start at 2, copy from 2
    -+
    -+ -+

    (This code from the tablex test module shows the use of pl.test.asserteq)

    -+ -+

    Whereas, move overwrites but does not delete the rest of the destination:

    -+ -+ -+
    -+asserteq(move({1,2,3,4,5,6},{20,30}),{20,30,3,4,5,6})
    -+asserteq(move({1,2,3,4,5,6},{20,30},2),{1,20,30,4,5,6})
    -+asserteq(move({1,2,3,4,5,6},{20,30},2,2),{1,30,3,4,5,6})
    -+
    -+ -+

    (The difference is somewhat like that between C's strcpy and memmove.)

    -+ -+

    To summarize, use copy or deepcopy to make a copy of an arbitrary table. To -+copy into a map-like table, use update; to copy into a list-like table use -+icopy, and move if you are updating a range in the destination.

    -+ -+

    To complete this set of operations, there is insertvalues which works like -+table.insert except that one provides a table of values to be inserted, and -+removevalues which removes a range of values.

    -+ -+ -+
    -+asserteq(insertvalues({1,2,3,4},2,{20,30}),{1,20,30,2,3,4})
    -+asserteq(insertvalues({1,2},{3,4}),{1,2,3,4})
    -+
    -+ -+

    Another example:

    -+ -+ -+
    -+> T = require 'pl.tablex'
    -+> t = {10,20,30,40}
    -+> = T.removevalues(t,2,3)
    -+{10,40}
    -+> = T.insertvalues(t,2,{20,30})
    -+{10,20,30,40}
    -+
    -+ -+

    In a similar spirit to deepcopy, deepcompare will take two tables and return -+true only if they have exactly the same values and structure.

    -+ -+ -+
    -+> t1 = {1,{2,3},4}
    -+> t2 = deepcopy(t1)
    -+> = t1 == t2
    -+false
    -+> = deepcompare(t1,t2)
    -+true
    -+
    -+ -+

    find will return the index of a given value in a list-like table. Note that -+like string.find you can specify an index to start searching, so that all -+instances can be found. There is an optional fourth argument, which makes the -+search start at the end and go backwards, so we could define rfind like so:

    -+ -+ -+
    -+function rfind(t,val,istart)
    -+    return tablex.find(t,val,istart,true)
    -+end
    -+
    -+ -+

    find does a linear search, so it can slow down code that depends on it. If -+efficiency is required for large tables, consider using an index map. -+index_map will return a table where the keys are the original values of the -+list, and the associated values are the indices. (It is almost exactly the -+representation needed for a set.)

    -+ -+ -+
    -+> t = {'one','two','three'}
    -+> = tablex.find(t,'two')
    -+2
    -+> = tablex.find(t,'four')
    -+nil
    -+> il = tablex.index_map(t)
    -+> = il['two']
    -+2
    -+> = il.two
    -+2
    -+
    -+ -+

    A version of index_map called makeset is also provided, where the values are -+just true. This is useful because two such sets can be compared for equality -+using deepcompare:

    -+ -+ -+
    -+> = deepcompare(makeset {1,2,3},makeset {2,1,3})
    -+true
    -+
    -+ -+

    Consider the problem of determining the new employees that have joined in a -+period. Assume we have two files of employee names:

    -+ -+ -+
    -+(last-month.txt)
    -+smith,john
    -+brady,maureen
    -+mongale,thabo
    -+
    -+(this-month.txt)
    -+smith,john
    -+smit,johan
    -+brady,maureen
    -+mogale,thabo
    -+van der Merwe,Piet
    -+
    -+ -+

    To find out differences, just make the employee lists into sets, like so:

    -+ -+ -+
    -+require 'pl'
    -+
    -+function read_employees(file)
    -+  local ls = List(io.lines(file)) -- a list of employees
    -+  return tablex.makeset(ls)
    -+end
    -+
    -+last = read_employees 'last-month.txt'
    -+this = read_employees 'this-month.txt'
    -+
    -+-- who is in this but not in last?
    -+diff = tablex.difference(this,last)
    -+
    -+-- in a set, the keys are the values...
    -+for e in pairs(diff) do print(e) end
    -+
    -+--  *output*
    -+-- van der Merwe,Piet
    -+-- smit,johan
    -+
    -+ -+

    The difference operation is easy to write and read:

    -+ -+ -+
    -+for e in pairs(this) do
    -+  if not last[e] then
    -+    print(e)
    -+  end
    -+end
    -+
    -+ -+

    Using difference here is not that it is a tricky thing to code, it is that you -+are stating your intentions clearly to other readers of your code. (And naturally -+to your future self, in six months time.)

    -+ -+

    find_if will search a table using a function. The optional third argument is a -+value which will be passed as a second argument to the function. pl.operator -+provides the Lua operators conveniently wrapped as functions, so the basic -+comparison functions are available:

    -+ -+ -+
    -+> ops = require 'pl.operator'
    -+> = tablex.find_if({10,20,30,40},ops.gt,20)
    -+3       true
    -+
    -+ -+

    Note that find_if will also return the actual value returned by the function, -+which of course is usually just true for a boolean function, but any value -+which is not nil and not false can be usefully passed back.

    -+ -+

    deepcompare does a thorough recursive comparison, but otherwise using the -+default equality operator. compare allows you to specify exactly what function -+to use when comparing two list-like tables, and compare_no_order is true if -+they contain exactly the same elements. Do note that the latter does not need an -+explicit comparison function - in this case the implementation is actually to -+compare the two sets, as above:

    -+ -+ -+
    -+> = compare_no_order({1,2,3},{2,1,3})
    -+true
    -+> = compare_no_order({1,2,3},{2,1,3},'==')
    -+true
    -+
    -+ -+

    (Note the special string '==' above; instead of saying ops.gt or ops.eq we -+can use the strings '>' or '==' respectively.)

    -+ -+

    sort and sortv return iterators that will iterate through the -+sorted elements of a table. sort iterates by sorted key order, and -+sortv iterates by sorted value order. For example, given a table -+with names and ages, it is trivial to iterate over the elements:

    -+ -+ -+
    -+> t = {john=27,jane=31,mary=24}
    -+> for name,age in tablex.sort(t) do print(name,age) end
    -+jane    31
    -+john    27
    -+mary    24
    -+> for name,age in tablex.sortv(t) do print(name,age) end
    -+mary    24
    -+john    27
    -+jane    31
    -+
    -+ -+

    There are several ways to merge tables in PL. If they are list-like, then see the -+operations defined by pl.List, like concatenation. If they are map-like, then -+merge provides two basic operations. If the third arg is false, then the result -+only contains the keys that are in common between the two tables, and if true, -+then the result contains all the keys of both tables. These are in fact -+generalized set union and intersection operations:

    -+ -+ -+
    -+> S1 = {john=27,jane=31,mary=24}
    -+> S2 = {jane=31,jones=50}
    -+> = tablex.merge(S1, S2, false)
    -+{jane=31}
    -+> = tablex.merge(S1, S2, true)
    -+{mary=24,jane=31,john=27,jones=50}
    -+
    -+ -+

    When working with tables, you will often find yourself writing loops like in the -+first example. Loops are second nature to programmers, but they are often not the -+most elegant and self-describing way of expressing an operation. Consider the -+map function, which creates a new table by applying a function to each element -+of the original:

    -+ -+ -+
    -+> = map(math.sin, {1,2,3,4})
    -+{  0.84,  0.91,  0.14, -0.76}
    -+> = map(function(x) return x*x end, {1,2,3,4})
    -+{1,4,9,16}
    -+
    -+ -+

    map saves you from writing a loop, and the resulting code is often clearer, as -+well as being shorter. This is not to say that 'loops are bad' (although you will -+hear that from some extremists), just that it's good to capture standard -+patterns. Then the loops you do write will stand out and acquire more significance.

    -+ -+

    pairmap is interesting, because the function works with both the key and the -+value.

    -+ -+ -+
    -+> t = {fred=10,bonzo=20,alice=4}
    -+> = pairmap(function(k,v) return v end, t)
    -+{4,10,20}
    -+> = pairmap(function(k,v) return k end, t)
    -+{'alice','fred','bonzo'}
    -+
    -+ -+

    (These are common enough operations that the first is defined as values and the -+second as keys.) If the function returns two values, then the second value is -+considered to be the new key:

    -+ -+ -+
    -+> = pairmap(t,function(k,v) return v+10, k:upper() end)
    -+{BONZO=30,FRED=20,ALICE=14}
    -+
    -+ -+

    map2 applies a function to two tables:

    -+ -+ -+
    -+> map2(ops.add,{1,2},{10,20})
    -+{11,22}
    -+> map2('*',{1,2},{10,20})
    -+{10,40}
    -+
    -+ -+

    The various map operations generate tables; reduce applies a function of two -+arguments over a table and returns the result as a scalar:

    -+ -+ -+
    -+> reduce ('+', {1,2,3})
    -+6
    -+> reduce ('..', {'one','two','three'})
    -+'onetwothree'
    -+
    -+ -+

    Finally, zip sews different tables together:

    -+ -+ -+
    -+> = zip({1,2,3},{10,20,30})
    -+{{1,10},{2,20},{3,30}}
    -+
    -+ -+

    Browsing through the documentation, you will find that tablex and List share -+methods. For instance, tablex.imap and List.map are basically the same -+function; they both operate over the array-part of the table and generate another -+table. This can also be expressed as a list comprehension C 'f(x) for x' (t) -+which makes the operation more explicit. So why are there different ways to do -+the same thing? The main reason is that not all tables are Lists: the expression -+ls:map('#') will return a list of the lengths of any elements of ls. A list -+is a thin wrapper around a table, provided by the metatable List. Sometimes you -+may wish to work with ordinary Lua tables; the List interface is not a -+compulsory way to use Penlight table operations.

    -+ -+

    -+

    Operations on two-dimensional tables

    -+ -+ -+

    Two-dimensional tables are of course easy to represent in Lua, for instance -+{{1,2},{3,4}} where we store rows as subtables and index like so A[col][row]. -+This is the common representation used by matrix libraries like -+LuaMatrix. pl.array2d does not provide -+matrix operations, since that is the job for a specialized library, but rather -+provides generalizations of the higher-level operations provided by pl.tablex -+for one-dimensional arrays.

    -+ -+

    iter is a useful generalization of ipairs. (The extra parameter determines -+whether you want the indices as well.)

    -+ -+ -+
    -+> a = {{1,2},{3,4}}
    -+> for i,j,v in array2d.iter(a,true) do print(i,j,v) end
    -+1       1       1
    -+1       2       2
    -+2       1       3
    -+2       2       4
    -+
    -+ -+

    Note that you can always convert an arbitrary 2D array into a 'list of lists' -+with List(tablex.map(List,a))

    -+ -+

    map will apply a function over all elements (notice that extra arguments can be -+provided, so this operation is in effect function(x) return x-1 end)

    -+ -+ -+
    -+> array2d.map('-',a,1)
    -+{{0,1},{2,3}}
    -+
    -+ -+

    2D arrays are stored as an array of rows, but columns can be extracted:

    -+ -+ -+
    -+> array2d.column(a,1)
    -+{1,3}
    -+
    -+ -+

    There are three equivalents to tablex.reduce. You can either reduce along the -+rows (which is the most efficient) or reduce along the columns. Either one will -+give you a 1D array. And reduce2 will apply two operations: the first one -+reduces the rows, and the second reduces the result.

    -+ -+ -+
    -+> array2d.reduce_rows('+',a)
    -+{3,7}
    -+> array2d.reduce_cols('+',a)
    -+{4,6}
    -+> -- same as tablex.reduce('*',array.reduce_rows('+',a))
    -+> array2d.reduce2('*','+',a)
    -+21    `
    -+
    -+ -+

    tablex.map2 applies an operation to two tables, giving another table. -+array2d.map2 does this for 2D arrays. Note that you have to provide the rank -+of the arrays involved, since it's hard to always correctly deduce this from the -+data:

    -+ -+ -+
    -+> b = {{10,20},{30,40}}
    -+> a = {{1,2},{3,4}}
    -+> = array2d.map2('+',2,2,a,b)  -- two 2D arrays
    -+{{11,22},{33,44}}
    -+> = array2d.map2('+',1,2,{10,100},a)  -- 1D, 2D
    -+{{11,102},{13,104}}
    -+> = array2d.map2('*',2,1,a,{1,-1})  -- 2D, 1D
    -+{{1,-2},{3,-4}}
    -+
    -+ -+

    Of course, you are not limited to simple arithmetic. Say we have a 2D array of -+strings, and wish to print it out with proper right justification. The first step -+is to create all the string lengths by mapping string.len over the array, the -+second is to reduce this along the columns using math.max to get maximum column -+widths, and last, apply stringx.rjust with these widths.

    -+ -+ -+
    -+maxlens = reduce_cols(math.max,map('#',lines))
    -+lines = map2(stringx.rjust,2,1,lines,maxlens)
    -+
    -+ -+

    There is product which returns the Cartesian product of two 1D arrays. The -+result is a 2D array formed from applying the function to all possible pairs from -+the two arrays.

    -+ -+ -+
    -+> array2d.product('{}',{1,2},{'a','b'})
    -+{{{1,'b'},{2,'a'}},{{1,'a'},{2,'b'}}}
    -+
    -+ -+

    There is a set of operations which work in-place on 2D arrays. You can -+swap_rows and swap_cols; the first really is a simple one-liner, but the idea -+here is to give the operation a name. remove_row and remove_col are -+generalizations of table.remove. Likewise, extract_rows and extract_cols -+are given arrays of indices and discard anything else. So, for instance, -+extract_cols(A,{2,4}) will leave just columns 2 and 4 in the array.

    -+ -+

    List.slice is often useful on 1D arrays; slice does the same thing, but is -+generally given a start (row,column) and a end (row,column).

    -+ -+ -+
    -+> A = {{1,2,3},{4,5,6},{7,8,9}}
    -+> B = slice(A,1,1,2,2)
    -+> write(B)
    -+ 1 2
    -+ 4 5
    -+> B = slice(A,2,2)
    -+> write(B,nil,'%4.1f')
    -+ 5.0 6.0
    -+ 8.0 9.0
    -+
    -+ -+

    Here write is used to print out an array nicely; the second parameter is nil, -+which is the default (stdout) but can be any file object and the third parameter -+is an optional format (as used in string.format).

    -+ -+

    parse_range will take a spreadsheet range like 'A1:B2' or 'R1C1:R2C2' and -+return the range as four numbers, which can be passed to slice. The rule is -+that slice will return an array of the appropriate shape depending on the -+range; if a range represents a row or a column, the result is 1D, otherwise 2D.

    -+ -+

    This applies to iter as well, which can also optionally be given a range:

    -+ -+ -+ -+
    -+> for i,j,v in iter(A,true,2,2) do print(i,j,v) end
    -+2       2       5
    -+2       3       6
    -+3       2       8
    -+3       3       9
    -+
    -+ -+

    new will construct a new 2D array with the given dimensions. You provide an -+initial value for the elements, which is interpreted as a function if it's -+callable. With L being utils.string_lambda we then have the following way to -+make an identity matrix:

    -+ -+ -+
    -+asserteq(
    -+    array.new(3,3,L'|i,j| i==j and 1 or 0'),
    -+    {{1,0,0},{0,1,0},{0,0,1}}
    -+)
    -+
    -+ -+

    Please note that most functions in array2d are covariant, that is, they -+return an array of the same type as they receive. In particular, any objects -+created with data.new or matrix.new will remain data or matrix objects when -+reshaped or sliced, etc. Data objects have the array2d functions available as -+methods.

    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/03-strings.md.html b/extra/penlight/docs/manual/03-strings.md.html -new file mode 100644 -index 0000000..7aa6756 ---- /dev/null -+++ b/extra/penlight/docs/manual/03-strings.md.html -@@ -0,0 +1,397 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Strings. Higher-level operations on strings.

    -+ -+

    -+

    Extra String Methods

    -+ -+ -+

    These are convenient borrowings from Python, as described in 3.6.1 of the Python -+reference, but note that indices in Lua always begin at one. stringx defines -+functions like isalpha and isdigit, which return true if s is only composed -+of letters or digits respectively. startswith and endswith are convenient -+ways to find substrings. (endswith works as in Python 2.5, so that `f:endswith -+{'.bat','.exe','.cmd'}` will be true for any filename which ends with these -+extensions.) There are justify methods and whitespace trimming functions like -+strip.

    -+ -+ -+
    -+> stringx.import()
    -+> ('bonzo.dog'):endswith {'.dog','.cat'}
    -+true
    -+> ('bonzo.txt'):endswith {'.dog','.cat'}
    -+false
    -+> ('bonzo.cat'):endswith {'.dog','.cat'}
    -+true
    -+> (' stuff'):ljust(20,'+')
    -+'++++++++++++++ stuff'
    -+> ('  stuff '):lstrip()
    -+'stuff '
    -+> ('  stuff '):rstrip()
    -+'  stuff'
    -+> ('  stuff '):strip()
    -+'stuff'
    -+> for s in ('one\ntwo\nthree\n'):lines() do print(s) end
    -+one
    -+two
    -+three
    -+
    -+ -+

    Most of these can be fairly easily implemented using the Lua string library, -+which is more general and powerful. But they are convenient operations to have -+easily at hand. Note that can be injected into the string table if you use -+stringx.import, but a simple alias like local stringx = require 'pl.stringx' -+is preferable. This is the recommended practice when writing modules for -+consumption by other people, since it is bad manners to change the global state -+of the rest of the system. Magic may be used for convenience, but there is always -+a price.

    -+ -+ -+

    -+

    String Templates

    -+ -+ -+

    Another borrowing from Python, string templates allow you to substitute values -+looked up in a table:

    -+ -+ -+
    -+local Template = require ('pl.text').Template
    -+t = Template('${here} is the $answer')
    -+print(t:substitute {here = 'Lua', answer = 'best'})
    -+==>
    -+Lua is the best
    -+
    -+ -+

    '$ variables' can optionally have curly braces; this form is useful if you are -+glueing text together to make variables, e.g ${prefix}_name_${postfix}. The -+substitute method will throw an error if a $ variable is not found in the -+table, and the safe_substitute method will not.

    -+ -+

    The Lua implementation has an extra method, indent_substitute which is very -+useful for inserting blocks of text, because it adjusts indentation. Consider -+this example:

    -+ -+ -+
    -+-- testtemplate.lua
    -+local Template = require ('pl.text').Template
    -+
    -+t = Template [[
    -+    for i = 1,#$t do
    -+        $body
    -+    end
    -+]]
    -+
    -+body = Template [[
    -+local row = $t[i]
    -+for j = 1,#row do
    -+    fun(row[j])
    -+end
    -+]]
    -+
    -+print(t:indent_substitute {body=body,t='tbl'})
    -+
    -+ -+

    And the output is:

    -+ -+ -+
    -+for i = 1,#tbl do
    -+    local row = tbl[i]
    -+    for j = 1,#row do
    -+        fun(row[j])
    -+    end
    -+end
    -+
    -+ -+

    indent_substitute can substitute templates, and in which case they themselves -+will be substituted using the given table. So in this case, $t was substituted -+twice.

    -+ -+

    pl.text also has a number of useful functions like dedent, which strips all -+the initial indentation from a multiline string. As in Python, this is useful for -+preprocessing multiline strings if you like indenting them with your code. The -+function wrap is passed a long string (a paragraph) and returns a list of -+lines that fit into a desired line width. As an extension, there is also indent -+for indenting multiline strings.

    -+ -+

    New in Penlight with the 0.9 series is text.format_operator. Calling this -+enables Python-style string formatting using the modulo operator %:

    -+ -+ -+
    -+> text.format_operator()
    -+> = '%s[%d]' % {'dog',1}
    -+dog[1]
    -+
    -+ -+

    So in its simplest form it saves the typing involved with string.format; it -+will also expand $ variables using named fields:

    -+ -+ -+
    -+> = '$animal[$num]' % {animal='dog',num=1}
    -+dog[1]
    -+
    -+ -+

    As with stringx.import you have to do this explicitly, since all strings share the same -+metatable. But in your own scripts you can feel free to do this.

    -+ -+

    -+

    Another Style of Template

    -+ -+

    A new module is template, which is a version of Rici Lake's Lua -+Preprocessor. This -+allows you to mix Lua code with your templates in a straightforward way. There -+are only two rules:

    -+ -+
      -+
    • Lines beginning with # are Lua
    • -+
    • Otherwise, anything inside $() is a Lua expression.
    • -+
    -+ -+

    So a template generating an HTML list would look like this:

    -+ -+ -+
    -+<ul>
    -+# for i,val in ipairs(T) do
    -+<li>$(i) = $(val:upper())</li>
    -+# end
    -+</ul>
    -+
    -+ -+

    Assume the text is inside tmpl, then the template can be expanded using:

    -+ -+ -+
    -+local template = require 'pl.template'
    -+local my_env = {
    -+  ipairs = ipairs,
    -+  T = {'one','two','three'}
    -+}
    -+res = template.substitute(tmpl, my_env)
    -+
    -+ -+

    and we get

    -+ -+ -+
    -+<ul>
    -+<li>1 = ONE</li>
    -+<li>2 = TWO</li>
    -+<li>3 = THREE</li>
    -+</ul>
    -+
    -+ -+

    There is a single function, template.substitute which is passed a template -+string and an environment table. This table may contain some special fields, -+like \_parent which can be set to a table representing a 'fallback' environment -+in case a symbol was not found. \_brackets is usually '()' and \_escape is -+usually '#' but it's sometimes necessary to redefine these if the defaults -+interfere with the target language - for instance, $(V) has another meaning in -+Make, and # means a preprocessor line in C/C++.

    -+ -+

    Finally, if something goes wrong, passing _debug will cause the intermediate -+Lua code to be dumped if there's a problem.

    -+ -+

    Here is a C code generation example; something that could easily be extended to -+be a minimal Lua extension skeleton generator.

    -+ -+ -+
    -+local subst = require 'pl.template'.substitute
    -+
    -+local templ = [[
    -+#include <lua.h>
    -+#include <lauxlib.h>
    -+#include <lualib.h>
    -+
    -+> for _,f in ipairs(mod) do
    -+static int l_$(f.name) (lua_State *L) {
    -+
    -+}
    -+> end
    -+
    -+static const luaL_reg $(mod.name)[] = {
    -+> for _,f in ipairs(mod) do
    -+    {"$(f.name)",l_$(f.name)},
    -+> end
    -+    {NULL,NULL}
    -+};
    -+
    -+int luaopen_$(mod.name) {
    -+   luaL_register (L, "$(mod.name)", $(mod.name));
    -+    return 1;
    -+}
    -+]]
    -+
    -+print(subst(templ,{
    -+    _escape = '>',
    -+    ipairs = ipairs,
    -+    mod = {
    -+        name = 'baggins';
    -+        {name='frodo'},
    -+        {name='bilbo'}
    -+    }
    -+}))
    -+
    -+ -+

    -+

    File-style I/O on Strings

    -+ -+

    pl.stringio provides just three functions; stringio.open is passed a string, -+and returns a file-like object for reading. It supports a read method, which -+takes the same arguments as standard file objects:

    -+ -+ -+
    -+> f = stringio.open 'first line\n10 20 30\n'
    -+> = f:read()
    -+first line
    -+> = f:read('*n','*n','*n')
    -+10    20    30
    -+
    -+ -+

    lines and seek are also supported.

    -+ -+

    stringio.lines is a useful short-cut for iterating over all the lines in a -+string.

    -+ -+

    stringio.create creates a writeable file-like object. You then use write to -+this stream, and finally extract the built string using value. This 'string -+builder' pattern is useful for efficiently creating large strings.

    -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/04-paths.md.html b/extra/penlight/docs/manual/04-paths.md.html -new file mode 100644 -index 0000000..03295a9 ---- /dev/null -+++ b/extra/penlight/docs/manual/04-paths.md.html -@@ -0,0 +1,329 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Paths and Directories

    -+ -+

    -+

    Working with Paths

    -+ -+

    Programs should not depend on quirks of your operating system. They will be -+harder to read, and need to be ported for other systems. The worst of course is -+hardcoding paths like 'c:\' in programs, and wondering why Vista complains so -+much. But even something like dir..'\'..file is a problem, since Unix can't -+understand backslashes in this way. dir..'/'..file is usually portable, but -+it's best to put this all into a simple function, path.join. If you -+consistently use path.join, then it's much easier to write cross-platform code, -+since it handles the directory separator for you.

    -+ -+

    pl.path provides the same functionality as Python's os.path module (11.1).

    -+ -+ -+
    -+> p = 'c:\\bonzo\\DOG.txt'
    -+> = path.normcase (p)  ---> only makes sense on Windows
    -+c:\bonzo\dog.txt
    -+> = path.splitext (p)
    -+c:\bonzo\DOG    .txt
    -+> = path.extension (p)
    -+.txt
    -+> = path.basename (p)
    -+DOG.txt
    -+> = path.exists(p)
    -+false
    -+> = path.join ('fred','alice.txt')
    -+fred\alice.txt
    -+> = path.exists 'pretty.lua'
    -+true
    -+> = path.getsize 'pretty.lua'
    -+2125
    -+> = path.isfile 'pretty.lua'
    -+true
    -+> = path.isdir 'pretty.lua'
    -+false
    -+
    -+ -+

    It is very important for all programmers, not just on Unix, to only write to -+where they are allowed to write. path.expanduser will expand '~' (tilde) into -+the home directory. Depending on your OS, this will be a guaranteed place where -+you can create files:

    -+ -+ -+
    -+> = path.expanduser '~/mydata.txt'
    -+'C:\Documents and Settings\SJDonova/mydata.txt'
    -+
    -+> = path.expanduser '~/mydata.txt'
    -+/home/sdonovan/mydata.txt
    -+
    -+ -+

    Under Windows, os.tmpname returns a path which leads to your drive root full of -+temporary files. (And increasingly, you do not have access to this root folder.) -+This is corrected by path.tmpname, which uses the environment variable TMP:

    -+ -+ -+
    -+> os.tmpname()  -- not a good place to put temporary files!
    -+'\s25g.'
    -+> path.tmpname()
    -+'C:\DOCUME~1\SJDonova\LOCALS~1\Temp\s25g.1'
    -+
    -+ -+

    A useful extra function is pl.path.package_path, which will tell you the path -+of a particular Lua module. So on my system, package_path('pl.path') returns -+'C:\Program Files\Lua\5.1\lualibs\pl\path.lua', and package_path('ifs') returns -+'C:\Program Files\Lua\5.1\clibs\lfs.dll'. It is implemented in terms of -+package.searchpath, which is a new function in Lua 5.2 which has been -+implemented for Lua 5.1 in Penlight.

    -+ -+

    -+

    File Operations

    -+ -+

    pl.file is a new module that provides more sensible names for common file -+operations. For instance, file.read and file.write are aliases for -+utils.readfile and utils.writefile.

    -+ -+

    Smaller files can be efficiently read and written in one operation. file.read -+is passed a filename and returns the contents as a string, if successful; if not, -+then it returns nil and the actual error message. There is an optional boolean -+parameter if you want the file to be read in binary mode (this makes no -+difference on Unix but remains important with Windows.)

    -+ -+

    In previous versions of Penlight, utils.readfile would read standard input if -+the file was not specified, but this can lead to nasty bugs; use io.read '*a' -+to grab all of standard input.

    -+ -+

    Similarly, file.write takes a filename and a string which will be written to -+that file.

    -+ -+

    For example, this little script converts a file into upper case:

    -+ -+ -+
    -+require 'pl'
    -+assert(#arg == 2, 'supply two filenames')
    -+text = assert(file.read(arg[1]))
    -+assert(file.write(arg[2],text:upper()))
    -+
    -+ -+

    Copying files is surprisingly tricky. file.copy and file.move attempt to use -+the best implementation possible. On Windows, they link to the API functions -+CopyFile and MoveFile, but only if the alien package is installed (this is -+true for Lua for Windows.) Otherwise, the system copy command is used. This can -+be ugly when writing Windows GUI applications, because of the dreaded flashing -+black-box problem with launching processes.

    -+ -+

    -+

    Directory Operations

    -+ -+

    pl.dir provides some useful functions for working with directories. fnmatch -+will match a filename against a shell pattern, and filter will return any files -+in the supplied list which match the given pattern, which correspond to the -+functions in the Python fnmatch module. getdirectories will return all -+directories contained in a directory, and getfiles will return all files in a -+directory which match a shell pattern. These functions return the files as a -+table, unlike lfs.dir which returns an iterator.)

    -+ -+

    dir.makepath can create a full path, creating subdirectories as necessary; -+rmtree is the Nuclear Option of file deleting functions, since it will -+recursively clear out and delete all directories found beginning at a path (there -+is a similar function with this name in the Python shutils module.)

    -+ -+ -+
    -+> = dir.makepath 't\\temp\\bonzo'
    -+> = path.isdir 't\\temp\\bonzo'
    -+true
    -+> = dir.rmtree 't'
    -+
    -+ -+

    dir.rmtree depends on dir.walk, which is a powerful tool for scanning a whole -+directory tree. Here is the implementation of dir.rmtree:

    -+ -+ -+
    -+--- remove a whole directory tree.
    -+-- @param path A directory path
    -+function dir.rmtree(fullpath)
    -+    for root,dirs,files in dir.walk(fullpath) do
    -+        for i,f in ipairs(files) do
    -+            os.remove(path.join(root,f))
    -+        end
    -+        lfs.rmdir(root)
    -+    end
    -+end
    -+
    -+ -+

    dir.clonetree clones directory trees. The first argument is a path that must -+exist, and the second path is the path to be cloned. (Note that this path cannot -+be inside the first path, since this leads to madness.) By default, it will -+then just recreate the directory structure. You can in addition provide a -+function, which will be applied for all files found.

    -+ -+ -+
    -+-- make a copy of my libs folder
    -+require 'pl'
    -+p1 = [[d:\dev\lua\libs]]
    -+p2 = [[D:\dev\lua\libs\..\tests]]
    -+dir.clonetree(p1,p2,dir.copyfile)
    -+
    -+ -+

    A more sophisticated version, which only copies files which have been modified:

    -+ -+ -+
    -+-- p1 and p2 as before, or from arg[1] and arg[2]
    -+dir.clonetree(p1,p2,function(f1,f2)
    -+  local res
    -+  local t1,t2 = path.getmtime(f1),path.getmtime(f2)
    -+  -- f2 might not exist, so be careful about t2
    -+  if not t2 or t1 > t2 then
    -+    res = dir.copyfile(f1,f2)
    -+  end
    -+  return res -- indicates successful operation
    -+end)
    -+
    -+ -+

    dir.clonetree uses path.common_prefix. With p1 and p2 defined above, the -+common path is 'd:\dev\lua'. So 'd:\dev\lua\libs\testfunc.lua' is copied to -+'d:\dev\lua\test\testfunc.lua', etc.

    -+ -+

    If you need to find the common path of list of files, then tablex.reduce will -+do the job:

    -+ -+ -+
    -+> p3 = [[d:\dev]]
    -+> = tablex.reduce(path.common_prefix,{p1,p2,p3})
    -+'d:\dev'
    -+
    -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/05-dates.md.html b/extra/penlight/docs/manual/05-dates.md.html -new file mode 100644 -index 0000000..946e08c ---- /dev/null -+++ b/extra/penlight/docs/manual/05-dates.md.html -@@ -0,0 +1,269 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Date and Time

    -+ -+

    -+ -+

    NOTE: the Date module is deprecated

    -+ -+

    -+

    Creating and Displaying Dates

    -+ -+

    The Date class provides a simplified way to work with date and -+time in Lua; it leans heavily on the functions -+os.date and os.time.

    -+ -+

    A Date object can be constructed from a table, just like with os.time. -+Methods are provided to get and set the various parts of the date.

    -+ -+ -+
    -+> d = Date {year = 2011, month = 3, day = 2 }
    -+> = d
    -+2011-03-02 12:00:00
    -+> = d:month(),d:year(),d:day()
    -+3    2011    2
    -+> d:month(4)
    -+> = d
    -+2011-04-02 12:00:00
    -+> d:add {day=1}
    -+> = d
    -+2011-04-03 12:00:00
    -+
    -+ -+

    add takes a table containing one of the date table fields.

    -+ -+ -+
    -+> = d:weekday_name()
    -+Sun
    -+> = d:last_day()
    -+2011-04-30 12:00:00
    -+> = d:month_name(true)
    -+April
    -+
    -+ -+

    There is a default conversion to text for date objects, but Date.Format gives -+you full control of the format for both parsing and displaying dates:

    -+ -+ -+
    -+> iso = Date.Format 'yyyy-mm-dd'
    -+> d = iso:parse '2010-04-10'
    -+> amer = Date.Format 'mm/dd/yyyy'
    -+> = amer:tostring(d)
    -+04/10/2010
    -+
    -+ -+

    With the 0.9.7 release, the Date constructor has become more flexible. You may -+omit any of the 'year', 'month' or 'day' fields:

    -+ -+ -+
    -+> = Date { year = 2008 }
    -+2008-01-01 12:00:00
    -+> = Date { month = 3 }
    -+2011-03-01 12:00:00
    -+> = Date { day = 20 }
    -+2011-10-20 12:00:00
    -+> = Date { hour = 14, min = 30 }
    -+2011-10-13 14:30:00
    -+
    -+ -+

    If 'year' is omitted, then the current year is assumed, and likewise for 'month'.

    -+ -+

    To set the time on such a partial date, you can use the fact that the 'setter' -+methods return the date object and so you can 'chain' these methods.

    -+ -+ -+
    -+> d = Date { day = 03 }
    -+> = d:hour(18):min(30)
    -+2011-10-03 18:30:00
    -+
    -+ -+

    Finally, Date also now accepts positional arguments:

    -+ -+ -+
    -+> = Date(2011,10,3)
    -+2011-10-03 12:00:00
    -+> = Date(2011,10,3,18,30,23)
    -+2011-10-03 18:30:23
    -+
    -+ -+

    Date.format has been extended. If you construct an instance without a pattern, -+then it will try to match against a set of known formats. This is useful for -+human-input dates since keeping to a strict format is not one of the strong -+points of users. It assumes that there will be a date, and then a date.

    -+ -+ -+
    -+> df = Date.Format()
    -+> = df:parse '5.30pm'
    -+2011-10-13 17:30:00
    -+> = df:parse '1730'
    -+nil     day out of range: 1730 is not between 1 and 31
    -+> = df:parse '17.30'
    -+2011-10-13 17:30:00
    -+> = df:parse 'mar'
    -+2011-03-01 12:00:00
    -+> = df:parse '3 March'
    -+2011-03-03 12:00:00
    -+> = df:parse '15 March'
    -+2011-03-15 12:00:00
    -+> = df:parse '15 March 2008'
    -+2008-03-15 12:00:00
    -+> = df:parse '15 March 2008 1.30pm'
    -+2008-03-15 13:30:00
    -+> = df:parse '2008-10-03 15:30:23'
    -+2008-10-03 15:30:23
    -+
    -+ -+

    ISO date format is of course a good idea if you need to deal with users from -+different countries. Here is the default behaviour for 'short' dates:

    -+ -+ -+
    -+> = df:parse '24/02/12'
    -+2012-02-24 12:00:00
    -+
    -+ -+

    That's not what Americans expect! It's tricky to work out in a cross-platform way -+exactly what the expected format is, so there is an explicit flag:

    -+ -+ -+
    -+> df:US_order(true)
    -+> = df:parse '9/11/01'
    -+2001-11-09 12:00:00
    -+
    -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/06-data.md.html b/extra/penlight/docs/manual/06-data.md.html -new file mode 100644 -index 0000000..62c775f ---- /dev/null -+++ b/extra/penlight/docs/manual/06-data.md.html -@@ -0,0 +1,1633 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Data

    -+ -+

    -+

    Reading Data Files

    -+ -+

    The first thing to consider is this: do you actually need to write a custom file -+reader? And if the answer is yes, the next question is: can you write the reader -+in as clear a way as possible? Correctness, Robustness, and Speed; pick the first -+two and the third can be sorted out later, if necessary.

    -+ -+

    A common sort of data file is the configuration file format commonly used on Unix -+systems. This format is often called a property file in the Java world.

    -+ -+ -+
    -+# Read timeout in seconds
    -+read.timeout=10
    -+
    -+# Write timeout in seconds
    -+write.timeout=10
    -+
    -+ -+

    Here is a simple Lua implementation:

    -+ -+ -+
    -+-- property file parsing with Lua string patterns
    -+props = []
    -+for line in io.lines() do
    -+    if line:find('#',1,true) ~= 1 and not line:find('^%s*$') then
    -+        local var,value = line:match('([^=]+)=(.*)')
    -+        props[var] = value
    -+    end
    -+end
    -+
    -+ -+

    Very compact, but it suffers from a similar disease in equivalent Perl programs; -+it uses odd string patterns which are 'lexically noisy'. Noisy code like this -+slows the casual reader down. (For an even more direct way of doing this, see the -+next section, 'Reading Configuration Files')

    -+ -+

    Another implementation, using the Penlight libraries:

    -+ -+ -+
    -+-- property file parsing with extended string functions
    -+require 'pl'
    -+stringx.import()
    -+props = []
    -+for line in io.lines() do
    -+    if not line:startswith('#') and not line:isspace() then
    -+        local var,value = line:splitv('=')
    -+        props[var] = value
    -+    end
    -+end
    -+
    -+ -+

    This is more self-documenting; it is generally better to make the code express -+the intention, rather than having to scatter comments everywhere - comments are -+necessary, of course, but mostly to give the higher view of your intention that -+cannot be expressed in code. It is slightly slower, true, but in practice the -+speed of this script is determined by I/O, so further optimization is unnecessary.

    -+ -+

    -+

    Reading Unstructured Text Data

    -+ -+

    Text data is sometimes unstructured, for example a file containing words. The -+pl.input module has a number of functions which makes processing such files -+easier. For example, a script to count the number of words in standard input -+using import.words:

    -+ -+ -+
    -+-- countwords.lua
    -+require 'pl'
    -+local k = 1
    -+for w in input.words(io.stdin) do
    -+    k = k + 1
    -+end
    -+print('count',k)
    -+
    -+ -+

    Or this script to calculate the average of a set of numbers using input.numbers:

    -+ -+ -+
    -+-- average.lua
    -+require 'pl'
    -+local k = 1
    -+local sum = 0
    -+for n in input.numbers(io.stdin) do
    -+    sum = sum + n
    -+    k = k + 1
    -+end
    -+print('average',sum/k)
    -+
    -+ -+

    These scripts can be improved further by eliminating loops In the last case, -+there is a perfectly good function seq.sum which can already take a sequence of -+numbers and calculate these numbers for us:

    -+ -+ -+
    -+-- average2.lua
    -+require 'pl'
    -+local total,n = seq.sum(input.numbers())
    -+print('average',total/n)
    -+
    -+ -+

    A further simplification here is that if numbers or words are not passed an -+argument, they will grab their input from standard input. The first script can -+be rewritten:

    -+ -+ -+
    -+-- countwords2.lua
    -+require 'pl'
    -+print('count',seq.count(input.words()))
    -+
    -+ -+

    A useful feature of a sequence generator like numbers is that it can read from -+a string source. Here is a script to calculate the sums of the numbers on each -+line in a file:

    -+ -+ -+
    -+-- sums.lua
    -+for line in io.lines() do
    -+    print(seq.sum(input.numbers(line))
    -+end
    -+
    -+ -+

    -+

    Reading Columnar Data

    -+ -+

    It is very common to find data in columnar form, either space or comma-separated, -+perhaps with an initial set of column headers. Here is a typical example:

    -+ -+ -+
    -+EventID    Magnitude    LocationX    LocationY    LocationZ
    -+981124001    2.0    18988.4    10047.1    4149.7
    -+981125001    0.8    19104.0    9970.4    5088.7
    -+981127003    0.5    19012.5    9946.9    3831.2
    -+...
    -+
    -+ -+

    input.fields is designed to extract several columns, given some delimiter -+(default to whitespace). Here is a script to calculate the average X location of -+all the events:

    -+ -+ -+
    -+-- avg-x.lua
    -+require 'pl'
    -+io.read() -- skip the header line
    -+local sum,count = seq.sum(input.fields {3})
    -+print(sum/count)
    -+
    -+ -+

    input.fields is passed either a field count, or a list of column indices, -+starting at one as usual. So in this case we're only interested in column 3. If -+you pass it a field count, then you get every field up to that count:

    -+ -+ -+
    -+for id,mag,locX,locY,locZ in input.fields (5) do
    -+....
    -+end
    -+
    -+ -+

    input.fields by default tries to convert each field to a number. It will skip -+lines which clearly don't match the pattern, but will abort the script if there -+are any fields which cannot be converted to numbers.

    -+ -+

    The second parameter is a delimiter, by default spaces. ' ' is understood to mean -+'any number of spaces', i.e. '%s+'. Any Lua string pattern can be used.

    -+ -+

    The third parameter is a data source, by default standard input (defined by -+input.create_getter.) It assumes that the data source has a read method which -+brings in the next line, i.e. it is a 'file-like' object. As a special case, a -+string will be split into its lines:

    -+ -+ -+
    -+> for x,y in input.fields(2,' ','10 20\n30 40\n') do print(x,y) end
    -+10      20
    -+30      40
    -+
    -+ -+

    Note the default behaviour for bad fields, which is to show the offending line -+number:

    -+ -+ -+
    -+> for x,y in input.fields(2,' ','10 20\n30 40x\n') do print(x,y) end
    -+10      20
    -+line 2: cannot convert '40x' to number
    -+
    -+ -+

    This behaviour of input.fields is appropriate for a script which you want to -+fail immediately with an appropriate user error message if conversion fails. -+The fourth optional parameter is an options table: {no_fail=true} means that -+conversion is attempted but if it fails it just returns the string, rather as AWK -+would operate. You are then responsible for checking the type of the returned -+field. {no_convert=true} switches off conversion altogether and all fields are -+returned as strings.

    -+ -+ -+

    Sometimes it is useful to bring a whole dataset into memory, for operations such -+as extracting columns. Penlight provides a flexible reader specifically for -+reading this kind of data, using the data module. Given a file looking like this:

    -+ -+ -+
    -+x,y
    -+10,20
    -+2,5
    -+40,50
    -+
    -+ -+

    Then data.read will create a table like this, with each row represented by a -+sublist:

    -+ -+ -+
    -+> t = data.read 'test.txt'
    -+> pretty.dump(t)
    -+{{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
    -+
    -+ -+

    You can now analyze this returned table using the supplied methods. For instance, -+the method column_by_name returns a table of all the values of that column.

    -+ -+ -+
    -+-- testdata.lua
    -+require 'pl'
    -+d = data.read('fev.txt')
    -+for _,name in ipairs(d.fieldnames) do
    -+    local col = d:column_by_name(name)
    -+    if type(col[1]) == 'number' then
    -+        local total,n = seq.sum(col)
    -+        utils.printf("Average for %s is %f\n",name,total/n)
    -+    end
    -+end
    -+
    -+ -+

    data.read tries to be clever when given data; by default it expects a first -+line of column names, unless any of them are numbers. It tries to deduce the -+column delimiter by looking at the first line. Sometimes it guesses wrong; these -+things can be specified explicitly. The second optional parameter is an options -+table: can override delim (a string pattern), fieldnames (a list or -+comma-separated string), specify no_convert (default is to convert), numfields -+(indices of columns known to be numbers, as a list) and thousands_dot (when the -+thousands separator in Excel CSV is '.')

    -+ -+

    A very powerful feature is a way to execute SQL-like queries on such data:

    -+ -+ -+
    -+-- queries on tabular data
    -+require 'pl'
    -+local d = data.read('xyz.txt')
    -+local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
    -+for x,y,z in q do
    -+    print(x,y,z)
    -+end
    -+
    -+ -+

    Please note that the format of queries is restricted to the following syntax:

    -+ -+ -+
    -+FIELDLIST [ 'where' CONDITION ] [ 'sort by' FIELD [asc|desc]]
    -+
    -+ -+

    Any valid Lua code can appear in CONDITION; remember it is not SQL and you -+have to use == (this warning comes from experience.)

    -+ -+

    For this to work, field names must be Lua identifiers. So read will massage -+fieldnames so that all non-alphanumeric chars are replaced with underscores. -+However, the original_fieldnames field always contains the original un-massaged -+fieldnames.

    -+ -+

    read can handle standard CSV files fine, although doesn't try to be a -+full-blown CSV parser. With the csv=true option, it's possible to have -+double-quoted fields, which may contain commas; then trailing commas become -+significant as well.

    -+ -+

    Spreadsheet programs are not always the best tool to -+process such data, strange as this might seem to some people. This is a toy CSV -+file; to appreciate the problem, imagine thousands of rows and dozens of columns -+like this:

    -+ -+ -+
    -+Department Name,Employee ID,Project,Hours Booked
    -+sales,1231,overhead,4
    -+sales,1255,overhead,3
    -+engineering,1501,development,5
    -+engineering,1501,maintenance,3
    -+engineering,1433,maintenance,10
    -+
    -+ -+

    The task is to reduce the dataset to a relevant set of rows and columns, perhaps -+do some processing on row data, and write the result out to a new CSV file. The -+write_row method uses the delimiter to write the row to a file; -+Data.select_row is like Data.select, except it iterates over rows, not -+fields; this is necessary if we are dealing with a lot of columns!

    -+ -+ -+
    -+names = {[1501]='don',[1433]='dilbert'}
    -+keepcols = {'Employee_ID','Hours_Booked'}
    -+t:write_row (outf,{'Employee','Hours_Booked'})
    -+q = t:select_row {
    -+    fields=keepcols,
    -+    where=function(row) return row[1]=='engineering' end
    -+}
    -+for row in q do
    -+    row[1] = names[row[1]]
    -+    t:write_row(outf,row)
    -+end
    -+
    -+ -+

    Data.select_row and Data.select can be passed a table specifying the query; a -+list of field names, a function defining the condition and an optional parameter -+sort_by. It isn't really necessary here, but if we had a more complicated row -+condition (such as belonging to a specified set) then it is not generally -+possible to express such a condition as a query string, without resorting to -+hackery such as global variables.

    -+ -+

    With 1.0.3, you can specify explicit conversion functions for selected columns. -+For instance, this is a log file with a Unix date stamp:

    -+ -+ -+
    -+Time Message
    -+1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00
    -+1266840760 closure data 0.000000 1972 1972 0
    -+1266840760 ++ 1266840760 EE 1
    -+1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00
    -+1266840764 closure data 0.000000 1972 1972 0
    -+
    -+ -+

    We would like the first column as an actual date object, so the convert -+field sets an explicit conversion for column 1. (Note that we have to explicitly -+convert the string to a number first.)

    -+ -+ -+
    -+Date = require 'pl.Date'
    -+
    -+function date_convert (ds)
    -+    return Date(tonumber(ds))
    -+end
    -+
    -+d = data.read(f,{convert={[1]=date_convert},last_field_collect=true})
    -+
    -+ -+

    This gives us a two-column dataset, where the first column contains Date objects -+and the second column contains the rest of the line. Queries can then easily -+pick out events on a day of the week:

    -+ -+ -+
    -+q = d:select "Time,Message where Time:weekday_name()=='Sun'"
    -+
    -+ -+

    Data does not have to come from files, nor does it necessarily come from the lab -+or the accounts department. On Linux, ps aux gives you a full listing of all -+processes running on your machine. It is straightforward to feed the output of -+this command into data.read and perform useful queries on it. Notice that -+non-identifier characters like '%' get converted into underscores:

    -+ -+ -+
    -+require 'pl'
    -+f = io.popen 'ps aux'
    -+s = data.read (f,{last_field_collect=true})
    -+f:close()
    -+print(s.fieldnames)
    -+print(s:column_by_name 'USER')
    -+qs = 'COMMAND,_MEM where _MEM > 5 and USER=="steve"'
    -+for name,mem in s:select(qs) do
    -+    print(mem,name)
    -+end
    -+
    -+ -+

    I've always been an admirer of the AWK programming language; with filter you -+can get Lua programs which are just as compact:

    -+ -+ -+
    -+-- printxy.lua
    -+require 'pl'
    -+data.filter 'x,y where x > 3'
    -+
    -+ -+

    It is common enough to have data files without headers of field names. -+data.read makes a special exception for such files if all fields are numeric. -+Since there are no column names to use in query expressions, you can use AWK-like -+column indexes, e.g. '$1,$2 where $1 > 3'. I have a little executable script on -+my system called lf which looks like this:

    -+ -+ -+
    -+#!/usr/bin/env lua
    -+require 'pl.data'.filter(arg[1])
    -+
    -+ -+

    And it can be used generally as a filter command to extract columns from data. -+(The column specifications may be expressions or even constants.)

    -+ -+ -+
    -+$ lf '$1,$5/10' < test.dat
    -+
    -+ -+

    (As with AWK, please note the single-quotes used in this command; this prevents -+the shell trying to expand the column indexes. If you are on Windows, then you -+must quote the expression in double-quotes so -+it is passed as one argument to your batch file.)

    -+ -+

    As a tutorial resource, have a look at test-data.lua in the PL tests directory -+for other examples of use, plus comments.

    -+ -+

    The data returned by read or constructed by Data.copy_select from a query is -+basically just an array of rows: {{1,2},{3,4}}. So you may use read to pull -+in any array-like dataset, and process with any function that expects such a -+implementation. In particular, the functions in array2d will work fine with -+this data. In fact, these functions are available as methods; e.g. -+array2d.flatten can be called directly like so to give us a one-dimensional list:

    -+ -+ -+
    -+v = data.read('dat.txt'):flatten()
    -+
    -+ -+

    The data is also in exactly the right shape to be treated as matrices by -+LuaMatrix:

    -+ -+ -+
    -+> matrix = require 'matrix'
    -+> m = matrix(data.read 'mat.txt')
    -+> = m
    -+1       0.2     0.3
    -+0.2     1       0.1
    -+0.1     0.2     1
    -+> = m^2  -- same as m*m
    -+1.07    0.46    0.62
    -+0.41    1.06    0.26
    -+0.24    0.42    1.05
    -+
    -+ -+

    write will write matrices back to files for you.

    -+ -+

    Finally, for the curious, the global variable _DEBUG can be used to print out -+the actual iterator function which a query generates and dynamically compiles. By -+using code generation, we can get pretty much optimal performance out of -+arbitrary queries.

    -+ -+ -+
    -+> lua -lpl -e "_DEBUG=true" -e "data.filter 'x,y where x > 4 sort by x'" < test.txt
    -+return function (t)
    -+        local i = 0
    -+        local v
    -+        local ls = {}
    -+        for i,v in ipairs(t) do
    -+            if v[1] > 4  then
    -+                    ls[#ls+1] = v
    -+            end
    -+        end
    -+        table.sort(ls,function(v1,v2)
    -+            return v1[1] < v2[1]
    -+        end)
    -+        local n = #ls
    -+        return function()
    -+            i = i + 1
    -+            v = ls[i]
    -+            if i > n then return end
    -+            return v[1],v[2]
    -+        end
    -+end
    -+
    -+10,20
    -+40,50
    -+
    -+ -+

    -+

    Reading Configuration Files

    -+ -+

    The config module provides a simple way to convert several kinds of -+configuration files into a Lua table. Consider the simple example:

    -+ -+ -+
    -+# test.config
    -+# Read timeout in seconds
    -+read.timeout=10
    -+
    -+# Write timeout in seconds
    -+write.timeout=5
    -+
    -+#acceptable ports
    -+ports = 1002,1003,1004
    -+
    -+ -+

    This can be easily brought in using config.read and the result shown using -+pretty.write:

    -+ -+ -+
    -+-- readconfig.lua
    -+local config = require 'pl.config'
    -+local pretty= require 'pl.pretty'
    -+
    -+local t = config.read(arg[1])
    -+print(pretty.write(t))
    -+
    -+ -+

    and the output of lua readconfig.lua test.config is:

    -+ -+ -+
    -+{
    -+  ports = {
    -+    1002,
    -+    1003,
    -+    1004
    -+  },
    -+  write_timeout = 5,
    -+  read_timeout = 10
    -+}
    -+
    -+ -+

    That is, config.read will bring in all key/value pairs, ignore # comments, and -+ensure that the key names are proper Lua identifiers by replacing non-identifier -+characters with '_'. If the values are numbers, then they will be converted. (So -+the value of t.write_timeout is the number 5). In addition, any values which -+are separated by commas will be converted likewise into an array.

    -+ -+

    Any line can be continued with a backslash. So this will all be considered one -+line:

    -+ -+ -+
    -+names=one,two,three, \
    -+four,five,six,seven, \
    -+eight,nine,ten
    -+
    -+ -+

    Windows-style INI files are also supported. The section structure of INI files -+translates naturally to nested tables in Lua:

    -+ -+ -+
    -+; test.ini
    -+[timeouts]
    -+read=10 ; Read timeout in seconds
    -+write=5 ; Write timeout in seconds
    -+[portinfo]
    -+ports = 1002,1003,1004
    -+
    -+ -+

    The output is:

    -+ -+ -+
    -+{
    -+  portinfo = {
    -+    ports = {
    -+      1002,
    -+      1003,
    -+      1004
    -+    }
    -+  },
    -+  timeouts = {
    -+    write = 5,
    -+    read = 10
    -+  }
    -+}
    -+
    -+ -+

    You can now refer to the write timeout as t.timeouts.write.

    -+ -+

    As a final example of the flexibility of config.read, if passed this simple -+comma-delimited file

    -+ -+ -+
    -+one,two,three
    -+10,20,30
    -+40,50,60
    -+1,2,3
    -+
    -+ -+

    it will produce the following table:

    -+ -+ -+
    -+{
    -+  { "one", "two", "three" },
    -+  { 10, 20, 30 },
    -+  { 40, 50, 60  },
    -+  { 1, 2, 3 }
    -+}
    -+
    -+ -+

    config.read isn't designed to read all CSV files in general, but intended to -+support some Unix configuration files not structured as key-value pairs, such as -+'/etc/passwd'.

    -+ -+

    This function is intended to be a Swiss Army Knife of configuration readers, but -+it does have to make assumptions, and you may not like them. So there is an -+optional extra parameter which allows some control, which is table that may have -+the following fields:

    -+ -+ -+
    -+{
    -+   variablilize = true,
    -+   convert_numbers = tonumber,
    -+   trim_space = true,
    -+   list_delim = ',',
    -+   trim_quotes = true,
    -+   ignore_assign = false,
    -+   keysep = '=',
    -+   smart = false,
    -+}
    -+
    -+ -+

    variablilize is the option that converted write.timeout in the first example -+to the valid Lua identifier write_timeout. If convert_numbers is true, then -+an attempt is made to convert any string that starts like a number. You can -+specify your own function (say one that will convert a string like '5224 kb' into -+a number.)

    -+ -+

    trim_space ensures that there is no starting or trailing whitespace with -+values, and list_delim is the character that will be used to decide whether to -+split a value up into a list (it may be a Lua string pattern such as '%s+'.)

    -+ -+

    For instance, the password file in Unix is colon-delimited:

    -+ -+ -+
    -+t = config.read('/etc/passwd',{list_delim=':'})
    -+
    -+ -+

    This produces the following output on my system (only last two lines shown):

    -+ -+ -+
    -+{
    -+  ...
    -+  {
    -+    "user",
    -+    "x",
    -+    "1000",
    -+    "1000",
    -+    "user,,,",
    -+    "/home/user",
    -+    "/bin/bash"
    -+  },
    -+  {
    -+    "sdonovan",
    -+    "x",
    -+    "1001",
    -+    "1001",
    -+    "steve donovan,28,,",
    -+    "/home/sdonovan",
    -+    "/bin/bash"
    -+  }
    -+}
    -+
    -+ -+

    You can get this into a more sensible format, where the usernames are the keys, -+with this (the tablex.pairmap function must return value, key!)

    -+ -+ -+
    -+t = tablex.pairmap(function(k,v) return v,v[1] end,t)
    -+
    -+ -+

    and you get:

    -+ -+ -+
    -+{ ...
    -+  sdonovan = {
    -+    "sdonovan",
    -+    "x",
    -+    "1001",
    -+    "1001",
    -+    "steve donovan,28,,",
    -+    "/home/sdonovan",
    -+    "/bin/bash"
    -+  }
    -+...
    -+}
    -+
    -+ -+

    Many common Unix configuration files can be read by tweaking these parameters. -+For /etc/fstab, the options {list_delim='%s+',ignore_assign=true} will -+correctly separate the columns. It's common to find 'KEY VALUE' assignments in -+files such as /etc/ssh/ssh_config; the options {keysep=' '} make -+config.read return a table where each KEY has a value VALUE.

    -+ -+

    Files in the Linux procfs usually use ':` as the field delimiter:

    -+ -+ -+
    -+> t = config.read('/proc/meminfo',{keysep=':'})
    -+> = t.MemFree
    -+220140 kB
    -+
    -+ -+

    That result is a string, since tonumber doesn't like it, but defining the -+convert_numbers option as `function(s) return tonumber((s:gsub(' kB$',''))) -+end` will get the memory figures as actual numbers in the result. (The extra -+parentheses are necessary so that tonumber only gets the first result from -+gsub). From `tests/test-config.lua':

    -+ -+ -+
    -+testconfig([[
    -+MemTotal:        1024748 kB
    -+MemFree:          220292 kB
    -+]],
    -+{ MemTotal = 1024748, MemFree = 220292 },
    -+{
    -+ keysep = ':',
    -+ convert_numbers = function(s)
    -+    s = s:gsub(' kB$','')
    -+    return tonumber(s)
    -+  end
    -+ }
    -+)
    -+
    -+ -+

    The smart option lets config.read make a reasonable guess for you; there -+are examples in tests/test-config.lua, but basically these common file -+formats (and those following the same pattern) can be processed directly in -+smart mode: 'etc/fstab', '/proc/XXXX/status', 'ssh_config' and 'pdatedb.conf'.

    -+ -+

    Please note that config.read can be passed a file-like object; if it's not a -+string and supports the read method, then that will be used. For instance, to -+read a configuration from a string, use stringio.open.

    -+ -+ -+

    -+ -+

    -+

    Lexical Scanning

    -+ -+

    Although Lua's string pattern matching is very powerful, there are times when -+something more powerful is needed. pl.lexer.scan provides lexical scanners -+which tokenize a string, classifying tokens into numbers, strings, etc.

    -+ -+ -+
    -+> lua -lpl
    -+Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
    -+> tok = lexer.scan 'alpha = sin(1.5)'
    -+> = tok()
    -+iden    alpha
    -+> = tok()
    -+=       =
    -+> = tok()
    -+iden    sin
    -+> = tok()
    -+(       (
    -+> = tok()
    -+number  1.5
    -+> = tok()
    -+)       )
    -+> = tok()
    -+(nil)
    -+
    -+ -+

    The scanner is a function, which is repeatedly called and returns the type and -+value of the token. Recognized basic types are 'iden','string','number', and -+'space'. and everything else is represented by itself. Note that by default the -+scanner will skip any 'space' tokens.

    -+ -+

    'comment' and 'keyword' aren't applicable to the plain scanner, which is not -+language-specific, but a scanner which understands Lua is available. It -+recognizes the Lua keywords, and understands both short and long comments and -+strings.

    -+ -+ -+
    -+> for t,v in lexer.lua 'for i=1,n do' do print(t,v) end
    -+keyword for
    -+iden    i
    -+=       =
    -+number  1
    -+,       ,
    -+iden    n
    -+keyword do
    -+
    -+ -+

    A lexical scanner is useful where you have highly-structured data which is not -+nicely delimited by newlines. For example, here is a snippet of a in-house file -+format which it was my task to maintain:

    -+ -+ -+
    -+points
    -+    (818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1)
    -+    ,(818327.4,-20388,-0.1),(818322,-20387.7,-0.1),(818316.3,-20388.6,-0.1)
    -+    ,(818309.7,-20389.4,-0.1),(818303.5,-20390.6,-0.1),(818295.8,-20388.3,-0.1)
    -+    ,(818290.5,-20386.9,-0.1),(818285.2,-20386.1,-0.1),(818279.3,-20383.6,-0.1)
    -+    ,(818274,-20381.2,-0.1),(818274,-20380.7,-0.1);
    -+
    -+ -+

    Here is code to extract the points using pl.lexer:

    -+ -+ -+
    -+-- assume 's' contains the text above...
    -+local lexer = require 'pl.lexer'
    -+local expecting = lexer.expecting
    -+local append = table.insert
    -+
    -+local tok = lexer.scan(s)
    -+
    -+local points = {}
    -+local t,v = tok() -- should be 'iden','points'
    -+
    -+while t ~= ';' do
    -+    c = {}
    -+    expecting(tok,'(')
    -+    c.x = expecting(tok,'number')
    -+    expecting(tok,',')
    -+    c.y = expecting(tok,'number')
    -+    expecting(tok,',')
    -+    c.z = expecting(tok,'number')
    -+    expecting(tok,')')
    -+    t,v = tok()  -- either ',' or ';'
    -+    append(points,c)
    -+end
    -+
    -+ -+

    The expecting function grabs the next token and if the type doesn't match, it -+throws an error. (pl.lexer, unlike other PL libraries, raises errors if -+something goes wrong, so you should wrap your code in pcall to catch the error -+gracefully.)

    -+ -+

    The scanners all have a second optional argument, which is a table which controls -+whether you want to exclude spaces and/or comments. The default for lexer.lua -+is {space=true,comments=true}. There is a third optional argument which -+determines how string and number tokens are to be processed.

    -+ -+

    The ultimate highly-structured data is of course, program source. Here is a -+snippet from 'text-lexer.lua':

    -+ -+ -+
    -+require 'pl'
    -+
    -+lines = [[
    -+for k,v in pairs(t) do
    -+    if type(k) == 'number' then
    -+        print(v) -- array-like case
    -+    else
    -+        print(k,v)
    -+    end
    -+end
    -+]]
    -+
    -+ls = List()
    -+for tp,val in lexer.lua(lines,{space=true,comments=true}) do
    -+    assert(tp ~= 'space' and tp ~= 'comment')
    -+    if tp == 'keyword' then ls:append(val) end
    -+end
    -+test.asserteq(ls,List{'for','in','do','if','then','else','end','end'})
    -+
    -+ -+

    Here is a useful little utility that identifies all common global variables found -+in a lua module (ignoring those declared locally for the moment):

    -+ -+ -+
    -+-- testglobal.lua
    -+require 'pl'
    -+
    -+local txt,err = utils.readfile(arg[1])
    -+if not txt then return print(err) end
    -+
    -+local globals = List()
    -+for t,v in lexer.lua(txt) do
    -+    if t == 'iden' and _G[v] then
    -+        globals:append(v)
    -+    end
    -+end
    -+pretty.dump(seq.count_map(globals))
    -+
    -+ -+

    Rather then dumping the whole list, with its duplicates, we pass it through -+seq.count_map which turns the list into a table where the keys are the values, -+and the associated values are the number of times those values occur in the -+sequence. Typical output looks like this:

    -+ -+ -+
    -+{
    -+  type = 2,
    -+  pairs = 2,
    -+  table = 2,
    -+  print = 3,
    -+  tostring = 2,
    -+  require = 1,
    -+  ipairs = 4
    -+}
    -+
    -+ -+

    You could further pass this through tablex.keys to get a unique list of -+symbols. This can be useful when writing 'strict' Lua modules, where all global -+symbols must be defined as locals at the top of the file.

    -+ -+

    For a more detailed use of lexer.scan, please look at testxml.lua in the -+examples directory.

    -+ -+

    -+

    XML

    -+ -+

    New in the 0.9.7 release is some support for XML. This is a large topic, and -+Penlight does not provide a full XML stack, which is properly the task of a more -+specialized library.

    -+ -+

    Parsing and Pretty-Printing

    -+ -+

    The semi-standard XML parser in the Lua universe is lua-expat. -+In particular, -+it has a function called lxp.lom.parse which will parse XML into the Lua Object -+Model (LOM) format. However, it does not provide a way to convert this data back -+into XML text. xml.parse will use this function, if lua-expat is -+available, and otherwise switches back to a pure Lua parser originally written by -+Roberto Ierusalimschy.

    -+ -+

    The resulting document object knows how to render itself as a string, which is -+useful for debugging:

    -+ -+ -+
    -+> d = xml.parse "<nodes><node id='1'>alice</node></nodes>"
    -+> = d
    -+<nodes><node id='1'>alice</node></nodes>
    -+> pretty.dump (d)
    -+{
    -+  {
    -+    "alice",
    -+    attr = {
    -+      "id",
    -+      id = "1"
    -+    },
    -+    tag = "node"
    -+  },
    -+  attr = {
    -+  },
    -+  tag = "nodes"
    -+}
    -+
    -+ -+

    Looking at the actual shape of the data reveals the structure of LOM:

    -+ -+
      -+
    • every element has a tag field with its name
    • -+
    • plus a attr field which is a table containing the attributes as fields, and -+ also as an array. It is always present.
    • -+
    • the children of the element are the array part of the element, so d[1] is -+ the first child of d, etc.
    • -+
    -+ -+

    It could be argued that having attributes also as the array part of attr is not -+essential (you cannot depend on attribute order in XML) but that's how -+it goes with this standard.

    -+ -+

    lua-expat is another soft dependency of Penlight; generally, the fallback -+parser is good enough for straightforward XML as is commonly found in -+configuration files, etc. doc.basic_parse is not intended to be a proper -+conforming parser (it's only sixty lines) but it handles simple kinds of -+documents that do not have comments or DTD directives. It is intelligent enough -+to ignore the <?xml directive and that is about it.

    -+ -+

    You can get pretty-printing by explicitly calling xml.tostring and passing it -+the initial indent and the per-element indent:

    -+ -+ -+
    -+> = xml.tostring(d,'','  ')
    -+
    -+<nodes>
    -+  <node id='1'>alice</node>
    -+</nodes>
    -+
    -+ -+

    There is a fourth argument which is the attribute indent:

    -+ -+ -+
    -+> a = xml.parse "<frodo name='baggins' age='50' type='hobbit'/>"
    -+> = xml.tostring(a,'','  ','  ')
    -+
    -+<frodo
    -+  type='hobbit'
    -+  name='baggins'
    -+  age='50'
    -+/>
    -+
    -+ -+

    Parsing and Working with Configuration Files

    -+ -+

    It's common to find configurations expressed with XML these days. It's -+straightforward to 'walk' the LOM -+data and extract the data in the form you want:

    -+ -+ -+
    -+require 'pl'
    -+
    -+local config = [[
    -+<config>
    -+    <alpha>1.3</alpha>
    -+    <beta>10</beta>
    -+    <name>bozo</name>
    -+</config>
    -+]]
    -+local d,err = xml.parse(config)
    -+
    -+local t = {}
    -+for item in d:childtags() do
    -+    t[item.tag] = item[1]
    -+end
    -+
    -+pretty.dump(t)
    -+--->
    -+{
    -+  beta = "10",
    -+  alpha = "1.3",
    -+  name = "bozo"
    -+}
    -+
    -+ -+

    The only gotcha is that here we must use the Doc:childtags method, which will -+skip over any text elements.

    -+ -+

    A more involved example is this excerpt from serviceproviders.xml, which is -+usually found at /usr/share/mobile-broadband-provider-info/serviceproviders.xml -+on Debian/Ubuntu Linux systems.

    -+ -+ -+
    -+d = xml.parse [[
    -+<serviceproviders format="2.0">
    -+...
    -+<country code="za">
    -+    <provider>
    -+        <name>Cell-c</name>
    -+        <gsm>
    -+            <network-id mcc="655" mnc="07"/>
    -+            <apn value="internet">
    -+                <username>Cellcis</username>
    -+                <dns>196.7.0.138</dns>
    -+                <dns>196.7.142.132</dns>
    -+            </apn>
    -+        </gsm>
    -+    </provider>
    -+    <provider>
    -+        <name>MTN</name>
    -+        <gsm>
    -+            <network-id mcc="655" mnc="10"/>
    -+            <apn value="internet">
    -+                <dns>196.11.240.241</dns>
    -+                <dns>209.212.97.1</dns>
    -+            </apn>
    -+        </gsm>
    -+    </provider>
    -+    <provider>
    -+        <name>Vodacom</name>
    -+        <gsm>
    -+            <network-id mcc="655" mnc="01"/>
    -+            <apn value="internet">
    -+                <dns>196.207.40.165</dns>
    -+                <dns>196.43.46.190</dns>
    -+            </apn>
    -+            <apn value="unrestricted">
    -+                <name>Unrestricted</name>
    -+                <dns>196.207.32.69</dns>
    -+                <dns>196.43.45.190</dns>
    -+            </apn>
    -+        </gsm>
    -+    </provider>
    -+    <provider>
    -+        <name>Virgin Mobile</name>
    -+        <gsm>
    -+            <apn value="vdata">
    -+                <dns>196.7.0.138</dns>
    -+                <dns>196.7.142.132</dns>
    -+            </apn>
    -+        </gsm>
    -+    </provider>
    -+</country>
    -+....
    -+</serviceproviders>
    -+]]
    -+
    -+ -+

    Getting the names of the providers per-country is straightforward:

    -+ -+ -+
    -+local t = {}
    -+for country in d:childtags() do
    -+    local providers = {}
    -+    t[country.attr.code] = providers
    -+    for provider in country:childtags() do
    -+        table.insert(providers,provider:child_with_name('name'):get_text())
    -+    end
    -+end
    -+
    -+pretty.dump(t)
    -+-->
    -+{
    -+  za = {
    -+    "Cell-c",
    -+    "MTN",
    -+    "Vodacom",
    -+    "Virgin Mobile"
    -+  }
    -+  ....
    -+}
    -+
    -+ -+

    Generating XML with 'xmlification'

    -+ -+

    This feature is inspired by the htmlify function used by -+Orbit to simplify HTML generation, -+except that no function environment magic is used; the tags function returns a -+set of constructors for elements of the given tag names.

    -+ -+ -+
    -+> nodes, node = xml.tags 'nodes, node'
    -+> = node 'alice'
    -+<node>alice</node>
    -+> = nodes { node {id='1','alice'}}
    -+<nodes><node id='1'>alice</node></nodes>
    -+
    -+ -+

    The flexibility of Lua tables is very useful here, since both the attributes and -+the children of an element can be encoded naturally. The argument to these tag -+constructors is either a single value (like a string) or a table where the -+attributes are the named keys and the children are the array values.

    -+ -+

    Generating XML using Templates

    -+ -+

    A template is a little XML document which contains dollar-variables. The subst -+method on a document is fed an array of tables containing values for these -+variables. Note how the parent tag name is specified:

    -+ -+ -+
    -+> templ = xml.parse "<node id='$id'>$name</node>"
    -+> = templ:subst {tag='nodes', {id=1,name='alice'},{id=2,name='john'}}
    -+<nodes><node id='1'>alice</node><node id='2'>john</node></nodes>
    -+
    -+ -+

    Substitution is very related to filtering documents. One of the annoying things -+about XML is that it is a document markup language first, and a data language -+second. Standard parsers will assume you really care about all those extra -+text elements. Consider this fragment, which has been changed by a five-year old:

    -+ -+ -+
    -+T = [[
    -+  <weather>
    -+    boops!
    -+    <current_conditions>
    -+      <condition data='$condition'/>
    -+      <temp_c data='$temp'/>
    -+      <bo>whoops!</bo>
    -+    </current_conditions>
    -+  </weather>
    -+]]
    -+
    -+ -+

    Conformant parsers will give you text elements with the line feed after <current_conditions> -+although it makes handling the data more irritating.

    -+ -+ -+
    -+local function parse (str)
    -+    return xml.parse(str,false,true)
    -+end
    -+
    -+ -+

    Second argument means 'string, not file' and third argument means use the built-in -+Lua parser (instead of LuaExpat if available) which by default is not interested in -+keeping such strings.

    -+ -+

    How to remove the string boops!? clone (also called filter when called as a -+method) copies a LOM document. It can be passed a filter function, which is applied -+to each string found. The powerful thing about this is that this function receives -+structural information - the parent node, and whether this was a tag name, a text -+element or a attribute name:

    -+ -+ -+
    -+d = parse (T)
    -+c = d:filter(function(s,kind,parent)
    -+    print(stringx.strip(s),kind,parent and parent.tag or '?')
    -+    if kind == '*TEXT' and #parent > 1 then return nil end
    -+    return s
    -+end)
    -+--->
    -+weather    *TAG    ?
    -+boops!    *TEXT    weather
    -+current_conditions    *TAG    weather
    -+condition    *TAG    current_conditions
    -+$condition    data    condition
    -+temp_c    *TAG    current_conditions
    -+$temp    data    temp_c
    -+bo    *TAG    current_conditions
    -+whoops!    *TEXT    bo
    -+
    -+ -+

    We can pull out 'boops' and not 'whoops' by discarding text elements which are not -+the single child of an element.

    -+ -+ -+ -+

    Extracting Data using Templates

    -+ -+

    Matching goes in the opposite direction. We have a document, and would like to -+extract values from it using a pattern.

    -+ -+

    A common use of this is parsing the XML result of API queries. The -+(undocumented and subsequently discontinued) Google Weather -+API is a -+good example. Grabbing the result of -+`http://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like -+this, after pretty-printing:

    -+ -+ -+
    -+<xml_api_reply version='1'>
    -+  <weather module_id='0' tab_id='0' mobile_zipped='1' section='0' row='0'
    -+
    -+ -+

    mobile_row='0'>

    -+ -+
    -+<forecast_information>
    -+  <city data='Johannesburg, Gauteng'/>
    -+  <postal_code data='Johannesburg,ZA'/>
    -+  <latitude_e6 data=''/>
    -+  <longitude_e6 data=''/>
    -+  <forecast_date data='2010-10-02'/>
    -+  <current_date_time data='2010-10-02 18:30:00 +0000'/>
    -+  <unit_system data='US'/>
    -+</forecast_information>
    -+<current_conditions>
    -+  <condition data='Clear'/>
    -+  <temp_f data='75'/>
    -+  <temp_c data='24'/>
    -+  <humidity data='Humidity: 19%'/>
    -+  <icon data='/ig/images/weather/sunny.gif'/>
    -+  <wind_condition data='Wind: NW at 7 mph'/>
    -+</current_conditions>
    -+<forecast_conditions>
    -+  <day_of_week data='Sat'/>
    -+  <low data='60'/>
    -+  <high data='89'/>
    -+  <icon data='/ig/images/weather/sunny.gif'/>
    -+  <condition data='Clear'/>
    -+</forecast_conditions>
    -+....
    -+/weather>
    -+l_api_reply>
    -+
    -+ -+

    Assume that the above XML has been read into google. The idea is to write a -+pattern looking like a template, and use it to extract some values of interest:

    -+ -+ -+
    -+t = [[
    -+  <weather>
    -+    <current_conditions>
    -+      <condition data='$condition'/>
    -+      <temp_c data='$temp'/>
    -+    </current_conditions>
    -+  </weather>
    -+]]
    -+
    -+local res, ret = google:match(t)
    -+pretty.dump(res)
    -+
    -+ -+

    And the output is:

    -+ -+ -+
    -+{
    -+  condition = "Clear",
    -+  temp = "24"
    -+}
    -+
    -+ -+

    The match method can be passed a LOM document or some text, which will be -+parsed first.

    -+ -+

    But what if we need to extract values from repeated elements? Match templates may -+contain 'array matches' which are enclosed in '{{..}}':

    -+ -+ -+
    -+<weather>
    -+  {{<forecast_conditions>
    -+    <day_of_week data='$day'/>
    -+    <low data='$low'/>
    -+    <high data='$high'/>
    -+    <condition data='$condition'/>
    -+  </forecast_conditions>}}
    -+</weather>
    -+
    -+ -+

    And the match result is:

    -+ -+ -+
    -+{
    -+  {
    -+    low = "60",
    -+    high = "89",
    -+    day = "Sat",
    -+    condition = "Clear",
    -+  },
    -+  {
    -+    low = "53",
    -+    high = "86",
    -+    day = "Sun",
    -+    condition = "Clear",
    -+  },
    -+  {
    -+    low = "57",
    -+    high = "87",
    -+    day = "Mon",
    -+    condition = "Clear",
    -+  },
    -+  {
    -+    low = "60",
    -+    high = "84",
    -+    day = "Tue",
    -+    condition = "Clear",
    -+  }
    -+}
    -+
    -+ -+

    With this array of tables, you can use tablex or List -+to reshape into the desired form, if you choose. Just as with reading a Unix password -+file with config, you can make the array into a map of days to conditions using:

    -+ -+ -+
    -+tablex.pairmap('|k,v| v,v.day',conditions)
    -+
    -+ -+

    (Here using the alternative string lambda option)

    -+ -+

    However, xml matches can shape the structure of the output. By replacing the day_of_week -+line of the template with <day_of_week data='$_'/> we get the same effect; $_ is -+a special symbol that means that this captured value (or simply capture) becomes the key.

    -+ -+

    Note that $NUMBER means a numerical index, so -+that $1 is the first element of the resulting array, and so forth. You can mix -+numbered and named captures, but it's strongly advised to make the numbered captures -+form a proper array sequence (everything from 1 to n inclusive). $0 has a -+special meaning; if it is the only capture ({[0]='foo'}) then the table is -+collapsed into 'foo'.

    -+ -+ -+
    -+<weather>
    -+  {{<forecast_conditions>
    -+    <day_of_week data='$_'/>
    -+    <low data='$1'/>
    -+    <high data='$2'/>
    -+    <condition data='$3'/>
    -+  </forecast_conditions>}}
    -+</weather>
    -+
    -+ -+

    Now the result is:

    -+ -+ -+
    -+{
    -+  Tue = {
    -+    "60",
    -+    "84",
    -+    "Clear"
    -+  },
    -+  Sun = {
    -+    "53",
    -+    "86",
    -+    "Clear"
    -+  },
    -+  Sat = {
    -+    "60",
    -+    "89",
    -+    "Clear"
    -+  },
    -+  Mon = {
    -+    "57",
    -+    "87",
    -+    "Clear"
    -+  }
    -+}
    -+
    -+ -+

    Applying matches to this config file poses another problem, because the actual -+tags matched are themselves meaningful.

    -+ -+ -+
    -+<config>
    -+    <alpha>1.3</alpha>
    -+    <beta>10</beta>
    -+    <name>bozo</name>
    -+</config>
    -+
    -+ -+

    So there are tag 'wildcards' which are element names ending with a hyphen.

    -+ -+ -+
    -+<config>
    -+    {{<key->$value</key->}}
    -+</config>
    -+
    -+ -+

    You will then get {{alpha='1.3'},...}. The most convenient format would be -+returned by this (note that _- behaves just like $_):

    -+ -+ -+
    -+<config>
    -+    {{<_->$0</_->}}
    -+</config>
    -+
    -+ -+

    which would return {alpha='1.3',beta='10',name='bozo'}.

    -+ -+

    We could play this game endlessly, and encode ways of converting captures, but -+the scheme is complex enough, and it's easy to do the conversion later

    -+ -+ -+
    -+local numbers = {alpha=true,beta=true}
    -+for k,v in pairs(res) do
    -+    if numbers[v] then res[k] = tonumber(v) end
    -+end
    -+
    -+ -+

    HTML Parsing

    -+ -+

    HTML is an unusually degenerate form of XML, and Dennis Schridde has contributed -+a feature which makes parsing it easier. For instance, from the tests:

    -+ -+ -+
    -+doc = xml.parsehtml [[
    -+<BODY>
    -+Hello dolly<br>
    -+HTML is <b>slack</b><br>
    -+</BODY>
    -+]]
    -+
    -+asserteq(xml.tostring(doc),[[
    -+<body>
    -+Hello dolly<br/>
    -+HTML is <b>slack</b><br/></body>]])
    -+
    -+ -+

    That is, all tags are converted to lowercase, and empty HTML elements like br -+are properly closed; attributes do not need to be quoted.

    -+ -+

    Also, DOCTYPE directives and comments are skipped. For truly badly formed HTML, -+this is not the tool for you!

    -+ -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/07-functional.md.html b/extra/penlight/docs/manual/07-functional.md.html -new file mode 100644 -index 0000000..d6d6eab ---- /dev/null -+++ b/extra/penlight/docs/manual/07-functional.md.html -@@ -0,0 +1,834 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Functional Programming

    -+ -+

    -+

    Sequences

    -+ -+ -+

    A Lua iterator (in its simplest form) is a function which can be repeatedly -+called to return a set of one or more values. The for in statement understands -+these iterators, and loops until the function returns nil. There are standard -+sequence adapters for tables in Lua (ipairs and pairs), and io.lines -+returns an iterator over all the lines in a file. In the Penlight libraries, such -+iterators are also called sequences. A sequence of single values (say from -+io.lines) is called single-valued, whereas the sequence defined by pairs is -+double-valued.

    -+ -+

    pl.seq provides a number of useful iterators, and some functions which operate -+on sequences. At first sight this example looks like an attempt to write Python -+in Lua, (with the sequence being inclusive):

    -+ -+ -+
    -+> for i in seq.range(1,4) do print(i) end
    -+1
    -+2
    -+3
    -+4
    -+
    -+ -+

    But range is actually equivalent to Python's xrange, since it generates a -+sequence, not a list. To get a list, use seq.copy(seq.range(1,10)), which -+takes any single-value sequence and makes a table from the result. seq.list is -+like ipairs except that it does not give you the index, just the value.

    -+ -+ -+
    -+> for x in seq.list {1,2,3} do print(x) end
    -+1
    -+2
    -+3
    -+
    -+ -+

    enum takes a sequence and turns it into a double-valued sequence consisting of -+a sequence number and the value, so enum(list(ls)) is actually equivalent to -+ipairs. A more interesting example prints out a file with line numbers:

    -+ -+ -+
    -+for i,v in seq.enum(io.lines(fname)) do print(i..' '..v) end
    -+
    -+ -+

    Sequences can be combined, either by 'zipping' them or by concatenating them.

    -+ -+ -+
    -+> for x,y in seq.zip(l1,l2) do print(x,y) end
    -+10      1
    -+20      2
    -+30      3
    -+> for x in seq.splice(l1,l2) do print(x) end
    -+10
    -+20
    -+30
    -+1
    -+2
    -+3
    -+
    -+ -+

    seq.printall is useful for printing out single-valued sequences, and provides -+some finer control over formatting, such as a delimiter, the number of fields per -+line, and a format string to use (@see string.format)

    -+ -+ -+
    -+> seq.printall(seq.random(10))
    -+0.0012512588885159 0.56358531449324 0.19330423902097 ....
    -+> seq.printall(seq.random(10), ',', 4, '%4.2f')
    -+0.17,0.86,0.71,0.51
    -+0.30,0.01,0.09,0.36
    -+0.15,0.17,
    -+
    -+ -+

    map will apply a function to a sequence.

    -+ -+ -+
    -+> seq.printall(seq.map(string.upper, {'one','two'}))
    -+ONE TWO
    -+> seq.printall(seq.map('+', {10,20,30}, 1))
    -+11 21 31
    -+
    -+ -+

    filter will filter a sequence using a boolean function (often called a -+predicate). For instance, this code only prints lines in a file which are -+composed of digits:

    -+ -+ -+
    -+for l in seq.filter(io.lines(file), stringx.isdigit) do print(l) end
    -+
    -+ -+

    The following returns a table consisting of all the positive values in the -+original table (equivalent to tablex.filter(ls, '>', 0))

    -+ -+ -+
    -+ls = seq.copy(seq.filter(ls, '>', 0))
    -+
    -+ -+

    We're already encountered seq.sum when discussing input.numbers. This can also -+be expressed with seq.reduce:

    -+ -+ -+
    -+> seq.reduce(function(x,y) return x + y end, seq.list{1,2,3,4})
    -+10
    -+
    -+ -+

    seq.reduce applies a binary function in a recursive fashion, so that:

    -+ -+ -+
    -+reduce(op,{1,2,3}) => op(1,reduce(op,{2,3}) => op(1,op(2,3))
    -+
    -+ -+

    it's now possible to easily generate other cumulative operations; the standard -+operations declared in pl.operator are useful here:

    -+ -+ -+
    -+> ops = require 'pl.operator'
    -+> -- can also say '*' instead of ops.mul
    -+> = seq.reduce(ops.mul,input.numbers '1 2 3 4')
    -+24
    -+
    -+ -+

    There are functions to extract statistics from a sequence of numbers:

    -+ -+ -+
    -+> l1 = List {10,20,30}
    -+> l2 = List {1,2,3}
    -+> = seq.minmax(l1)
    -+10      30
    -+> = seq.sum(l1)
    -+60      3
    -+
    -+ -+

    It is common to get sequences where values are repeated, say the words in a file. -+count_map will take such a sequence and count the values, returning a table -+where the keys are the unique values, and the value associated with each key is -+the number of times they occurred:

    -+ -+ -+
    -+> t = seq.count_map {'one','fred','two','one','two','two'}
    -+> = t
    -+{one=2,fred=1,two=3}
    -+
    -+ -+

    This will also work on numerical sequences, but you cannot expect the result to -+be a proper list, i.e. having no 'holes'. Instead, you always need to use pairs -+to iterate over the result - note that there is a hole at index 5:

    -+ -+ -+
    -+> t = seq.count_map {1,2,4,2,2,3,4,2,6}
    -+> for k,v in pairs(t) do print(k,v) end
    -+1       1
    -+2       4
    -+3       1
    -+4       2
    -+6       1
    -+
    -+ -+

    unique uses count_map to return a list of the unique values, that is, just -+the keys of the resulting table.

    -+ -+

    last turns a single-valued sequence into a double-valued sequence with the -+current value and the last value:

    -+ -+ -+
    -+> for current,last in seq.last {10,20,30,40} do print (current,last) end
    -+20      10
    -+30      20
    -+40      30
    -+
    -+ -+

    This makes it easy to do things like identify repeated lines in a file, or -+construct differences between values. filter can handle double-valued sequences -+as well, so one could filter such a sequence to only return cases where the -+current value is less than the last value by using operator.lt or just '<'. -+This code then copies the resulting code into a table.

    -+ -+ -+
    -+> ls = {10,9,10,3}
    -+> = seq.copy(seq.filter(seq.last(s),'<'))
    -+{9,3}
    -+
    -+ -+

    -+

    Sequence Wrappers

    -+ -+

    The functions in pl.seq cover the common patterns when dealing with sequences, -+but chaining these functions together can lead to ugly code. Consider the last -+example of the previous section; seq is repeated three times and the resulting -+expression has to be read right-to-left. The first issue can be helped by local -+aliases, so that the expression becomes copy(filter(last(s),'<')) but the -+second issue refers to the somewhat unnatural order of functional application. -+We tend to prefer reading operations from left to right, which is one reason why -+object-oriented notation has become popular. Sequence adapters allow this -+expression to be written like so:

    -+ -+ -+
    -+seq(s):last():filter('<'):copy()
    -+
    -+ -+

    With this notation, the operation becomes a chain of method calls running from -+left to right.

    -+ -+

    'Sequence' is not a basic Lua type, they are generally functions or callable -+objects. The expression seq(s) wraps a sequence in a sequence wrapper, which -+is an object which understands all the functions in pl.seq as methods. This -+object then explicitly represents sequences.

    -+ -+

    As a special case, the constructor (which is when you call the table seq) will -+make a wrapper for a plain list-like table. Here we apply the length operator to -+a sequence of strings, and print them out.

    -+ -+ -+
    -+> seq{'one','tw','t'} :map '#' :printall()
    -+3 2 1
    -+
    -+ -+

    As a convenience, there is a function seq.lines which behaves just like -+io.lines except it wraps the result as an explicit sequence type. This takes -+the first 10 lines from standard input, makes it uppercase, turns it into a -+sequence with a count and the value, glues these together with the concatenation -+operator, and finally prints out the sequence delimited by a newline.

    -+ -+ -+
    -+seq.lines():take(10):upper():enum():map('..'):printall '\n'
    -+
    -+ -+

    Note the method upper, which is not a seq function. if an unknown method is -+called, sequence wrappers apply that method to all the values in the sequence -+(this is implicit use of mapmethod)

    -+ -+

    It is straightforward to create custom sequences that can be used in this way. On -+Unix, /dev/random gives you an endless sequence of random bytes, so we use -+take to limit the sequence, and then map to scale the result into the desired -+range. The key step is to use seq to wrap the iterator function:

    -+ -+ -+
    -+-- random.lua
    -+local seq = require 'pl.seq'
    -+
    -+function dev_random()
    -+    local f = io.open('/dev/random')
    -+    local byte = string.byte
    -+    return seq(function()
    -+        -- read two bytes into a string and convert into a 16-bit number
    -+        local s = f:read(2)
    -+        return byte(s,1) + 256*byte(s,2)
    -+    end)
    -+end
    -+
    -+-- print 10 random numbers from 0 to 1 !
    -+dev_random():take(10):map('%',100):map('/',100):printall ','
    -+
    -+ -+

    Another Linux one-liner depends on the /proc filesystem and makes a list of all -+the currently running processes:

    -+ -+ -+
    -+pids = seq(lfs.dir '/proc'):filter(stringx.isdigit):map(tonumber):copy()
    -+
    -+ -+

    This version of Penlight has an experimental feature which relies on the fact -+that all Lua types can have metatables, including functions. This makes -+implicit sequence wrapping possible:

    -+ -+ -+
    -+> seq.import()
    -+> seq.random(5):printall(',',5,'%4.1f')
    -+ 0.0, 0.1, 0.4, 0.1, 0.2
    -+
    -+ -+

    This avoids the awkward seq(seq.random(5)) construction. Or the iterator can -+come from somewhere else completely:

    -+ -+ -+
    -+> ('one two three'):gfind('%a+'):printall(',')
    -+one,two,three,
    -+
    -+ -+

    After seq.import, it is no longer necessary to explicitly wrap sequence -+functions.

    -+ -+

    But there is a price to pay for this convenience. Every function is affected, -+so that any function can be used, appropriate or not:

    -+ -+ -+
    -+> math.sin:printall()
    -+..seq.lua:287: bad argument #1 to '(for generator)' (number expected, got nil)
    -+> a = tostring
    -+> = a:find(' ')
    -+function: 0042C920
    -+
    -+ -+

    What function is returned? It's almost certain to be something that makes no -+sense in the current context. So implicit sequences may make certain kinds of -+programming mistakes harder to catch - they are best used for interactive -+exploration and small scripts.

    -+ -+

    -+ -+

    -+

    List Comprehensions

    -+ -+

    List comprehensions are a compact way to create tables by specifying their -+elements. In Python, you can say this:

    -+ -+ -+
    -+ls = [x for x in range(5)]  # == [0,1,2,3,4]
    -+
    -+ -+

    In Lua, using pl.comprehension:

    -+ -+ -+
    -+> C = require('pl.comprehension').new()
    -+> = C ('x for x=1,10') ()
    -+{1,2,3,4,5,6,7,8,9,10}
    -+
    -+ -+

    C is a function which compiles a list comprehension string into a function. -+In this case, the function has no arguments. The parentheses are redundant for a -+function taking a string argument, so this works as well:

    -+ -+ -+
    -+> = C 'x^2 for x=1,4' ()
    -+{1,4,9,16}
    -+> = C '{x,x^2} for x=1,4' ()
    -+{{1,1},{2,4},{3,9},{4,16}}
    -+
    -+ -+

    Note that the expression can be any function of the variable x!

    -+ -+

    The basic syntax so far is <expr> for <set>, where <set> can be anything that -+the Lua for statement understands. <set> can also just be the variable, in -+which case the values will come from the argument of the comprehension. Here -+I'm emphasizing that a comprehension is a function which can take a list argument:

    -+ -+ -+
    -+> = C '2*x for x' {1,2,3}
    -+{2,4,6}
    -+> dbl = C '2*x for x'
    -+> = dbl {10,20,30}
    -+{20,40,60}
    -+
    -+ -+

    Here is a somewhat more explicit way of saying the same thing; _1 is a -+placeholder referring to the first argument passed to the comprehension.

    -+ -+ -+
    -+> = C '2*x for _,x in pairs(_1)' {10,20,30}
    -+{20,40,60}
    -+> = C '_1(x) for x'(tostring,{1,2,3,4})
    -+{'1','2','3','4'}
    -+
    -+ -+

    This extended syntax is useful when you wish to collect the result of some -+iterator, such as io.lines. This comprehension creates a function which creates -+a table of all the lines in a file:

    -+ -+ -+
    -+> f = io.open('array.lua')
    -+> lines = C 'line for line in _1:lines()' (f)
    -+> = #lines
    -+118
    -+
    -+ -+

    There are a number of functions that may be applied to the result of a -+comprehension:

    -+ -+ -+
    -+> = C 'min(x for x)' {1,44,0}
    -+0
    -+> = C 'max(x for x)' {1,44,0}
    -+44
    -+> = C 'sum(x for x)' {1,44,0}
    -+45
    -+
    -+ -+

    (These are equivalent to a reduce operation on a list.)

    -+ -+

    After the for part, there may be a condition, which filters the output. This -+comprehension collects the even numbers from a list:

    -+ -+ -+
    -+> = C 'x for x if x % 2 == 0' {1,2,3,4,5}
    -+{2,4}
    -+
    -+ -+

    There may be a number of for parts:

    -+ -+ -+
    -+> = C '{x,y} for x = 1,2 for y = 1,2' ()
    -+{{1,1},{1,2},{2,1},{2,2}}
    -+> = C '{x,y} for x for y' ({1,2},{10,20})
    -+{{1,10},{1,20},{2,10},{2,20}}
    -+
    -+ -+

    These comprehensions are useful when dealing with functions of more than one -+variable, and are not so easily achieved with the other Penlight functional forms.

    -+ -+

    -+ -+

    -+

    Creating Functions from Functions

    -+ -+ -+

    Lua functions may be treated like any other value, although of course you cannot -+multiply or add them. One operation that makes sense is function composition, -+which chains function calls (so (f * g)(x) is f(g(x)).)

    -+ -+ -+
    -+> func = require 'pl.func'
    -+> printf = func.compose(io.write,string.format)
    -+> printf("hello %s\n",'world')
    -+hello world
    -+true
    -+
    -+ -+

    Many functions require you to pass a function as an argument, say to apply to all -+values of a sequence or as a callback. Often useful functions have the wrong -+number of arguments. So there is a need to construct a function of one argument -+from one of two arguments, binding the extra argument to a given value.

    -+ -+

    partial application takes a function of n arguments and returns a function of n-1 -+arguments where the first argument is bound to some value:

    -+ -+ -+
    -+> p2 = func.bind1(print,'start>')
    -+> p2('hello',2)
    -+start>  hello   2
    -+> ops = require 'pl.operator'
    -+> = tablex.filter({1,-2,10,-1,2},bind1(ops.gt,0))
    -+{-2,-1}
    -+> tablex.filter({1,-2,10,-1,2},bind1(ops.le,0))
    -+{1,10,2}
    -+
    -+ -+

    The last example unfortunately reads backwards, because bind1 always binds the -+first argument! Also unfortunately, in my youth I confused 'currying' with -+'partial application', so the old name for bind1 is curry - this alias still exists.

    -+ -+

    This is a specialized form of function argument binding. Here is another way -+to say the print example:

    -+ -+ -+
    -+> p2 = func.bind(print,'start>',func._1,func._2)
    -+> p2('hello',2)
    -+start>  hello   2
    -+
    -+ -+

    where _1 and _2 are placeholder variables, corresponding to the first and -+second argument respectively.

    -+ -+

    Having func all over the place is distracting, so it's useful to pull all of -+pl.func into the local context. Here is the filter example, this time the right -+way around:

    -+ -+ -+
    -+> utils.import 'pl.func'
    -+> tablex.filter({1,-2,10,-1,2},bind(ops.gt, _1, 0))
    -+{1,10,2}
    -+
    -+ -+

    tablex.merge does a general merge of two tables. This example shows the -+usefulness of binding the last argument of a function.

    -+ -+ -+
    -+> S1 = {john=27, jane=31, mary=24}
    -+> S2 = {jane=31, jones=50}
    -+> intersection = bind(tablex.merge, _1, _2, false)
    -+> union = bind(tablex.merge, _1, _2, true)
    -+> = intersection(S1,S2)
    -+{jane=31}
    -+> = union(S1,S2)
    -+{mary=24,jane=31,john=27,jones=50}
    -+
    -+ -+

    When using bind with print, we got a function of precisely two arguments, -+whereas we really want our function to use varargs like print. This is the role -+of _0:

    -+ -+ -+
    -+> _DEBUG = true
    -+> p = bind(print,'start>', _0)
    -+return function (fn,_v1)
    -+    return function(...) return fn(_v1,...) end
    -+end
    -+
    -+> p(1,2,3,4,5)
    -+start>  1       2       3       4       5
    -+
    -+ -+

    I've turned on the global _DEBUG flag, so that the function generated is -+printed out. It is actually a function which generates the required function; -+the first call binds the value of _v1 to 'start>'.

    -+ -+

    -+

    Placeholder Expressions

    -+ -+

    A common pattern in Penlight is a function which applies another function to all -+elements in a table or a sequence, such as tablex.map or seq.filter. Lua does -+anonymous functions well, although they can be a bit tedious to type:

    -+ -+ -+
    -+> = tablex.map(function(x) return x*x end, {1,2,3,4})
    -+{1,4,9,16}
    -+
    -+ -+

    pl.func allows you to define placeholder expressions, which can cut down on -+the typing required, and also make your intent clearer. First, we bring contents -+of pl.func into our context, and then supply an expression using placeholder -+variables, such as _1,_2,etc. (C++ programmers will recognize this from the -+Boost libraries.)

    -+ -+ -+
    -+> utils.import 'pl.func'
    -+> = tablex.map(_1*_1, {1,2,3,4})
    -+{1,4,9,16}
    -+
    -+ -+

    Functions of up to 5 arguments can be generated.

    -+ -+ -+
    -+> = tablex.map2(_1+_2,{1,2,3}, {10,20,30})
    -+{11,22,33}
    -+
    -+ -+

    These expressions can use arbitrary functions, although they must first be -+registered with the functional library. func.register brings in a single -+function, and func.import brings in a whole table of functions, such as math.

    -+ -+ -+
    -+> sin = register(math.sin)
    -+> = tablex.map(sin(_1), {1,2,3,4})
    -+{0.8414709848079,0.90929742682568,0.14112000805987,-0.75680249530793}
    -+> import 'math'
    -+> = tablex.map(cos(2*_1),{1,2,3,4})
    -+{-0.41614683654714,-0.65364362086361,0.96017028665037,-0.14550003380861}
    -+
    -+ -+

    A common operation is calling a method of a set of objects:

    -+ -+ -+
    -+> = tablex.map(_1:sub(1,1), {'one','four','x'})
    -+{'o','f','x'}
    -+
    -+ -+

    There are some restrictions on what operators can be used in PEs. For instance, -+because the __len metamethod cannot be overridden by plain Lua tables, we need -+to define a special function to express `#_1':

    -+ -+ -+
    -+> = tablex.map(Len(_1), {'one','four','x'})
    -+{3,4,1}
    -+
    -+ -+

    Likewise for comparison operators, which cannot be overloaded for different -+types, and thus also have to be expressed as a special function:

    -+ -+ -+
    -+> = tablex.filter(Gt(_1,0), {1,-1,2,4,-3})
    -+{1,2,4}
    -+
    -+ -+

    It is useful to express the fact that a function returns multiple values. For -+instance, tablex.pairmap expects a function that will be called with the key -+and the value, and returns the new value and the key, in that order.

    -+ -+ -+
    -+> = pairmap(Args(_2,_1:upper()),{fred=1,alice=2})
    -+{ALICE=2,FRED=1}
    -+
    -+ -+

    PEs cannot contain nil values, since PE function arguments are represented as -+an array. Instead, a special value called Nil is provided. So say -+_1:f(Nil,1) instead of _1:f(nil,1).

    -+ -+

    A placeholder expression cannot be automatically used as a Lua function. The -+technical reason is that the call operator must be overloaded to construct -+function calls like _1(1). If you want to force a PE to return a function, use -+func.I.

    -+ -+ -+
    -+> = tablex.map(_1(10),{I(2*_1),I(_1*_1),I(_1+2)})
    -+{20,100,12}
    -+
    -+ -+

    Here we make a table of functions taking a single argument, and then call them -+all with a value of 10.

    -+ -+

    The essential idea with PEs is to 'quote' an expression so that it is not -+immediately evaluated, but instead turned into a function that can be applied -+later to some arguments. The basic mechanism is to wrap values and placeholders -+so that the usual Lua operators have the effect of building up an expression -+tree. (It turns out that you can do symbolic algebra using PEs, see -+symbols.lua in the examples directory, and its test runner testsym.lua, which -+demonstrates symbolic differentiation.)

    -+ -+

    The rule is that if any operator has a PE operand, the result will be quoted. -+Sometimes we need to quote things explicitly. For instance, say we want to pass a -+function to a filter that must return true if the element value is in a set. -+set[_1] is the obvious expression, but it does not give the desired result, -+since it evaluates directly, giving nil. Indexing works differently than a -+binary operation like addition (set+_1 is properly quoted) so there is a need -+for an explicit quoting or wrapping operation. This is the job of the _ -+function; the PE in this case should be _(set)[_1]. This works for functions -+as well, as a convenient alternative to registering functions: _(math.sin)(_1). -+This is equivalent to using the `lines' method:

    -+ -+ -+
    -+for line in I(_(f):read()) do print(line) end
    -+
    -+ -+

    Now this will work for any 'file-like' object which which has a read method -+returning the next line. If you had a LuaSocket client which was being 'pushed' -+by lines sent from a server, then _(s):receive '*l' would create an iterator -+for accepting input. These forms can be convenient for adapting your data flow so -+that it can be passed to the sequence functions in `pl.seq'.

    -+ -+

    Placeholder expressions can be mixed with sequence wrapper expressions. -+lexer.lua will give us a double-valued sequence of tokens, where the first -+value is a type, and the second is a value. We filter out only the values where -+the type is 'iden', extract the actual value using map, get the unique values -+and finally copy to a list.

    -+ -+ -+
    -+> str = 'for i=1,10 do for j = 1,10 do print(i,j) end end'
    -+> = seq(lexer.lua(str)):filter('==','iden'):map(_2):unique():copy()
    -+{i,print,j}
    -+
    -+ -+

    This is a particularly intense line (and I don't always suggest making everything -+a one-liner!); the key is the behaviour of map, which will take both values of -+the sequence, so _2 returns the value part. (Since filter here takes extra -+arguments, it only operates on the type values.)

    -+ -+

    There are some performance considerations to using placeholder expressions. -+Instantiating a PE requires constructing and compiling a function, which is not -+such a fast operation. So to get best performance, factor out PEs from loops like -+this;

    -+ -+ -+
    -+local fn = I(_1:f() + _2:g())
    -+for i = 1,n do
    -+    res[i] = tablex.map2(fn,first[i],second[i])
    -+end
    -+
    -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/08-additional.md.html b/extra/penlight/docs/manual/08-additional.md.html -new file mode 100644 -index 0000000..3f96f05 ---- /dev/null -+++ b/extra/penlight/docs/manual/08-additional.md.html -@@ -0,0 +1,815 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Additional Libraries

    -+ -+

    Libraries in this section are no longer considered to be part of the Penlight -+core, but still provide specialized functionality when needed.

    -+ -+

    -+ -+

    -+

    Simple Input Patterns

    -+ -+

    Lua string pattern matching is very powerful, and usually you will not need a -+traditional regular expression library. Even so, sometimes Lua code ends up -+looking like Perl, which happens because string patterns are not always the -+easiest things to read, especially for the casual reader. Here is a program -+which needs to understand three distinct date formats:

    -+ -+ -+
    -+-- parsing dates using Lua string patterns
    -+months={Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,
    -+Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12}
    -+
    -+function check_and_process(d,m,y)
    -+    d = tonumber(d)
    -+    m = tonumber(m)
    -+    y = tonumber(y)
    -+    ....
    -+end
    -+
    -+for line in f:lines() do
    -+    -- ordinary (English) date format
    -+    local d,m,y = line:match('(%d+)/(%d+)/(%d+)')
    -+    if d then
    -+        check_and_process(d,m,y)
    -+    else -- ISO date??
    -+        y,m,d = line:match('(%d+)%-(%d+)%-(%d+)')
    -+        if y then
    -+            check_and_process(d,m,y)
    -+        else -- <day> <month-name> <year>?
    -+            d,mm,y = line:match('%(d+)%s+(%a+)%s+(%d+)')
    -+            m = months[mm]
    -+            check_and_process(d,m,y)
    -+        end
    -+    end
    -+end
    -+
    -+ -+

    These aren't particularly difficult patterns, but already typical issues are -+appearing, such as having to escape '-'. Also, string.match returns its -+captures, so that we're forced to use a slightly awkward nested if-statement.

    -+ -+

    Verification issues will further cloud the picture, since regular expression -+people try to enforce constraints (like year cannot be more than four digits) -+using regular expressions, on the usual grounds that you shouldn't stop using a -+hammer when you are enjoying yourself.

    -+ -+

    pl.sip provides a simple, intuitive way to detect patterns in strings and -+extract relevant parts.

    -+ -+ -+
    -+> sip = require 'pl.sip'
    -+> dump = require('pl.pretty').dump
    -+> res = {}
    -+> c = sip.compile 'ref=$S{file}:$d{line}'
    -+> = c('ref=hello.c:10',res)
    -+true
    -+> dump(res)
    -+{
    -+  line = 10,
    -+  file = "hello.c"
    -+}
    -+> = c('ref=long name, no line',res)
    -+false
    -+
    -+ -+

    sip.compile creates a pattern matcher function, which takes a string and a -+table as arguments. If the string matches the pattern, then true is returned -+and the table is populated according to the captures within the pattern.

    -+ -+

    Here is another version of the date parser:

    -+ -+ -+
    -+-- using SIP patterns
    -+function check(t)
    -+    check_and_process(t.day,t.month,t.year)
    -+end
    -+
    -+shortdate = sip.compile('$d{day}/$d{month}/$d{year}')
    -+longdate = sip.compile('$d{day} $v{mon} $d{year}')
    -+isodate = sip.compile('$d{year}-$d{month}-$d{day}')
    -+
    -+for line in f:lines() do
    -+    local res = {}
    -+    if shortdate(str,res) then
    -+        check(res)
    -+    elseif isodate(str,res) then
    -+        check(res)
    -+    elseif longdate(str,res) then
    -+        res.month = months[res.mon]
    -+        check(res)
    -+    end
    -+end
    -+
    -+ -+

    SIP captures start with '$', then a one-character type, and then an -+optional variable name in curly braces.

    -+ -+ -+
    -+Type      Meaning
    -+v         identifier
    -+i         possibly signed integer
    -+f         floating-point number
    -+r         rest of line
    -+q         quoted string (quoted using either ' or ")
    -+p         a path name
    -+(         anything inside balanced parentheses
    -+[         anything inside balanced brackets
    -+{         anything inside balanced curly brackets
    -+<         anything inside balanced angle brackets
    -+
    -+ -+

    If a type is not one of the above, then it's assumed to be one of the standard -+Lua character classes, and will match one or more repetitions of that class. -+Any spaces you leave in your pattern will match any number of spaces, including -+zero, unless the spaces are between two identifier characters or patterns -+matching them; in that case, at least one space will be matched.

    -+ -+

    SIP captures (like $v{mon}) do not have to be named. You can use just $v, but -+you have to be consistent; if a pattern contains unnamed captures, then all -+captures must be unnamed. In this case, the result table is a simple list of -+values.

    -+ -+

    sip.match is a useful shortcut if you want to compile and match in one call, -+without saving the compiled pattern. It caches the result, so it is not much -+slower than explicitly using sip.compile.

    -+ -+ -+
    -+> sip.match('($q{first},$q{second})','("john","smith")',res)
    -+true
    -+> res
    -+{second='smith',first='john'}
    -+> res = {}
    -+> sip.match('($q,$q)','("jan","smit")',res)  -- unnamed captures
    -+true
    -+> res
    -+{'jan','smit'}
    -+> sip.match('($q,$q)','("jan", "smit")',res)
    -+false   ---> oops! Can't handle extra space!
    -+> sip.match('( $q , $q )','("jan", "smit")',res)
    -+true
    -+
    -+ -+

    As a general rule, allow for whitespace in your patterns.

    -+ -+

    Finally, putting a '$' at the end of a pattern means 'capture the rest of the -+line, starting at the first non-space'. It is a shortcut for '$r{rest}', -+or just '$r' if no named captures are used.

    -+ -+ -+
    -+> sip.match('( $q , $q ) $','("jan", "smit") and a string',res)
    -+true
    -+> res
    -+{'jan','smit','and a string'}
    -+> res = {}
    -+> sip.match('( $q{first} , $q{last} ) $','("jan", "smit") and a string',res)
    -+true
    -+> res
    -+{first='jan',rest='and a string',last='smit'}
    -+
    -+ -+

    -+ -+

    -+

    Command-line Programs with Lapp

    -+ -+

    pl.lapp is a small and focused Lua module which aims to make standard -+command-line parsing easier and intuitive. It implements the standard GNU style, -+i.e. short flags with one letter start with '-', and there may be an additional -+long flag which starts with '--'. Generally options which take an argument expect -+to find it as the next parameter (e.g. 'gcc test.c -o test') but single short -+options taking a value can dispense with the space (e.g. 'head -n4 -+test.c' or gcc -I/usr/include/lua/5.1 ...)

    -+ -+

    As far as possible, Lapp will convert parameters into their equivalent Lua types, -+i.e. convert numbers and convert filenames into file objects. If any conversion -+fails, or a required parameter is missing, an error will be issued and the usage -+text will be written out. So there are two necessary tasks, supplying the flag -+and option names and associating them with a type.

    -+ -+

    For any non-trivial script, even for personal consumption, it's necessary to -+supply usage text. The novelty of Lapp is that it starts from that point and -+defines a loose format for usage strings which can specify the names and types of -+the parameters.

    -+ -+

    An example will make this clearer:

    -+ -+ -+
    -+-- scale.lua
    -+  lapp = require 'pl.lapp'
    -+  local args = lapp [[
    -+  Does some calculations
    -+    -o,--offset (default 0.0)  Offset to add to scaled number
    -+    -s,--scale  (number)  Scaling factor
    -+    <number> (number)  Number to be scaled
    -+  ]]
    -+
    -+  print(args.offset + args.scale * args.number)
    -+
    -+ -+

    Here is a command-line session using this script:

    -+ -+ -+
    -+$ lua scale.lua
    -+scale.lua:missing required parameter: scale
    -+
    -+Does some calculations
    -+ -o,--offset (default 0.0)  Offset to add to scaled number
    -+ -s,--scale  (number)  Scaling factor
    -+  <number> (number )  Number to be scaled
    -+
    -+$ lua scale.lua -s 2.2 10
    -+22
    -+
    -+$ lua scale.lua -s 2.2 x10
    -+scale.lua:unable to convert to number: x10
    -+
    -+....(usage as before)
    -+
    -+ -+

    There are two kinds of lines in Lapp usage strings which are meaningful; option -+and parameter lines. An option line gives the short option, optionally followed -+by the corresponding long option. A type specifier in parentheses may follow. -+Similarly, a parameter line starts with '', followed by a type -+specifier.

    -+ -+

    Type specifiers usually start with a type name: one of 'boolean', 'string','number','file-in' or -+'file-out'. You may leave this out, but then must say 'default' followed by a value. -+If a flag or parameter has a default, it is not required and is set to the default. The actual -+type is deduced from this value (number, string, file or boolean) if not provided directly. -+'Deduce' is a fancy word for 'guess' and it can be wrong, e.g '(default 1)' -+will always be a number. You can say '(string default 1)' to override the guess. -+There are file values for the predefined console streams: stdin, stdout, stderr.

    -+ -+

    The boolean type is the default for flags. Not providing the type specifier is equivalent to -+'(boolean default false)`. If the flag is meant to be 'turned off' then either the full -+'(boolean default true)` or the shortcut '(default true)' will work.

    -+ -+

    An alternative to default is optional:

    -+ -+ -+
    -+local lapp = require 'pl.lapp'
    -+local args = lapp [[
    -+   --cmd (optional string) Command to run.
    -+]]
    -+
    -+if args.cmd then
    -+  os.execute(args.cmd)
    -+end
    -+
    -+ -+

    Here we're implying that cmd need not be specified (just as with default) but if not -+present, then args.cmd is nil, which will always test false.

    -+ -+

    The rest of the line is ignored and can be used for explanatory text.

    -+ -+

    This script shows the relation between the specified parameter names and the -+fields in the output table.

    -+ -+ -+
    -+-- simple.lua
    -+local args = require ('pl.lapp') [[
    -+Various flags and option types
    -+  -p          A simple optional flag, defaults to false
    -+  -q,--quiet  A simple flag with long name
    -+  -o  (string)  A required option with argument
    -+  -s  (default 'save') Optional string with default 'save' (single quotes ignored)
    -+  -n  (default 1) Optional numerical flag with default 1
    -+  -b  (string default 1)  Optional string flag with default '1' (type explicit)
    -+  <input> (default stdin)  Optional input file parameter, reads from stdin
    -+]]
    -+
    -+for k,v in pairs(args) do
    -+    print(k,v)
    -+end
    -+
    -+ -+

    I've just dumped out all values of the args table; note that args.quiet has -+become true, because it's specified; args.p defaults to false. If there is a long -+name for an option, that will be used in preference as a field name. A type or -+default specifier is not necessary for simple flags, since the default type is -+boolean.

    -+ -+ -+
    -+$ simple -o test -q simple.lua
    -+p       false
    -+input   file (781C1BD8)
    -+quiet   true
    -+o       test
    -+input_name      simple.lua
    -+D:\dev\lua\lapp>simple -o test simple.lua one two three
    -+1       one
    -+2       two
    -+3       three
    -+p       false
    -+quiet   false
    -+input   file (781C1BD8)
    -+o       test
    -+input_name      simple.lua
    -+
    -+ -+

    The parameter input has been set to an open read-only file object - we know it -+must be a read-only file since that is the type of the default value. The field -+input_name is automatically generated, since it's often useful to have access to -+the original filename.

    -+ -+

    Notice that any extra parameters supplied will be put in the result table with -+integer indices, i.e. args[i] where i goes from 1 to #args.

    -+ -+

    Files don't really have to be closed explicitly for short scripts with a quick -+well-defined mission, since the result of garbage-collecting file objects is to -+close them.

    -+ -+

    Enforcing a Range and Enumerations

    -+ -+

    The type specifier can also be of the form '(' MIN '..' MAX ')' or a set of strings -+separated by '|'.

    -+ -+ -+
    -+local lapp = require 'pl.lapp'
    -+local args = lapp [[
    -+    Setting ranges
    -+    <x> (1..10)  A number from 1 to 10
    -+    <y> (-5..1e6) Bigger range
    -+    <z> (slow|medium|fast)
    -+]]
    -+
    -+print(args.x,args.y)
    -+
    -+ -+

    Here the meaning of ranges is that the value is greater or equal to MIN and less or equal -+to MAX. -+An 'enum' is a string that can only have values from a specified set.

    -+ -+

    Custom Types

    -+ -+

    There is no builti-in way to force a parameter to be a whole number, but -+you may define a custom type that does this:

    -+ -+ -+
    -+lapp = require ('pl.lapp')
    -+
    -+lapp.add_type('integer','number',
    -+    function(x)
    -+        lapp.assert(math.ceil(x) == x, 'not an integer!')
    -+    end
    -+)
    -+
    -+local args =  lapp [[
    -+    <ival> (integer) Process PID
    -+]]
    -+
    -+print(args.ival)
    -+
    -+ -+

    lapp.add_type takes three parameters, a type name, a converter and a constraint -+function. The constraint function is expected to throw an assertion if some -+condition is not true; we use lapp.assert because it fails in the standard way -+for a command-line script. The converter argument can either be a type name known -+to Lapp, or a function which takes a string and generates a value.

    -+ -+

    Here's a useful custom type that allows dates to be input as pl.Date values:

    -+ -+ -+
    -+local df = Date.Format()
    -+
    -+lapp.add_type('date',
    -+    function(s)
    -+        local d,e = df:parse(s)
    -+        lapp.assert(d,e)
    -+        return d
    -+    end
    -+)
    -+
    -+ -+

    'varargs' Parameter Arrays

    -+ -+ -+
    -+lapp = require 'pl.lapp'
    -+local args = lapp [[
    -+Summing numbers
    -+    <numbers...> (number) A list of numbers to be summed
    -+]]
    -+
    -+local sum = 0
    -+for i,x in ipairs(args.numbers) do
    -+    sum = sum + x
    -+end
    -+print ('sum is '..sum)
    -+
    -+ -+

    The parameter number has a trailing '...', which indicates that this parameter is -+a 'varargs' parameter. It must be the last parameter, and args.number will be an -+array.

    -+ -+

    Consider this implementation of the head utility from Mac OS X:

    -+ -+ -+
    -+-- implements a BSD-style head
    -+-- (see http://www.manpagez.com/man/1/head/osx-10.3.php)
    -+
    -+lapp = require ('pl.lapp')
    -+
    -+local args = lapp [[
    -+Print the first few lines of specified files
    -+   -n         (default 10)    Number of lines to print
    -+   <files...> (default stdin) Files to print
    -+]]
    -+
    -+-- by default, lapp converts file arguments to an actual Lua file object.
    -+-- But the actual filename is always available as <file>_name.
    -+-- In this case, 'files' is a varargs array, so that 'files_name' is
    -+-- also an array.
    -+local nline = args.n
    -+local nfile = #args.files
    -+for i = 1,nfile do
    -+    local file = args.files[i]
    -+    if nfile > 1 then
    -+        print('==> '..args.files_name[i]..' <==')
    -+    end
    -+    local n = 0
    -+    for line in file:lines() do
    -+        print(line)
    -+        n = n + 1
    -+        if n == nline then break end
    -+    end
    -+end
    -+
    -+ -+

    Note how we have access to all the filenames, because the auto-generated field -+files_name is also an array!

    -+ -+

    (This is probably not a very considerate script, since Lapp will open all the -+files provided, and only close them at the end of the script. See the xhead.lua -+example for another implementation.)

    -+ -+

    Flags and options may also be declared as vararg arrays, and can occur anywhere. -+If there is both a short and long form, then the trailing "..." must happen after the long form, -+for example "-x,--network... (string)...",

    -+ -+

    Bear in mind that short options can be combined (like 'tar -xzf'), so it's -+perfectly legal to have '-vvv'. But normally the value of args.v is just a simple -+true value.

    -+ -+ -+
    -+local args = require ('pl.lapp') [[
    -+   -v...  Verbosity level; can be -v, -vv or -vvv
    -+]]
    -+vlevel = not args.v[1] and 0 or #args.v
    -+print(vlevel)
    -+
    -+ -+

    The vlevel assignment is a bit of Lua voodoo, so consider the cases:

    -+ -+ -+
    -+* No -v flag, v is just { false }
    -+* One -v flags, v is { true }
    -+* Two -v flags, v is { true, true }
    -+* Three -v flags, v is { true, true, true }
    -+
    -+ -+

    Defining a Parameter Callback

    -+ -+

    If a script implements lapp.callback, then Lapp will call it after each -+argument is parsed. The callback is passed the parameter name, the raw unparsed -+value, and the result table. It is called immediately after assignment of the -+value, so the corresponding field is available.

    -+ -+ -+
    -+lapp = require ('pl.lapp')
    -+
    -+function lapp.callback(parm,arg,args)
    -+    print('+',parm,arg)
    -+end
    -+
    -+local args = lapp [[
    -+Testing parameter handling
    -+    -p               Plain flag (defaults to false)
    -+    -q,--quiet       Plain flag with GNU-style optional long name
    -+    -o  (string)     Required string option
    -+    -n  (number)     Required number option
    -+    -s (default 1.0) Option that takes a number, but will default
    -+    <start> (number) Required number argument
    -+    <input> (default stdin)  A parameter which is an input file
    -+    <output> (default stdout) One that is an output file
    -+]]
    -+print 'args'
    -+for k,v in pairs(args) do
    -+    print(k,v)
    -+end
    -+
    -+ -+

    This produces the following output:

    -+ -+ -+
    -+$ args -o name -n 2 10 args.lua
    -++       o       name
    -++       n       2
    -++       start   10
    -++       input   args.lua
    -+args
    -+p       false
    -+s       1
    -+input_name      args.lua
    -+quiet   false
    -+output  file (781C1B98)
    -+start   10
    -+input   file (781C1BD8)
    -+o       name
    -+n       2
    -+
    -+ -+

    Callbacks are needed when you want to take action immediately on parsing an -+argument.

    -+ -+

    Slack Mode

    -+ -+

    If you'd like to use a multi-letter 'short' parameter you need to set -+the lapp.slack variable to true.

    -+ -+

    In the following example we also see how default false and default true flags can be used -+and how to overwrite the default -h help flag (--help still works fine) - this applies -+to non-slack mode as well.

    -+ -+ -+
    -+-- Parsing the command line ----------------------------------------------------
    -+-- test.lua
    -+local lapp = require 'pl.lapp'
    -+local pretty = require 'pl.pretty'
    -+lapp.slack = true
    -+local args = lapp [[
    -+Does some calculations
    -+   -v, --video              (string)             Specify input video
    -+   -w, --width              (default 256)        Width of the video
    -+   -h, --height             (default 144)        Height of the video
    -+   -t, --time               (default 10)         Seconds of video to process
    -+   -sk,--seek               (default 0)          Seek number of seconds
    -+   -f1,--flag1                                   A false flag
    -+   -f2,--flag2                                   A false flag
    -+   -f3,--flag3              (default true)       A true flag
    -+   -f4,--flag4              (default true)       A true flag
    -+]]
    -+
    -+pretty.dump(args)
    -+
    -+ -+

    And here we can see the output of test.lua:

    -+ -+ -+
    -+$> lua test.lua -v abc --time 40 -h 20 -sk 15 --flag1 -f3
    -+---->
    -+{
    -+  width = 256,
    -+  flag1 = true,
    -+  flag3 = false,
    -+  seek = 15,
    -+  flag2 = false,
    -+  video = abc,
    -+  time = 40,
    -+  height = 20,
    -+  flag4 = true
    -+}
    -+
    -+ -+

    -+

    Simple Test Framework

    -+ -+

    pl.test was originally developed for the sole purpose of testing Penlight itself, -+but you may find it useful for your own applications. (There are many other options.)

    -+ -+

    Most of the goodness is in test.asserteq. It uses tablex.deepcompare on its two arguments, -+and by default quits the test application with a non-zero exit code, and an informative -+message printed to stderr:

    -+ -+ -+
    -+local test = require 'pl.test'
    -+
    -+test.asserteq({10,20,30},{10,20,30.1})
    -+
    -+--~ test-test.lua:3: assertion failed
    -+--~ got:    {
    -+--~  [1] = 10,
    -+--~  [2] = 20,
    -+--~  [3] = 30
    -+--~ }
    -+--~ needed:    {
    -+--~  [1] = 10,
    -+--~  [2] = 20,
    -+--~  [3] = 30.1
    -+--~ }
    -+--~ these values were not equal
    -+
    -+ -+

    This covers most cases but it's also useful to compare strings using string.match

    -+ -+ -+
    -+-- must start with bonzo the dog
    -+test.assertmatch ('bonzo the dog is here','^bonzo the dog')
    -+-- must end with an integer
    -+test.assertmatch ('hello 42','%d+$')
    -+
    -+ -+

    Since Lua errors are usually strings, this matching strategy is used to test 'exceptions':

    -+ -+ -+
    -+test.assertraise(function()
    -+    local t = nil
    -+    print(t.bonzo)
    -+end,'nil value')
    -+
    -+ -+

    (Some care is needed to match the essential part of the thrown error if you care -+for portability, since in Lua 5.2 -+the exact error is "attempt to index local 't' (a nil value)" and in Lua 5.3 the error -+is "attempt to index a nil value (local 't')")

    -+ -+

    There is an extra optional argument to these test functions, which is helpful when writing -+test helper functions. There you want to highlight the failed line, not the actual call -+to asserteq or assertmatch - line 33 here is the call to is_iden

    -+ -+ -+
    -+function is_iden(str)
    -+    test.assertmatch(str,'^[%a_][%w_]*$',1)
    -+end
    -+
    -+is_iden 'alpha_dog'
    -+is_iden '$dollars'
    -+
    -+--~ test-test.lua:33: assertion failed
    -+--~ got:    "$dollars"
    -+--~ needed:    "^[%a_][%w_]*$"
    -+--~ these strings did not match
    -+
    -+ -+

    Useful Lua functions often return multiple values, and test.tuple is a convenient way to -+capture these values, whether they contain nils or not.

    -+ -+ -+
    -+T = test.tuple
    -+
    -+--- common error pattern
    -+function failing()
    -+    return nil,'failed'
    -+end
    -+
    -+test.asserteq(T(failing()),T(nil,'failed'))
    -+
    -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/docs/manual/09-discussion.md.html b/extra/penlight/docs/manual/09-discussion.md.html -new file mode 100644 -index 0000000..ad19e96 ---- /dev/null -+++ b/extra/penlight/docs/manual/09-discussion.md.html -@@ -0,0 +1,233 @@ -+ -+ -+ -+ -+ Penlight Documentation -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+

    Technical Choices

    -+ -+

    -+

    Modularity and Granularity

    -+ -+

    In an ideal world, a program should only load the libraries it needs. Penlight is -+intended to work in situations where an extra 100Kb of bytecode could be a -+problem. It is straightforward but tedious to load exactly what you need:

    -+ -+ -+
    -+local data = require 'pl.data'
    -+local List = require 'pl.List'
    -+local array2d = require 'pl.array2d'
    -+local seq = require 'pl.seq'
    -+local utils = require 'pl.utils'
    -+
    -+ -+

    This is the style that I follow in Penlight itself, so that modules don't mess -+with the global environment; also, stringx.import() is not used because it will -+update the global string table.

    -+ -+

    But require 'pl' is more convenient in scripts; the question is how to ensure -+that one doesn't load the whole kitchen sink as the price of convenience. The -+strategy is to only load modules when they are referenced. In 'init.lua' (which -+is loaded by require 'pl') a metatable is attached to the global table with an -+__index metamethod. Any unknown name is looked up in the list of modules, and -+if found, we require it and make that module globally available. So when -+tablex.deepcompare is encountered, looking up tablex causes 'pl.tablex' to be -+required. .

    -+ -+

    Modifying the behaviour of the global table has consequences. For instance, there -+is the famous module strict which comes with Lua itself (perhaps the only -+standard Lua module written in Lua itself) which also does this modification so -+that global variiables must be defined before use. So the implementation in -+'init.lua' allows for a 'not found' hook, which 'pl.strict.lua' uses. Other -+libraries may install their own metatables for _G, but Penlight will now -+forward any unknown name to the __index defined by the original metatable.

    -+ -+

    But the strategy is worth the effort: the old 'kitchen sink' 'init.lua' would -+pull in about 260K of bytecode, whereas now typical programs use about 100K less, -+and short scripts even better - for instance, if they were only needing -+functionality in utils.

    -+ -+

    There are some functions which mark their output table with a special metatable, -+when it seems particularly appropriate. For instance, tablex.makeset creates a -+Set, and seq.copy creates a List. But this does not automatically result in -+the loading of pl.Set and pl.List; only if you try to access any of these -+methods. In 'utils.lua', there is an exported table called stdmt:

    -+ -+ -+
    -+stdmt = { List = {}, Map = {}, Set = {}, MultiMap = {} }
    -+
    -+ -+

    If you go through 'init.lua', then these plain little 'identity' tables get an -+__index metamethod which forces the loading of the full functionality. Here is -+the code from 'list.lua' which starts the ball rolling for lists:

    -+ -+ -+
    -+List = utils.stdmt.List
    -+List.__index = List
    -+List._name = "List"
    -+List._class = List
    -+
    -+ -+

    The 'load-on-demand' strategy helps to modularize the library. Especially for -+more casual use, require 'pl' is a good compromise between convenience and -+modularity.

    -+ -+

    In this current version, I have generally reduced the amount of trickery -+involved. Previously, Map was defined in pl.class; now it is sensibly defined -+in pl.Map; pl.class only contains the basic class mechanism (and returns that -+function.) For consistency, List is returned directly by require 'pl.List' -+(note the uppercase 'L'), Also, the amount of module dependencies in the -+non-core libraries like pl.config have been reduced.

    -+ -+

    -+

    Defining what is Callable

    -+ -+

    'utils.lua' exports function_arg which is used extensively throughout Penlight. -+It defines what is meant by 'callable'. Obviously true functions are immediately -+passed back. But what about strings? The first option is that it represents an -+operator in 'operator.lua', so that '<' is just an alias for operator.lt.

    -+ -+

    We then check whether there is a function factory defined for the metatable of -+the value.

    -+ -+

    (It is true that strings can be made callable, but in practice this turns out to -+be a cute but dubious idea, since all strings share the same metatable. A -+common programming error is to pass the wrong kind of object to a function, and -+it's better to get a nice clean 'attempting to call a string' message rather than -+some obscure trace from the bowels of your library.)

    -+ -+

    The other module that registers a function factory is pl.func. Placeholder -+expressions cannot be directly calleable, and so need to be instantiated and -+cached in as efficient way as possible.

    -+ -+

    (An inconsistency is that utils.is_callable does not do this thorough check.)

    -+ -+ -+ -+ -+
    -+
    -+
    -+generated by LDoc 1.5.0 -+
    -+
    -+ -+ -diff --git a/extra/penlight/doc/manual/01-introduction.md b/extra/penlight/docs_topics/01-introduction.md -similarity index 97% -rename from doc/manual/01-introduction.md -rename to docs_topics/01-introduction.md -index a8bf26a..bbc643d 100644 ---- a/extra/penlight/doc/manual/01-introduction.md -+++ b/extra/penlight/docs_topics/01-introduction.md -@@ -41,10 +41,10 @@ the order, so that the function is passed the value and then the key. Although - perverse, this matches the intended use better. - - The only important external dependence of Penlight is --[LuaFileSystem](http://keplerproject.github.com/luafilesystem/manual.html) -+[LuaFileSystem](https://lunarmodules.github.io/luafilesystem/manual.html) - (`lfs`), and if you want `dir.copyfile` to work cleanly on Windows, you will need --either [alien](http://alien.luaforge.net/) or be using --[LuaJIT](http://luajit.org) as well. (The fallback is to call the equivalent -+either [alien](https://github.com/mascarenhas/alien) or be using -+[LuaJIT](https://luajit.org) as well. (The fallback is to call the equivalent - shell commands.) - - ### To Inject or not to Inject? -@@ -175,7 +175,7 @@ For example, - - return M - --If you were to accidently type `mymod.Answer()`, then you would get a runtime -+If you were to accidentally type `mymod.Answer()`, then you would get a runtime - error: "variable 'Answer' is not declared in 'mymod'". - - This can be applied to existing modules. You may desire to have the same level -@@ -231,7 +231,7 @@ a function to all elements of a list is a common operation: - res[i] = fun(ls[i]) - end - --This can be efficiently and succintly expressed as `ls:map(fun)`. Not only is -+This can be efficiently and succinctly expressed as `ls:map(fun)`. Not only is - there less typing but the intention of the code is clearer. If readers of your - code spend too much time trying to guess your intention by analyzing your loops, - then you have failed to express yourself clearly. Similarly, `ls:filter('>',0)` -@@ -311,7 +311,7 @@ upfront, since in general you won't know what values are needed. - - Penlight is fully compatible with Lua 5.1, 5.2 and LuaJIT 2. To ensure this, - `utils` also defines the global Lua 5.2 --[load](http://www.lua.org/work/doc/manual.html#pdf-load) function as `utils.load` -+[load](https://www.lua.org/work/doc/manual.html#pdf-load) function as `utils.load` - - * the input (either a string or a function) - * the source name used in debug information -@@ -330,7 +330,7 @@ for functions which don't access any globals. - - `app.parse_args` is a simple command-line argument parser. If called without any - arguments, it tries to use the global `arg` array. It returns the _flags_ --(options begining with '-') as a table of name/value pairs, and the _arguments_ -+(options beginning with '-') as a table of name/value pairs, and the _arguments_ - as an array. It knows about long GNU-style flag names, e.g. `--value`, and - groups of short flags are understood, so that `-ab` is short for `-a -b`. The - flags result would then look like `{value=true,a=true,b=true}`. -@@ -483,7 +483,7 @@ if no `__tostring` method is explicitly defined. - So `Alice = class(); Alice._name = 'Alice'` is exactly the same as `class.Alice()`. - - This useful notation is borrowed from Hugo Etchegoyen's --[classlib](http://lua-users.org/wiki/MultipleInheritanceClasses) which further -+[classlib](https://lua-users.org/wiki/MultipleInheritanceClasses) which further - extends this concept to allow for multiple inheritance. Notice that the - more convenient form puts the class name in the _current environment_! That is, - you may use it safely within modules using the old-fashioned `module()` -diff --git a/extra/penlight/doc/manual/02-arrays.md b/extra/penlight/docs_topics/02-arrays.md -similarity index 99% -rename from doc/manual/02-arrays.md -rename to docs_topics/02-arrays.md -index 9ee292f..cb9bc71 100644 ---- a/extra/penlight/doc/manual/02-arrays.md -+++ b/extra/penlight/docs_topics/02-arrays.md -@@ -129,7 +129,7 @@ there is already `pop` (remove and return last value) and `append` acts like - `push` (add a value to the end). `push` is provided as an alias for `append`, and - the other stack operation (size) is simply the size operator `#`. Queues can - also be implemented; you use `pop` to take values out of the queue, and `put` to --insert a value at the begining. -+insert a value at the beginning. - - You may derive classes from `List`, and since the list-returning methods - are covariant, the result of `slice` etc will return lists of the derived type, -@@ -520,7 +520,7 @@ compulsory way to use Penlight table operations. - Two-dimensional tables are of course easy to represent in Lua, for instance - `{{1,2},{3,4}}` where we store rows as subtables and index like so `A[col][row]`. - This is the common representation used by matrix libraries like --[LuaMatrix](http://lua-users.org/wiki/LuaMatrix). `pl.array2d` does not provide -+[LuaMatrix](https://lua-users.org/wiki/LuaMatrix). `pl.array2d` does not provide - matrix operations, since that is the job for a specialized library, but rather - provides generalizations of the higher-level operations provided by `pl.tablex` - for one-dimensional arrays. -diff --git a/extra/penlight/doc/manual/03-strings.md b/extra/penlight/docs_topics/03-strings.md -similarity index 95% -rename from doc/manual/03-strings.md -rename to docs_topics/03-strings.md -index 3808175..7aa00cf 100644 ---- a/extra/penlight/doc/manual/03-strings.md -+++ b/extra/penlight/docs_topics/03-strings.md -@@ -37,7 +37,7 @@ Most of these can be fairly easily implemented using the Lua string library, - which is more general and powerful. But they are convenient operations to have - easily at hand. Note that can be injected into the `string` table if you use - `stringx.import`, but a simple alias like `local stringx = require 'pl.stringx'` --is preferrable. This is the recommended practice when writing modules for -+is preferable. This is the recommended practice when writing modules for - consumption by other people, since it is bad manners to change the global state - of the rest of the system. Magic may be used for convenience, but there is always - a price. -@@ -104,7 +104,7 @@ lines that fit into a desired line width. As an extension, there is also `indent - for indenting multiline strings. - - New in Penlight with the 0.9 series is `text.format_operator`. Calling this --enables Python-style string formating using the modulo operator `%`: -+enables Python-style string formatting using the modulo operator `%`: - - > text.format_operator() - > = '%s[%d]' % {'dog',1} -@@ -122,11 +122,11 @@ metatable. But in your own scripts you can feel free to do this. - ### Another Style of Template - - A new module is `template`, which is a version of Rici Lake's [Lua --Preprocessor](http://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor). This -+Preprocessor](https://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor). This - allows you to mix Lua code with your templates in a straightforward way. There - are only two rules: - -- - Lines begining with `#` are Lua -+ - Lines beginning with `#` are Lua - - Otherwise, anything inside `$()` is a Lua expression. - - So a template generating an HTML list would look like this: -@@ -223,6 +223,6 @@ takes the same arguments as standard file objects: - string. - - `stringio.create` creates a writeable file-like object. You then use `write` to --this stream, and finally extract the builded string using `value`. This 'string -+this stream, and finally extract the built string using `value`. This 'string - builder' pattern is useful for efficiently creating large strings. - -diff --git a/extra/penlight/doc/manual/04-paths.md b/extra/penlight/docs_topics/04-paths.md -similarity index 97% -rename from doc/manual/04-paths.md -rename to docs_topics/04-paths.md -index 4367fe6..9717713 100644 ---- a/extra/penlight/doc/manual/04-paths.md -+++ b/extra/penlight/docs_topics/04-paths.md -@@ -90,7 +90,7 @@ For example, this little script converts a file into upper case: - text = assert(file.read(arg[1])) - assert(file.write(arg[2],text:upper())) - --Copying files is suprisingly tricky. `file.copy` and `file.move` attempt to use -+Copying files is surprisingly tricky. `file.copy` and `file.move` attempt to use - the best implementation possible. On Windows, they link to the API functions - `CopyFile` and `MoveFile`, but only if the `alien` package is installed (this is - true for Lua for Windows.) Otherwise, the system copy command is used. This can -@@ -109,7 +109,7 @@ table, unlike `lfs.dir` which returns an iterator.) - - `dir.makepath` can create a full path, creating subdirectories as necessary; - `rmtree` is the Nuclear Option of file deleting functions, since it will --recursively clear out and delete all directories found begining at a path (there -+recursively clear out and delete all directories found beginning at a path (there - is a similar function with this name in the Python `shutils` module.) - - > = dir.makepath 't\\temp\\bonzo' -diff --git a/extra/penlight/doc/manual/05-dates.md b/extra/penlight/docs_topics/05-dates.md -similarity index 93% -rename from doc/manual/05-dates.md -rename to docs_topics/05-dates.md -index c2431cf..0df29d8 100644 ---- a/extra/penlight/doc/manual/05-dates.md -+++ b/extra/penlight/docs_topics/05-dates.md -@@ -2,10 +2,12 @@ - - - -+NOTE: the Date module is deprecated -+ - ### Creating and Displaying Dates - - The `Date` class provides a simplified way to work with [date and --time](http://www.lua.org/pil/22.1.html) in Lua; it leans heavily on the functions -+time](https://www.lua.org/pil/22.1.html) in Lua; it leans heavily on the functions - `os.date` and `os.time`. - - A `Date` object can be constructed from a table, just like with `os.time`. -@@ -41,7 +43,7 @@ you full control of the format for both parsing and displaying dates: - > = amer:tostring(d) - 04/10/2010 - --With the 0.9.7 relase, the `Date` constructor has become more flexible. You may -+With the 0.9.7 release, the `Date` constructor has become more flexible. You may - omit any of the 'year', 'month' or 'day' fields: - - > = Date { year = 2008 } -diff --git a/extra/penlight/doc/manual/06-data.md b/extra/penlight/docs_topics/06-data.md -similarity index 98% -rename from doc/manual/06-data.md -rename to docs_topics/06-data.md -index 8c759d7..038e297 100644 ---- a/extra/penlight/doc/manual/06-data.md -+++ b/extra/penlight/docs_topics/06-data.md -@@ -353,7 +353,7 @@ this data. In fact, these functions are available as methods; e.g. - v = data.read('dat.txt'):flatten() - - The data is also in exactly the right shape to be treated as matrices by --[LuaMatrix](http://lua-users.org/wiki/LuaMatrix): -+[LuaMatrix](https://lua-users.org/wiki/LuaMatrix): - - > matrix = require 'matrix' - > m = matrix(data.read 'mat.txt') -@@ -665,7 +665,7 @@ nicely delimited by newlines. For example, here is a snippet of a in-house file - format which it was my task to maintain: - - points --(818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1) -+ (818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1) - ,(818327.4,-20388,-0.1),(818322,-20387.7,-0.1),(818316.3,-20388.6,-0.1) - ,(818309.7,-20389.4,-0.1),(818303.5,-20390.6,-0.1),(818295.8,-20388.3,-0.1) - ,(818290.5,-20386.9,-0.1),(818285.2,-20386.1,-0.1),(818279.3,-20383.6,-0.1) -@@ -704,7 +704,7 @@ gracefully.) - The scanners all have a second optional argument, which is a table which controls - whether you want to exclude spaces and/or comments. The default for `lexer.lua` - is `{space=true,comments=true}`. There is a third optional argument which --determines how string and number tokens are to be processsed. -+determines how string and number tokens are to be processed. - - The ultimate highly-structured data is of course, program source. Here is a - snippet from 'text-lexer.lua': -@@ -775,7 +775,7 @@ specialized library. - - #### Parsing and Pretty-Printing - --The semi-standard XML parser in the Lua universe is [lua-expat](http://matthewwild.co.uk/projects/luaexpat/). -+The semi-standard XML parser in the Lua universe is [lua-expat](https://lunarmodules.github.io/luaexpat/). - In particular, - it has a function called `lxp.lom.parse` which will parse XML into the Lua Object - Model (LOM) format. However, it does not provide a way to convert this data back -@@ -846,7 +846,7 @@ There is a fourth argument which is the _attribute indent_: - #### Parsing and Working with Configuration Files - - It's common to find configurations expressed with XML these days. It's --straightforward to 'walk' the [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) -+straightforward to 'walk' the [LOM](https://lunarmodules.github.io/luaexpat/lom.html) - data and extract the data in the form you want: - - require 'pl' -@@ -960,7 +960,7 @@ Getting the names of the providers per-country is straightforward: - #### Generating XML with 'xmlification' - - This feature is inspired by the `htmlify` function used by --[Orbit](http://keplerproject.github.com/orbit/) to simplify HTML generation, -+[Orbit](https://keplerproject.github.io/orbit/) to simplify HTML generation, - except that no function environment magic is used; the `tags` function returns a - set of _constructors_ for elements of the given tag names. - -@@ -1047,9 +1047,9 @@ extract values from it using a pattern. - - A common use of this is parsing the XML result of API queries. The - [(undocumented and subsequently discontinued) Google Weather --API](http://blog.programmableweb.com/2010/02/08/googles-secret-weather-api/) is a -+API](https://blog.programmableweb.com/2010/02/08/googles-secret-weather-api/) is a - good example. Grabbing the result of --`http://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like -+`https://www.google.com/ig/api?weather=Johannesburg,ZA" we get something like - this, after pretty-printing: - - -diff --git a/extra/penlight/doc/manual/07-functional.md b/extra/penlight/docs_topics/07-functional.md -similarity index 97% -rename from doc/manual/07-functional.md -rename to docs_topics/07-functional.md -index 5921a3d..30a447f 100644 ---- a/extra/penlight/doc/manual/07-functional.md -+++ b/extra/penlight/docs_topics/07-functional.md -@@ -54,7 +54,7 @@ Sequences can be _combined_, either by 'zipping' them or by concatenating them. - 3 - - `seq.printall` is useful for printing out single-valued sequences, and provides --some finer control over formating, such as a delimiter, the number of fields per -+some finer control over formatting, such as a delimiter, the number of fields per - line, and a format string to use (@see string.format) - - > seq.printall(seq.random(10)) -@@ -82,7 +82,7 @@ original table (equivalent to `tablex.filter(ls, '>', 0)`) - - ls = seq.copy(seq.filter(ls, '>', 0)) - --We're already encounted `seq.sum` when discussing `input.numbers`. This can also -+We're already encountered `seq.sum` when discussing `input.numbers`. This can also - be expressed with `seq.reduce`: - - > seq.reduce(function(x,y) return x + y end, seq.list{1,2,3,4}) -@@ -289,7 +289,7 @@ I'm emphasizing that a comprehension is a function which can take a list argumen - {20,40,60} - - Here is a somewhat more explicit way of saying the same thing; `_1` is a --_placeholder_ refering to the _first_ argument passed to the comprehension. -+_placeholder_ referring to the _first_ argument passed to the comprehension. - - > = C '2*x for _,x in pairs(_1)' {10,20,30} - {20,40,60} -@@ -366,7 +366,7 @@ arguments where the first argument is bound to some value: - > tablex.filter({1,-2,10,-1,2},bind1(ops.le,0)) - {1,10,2} - --The last example unfortunately reads backwards, because `bind1` alway binds the -+The last example unfortunately reads backwards, because `bind1` always binds the - first argument! Also unfortunately, in my youth I confused 'currying' with - 'partial application', so the old name for `bind1` is `curry` - this alias still exists. - -@@ -441,7 +441,7 @@ Functions of up to 5 arguments can be generated. - > = tablex.map2(_1+_2,{1,2,3}, {10,20,30}) - {11,22,33} - --These expressions can use arbitrary functions, altho they must first be -+These expressions can use arbitrary functions, although they must first be - registered with the functional library. `func.register` brings in a single - function, and `func.import` brings in a whole table of functions, such as `math`. - -@@ -458,7 +458,7 @@ A common operation is calling a method of a set of objects: - {'o','f','x'} - - There are some restrictions on what operators can be used in PEs. For instance, --because the `__len` metamethod cannot be overriden by plain Lua tables, we need -+because the `__len` metamethod cannot be overridden by plain Lua tables, we need - to define a special function to express `#_1': - - > = tablex.map(Len(_1), {'one','four','x'}) -diff --git a/extra/penlight/doc/manual/08-additional.md b/extra/penlight/docs_topics/08-additional.md -similarity index 99% -rename from doc/manual/08-additional.md -rename to docs_topics/08-additional.md -index 2c99497..149056b 100644 ---- a/extra/penlight/doc/manual/08-additional.md -+++ b/extra/penlight/docs_topics/08-additional.md -@@ -373,7 +373,7 @@ array. - Consider this implementation of the head utility from Mac OS X: - - -- implements a BSD-style head -- -- (see http://www.manpagez.com/man/1/head/osx-10.3.php) -+ -- (see https://www.manpagez.com/man/1/head/osx-10.3.php) - - lapp = require ('pl.lapp') - -@@ -423,7 +423,7 @@ perfectly legal to have '-vvv'. But normally the value of args.v is just a simpl - vlevel = not args.v[1] and 0 or #args.v - print(vlevel) - --The vlevel assigment is a bit of Lua voodoo, so consider the cases: -+The vlevel assignment is a bit of Lua voodoo, so consider the cases: - - * No -v flag, v is just { false } - * One -v flags, v is { true } -@@ -528,7 +528,7 @@ And here we can see the output of `test.lua`: - ### Simple Test Framework - - `pl.test` was originally developed for the sole purpose of testing Penlight itself, --but you may find it useful for your own applications. ([There are many other options](http://lua-users.org/wiki/UnitTesting).) -+but you may find it useful for your own applications. ([There are many other options](https://lua-users.org/wiki/UnitTesting).) - - Most of the goodness is in `test.asserteq`. It uses `tablex.deepcompare` on its two arguments, - and by default quits the test application with a non-zero exit code, and an informative -diff --git a/extra/penlight/doc/manual/09-discussion.md b/extra/penlight/docs_topics/09-discussion.md -similarity index 100% -rename from doc/manual/09-discussion.md -rename to docs_topics/09-discussion.md -diff --git a/extra/penlight/examples/seesubst.lua b/extra/penlight/examples/seesubst.lua -index e13a5fb..a2d0f18 100644 ---- a/extra/penlight/examples/seesubst.lua -+++ b/extra/penlight/examples/seesubst.lua -@@ -4,10 +4,11 @@ - -- or 'pl.seq.map' (a function reference); these cases must be distinguished - -- and a Markdown link generated pointing to the LuaDoc file. - --require 'pl' -+local sip = require 'pl.sip' -+local stringx = require 'pl.stringx' - - local res = {} --s = [[ -+local s = [[ - (@see pl.bonzo.dog) - remember about @see pl.bonzo - -@@ -15,7 +16,7 @@ remember about @see pl.bonzo - - local _gsub_patterns = {} - --function gsub (s,pat,subst,start) -+local function gsub (s,pat,subst,start) - local fpat = _gsub_patterns[pat] - if not fpat then - -- use SIP to generate a proper string pattern. -diff --git a/extra/penlight/examples/sipscan.lua b/extra/penlight/examples/sipscan.lua -index 37f712e..78ac75b 100644 ---- a/extra/penlight/examples/sipscan.lua -+++ b/extra/penlight/examples/sipscan.lua -@@ -3,9 +3,10 @@ - -- pattern generated: - -- SYNC%s*%[([+%-%d]%d*)%]%s*([+%-%d]%d*)%s*([+%-%d]%d*) - --require 'pl' -+local sip = require 'pl.sip' -+local stringx = require 'pl.stringx' - --s = [[ -+local s = [[ - SYNC [1] 0 547 (14679 sec) - SYNC [2] 0 555 (14679 sec) - SYNC [3] 0 563 (14679 sec) -@@ -16,7 +17,7 @@ SYNC [6] 0 587 (14679 sec) - - - local first = true --local start -+local expected - local res = {} - local pat = 'SYNC [$i{seq}] $i{diff} $i{val}' - print(sip.create_pattern(pat)) -diff --git a/extra/penlight/examples/symbols.lua b/extra/penlight/examples/symbols.lua -index 1aed745..e73c4ba 100644 ---- a/extra/penlight/examples/symbols.lua -+++ b/extra/penlight/examples/symbols.lua -@@ -4,7 +4,7 @@ local ops = require 'pl.operator' - local List = require 'pl.List' - local append,concat = table.insert,table.concat - local compare,find_if,compare_no_order,imap,reduce,count_map = tablex.compare,tablex.find_if,tablex.compare_no_order,tablex.imap,tablex.reduce,tablex.count_map --local unpack = utils.unpack -+local unpack = table.unpack - - function bindval (self,val) - rawset(self,'value',val) -diff --git a/extra/penlight/examples/test-cmp.lua b/extra/penlight/examples/test-cmp.lua -index e3748a3..cbab394 100644 ---- a/extra/penlight/examples/test-cmp.lua -+++ b/extra/penlight/examples/test-cmp.lua -@@ -1,4 +1,3 @@ --A = require 'pl.tablex' --ops = require 'pl.operator' -+local A = require 'pl.tablex' - print(A.compare_no_order({1,2,3},{2,1,3})) - print(A.compare_no_order({1,2,3},{2,1,3},'==')) -diff --git a/extra/penlight/examples/test-listcallbacks.lua b/extra/penlight/examples/test-listcallbacks.lua -index 6bcdad3..a9a31c3 100644 ---- a/extra/penlight/examples/test-listcallbacks.lua -+++ b/extra/penlight/examples/test-listcallbacks.lua -@@ -1,7 +1,8 @@ - -- demonstrates how to use a list of callbacks --require 'pl' --actions = List() --L = utils.string_lambda -+local List = require 'pl.List' -+local utils = require 'pl.utils' -+local actions = List() -+local L = utils.string_lambda - - actions:append(function() print 'hello' end) - actions:append(L '|| print "yay"') -diff --git a/extra/penlight/examples/test-pretty.lua b/extra/penlight/examples/test-pretty.lua -index 1423c1c..7b2b553 100644 ---- a/extra/penlight/examples/test-pretty.lua -+++ b/extra/penlight/examples/test-pretty.lua -@@ -1,6 +1,6 @@ - local pretty = require 'pl.pretty' - --tb = { -+local tb = { - 'one','two','three',{1,2,3}, - alpha=1,beta=2,gamma=3,['&']=true,[0]=false, - _fred = {true,true}, -diff --git a/extra/penlight/examples/testapp.lua b/extra/penlight/examples/testapp.lua -deleted file mode 100644 -index 650ac4e..0000000 ---- a/extra/penlight/examples/testapp.lua -+++ /dev/null -@@ -1,5 +0,0 @@ ---- shows how a script can get a private file path ---- the output on my Windows machine is: ---- C:\Documents and Settings\steve\.testapp\test.txt --require 'pl' --print(app.appfile 'test.txt') -diff --git a/extra/penlight/examples/testclone.lua b/extra/penlight/examples/testclone.lua -index c211bc4..b0d948f 100644 ---- a/extra/penlight/examples/testclone.lua -+++ b/extra/penlight/examples/testclone.lua -@@ -1,8 +1,10 @@ - --cloning a directory tree. - local lfs = require 'lfs' --require 'pl' --p1 = [[examples]] --p2 = [[copy/of/examples]] -+local path = require 'pl.path' -+local dir = require 'pl.dir' -+ -+local p1 = [[examples]] -+local p2 = [[copy/of/examples]] - - if not path.isfile 'examples/testclone.lua' then - return print 'please run this in the penlight folder (below examples)' -@@ -14,7 +16,7 @@ dir.clonetree(p1,p2,dir.copyfile) - assert(path.isdir 'copy') - - print '---' --t = os.time() -+local t = os.time() - print(lfs.touch('examples/testclone.lua',t,t+10)) - - -- this should only update this file -diff --git a/extra/penlight/examples/testconfig.lua b/extra/penlight/examples/testconfig.lua -index 03349d6..4712747 100644 ---- a/extra/penlight/examples/testconfig.lua -+++ b/extra/penlight/examples/testconfig.lua -@@ -1,7 +1,7 @@ - local stringio = require 'pl.stringio' - local config = require 'pl.config' - --function dump(t,indent) -+local function dump(t,indent) - if type(t) == 'table' then - io.write(indent,'{\n') - local newindent = indent..' ' -@@ -17,7 +17,7 @@ function dump(t,indent) - end - - --function testconfig(test) -+local function testconfig(test) - local f = stringio.open(test) - local c = config.read(f) - f:close() -diff --git a/extra/penlight/examples/testglobal.lua b/extra/penlight/examples/testglobal.lua -index 053b6bf..0baaaef 100644 ---- a/extra/penlight/examples/testglobal.lua -+++ b/extra/penlight/examples/testglobal.lua -@@ -15,7 +15,7 @@ local path = require 'pl.path' - - utils.on_error 'quit' - --local txt,err = file.read(arg[1] or path.normpath('examples/testglobal.lua')) -+local txt = file.read(arg[1] or path.normpath('examples/testglobal.lua')) - local globals = List() - for t,v in lexer.lua(txt) do - if t == 'iden' and rawget(_G,v) then -diff --git a/extra/penlight/examples/testinputfields.lua b/extra/penlight/examples/testinputfields.lua -index 37b3a8c..9269488 100644 ---- a/extra/penlight/examples/testinputfields.lua -+++ b/extra/penlight/examples/testinputfields.lua -@@ -1,4 +1,4 @@ --require 'pl' -+local input = require 'pl.input' - local sum = 0.0 - local count = 0 - local text = [[ -diff --git a/extra/penlight/examples/testinputfields2.lua b/extra/penlight/examples/testinputfields2.lua -index fd7b0cd..fc649eb 100644 ---- a/extra/penlight/examples/testinputfields2.lua -+++ b/extra/penlight/examples/testinputfields2.lua -@@ -1,4 +1,5 @@ --require 'pl' -+local input = require 'pl.input' -+local seq = require 'pl.seq' - local text = [[ - 981124001 2.0 18988.4 10047.1 4149.7 - 981125001 0.8 19104.0 9970.4 5088.7 -diff --git a/extra/penlight/examples/testxml.lua b/extra/penlight/examples/testxml.lua -index e7c7476..2528020 100644 ---- a/extra/penlight/examples/testxml.lua -+++ b/extra/penlight/examples/testxml.lua -@@ -3,12 +3,13 @@ - -- This is (clearly) not a professional XML parser, so don't use it - -- on your homework! - --require 'pl' -+local lexer = require 'pl.lexer' -+local pretty = require 'pl.pretty' - - local append = table.insert - local skipws,expecting = lexer.skipws,lexer.expecting - --function parse_element (tok,tag) -+local function parse_element (tok,tag) - local tbl,t,v,attrib - tbl = {} - tbl.tag = tag -- LOM 'tag' is the element tag -@@ -51,21 +52,22 @@ function parse_element (tok,tag) - end - end - --function parse_xml (tok) -- local t,v = skipws(tok) -+local function parse_xml (tok) -+ local t = skipws(tok) -+ local v - while t == '<' do - t,v = tok() - if t == '?' or t == '!' then - -- skip meta stuff and commentary - repeat t = tok() until t == '>' -- t,v = expecting(tok,'<') -+ t = expecting(tok,'<') - else - return parse_element(tok,v) - end - end - end - --s = [[ -+local s = [[ - - - -+ -+ -+ -+ $(ldoc.title) -+ -+# if ldoc.custom_css then -- add custom CSS file if configured. -+ -+# end -+ -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+# local no_spaces = ldoc.no_spaces -+# local use_li = ldoc.use_li -+# local display_name = ldoc.display_name -+# local iter = ldoc.modules.iter -+# local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end -+# local nowrap = ldoc.wrap and '' or 'nowrap' -+ -+ -+ -+ -+ -+
    -+ -+# if ldoc.body then -- verbatim HTML as contents; 'non-code' entries -+ $(ldoc.body) -+# elseif module then -- module documentation -+

    $(ldoc.module_typename(module)) $(module.name)

    -+

    $(M(module.summary,module))

    -+

    $(M(module.description,module))

    -+# if module.tags.include then -+ $(M(ldoc.include_file(module.tags.include))) -+# end -+# if module.see then -+# local li,il = use_li(module.see) -+

    See also:

    -+
      -+# for see in iter(module.see) do -+ $(li)$(see.label)$(il) -+# end -- for -+
    -+# end -- if see -+# if module.usage then -+# local li,il = use_li(module.usage) -+

    Usage:

    -+
      -+# for usage in iter(module.usage) do -+ $(li)
      $(ldoc.escape(usage))
      $(il) -+# end -- for -+
    -+# end -- if usage -+# if module.info then -+

    Info:

    -+
      -+# for tag, value in module.info:iter() do -+
    • $(tag): $(M(value,module))
    • -+# end -+
    -+# end -- if module.info -+ -+ -+# if not ldoc.no_summary then -+# -- bang out the tables of item types for this module (e.g Functions, Tables, etc) -+# for kind,items in module.kinds() do -+

    $(kind)

    -+ -+# for item in items() do -+ -+ -+ -+ -+# end -- for items -+
    $(display_name(item))$(M(item.summary,item))
    -+#end -- for kinds -+ -+
    -+
    -+ -+#end -- if not no_summary -+ -+# --- currently works for both Functions and Tables. The params field either contains -+# --- function parameters or table fields. -+# local show_return = not ldoc.no_return_or_parms -+# local show_parms = show_return -+# for kind, items in module.kinds() do -+# local kitem = module.kinds:get_item(kind) -+# local has_description = kitem and ldoc.descript(kitem) ~= "" -+

    $(kind)

    -+ $(M(module.kinds:get_section_description(kind),nil)) -+# if kitem then -+# if has_description then -+
    -+ $(M(ldoc.descript(kitem),kitem)) -+
    -+# end -+# if kitem.usage then -+

    Usage:

    -+
    $(ldoc.prettify(kitem.usage[1]))
    -+# end -+# end -+
    -+# for item in items() do -+
    -+ -+ $(display_name(item)) -+# if ldoc.prettify_files and ldoc.is_file_prettified[item.module.file.filename] then -+ line $(item.lineno) -+# end -+
    -+
    -+ $(M(ldoc.descript(item),item)) -+ -+# if ldoc.custom_tags then -+# for custom in iter(ldoc.custom_tags) do -+# local tag = item.tags[custom[1]] -+# if tag and not custom.hidden then -+# local li,il = use_li(tag) -+

    $(custom.title or custom[1]):

    -+
      -+# for value in iter(tag) do -+ $(li)$(custom.format and custom.format(value) or M(value))$(il) -+# end -- for -+# end -- if tag -+
    -+# end -- iter tags -+# end -+ -+# if show_parms and item.params and #item.params > 0 then -+# local subnames = module.kinds:type_of(item).subnames -+# if subnames then -+

    $(subnames):

    -+# end -+
      -+# for parm in iter(item.params) do -+# local param,sublist = item:subparam(parm) -+# if sublist then -+
    • $(sublist)$(M(item.params.map[sublist],item)) -+
        -+# end -+# for p in iter(param) do -+# local name,tp,def = item:display_name_of(p), ldoc.typename(item:type_of_param(p)), item:default_of_param(p) -+
      • $(name) -+# if tp ~= '' then -+ $(tp) -+# end -+ $(M(item.params.map[p],item)) -+# if def == true then -+ (optional) -+# elseif def then -+ (default $(def)) -+# end -+# if item:readonly(p) then -+ readonly -+# end -+
      • -+# end -+# if sublist then -+
      -+# end -+# end -- for -+
    -+# end -- if params -+ -+# if show_return and item.retgroups then local groups = item.retgroups -+

    Returns:

    -+# for i,group in ldoc.ipairs(groups) do local li,il = use_li(group) -+
      -+# for r in group:iter() do local type, ctypes = item:return_type(r); local rt = ldoc.typename(type) -+ $(li) -+# if rt ~= '' then -+ $(rt) -+# end -+ $(M(r.text,item))$(il) -+# if ctypes then -+
        -+# for c in ctypes:iter() do -+
      • $(c.name) -+ $(ldoc.typename(c.type)) -+ $(M(c.comment,item))
      • -+# end -+
      -+# end -- if ctypes -+# end -- for r -+
    -+# if i < #groups then -+

    Or

    -+# end -+# end -- for group -+# end -- if returns -+ -+# if show_return and item.raise then -+

    Raises:

    -+ $(M(item.raise,item)) -+# end -+ -+# if item.see then -+# local li,il = use_li(item.see) -+

    See also:

    -+
      -+# for see in iter(item.see) do -+ $(li)$(see.label)$(il) -+# end -- for -+
    -+# end -- if see -+ -+# if item.usage then -+# local li,il = use_li(item.usage) -+

    Usage:

    -+
      -+# for usage in iter(item.usage) do -+ $(li)
      $(ldoc.prettify(usage))
      $(il) -+# end -- for -+
    -+# end -- if usage -+ -+
    -+# end -- for items -+
    -+# end -- for kinds -+ -+# else -- if module; project-level contents -+ -+# if ldoc.description then -+

    $(M(ldoc.description,nil))

    -+# end -+# if ldoc.full_description then -+

    $(M(ldoc.full_description,nil))

    -+# end -+ -+# for kind, mods in ldoc.kinds() do -+

    $(kind)

    -+# kind = kind:lower() -+ -+# for m in mods() do -+ -+ -+ -+ -+# end -- for modules -+
    $(m.name)$(M(ldoc.strip_header(m.summary),m))
    -+# end -- for kinds -+# end -- if module -+ -+
    -+
    -+
    -+generated by LDoc $(ldoc.version) -+
    -+
    -+ -+ -diff --git a/extra/penlight/lua/pl/Date.lua b/extra/penlight/lua/pl/Date.lua -index 7a5d1ed..039dbaf 100644 ---- a/extra/penlight/lua/pl/Date.lua -+++ b/extra/penlight/lua/pl/Date.lua -@@ -1,6 +1,9 @@ - --- Date and Date Format classes. - -- See @{05-dates.md|the Guide}. - -- -+-- NOTE: the date module is deprecated! see -+-- https://github.com/lunarmodules/Penlight/issues/285 -+-- - -- Dependencies: `pl.class`, `pl.stringx`, `pl.utils` - -- @classmod pl.Date - -- @pragma nostrip -@@ -11,6 +14,15 @@ local stringx = require 'pl.stringx' - local utils = require 'pl.utils' - local assert_arg,assert_string = utils.assert_arg,utils.assert_string - -+ -+utils.raise_deprecation { -+ source = "Penlight " .. utils._VERSION, -+ message = "the 'Date' module is deprecated, see https://github.com/lunarmodules/Penlight/issues/285", -+ version_removed = "2.0.0", -+ version_deprecated = "1.9.2", -+} -+ -+ - local Date = class() - Date.Format = class() - -@@ -94,9 +106,9 @@ function Date.tzone (ts) - ts = os_time() - elseif type(ts) == "table" then - if getmetatable(ts) == Date then -- ts = ts.time -+ ts = ts.time - else -- ts = Date(ts).time -+ ts = Date(ts).time - end - end - local utc = os_date('!*t',ts) -@@ -510,7 +522,7 @@ Allowed patterns: - - ]] - --local function looks_like_a_month(w) -+local function looks_like_a_month(w) - return w:match '^%a+,*$' ~= nil - end - local is_number = stringx.isdigit -@@ -537,7 +549,7 @@ local function parse_iso_end(p,ns,sec) - -- (we're working with the date as lower case, hence 'z') - if p:match 'z$' then -- we're UTC! - return sec, {h=0,m=0} -- end -+ end - p = p:gsub(':','') -- turn 00:30 to 0030 - local _,_,sign,offs = p:find('^([%+%-])(%d+)') - if not sign then return sec, nil end -- not UTC -diff --git a/extra/penlight/lua/pl/List.lua b/extra/penlight/lua/pl/List.lua -index 95d8c0e..a65cf3b 100644 ---- a/extra/penlight/lua/pl/List.lua -+++ b/extra/penlight/lua/pl/List.lua -@@ -68,7 +68,7 @@ end - -- this will return a plain table with an appropriate metatable. - -- we pass anything which isn't a simple table to iterate() to work out - -- an appropriate iterator ---- @see List.iterate -+-- @see List.iterate - -- @param[opt] t An optional list-like table - -- @return a new List - -- @usage ls = List(); ls = List {1,2,3,4} -@@ -85,7 +85,7 @@ function List:clone() - return ls - end - -----Add an item to the end of the list. -+--- Add an item to the end of the list. - -- @param i An item - -- @return the list - function List:append(i) -@@ -116,7 +116,7 @@ function List:insert(i, x) - return self - end - ----- Insert an item at the begining of the list. -+--- Insert an item at the beginning of the list. - -- @param x a data item - -- @return the list - function List:put (x) -@@ -169,7 +169,7 @@ List.get = List.pop - local tfind = tablex.find - List.index = tfind - ----- does this list contain the value?. -+--- Does this list contain the value? - -- @param x A data value - -- @return true or false - function List:contains(x) -@@ -196,7 +196,7 @@ function List:sort(cmp) - return self - end - ----- return a sorted copy of this list. -+--- Return a sorted copy of this list. - -- @func[opt='<'] cmp an optional comparison function - -- @return a new list - function List:sorted(cmp) -@@ -215,7 +215,7 @@ function List:reverse() - return self - end - ----- return the minimum and the maximum value of the list. -+--- Return the minimum and the maximum value of the list. - -- @return minimum value - -- @return maximum value - function List:minmax() -@@ -239,7 +239,7 @@ function List:slice(first,last) - return tsub(self,first,last) - end - ----- empty the list. -+--- Empty the list. - -- @return the list - function List:clear() - for i=1,#self do tremove(self) end -@@ -309,7 +309,7 @@ function List:splice(idx,list) - return self - end - ----- general slice assignment s[i1:i2] = seq. -+--- General slice assignment s[i1:i2] = seq. - -- @int i1 start index - -- @int i2 end index - -- @tparam List seq a list -@@ -323,7 +323,7 @@ function List:slice_assign(i1,i2,seq) - return self - end - ----- concatenation operator. -+--- Concatenation operator. - -- @within metamethods - -- @tparam List L another List - -- @return a new list consisting of the list with the elements of the new list appended -@@ -334,7 +334,7 @@ function List:__concat(L) - return ls - end - ----- equality operator ==. True iff all elements of two lists are equal. -+--- Equality operator ==. True iff all elements of two lists are equal. - -- @within metamethods - -- @tparam List L another List - -- @return true or false -@@ -346,7 +346,7 @@ function List:__eq(L) - return true - end - ----- join the elements of a list using a delimiter. -+--- Join the elements of a list using a delimiter. - -- This method uses tostring on all elements. - -- @string[opt=''] delim a delimiter string, can be empty. - -- @return a string -@@ -356,7 +356,7 @@ function List:join (delim) - return concat(array_tostring(self),delim) - end - ----- join a list of strings.
    -+--- Join a list of strings.
    - -- Uses `table.concat` directly. - -- @function List:concat - -- @string[opt=''] delim a delimiter -@@ -371,14 +371,14 @@ local function tostring_q(val) - return s - end - ----- how our list should be rendered as a string. Uses join(). -+--- How our list should be rendered as a string. Uses join(). - -- @within metamethods - -- @see List:join - function List:__tostring() - return '{'..self:join(',',tostring_q)..'}' - end - ----- call the function on each element of the list. -+--- Call the function on each element of the list. - -- @func fun a function or callable object - -- @param ... optional values to pass to function - function List:foreach (fun,...) -@@ -394,7 +394,7 @@ local function lookup_fun (obj,name) - return f - end - ----- call the named method on each element of the list. -+--- Call the named method on each element of the list. - -- @string name the method name - -- @param ... optional values to pass to function - function List:foreachm (name,...) -@@ -405,7 +405,7 @@ function List:foreachm (name,...) - end - end - ----- create a list of all elements which match a function. -+--- Create a list of all elements which match a function. - -- @func fun a boolean function - -- @param[opt] arg optional argument to be passed as second argument of the predicate - -- @return a new filtered list. -@@ -413,7 +413,7 @@ function List:filter (fun,arg) - return makelist(filter(self,fun,arg),self) - end - ----- split a string using a delimiter. -+--- Split a string using a delimiter. - -- @string s the string - -- @string[opt] delim the delimiter (default spaces) - -- @return a List of strings -@@ -423,7 +423,7 @@ function List.split (s,delim) - return makelist(split(s,delim)) - end - ----- apply a function to all elements. -+--- Apply a function to all elements. - -- Any extra arguments will be passed to the function. - -- @func fun a function of at least one argument - -- @param ... arbitrary extra arguments. -@@ -434,17 +434,17 @@ function List:map (fun,...) - return makelist(imap(fun,self,...),self) - end - ----- apply a function to all elements, in-place. -+--- Apply a function to all elements, in-place. - -- Any extra arguments are passed to the function. - -- @func fun A function that takes at least one argument - -- @param ... arbitrary extra arguments. - -- @return the list. - function List:transform (fun,...) - transform(fun,self,...) -- return self -+ return self - end - ----- apply a function to elements of two lists. -+--- Apply a function to elements of two lists. - -- Any extra arguments will be passed to the function - -- @func fun a function of at least two arguments - -- @tparam List ls another list -@@ -501,7 +501,7 @@ function List:reduce (fun) - return reduce(fun,self) - end - ----- partition a list using a classifier function. -+--- Partition a list using a classifier function. - -- The function may return nil, but this will be converted to the string key ''. - -- @func fun a function of at least one argument - -- @param ... will also be passed to the function -@@ -526,7 +526,7 @@ function List:iter () - return iter(self) - end - ----- Create an iterator over a seqence. -+--- Create an iterator over a sequence. - -- This captures the Python concept of 'sequence'. - -- For tables, iterates over all values with integer indices. - -- @param seq a sequence; a string (over characters), a table, a file object (over lines) or an iterator function -diff --git a/extra/penlight/lua/pl/Map.lua b/extra/penlight/lua/pl/Map.lua -index b4c79ec..06fd096 100644 ---- a/extra/penlight/lua/pl/Map.lua -+++ b/extra/penlight/lua/pl/Map.lua -@@ -37,10 +37,14 @@ local function makelist(t) - return setmetatable(t, require('pl.List')) - end - ----- list of keys. -+--- return a List of all keys. -+-- @class function -+-- @name Map:keys - Map.keys = tablex.keys - ----- list of values. -+--- return a List of all values. -+-- @class function -+-- @name Map:values - Map.values = tablex.values - - --- return an iterator over all key-value pairs. -@@ -48,17 +52,24 @@ function Map:iter () - return pairs(self) - end - ----- return a List of all key-value pairs, sorted by the keys. -+--- return a List of all key-value pairs, sorted by the keys in ascending order. - function Map:items() - local ls = makelist(tablex.pairmap (function (k,v) return makelist {k,v} end, self)) -- ls:sort(function(t1,t2) return t1[1] < t2[1] end) -- return ls -+ ls:sort(function(t1,t2) return t1[1] < t2[1] end) -+ return ls - end - ---- Will return the existing value, or if it doesn't exist it will set ---- a default value and return it. --function Map:setdefault(key, defaultval) -- return self[key] or self:set(key,defaultval) or defaultval -+--- set a value in the map if it doesn't exist yet. -+-- @param key the key -+-- @param default value to set -+-- @return the value stored in the map (existing value, or the new value) -+function Map:setdefault(key, default) -+ local val = self[key] -+ if val ~= nil then -+ return val -+ end -+ self:set(key,default) -+ return default - end - - --- size of map. -diff --git a/extra/penlight/lua/pl/OrderedMap.lua b/extra/penlight/lua/pl/OrderedMap.lua -index 3e49af5..379c44f 100644 ---- a/extra/penlight/lua/pl/OrderedMap.lua -+++ b/extra/penlight/lua/pl/OrderedMap.lua -@@ -32,7 +32,7 @@ end - local assert_arg,raise = utils.assert_arg,utils.raise - - --- update an OrderedMap using a table. ---- If the table is itself an OrderedMap, then its entries will be appended. -+-- If the table is itself an OrderedMap, then its entries will be appended. - -- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended. - -- - -- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. -@@ -132,7 +132,7 @@ end - function OrderedMap:iter () - local i = 0 - local keys = self._keys -- local n,idx = #keys -+ local idx - return function() - i = i + 1 - if i > #keys then return nil end -diff --git a/extra/penlight/lua/pl/Set.lua b/extra/penlight/lua/pl/Set.lua -index ffc50a7..ce428f0 100644 ---- a/extra/penlight/lua/pl/Set.lua -+++ b/extra/penlight/lua/pl/Set.lua -@@ -20,7 +20,7 @@ - -- [banana,apricot,apple,orange] [banana,apple,orange] - -- - -- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.Map`, (`pl.List` if __tostring is used) ---- @module pl.Set -+-- @classmod pl.Set - - local tablex = require 'pl.tablex' - local utils = require 'pl.utils' -diff --git a/extra/penlight/lua/pl/app.lua b/extra/penlight/lua/pl/app.lua -index 21b6a7b..736e166 100644 ---- a/extra/penlight/lua/pl/app.lua -+++ b/extra/penlight/lua/pl/app.lua -@@ -10,29 +10,55 @@ local path = require 'pl.path' - - local app = {} - --local function check_script_name () -- if _G.arg == nil then error('no command line args available\nWas this run from a main script?') end -- return _G.arg[0] -+--- return the name of the current script running. -+-- The name will be the name as passed on the command line -+-- @return string filename -+function app.script_name() -+ if _G.arg and _G.arg[0] then -+ return _G.arg[0] -+ end -+ return utils.raise("No script name found") - end - ----- add the current script's path to the Lua module path. -+--- prefixes the current script's path to the Lua module path. - -- Applies to both the source and the binary module paths. It makes it easy for - -- the main file of a multi-file program to access its modules in the same directory. - -- `base` allows these modules to be put in a specified subdirectory, to allow for - -- cleaner deployment and resolve potential conflicts between a script name and its - -- library directory. ---- @string base optional base directory. -+-- -+-- Note: the path is prefixed, so it is searched first when requiring modules. -+-- @string base optional base directory (absolute, or relative path). -+-- @bool nofollow always use the invocation's directory, even if the invoked file is a symlink - -- @treturn string the current script's path with a trailing slash --function app.require_here (base) -- local p = path.dirname(check_script_name()) -+function app.require_here (base, nofollow) -+ local p = app.script_name() - if not path.isabs(p) then - p = path.join(path.currentdir(),p) - end -+ if not nofollow then -+ local t = path.link_attrib(p) -+ if t and t.mode == 'link' then -+ t = t.target -+ if not path.isabs(t) then -+ t = path.join(path.dirname(p), t) -+ end -+ p = t -+ end -+ end -+ p = path.normpath(path.dirname(p)) - if p:sub(-1,-1) ~= path.sep then - p = p..path.sep - end - if base then -- p = p..base..path.sep -+ if path.is_windows then -+ base = base:gsub('/','\\') -+ end -+ if path.isabs(base) then -+ p = base .. path.sep -+ else -+ p = p..base..path.sep -+ end - end - local so_ext = path.is_windows and 'dll' or 'so' - local lsep = package.path:find '^;' and '' or ';' -@@ -45,16 +71,24 @@ end - --- return a suitable path for files private to this application. - -- These will look like '~/.SNAME/file', with '~' as with expanduser and - -- SNAME is the name of the script without .lua extension. -+-- If the directory does not exist, it will be created. - -- @string file a filename (w/out path) - -- @return a full pathname, or nil ---- @return 'cannot create' error --function app.appfile (file) -- local sname = path.basename(check_script_name()) -- local name,ext = path.splitext(sname) -+-- @return cannot create directory error -+-- @usage -+-- -- when run from a script called 'testapp' (on Windows): -+-- local app = require 'pl.app' -+-- print(app.appfile 'test.txt') -+-- -- C:\Documents and Settings\steve\.testapp\test.txt -+function app.appfile(file) -+ local sfullname, err = app.script_name() -+ if not sfullname then return utils.raise(err) end -+ local sname = path.basename(sfullname) -+ local name = path.splitext(sname) - local dir = path.join(path.expanduser('~'),'.'..name) - if not path.isdir(dir) then - local ret = path.mkdir(dir) -- if not ret then return utils.raise ('cannot create '..dir) end -+ if not ret then return utils.raise('cannot create '..dir) end - end - return path.join(dir,file) - end -@@ -74,38 +108,141 @@ function app.platform() - end - - --- return the full command-line used to invoke this script. ---- Any extra flags occupy slots, so that `lua -lpl` gives us `{[-2]='lua',[-1]='-lpl'}` -+-- It will not include the scriptname itself, see `app.script_name`. - -- @return command-line - -- @return name of Lua program used --function app.lua () -- local args = _G.arg or error "not in a main program" -- local imin = 0 -- for i in pairs(args) do -- if i < imin then imin = i end -+-- @usage -+-- -- execute: lua -lluacov -e 'print(_VERSION)' myscript.lua -+-- -+-- -- myscript.lua -+-- print(require("pl.app").lua()) --> "lua -lluacov -e 'print(_VERSION)'", "lua" -+function app.lua() -+ local args = _G.arg -+ if not args then -+ return utils.raise "not in a main program" - end -- local cmd, append = {}, table.insert -- for i = imin,-1 do -- append(cmd, utils.quote_arg(args[i])) -+ -+ local cmd = {} -+ local i = -1 -+ while true do -+ table.insert(cmd, 1, args[i]) -+ if not args[i-1] then -+ return utils.quote_arg(cmd), args[i] -+ end -+ i = i - 1 - end -- return table.concat(cmd,' '),args[imin] - end - - --- parse command-line arguments into flags and parameters. - -- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`). ---- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`); ---- note that a number value can be given without a space. -+-- -+-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`), -+-- a number value can be given without a space. If the flag is marked -+-- as having a value, then a space-separated value is also accepted (`-i hello`), -+-- see the `flags_with_values` argument). -+-- - -- Multiple short args can be combined like so: ( `-abcd`). -+-- -+-- When specifying the `flags_valid` parameter, its contents can also contain -+-- aliases, to convert short/long flags to the same output name. See the -+-- example below. -+-- -+-- Note: if a flag is repeated, the last value wins. - -- @tparam {string} args an array of strings (default is the global `arg`) ---- @tab flags_with_values any flags that take values, e.g. `{out=true}` -+-- @tab flags_with_values any flags that take values, either list or hash -+-- table e.g. `{ out=true }` or `{ "out" }`. -+-- @tab flags_valid (optional) flags that are valid, either list or hashtable. -+-- If not given, everything -+-- will be accepted(everything in `flags_with_values` will automatically be allowed) - -- @return a table of flags (flag=value pairs) - -- @return an array of parameters - -- @raise if args is nil, then the global `args` must be available! --function app.parse_args (args,flags_with_values) -+-- @usage -+-- -- Simple form: -+-- local flags, params = app.parse_args(nil, -+-- { "hello", "world" }, -- list of flags taking values -+-- { "l", "a", "b"}) -- list of allowed flags (value ones will be added) -+-- -+-- -- More complex example using aliases: -+-- local valid = { -+-- long = "l", -- if 'l' is specified, it is reported as 'long' -+-- new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new' -+-- } -+-- local values = { -+-- "value", -- will automatically be added to the allowed set of flags -+-- "new", -- will mark 'n' and 'old' as requiring a value as well -+-- } -+-- local flags, params = app.parse_args(nil, values, valid) -+-- -+-- -- command: myapp.lua -l --old:hello --value world param1 param2 -+-- -- will yield: -+-- flags = { -+-- long = true, -- input from 'l' -+-- new = "hello", -- input from 'old' -+-- value = "world", -- allowed because it was in 'values', note: space separated! -+-- } -+-- params = { -+-- [1] = "param1" -+-- [2] = "param2" -+-- } -+function app.parse_args (args,flags_with_values, flags_valid) - if not args then - args = _G.arg -- if not args then error "Not in a main program: 'arg' not found" end -+ if not args then utils.raise "Not in a main program: 'arg' not found" end -+ end -+ -+ local with_values = {} -+ for k,v in pairs(flags_with_values or {}) do -+ if type(k) == "number" then -+ k = v -+ end -+ with_values[k] = true - end -- flags_with_values = flags_with_values or {} -+ -+ local valid -+ if not flags_valid then -+ -- if no allowed flags provided, we create a table that always returns -+ -- the keyname, no matter what you look up -+ valid = setmetatable({},{ __index = function(_, key) return key end }) -+ else -+ valid = {} -+ for k,aliases in pairs(flags_valid) do -+ if type(k) == "number" then -- array/list entry -+ k = aliases -+ end -+ if type(aliases) == "string" then -- single alias -+ aliases = { aliases } -+ end -+ if type(aliases) == "table" then -- list of aliases -+ -- it's the alternate name, so add the proper mappings -+ for i, alias in ipairs(aliases) do -+ valid[alias] = k -+ end -+ end -+ valid[k] = k -+ end -+ do -+ local new_with_values = {} -- needed to prevent "invalid key to 'next'" error -+ for k,v in pairs(with_values) do -+ if not valid[k] then -+ valid[k] = k -- add the with_value entry as a valid one -+ new_with_values[k] = true -+ else -+ new_with_values[valid[k]] = true --set, but by its alias -+ end -+ end -+ with_values = new_with_values -+ end -+ end -+ -+ -- now check that all flags with values are reported as such under all -+ -- of their aliases -+ for k, main_alias in pairs(valid) do -+ if with_values[main_alias] then -+ with_values[k] = true -+ end -+ end -+ - local _args = {} - local flags = {} - local i = 1 -@@ -113,20 +250,24 @@ function app.parse_args (args,flags_with_values) - local a = args[i] - local v = a:match('^-(.+)') - local is_long -- if v then -- we have a flag -+ if not v then -+ -- we have a parameter -+ _args[#_args+1] = a -+ else -+ -- it's a flag - if v:find '^-' then - is_long = true - v = v:sub(2) - end -- if flags_with_values[v] then -+ if with_values[v] then - if i == #args or args[i+1]:find '^-' then - return utils.raise ("no value for '"..v.."'") - end -- flags[v] = args[i+1] -+ flags[valid[v]] = args[i+1] - i = i + 1 - else - -- a value can also be indicated with = or : -- local var,val = utils.splitv (v,'[=:]') -+ local var,val = utils.splitv (v,'[=:]', false, 2) - var = var or v - val = val or true - if not is_long then -@@ -136,7 +277,13 @@ function app.parse_args (args,flags_with_values) - var = var:sub(1,1) - else -- multiple short flags - for i = 1,#var do -- flags[var:sub(i,i)] = true -+ local f = var:sub(i,i) -+ if not valid[f] then -+ return utils.raise("unknown flag '"..f.."'") -+ else -+ f = valid[f] -+ end -+ flags[f] = true - end - val = nil -- prevents use of var as a flag below - end -@@ -145,11 +292,14 @@ function app.parse_args (args,flags_with_values) - end - end - if val then -+ if not valid[var] then -+ return utils.raise("unknown flag '"..var.."'") -+ else -+ var = valid[var] -+ end - flags[var] = val - end - end -- else -- _args[#_args+1] = a - end - i = i + 1 - end -diff --git a/extra/penlight/lua/pl/array2d.lua b/extra/penlight/lua/pl/array2d.lua -index b15e39e..0bc910e 100644 ---- a/extra/penlight/lua/pl/array2d.lua -+++ b/extra/penlight/lua/pl/array2d.lua -@@ -1,11 +1,17 @@ - --- Operations on two-dimensional arrays. - -- See @{02-arrays.md.Operations_on_two_dimensional_tables|The Guide} - -- -+-- The size of the arrays is determined by using the length operator `#` hence -+-- the module is not `nil` safe, and the usual precautions apply. -+-- -+-- Note: all functions taking `i1,j1,i2,j2` as arguments will normalize the -+-- arguments using `default_range`. -+-- - -- Dependencies: `pl.utils`, `pl.tablex`, `pl.types` - -- @module pl.array2d - --local type,tonumber,assert,tostring,io,ipairs,string,table = -- _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table -+local tonumber,tostring,io,ipairs,string,table = -+ _G.tonumber,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table - local setmetatable,getmetatable = setmetatable,getmetatable - - local tablex = require 'pl.tablex' -@@ -16,6 +22,8 @@ local remove = table.remove - local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg - local byte = string.byte - local stdout = io.stdout -+local min = math.min -+ - - local array2d = {} - -@@ -31,29 +39,48 @@ local function makelist (res) - return setmetatable(res, require('pl.List')) - end - -- --local function index (t,k) -- return t[k] -+--- return the row and column size. -+-- Size is calculated using the Lua length operator #, so usual precautions -+-- regarding `nil` values apply. -+-- @array2d a a 2d array -+-- @treturn int number of rows (`#a`) -+-- @treturn int number of cols (`#a[1]`) -+function array2d.size (a) -+ assert_arg(1,a,'table') -+ return #a,#a[1] - end - ----- return the row and column size. ---- @array2d t a 2d array ---- @treturn int number of rows ---- @treturn int number of cols --function array2d.size (t) -- assert_arg(1,t,'table') -- return #t,#t[1] -+do -+ local function index (t,k) -+ return t[k] -+ end -+ -+ --- extract a column from the 2D array. -+ -- @array2d a 2d array -+ -- @param j column index -+ -- @return 1d array -+ function array2d.column (a,j) -+ assert_arg(1,a,'table') -+ return makelist(imap(index,a,j)) -+ end - end -+local column = array2d.column - ----- extract a column from the 2D array. -+--- extract a row from the 2D array. -+-- Added in line with `column`, for read-only purposes directly -+-- accessing a[i] is more performant. - -- @array2d a 2d array ---- @param key an index or key ---- @return 1d array --function array2d.column (a,key) -+-- @param i row index -+-- @return 1d array (copy of the row) -+function array2d.row(a,i) - assert_arg(1,a,'table') -- return makelist(imap(index,a,key)) -+ local row = a[i] -+ local r = {} -+ for n,v in ipairs(row) do -+ r[n] = v -+ end -+ return makelist(r) - end --local column = array2d.column - - --- map a function over a 2D array - -- @func f a function of at least one argument -@@ -61,7 +88,7 @@ local column = array2d.column - -- @param arg an optional extra argument to be passed to the function. - -- @return 2d array - function array2d.map (f,a,arg) -- assert_arg(1,a,'table') -+ assert_arg(2,a,'table') - f = utils.function_arg(1,f) - return obj(a,imap(function(row) return imap(f,row,arg) end, a)) - end -@@ -96,15 +123,11 @@ function array2d.reduce2 (opc,opr,a) - return reduce(opc,tmp) - end - --local function dimension (t) -- return type(t[1])=='table' and 2 or 1 --end -- - --- map a function over two arrays. - -- They can be both or either 2D arrays - -- @func f function of at least two arguments ---- @int ad order of first array (1 or 2) ---- @int bd order of second array (1 or 2) -+-- @int ad order of first array (`1` if `a` is a list/array, `2` if it is a 2d array) -+-- @int bd order of second array (`1` if `b` is a list/array, `2` if it is a 2d array) - -- @tab a 1d or 2d array - -- @tab b 1d or 2d array - -- @param arg optional extra argument to pass to function -@@ -140,9 +163,9 @@ function array2d.product (f,t1,t2) - f = utils.function_arg(1,f) - assert_arg(2,t1,'table') - assert_arg(3,t2,'table') -- local res, map = {}, tablex.map -+ local res = {} - for i,v in ipairs(t2) do -- res[i] = map(f,t1,v) -+ res[i] = tmap(f,t1,v) - end - return res - end -@@ -155,19 +178,21 @@ end - function array2d.flatten (t) - local res = {} - local k = 1 -- for _,a in ipairs(t) do -- for all rows -- for i = 1,#a do -- res[k] = a[i] -+ local rows, cols = array2d.size(t) -+ for r = 1, rows do -+ local row = t[r] -+ for c = 1, cols do -+ res[k] = row[c] - k = k + 1 - end - end - return makelist(res) - end - ----- reshape a 2D array. -+--- reshape a 2D array. Reshape the array by specifying a new nr of rows. - -- @array2d t 2d array - -- @int nrows new number of rows ---- @bool co column-order (Fortran-style) (default false) -+-- @bool co use column-order (Fortran-style) (default false) - -- @return a new 2d array - function array2d.reshape (t,nrows,co) - local nr,nc = array2d.size(t) -@@ -197,30 +222,43 @@ function array2d.reshape (t,nrows,co) - return obj(t,res) - end - -+--- transpose a 2D array. -+-- @array2d t 2d array -+-- @return a new 2d array -+function array2d.transpose(t) -+ assert_arg(1,t,'table') -+ local _, c = array2d.size(t) -+ return array2d.reshape(t,c,true) -+end -+ - --- swap two rows of an array. - -- @array2d t a 2d array - -- @int i1 a row index - -- @int i2 a row index -+-- @return t (same, modified 2d array) - function array2d.swap_rows (t,i1,i2) - assert_arg(1,t,'table') - t[i1],t[i2] = t[i2],t[i1] -+ return t - end - - --- swap two columns of an array. - -- @array2d t a 2d array - -- @int j1 a column index - -- @int j2 a column index -+-- @return t (same, modified 2d array) - function array2d.swap_cols (t,j1,j2) - assert_arg(1,t,'table') -- for i = 1,#t do -- local row = t[i] -+ for _, row in ipairs(t) do - row[j1],row[j2] = row[j2],row[j1] - end -+ return t - end - - --- extract the specified rows. - -- @array2d t 2d array - -- @tparam {int} ridx a table of row indices -+-- @return a new 2d array with the extracted rows - function array2d.extract_rows (t,ridx) - return obj(t,index_by(t,ridx)) - end -@@ -228,6 +266,7 @@ end - --- extract the specified columns. - -- @array2d t 2d array - -- @tparam {int} cidx a table of column indices -+-- @return a new 2d array with the extracted columns - function array2d.extract_cols (t,cidx) - assert_arg(1,t,'table') - local res = {} -@@ -253,74 +292,97 @@ function array2d.remove_col (t,j) - end - end - --local Ai = byte 'A' -+do -+ local function _parse (s) -+ local r, c = s:match 'R(%d+)C(%d+)' -+ if r then -+ r,c = tonumber(r),tonumber(c) -+ return r,c -+ end -+ c,r = s:match '(%a+)(%d+)' -+ if c then -+ local cv = 0 -+ for i = 1, #c do -+ cv = cv * 26 + byte(c:sub(i,i)) - byte 'A' + 1 -+ end -+ return tonumber(r), cv -+ end -+ error('bad cell specifier: '..s) -+ end - --local function _parse (s) -- local c,r -- if s:sub(1,1) == 'R' then -- r,c = s:match 'R(%d+)C(%d+)' -- r,c = tonumber(r),tonumber(c) -- else -- c,r = s:match '(.)(.)' -- c = byte(c) - byte 'A' + 1 -- r = tonumber(r) -+ --- parse a spreadsheet range or cell. -+ -- The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for -+ -- single cells as 'A1' or 'R1C1'. -+ -- @string s a range (case insensitive). -+ -- @treturn int start row -+ -- @treturn int start col -+ -- @treturn int end row (or `nil` if the range was a single cell) -+ -- @treturn int end col (or `nil` if the range was a single cell) -+ function array2d.parse_range (s) -+ assert_arg(1,s,'string') -+ s = s:upper() -+ if s:find ':' then -+ local start,finish = splitv(s,':') -+ local i1,j1 = _parse(start) -+ local i2,j2 = _parse(finish) -+ return i1,j1,i2,j2 -+ else -- single value -+ local i,j = _parse(s) -+ return i,j -+ end - end -- assert(c ~= nil and r ~= nil,'bad cell specifier: '..s) -- return r,c - end - ----- parse a spreadsheet range. ---- The range can be specified either as 'A1:B2' or 'R1C1:R2C2'; ---- a special case is a single element (e.g 'A1' or 'R1C1') ---- @string s a range. ---- @treturn int start col ---- @treturn int start row ---- @treturn int end col ---- @treturn int end row --function array2d.parse_range (s) -- if s:find ':' then -- local start,finish = splitv(s,':') -- local i1,j1 = _parse(start) -- local i2,j2 = _parse(finish) -- return i1,j1,i2,j2 -- else -- single value -- local i,j = _parse(s) -- return i,j -- end -+--- get a slice of a 2D array. -+-- Same as `slice`. -+-- @see slice -+function array2d.range (...) -+ return array2d.slice(...) - end - ----- get a slice of a 2D array using spreadsheet range notation. @see parse_range ---- @array2d t a 2D array ---- @string rstr range expression ---- @return a slice ---- @see array2d.parse_range ---- @see array2d.slice --function array2d.range (t,rstr) -- assert_arg(1,t,'table') -- local i1,j1,i2,j2 = array2d.parse_range(rstr) -- if i2 then -- return array2d.slice(t,i1,j1,i2,j2) -- else -- single value -- return t[i1][j1] -+local default_range do -+ local function norm_value(v, max) -+ if not v then return v end -+ if v < 0 then -+ v = max + v + 1 -+ end -+ if v < 1 then v = 1 end -+ if v > max then v = max end -+ return v - end --end - --local function default_range (t,i1,j1,i2,j2) -- local nr, nc = array2d.size(t) -- i1,j1 = i1 or 1, j1 or 1 -- i2,j2 = i2 or nr, j2 or nc -- if i2 < 0 then i2 = nr + i2 + 1 end -- if j2 < 0 then j2 = nc + j2 + 1 end -- return i1,j1,i2,j2 -+ --- normalizes coordinates to valid positive entries and defaults. -+ -- Negative indices will be counted from the end, too low, or too high -+ -- will be limited by the array sizes. -+ -- @array2d t a 2D array -+ -- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+ -- @tparam[opt=1] int j1 start col -+ -- @tparam[opt=N] int i2 end row -+ -- @tparam[opt=M] int j2 end col -+ -- @see parse_range -+ -- @return i1, j1, i2, j2 -+ function array2d.default_range (t,i1,j1,i2,j2) -+ if (type(i1) == 'string') and not (j1 or i2 or j2) then -+ i1, j1, i2, j2 = array2d.parse_range(i1) -+ end -+ local nr, nc = array2d.size(t) -+ i1 = norm_value(i1 or 1, nr) -+ j1 = norm_value(j1 or 1, nc) -+ i2 = norm_value(i2 or nr, nr) -+ j2 = norm_value(j2 or nc, nc) -+ return i1,j1,i2,j2 -+ end -+ default_range = array2d.default_range - end - - --- get a slice of a 2D array. Note that if the specified range has - -- a 1D result, the rank of the result will be 1. - -- @array2d t a 2D array ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range - -- @return an array, 2D in general but 1D in special cases. - function array2d.slice (t,i1,j1,i2,j2) - assert_arg(1,t,'table') -@@ -345,16 +407,25 @@ end - - --- set a specified range of an array to a value. - -- @array2d t a 2D array ---- @param value the value (may be a function) ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) -+-- @param value the value (may be a function, called as `val(i,j)`) -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range - -- @see tablex.set - function array2d.set (t,value,i1,j1,i2,j2) - i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) -- for i = i1,i2 do -+ local i = i1 -+ if types.is_callable(value) then -+ local old_f = value -+ value = function(j) -+ return old_f(i,j) -+ end -+ end -+ while i <= i2 do - tset(t[i],value,j1,j2) -+ i = i + 1 - end - end - -@@ -362,10 +433,11 @@ end - -- @array2d t a 2D array - -- @param f a file object (default stdout) - -- @string fmt a format string (default is just to use tostring) ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range - function array2d.write (t,f,fmt,i1,j1,i2,j2) - assert_arg(1,t,'table') - f = f or stdout -@@ -383,12 +455,13 @@ end - - --- perform an operation for all values in a 2D array. - -- @array2d t 2D array ---- @func row_op function to call on each value ---- @func end_row_op function to call at end of each row ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) -+-- @func row_op function to call on each value; `row_op(row,j)` -+-- @func end_row_op function to call at end of each row; `end_row_op(i)` -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range - function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2) - assert_arg(1,t,'table') - i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) -@@ -401,17 +474,16 @@ function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2) - end - end - --local min, max = math.min, math.max -- - ---- move a block from the destination to the source. - -- @array2d dest a 2D array - -- @int di start row in dest - -- @int dj start col in dest - -- @array2d src a 2D array ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range - function array2d.move (dest,di,dj,src,i1,j1,i2,j2) - assert_arg(1,dest,'table') - assert_arg(4,src,'table') -@@ -430,27 +502,27 @@ end - - --- iterate over all elements in a 2D array, with optional indices. - -- @array2d a 2D array ---- @tparam {int} indices with indices (default false) ---- @int i1 start row (default 1) ---- @int j1 start col (default 1) ---- @int i2 end row (default N) ---- @int j2 end col (default M) ---- @return either value or i,j,value depending on indices --function array2d.iter (a,indices,i1,j1,i2,j2) -+-- @bool indices with indices (default false) -+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range` -+-- @tparam[opt=1] int j1 start col -+-- @tparam[opt=N] int i2 end row -+-- @tparam[opt=M] int j2 end col -+-- @see parse_range -+-- @return either `value` or `i,j,value` depending on the value of `indices` -+function array2d.iter(a,indices,i1,j1,i2,j2) - assert_arg(1,a,'table') -- local norowset = not (i2 and j2) - i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2) -- local n,i,j = i2-i1+1,i1-1,j1-1 -- local row,nr = nil,0 -- local onr = j2 - j1 + 1 -+ local i,j = i1,j1-1 -+ local row = a[i] - return function() - j = j + 1 -- if j > nr then -+ if j > j2 then - j = j1 - i = i + 1 -- if i > i2 then return nil end - row = a[i] -- nr = norowset and #row or onr -+ if i > i2 then -+ return nil -+ end - end - if indices then - return i,j,row[j] -@@ -462,16 +534,32 @@ end - - --- iterate over all columns. - -- @array2d a a 2D array ---- @return each column in turn --function array2d.columns (a) -- assert_arg(1,a,'table') -- local n = a[1][1] -- local i = 0 -- return function() -- i = i + 1 -- if i > n then return nil end -- return column(a,i) -- end -+-- @return column, column-index -+function array2d.columns(a) -+ assert_arg(1,a,'table') -+ local n = #a[1] -+ local i = 0 -+ return function() -+ i = i + 1 -+ if i > n then return nil end -+ return column(a,i), i -+ end -+end -+ -+--- iterate over all rows. -+-- Returns a copy of the row, for read-only purposes directly iterating -+-- is more performant; `ipairs(a)` -+-- @array2d a a 2D array -+-- @return row, row-index -+function array2d.rows(a) -+ assert_arg(1,a,'table') -+ local n = #a -+ local i = 0 -+ return function() -+ i = i + 1 -+ if i > n then return nil end -+ return array2d.row(a,i), i -+ end - end - - --- new array of specified dimensions -@@ -495,5 +583,3 @@ function array2d.new(rows,cols,val) - end - - return array2d -- -- -diff --git a/extra/penlight/lua/pl/class.lua b/extra/penlight/lua/pl/class.lua -index a57ac71..cd041c8 100644 ---- a/extra/penlight/lua/pl/class.lua -+++ b/extra/penlight/lua/pl/class.lua -@@ -1,4 +1,4 @@ ----- Provides a reuseable and convenient framework for creating classes in Lua. -+--- Provides a reusable and convenient framework for creating classes in Lua. - -- Two possible notations: - -- - -- B = class(A) -@@ -17,23 +17,29 @@ local compat - -- this trickery is necessary to prevent the inheritance of 'super' and - -- the resulting recursive call problems. - local function call_ctor (c,obj,...) -- -- nice alias for the base class ctor -- local base = rawget(c,'_base') -- if base then -- local parent_ctor = rawget(base,'_init') -- while not parent_ctor do -- base = rawget(base,'_base') -- if not base then break end -- parent_ctor = rawget(base,'_init') -+ local init = rawget(c,'_init') -+ local parent_with_init = rawget(c,'_parent_with_init') -+ -+ if parent_with_init then -+ if not init then -- inheriting an init -+ init = rawget(parent_with_init, '_init') -+ parent_with_init = rawget(parent_with_init, '_parent_with_init') - end -- if parent_ctor then -+ if parent_with_init then -- super() points to one above wherever _init came from - rawset(obj,'super',function(obj,...) -- call_ctor(base,obj,...) -+ call_ctor(parent_with_init,obj,...) - end) - end -+ else -+ -- Without this, calling super() where none exists will sometimes loop and stack overflow -+ rawset(obj,'super',nil) -+ end -+ -+ local res = init(obj,...) -+ if parent_with_init then -- If this execution of call_ctor set a super, unset it -+ rawset(obj,'super',nil) - end -- local res = c._init(obj,...) -- rawset(obj,'super',nil) -+ - return res - end - -@@ -60,7 +66,7 @@ end - -- if pussycat:is_a(Cat) then - -- -- it's true, it is a Lion, but also a Cat - -- end ---- -+-- - -- if pussycat:is_a() == Lion then - -- -- It's true - -- end -@@ -131,7 +137,7 @@ local function _class(base,c_arg,c) - else - c = c or {} - end -- -+ - if type(base) == 'table' then - -- our new class is a shallow copy of the base class! - -- but be careful not to wipe out any methods we have been given at this point! -@@ -146,6 +152,7 @@ local function _class(base,c_arg,c) - c.__index = c - setmetatable(c,mt) - if not plain then -+ if base and rawget(base,'_init') then c._parent_with_init = base end -- For super and inherited init - c._init = nil - end - -@@ -160,15 +167,12 @@ local function _class(base,c_arg,c) - if not obj then obj = {} end - setmetatable(obj,c) - -- if rawget(c,'_init') then -- explicit constructor -+ if rawget(c,'_init') or rawget(c,'_parent_with_init') then -- constructor exists - local res = call_ctor(c,obj,...) - if res then -- _if_ a ctor returns a value, it becomes the object... - obj = res - setmetatable(obj,c) - end -- elseif base and rawget(base,'_init') then -- default constructor -- -- make sure that any stuff from the base class is initialized! -- call_ctor(base,obj,...) - end - - if base and rawget(base,'_post_init') then -diff --git a/extra/penlight/lua/pl/compat.lua b/extra/penlight/lua/pl/compat.lua -index 58cf6b2..dfcb91d 100644 ---- a/extra/penlight/lua/pl/compat.lua -+++ b/extra/penlight/lua/pl/compat.lua -@@ -1,35 +1,58 @@ - ---------------- - --- Lua 5.1/5.2/5.3 compatibility. ---- Ensures that `table.pack` and `package.searchpath` are available ---- for Lua 5.1 and LuaJIT. ---- The exported function `load` is Lua 5.2 compatible. ---- `compat.setfenv` and `compat.getfenv` are available for Lua 5.2, although ---- they are not always guaranteed to work. -+-- Injects `table.pack`, `table.unpack`, and `package.searchpath` in the global -+-- environment, to make sure they are available for Lua 5.1 and LuaJIT. -+-- -+-- All other functions are exported as usual in the returned module table. -+-- -+-- NOTE: everything in this module is also available in `pl.utils`. - -- @module pl.compat -- - local compat = {} - -+--- boolean flag this is Lua 5.1 (or LuaJIT). -+-- @field lua51 - compat.lua51 = _VERSION == 'Lua 5.1' - --local isJit = (tostring(assert):match('builtin') ~= nil) --if isJit then -+--- boolean flag this is LuaJIT. -+-- @field jit -+compat.jit = (tostring(assert):match('builtin') ~= nil) -+ -+--- boolean flag this is LuaJIT with 5.2 compatibility compiled in. -+-- @field jit52 -+if compat.jit then - -- 'goto' is a keyword when 52 compatibility is enabled in LuaJit - compat.jit52 = not loadstring("local goto = 1") - end - -+--- the directory separator character for the current platform. -+-- @field dir_separator - compat.dir_separator = _G.package.config:sub(1,1) -+ -+--- boolean flag this is a Windows platform. -+-- @field is_windows - compat.is_windows = compat.dir_separator == '\\' - ----- execute a shell command. ---- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 -+--- execute a shell command, in a compatible and platform independent way. -+-- This is a compatibility function that returns the same for Lua 5.1 and -+-- Lua 5.2+. -+-- -+-- NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems -+-- only use exitcodes 0-255, anything else is undefined. -+-- -+-- NOTE2: In Lua 5.2 and 5.3 a Windows exitcode of -1 would not properly be -+-- returned, this function will return it properly for all versions. - -- @param cmd a shell command - -- @return true if successful - -- @return actual return code --function compat.execute (cmd) -- local res1,_,res3 = os.execute(cmd) -+function compat.execute(cmd) -+ local res1,res2,res3 = os.execute(cmd) -+ if res2 == "No error" and res3 == 0 and compat.is_windows then -+ -- os.execute bug in Lua 5.2/5.3 not reporting -1 properly on Windows -+ -- this was fixed in 5.4 -+ res3 = -1 -+ end - if compat.lua51 and not compat.jit52 then - if compat.is_windows then -- res1 = res1 > 255 and res1 % 256 or res1 - return res1==0,res1 - else - res1 = res1 > 255 and res1 / 256 or res1 -@@ -37,7 +60,6 @@ function compat.execute (cmd) - end - else - if compat.is_windows then -- res3 = res3 > 255 and res3 % 256 or res3 - return res3==0,res3 - else - return not not res1,res3 -@@ -46,7 +68,7 @@ function compat.execute (cmd) - end - - ---------------- ---- Load Lua code as a text or binary chunk. -+-- Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way). - -- @param ld code string or loader - -- @param[opt] source name of chunk for errors - -- @param[opt] mode 'b', 't' or 'bt' -@@ -54,20 +76,21 @@ end - -- @function compat.load - - --------------- ---- Get environment of a function. ---- With Lua 5.2, may return nil for a function with no global references! -+-- Get environment of a function (in a Lua 5.1 compatible way). -+-- Not 100% compatible, so with Lua 5.2 it may return nil for a function with no -+-- global references! - -- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html) - -- @param f a function or a call stack reference - -- @function compat.getfenv - - --------------- ---- Set environment of a function -+-- Set environment of a function (in a Lua 5.1 compatible way). - -- @param f a function or a call stack reference - -- @param env a table that becomes the new environment of `f` - -- @function compat.setfenv - - if compat.lua51 then -- define Lua 5.2 style load() -- if not isJit then -- but LuaJIT's load _is_ compatible -+ if not compat.jit then -- but LuaJIT's load _is_ compatible - local lua51_load = load - function compat.load(str,src,mode,env) - local chunk,err -@@ -122,36 +145,108 @@ else - end - end - ----- Lua 5.2 Functions Available for 5.1 -+ -+--- Global exported functions (for Lua 5.1 & LuaJIT) - -- @section lua52 - - --- pack an argument list into a table. - -- @param ... any arguments - -- @return a table with field n set to the length ---- @return the length - -- @function table.pack - if not table.pack then -- function table.pack (...) -+ function table.pack (...) -- luacheck: ignore - return {n=select('#',...); ...} - end - end - -------- ---- return the full path where a Lua module name would be matched. ---- @param mod module name, possibly dotted ---- @param path a path in the same form as package.path or package.cpath -+--- unpack a table and return the elements. -+-- -+-- NOTE: this version does NOT honor the n field, and hence it is not nil-safe. -+-- See `utils.unpack` for a version that is nil-safe. -+-- @param t table to unpack -+-- @param[opt] i index from which to start unpacking, defaults to 1 -+-- @param[opt] j index of the last element to unpack, defaults to #t -+-- @return multiple return values from the table -+-- @function table.unpack -+-- @see utils.unpack -+if not table.unpack then -+ table.unpack = unpack -- luacheck: ignore -+end -+ -+--- return the full path where a file name would be matched. -+-- This function was introduced in Lua 5.2, so this compatibility version -+-- will be injected in Lua 5.1 engines. -+-- @string name file name, possibly dotted -+-- @string path a path-template in the same form as package.path or package.cpath -+-- @string[opt] sep template separate character to be replaced by path separator. Default: "." -+-- @string[opt] rep the path separator to use, defaults to system separator. Default; "/" on Unixes, "\" on Windows. - -- @see path.package_path - -- @function package.searchpath -+-- @return on success: path of the file -+-- @return on failure: nil, error string listing paths tried - if not package.searchpath then -- local sep = package.config:sub(1,1) -- function package.searchpath (mod,path) -- mod = mod:gsub('%.',sep) -+ function package.searchpath (name,path,sep,rep) -- luacheck: ignore -+ if type(name) ~= "string" then -+ error(("bad argument #1 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) -+ end -+ if type(path) ~= "string" then -+ error(("bad argument #2 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) -+ end -+ if sep ~= nil and type(sep) ~= "string" then -+ error(("bad argument #3 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) -+ end -+ if rep ~= nil and type(rep) ~= "string" then -+ error(("bad argument #4 to 'searchpath' (string expected, got %s)"):format(type(path)), 2) -+ end -+ sep = sep or "." -+ rep = rep or compat.dir_separator -+ do -+ local s, e = name:find(sep, nil, true) -+ while s do -+ name = name:sub(1, s-1) .. rep .. name:sub(e+1, -1) -+ s, e = name:find(sep, s + #rep + 1, true) -+ end -+ end -+ local tried = {} - for m in path:gmatch('[^;]+') do -- local nm = m:gsub('?',mod) -+ local nm = m:gsub('?', name) -+ tried[#tried+1] = nm - local f = io.open(nm,'r') - if f then f:close(); return nm end - end -+ return nil, "\tno file '" .. table.concat(tried, "'\n\tno file '") .. "'" -+ end -+end -+ -+--- Global exported functions (for Lua < 5.4) -+-- @section lua54 -+ -+--- raise a warning message. -+-- This functions mimics the `warn` function added in Lua 5.4. -+-- @function warn -+-- @param ... any arguments -+if not rawget(_G, "warn") then -+ local enabled = false -+ local function warn(arg1, ...) -+ if type(arg1) == "string" and arg1:sub(1, 1) == "@" then -+ -- control message -+ if arg1 == "@on" then -+ enabled = true -+ return -+ end -+ if arg1 == "@off" then -+ enabled = false -+ return -+ end -+ return -- ignore unknown control messages -+ end -+ if enabled then -+ io.stderr:write("Lua warning: ", arg1, ...) -+ io.stderr:write("\n") -+ end - end -+ -- use rawset to bypass OpenResty's protection of global scope -+ rawset(_G, "warn", warn) - end - - return compat -diff --git a/extra/penlight/lua/pl/comprehension.lua b/extra/penlight/lua/pl/comprehension.lua -index 9d8aa59..39be7c5 100644 ---- a/extra/penlight/lua/pl/comprehension.lua -+++ b/extra/penlight/lua/pl/comprehension.lua -@@ -73,7 +73,6 @@ local ops = { - -- @usage "(x+y)^2 for x for y if x > y" -- nested - -- - local function parse_comprehension(expr) -- local t = {} - local pos = 1 - - -- extract opname (if exists) -diff --git a/extra/penlight/lua/pl/config.lua b/extra/penlight/lua/pl/config.lua -index a71c472..ebd531c 100644 ---- a/extra/penlight/lua/pl/config.lua -+++ b/extra/penlight/lua/pl/config.lua -@@ -48,9 +48,9 @@ end - - local config = {} - ----- like io.lines(), but allows for lines to be continued with '\'. -+--- like `io.lines`, but allows for lines to be continued with '`\\`'. - -- @param file a file-like object (anything where read() returns the next line) or a filename. ---- Defaults to stardard input. -+-- Defaults to standard input. - -- @return an iterator over the lines, or nil - -- @return error 'not a file-like object' or 'file is nil' - function config.lines(file) -@@ -93,7 +93,7 @@ end - -- @tab[opt] cnfg a configuration table that may contain these fields: - -- - -- * `smart` try to deduce what kind of config file we have (default false) ---- * `variablilize` make names into valid Lua identifiers (default true) -+-- * `variabilize` make names into valid Lua identifiers (default true) - -- * `convert_numbers` try to convert values into numbers (default true) - -- * `trim_space` ensure that there is no starting or trailing whitespace with values (default true) - -- * `trim_quotes` remove quotes from strings (default false) -@@ -103,7 +103,7 @@ end - -- @return a table containing items, or `nil` - -- @return error message (same as @{config.lines} - function config.read(file,cnfg) -- local f,openf,err,auto -+ local auto - - local iter,err = config.lines(file) - if not iter then return nil,err end -@@ -136,7 +136,7 @@ function config.read(file,cnfg) - local initial_digits = '^[%d%+%-]' - local t = {} - local top_t = t -- local variablilize = check_cnfg ('variabilize',true) -+ local variabilize = check_cnfg ('variabilize',true) - local list_delim = check_cnfg('list_delim',',') - local convert_numbers = check_cnfg('convert_numbers',true) - local convert_boolean = check_cnfg('convert_boolean',false) -@@ -148,7 +148,7 @@ function config.read(file,cnfg) - if list_delim == ' ' then list_delim = '%s+' end - - local function process_name(key) -- if variablilize then -+ if variabilize then - key = key:gsub('[^%w]','_') - end - return key -diff --git a/extra/penlight/lua/pl/data.lua b/extra/penlight/lua/pl/data.lua -index 84f26bd..67b8d71 100644 ---- a/extra/penlight/lua/pl/data.lua -+++ b/extra/penlight/lua/pl/data.lua -@@ -189,10 +189,6 @@ local function open_file (f,mode) - return f,nil,opened - end - --local function all_n () -- --end -- - --- read a delimited file in a Lua table. - -- By default, attempts to treat first line as separated list of fieldnames. - -- @param file a filename or a file-like object -@@ -210,7 +206,7 @@ end - -- @return error message. May be a file error, 'not a file-like object' - -- or a conversion error - function data.read(file,cnfg) -- local err,opened,count,line -+ local count,line - local D = {} - if not cnfg then cnfg = {} end - local f,err,opened = open_file(file,'r') -@@ -221,7 +217,7 @@ function data.read(file,cnfg) - - -- note that using dot as the thousands separator (@thousands_dot) - -- requires a special conversion function! For CSV, _empty fields_ are -- -- considered to default to numerial zeroes. -+ -- considered to default to numerical zeroes. - local tonumber = tonumber - local function try_number(x) - if thousands_dot then x = x:gsub('%.(...)','%1') end -@@ -473,7 +469,6 @@ end - - local function process_select (data,parms) - --- preparing fields ---- -- local res,ret - field_error = nil - local fields = parms.fields - local numfields = fields:find '%$' or #data.fieldnames == 0 -@@ -495,7 +490,7 @@ local function process_select (data,parms) - fields = rstrip(fields):gsub('[^,%w]','_') - end - local massage_fields = utils.bind1(massage_fields,data) -- ret = gsub(fields,idpat,massage_fields) -+ local ret = gsub(fields,idpat,massage_fields) - if field_error then return nil,field_error end - parms.fields = fields - parms.proc_fields = ret -@@ -555,7 +550,7 @@ function data.query(data,condn,context,return_row) - else - return nil, "condition must be a string or a table" - end -- local query, k -+ local query - if condn.sort_by then -- use sorted_query - query = sorted_query - else -@@ -565,7 +560,7 @@ function data.query(data,condn,context,return_row) - if return_row then - fields = '{'..fields..'}' - end -- query,k = query:gsub('FIELDLIST',fields) -+ query = query:gsub('FIELDLIST',fields) - if is_string(condn.where) then - query = query:gsub('CONDITION',condn.where) - condn.where = nil -@@ -636,7 +631,6 @@ end - -- @param outfile filename or file-like object - -- @bool dont_fail true if you want to return an error, not just fail - function data.filter (Q,infile,outfile,dont_fail) -- local err - local d = data.read(infile or 'stdin') - local out = open_file(outfile or 'stdout') - local iter,err = d:select(Q) -diff --git a/extra/penlight/lua/pl/dir.lua b/extra/penlight/lua/pl/dir.lua -index 6fc9b12..cde16dc 100644 ---- a/extra/penlight/lua/pl/dir.lua -+++ b/extra/penlight/lua/pl/dir.lua -@@ -15,10 +15,11 @@ local sub = string.sub - local os,pcall,ipairs,pairs,require,setmetatable = os,pcall,ipairs,pairs,require,setmetatable - local remove = os.remove - local append = table.insert --local wrap = coroutine.wrap --local yield = coroutine.yield - local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise - -+local exists, isdir = path.exists, path.isdir -+local sep = path.sep -+ - local dir = {} - - local function makelist(l) -@@ -51,7 +52,7 @@ end - - --- Return a list of all file names within an array which match a pattern. - -- @tab filenames An array containing file names. ---- @string pattern A shell pattern. -+-- @string pattern A shell pattern (see `fnmatch`). - -- @treturn List(string) List of matching file names. - -- @raise dir and mask must be strings - function dir.filter(filenames,pattern) -@@ -65,13 +66,13 @@ function dir.filter(filenames,pattern) - return makelist(res) - end - --local function _listfiles(dir,filemode,match) -+local function _listfiles(dirname,filemode,match) - local res = {} - local check = utils.choose(filemode,path.isfile,path.isdir) -- if not dir then dir = '.' end -- for f in ldir(dir) do -+ if not dirname then dirname = '.' end -+ for f in ldir(dirname) do - if f ~= '.' and f ~= '..' then -- local p = path.join(dir,f) -+ local p = path.join(dirname,f) - if check(p) and (not match or match(f)) then - append(res,p) - end -@@ -80,13 +81,14 @@ local function _listfiles(dir,filemode,match) - return makelist(res) - end - ----- return a list of all files in a directory which match the a shell pattern. ---- @string dir A directory. If not given, all files in current directory are returned. ---- @string mask A shell pattern. If not given, all files are returned. -+--- return a list of all files in a directory which match a shell pattern. -+-- @string[opt='.'] dirname A directory. -+-- @string[opt] mask A shell pattern (see `fnmatch`). If not given, all files are returned. - -- @treturn {string} list of files ---- @raise dir and mask must be strings --function dir.getfiles(dir,mask) -- assert_dir(1,dir) -+-- @raise dirname and mask must be strings -+function dir.getfiles(dirname,mask) -+ dirname = dirname or '.' -+ assert_dir(1,dirname) - if mask then assert_string(2,mask) end - local match - if mask then -@@ -95,16 +97,17 @@ function dir.getfiles(dir,mask) - return path.normcase(f):find(mask) - end - end -- return _listfiles(dir,true,match) -+ return _listfiles(dirname,true,match) - end - - --- return a list of all subdirectories of the directory. ---- @string dir A directory -+-- @string[opt='.'] dirname A directory. - -- @treturn {string} a list of directories ---- @raise dir must be a a valid directory --function dir.getdirectories(dir) -- assert_dir(1,dir) -- return _listfiles(dir,false) -+-- @raise dir must be a valid directory -+function dir.getdirectories(dirname) -+ dirname = dirname or '.' -+ assert_dir(1,dirname) -+ return _listfiles(dirname,false) - end - - local alien,ffi,ffi_checked,CopyFile,MoveFile,GetLastError,win32_errors,cmd_tmpfile -@@ -196,9 +199,10 @@ local function file_op (is_copy,src,dest,flag) - -- fallback if there's no Alien, just use DOS commands *shudder* - -- 'rename' involves a copy and then deleting the source. - if not CopyFile then -- src = path.normcase(src) -- dest = path.normcase(dest) -- local cmd = is_copy and 'copy' or 'rename' -+ if path.is_windows then -+ src = src:gsub("/","\\") -+ dest = dest:gsub("/","\\") -+ end - local res, err = execute_command('copy',two_arguments(src,dest)) - if not res then return false,err end - if not is_copy then -@@ -209,7 +213,7 @@ local function file_op (is_copy,src,dest,flag) - if path.isdir(dest) then - dest = path.join(dest,path.basename(src)) - end -- local ret -+ local ret - if is_copy then ret = CopyFile(src,dest,flag) - else ret = MoveFile(src,dest) end - if ret == 0 then -@@ -251,12 +255,12 @@ function dir.movefile (src,dest) - return file_op(false,src,dest,0) - end - --local function _dirfiles(dir,attrib) -+local function _dirfiles(dirname,attrib) - local dirs = {} - local files = {} -- for f in ldir(dir) do -+ for f in ldir(dirname) do - if f ~= '.' and f ~= '..' then -- local p = path.join(dir,f) -+ local p = path.join(dirname,f) - local mode = attrib(p,'mode') - if mode=='directory' then - append(dirs,f) -@@ -269,15 +273,6 @@ local function _dirfiles(dir,attrib) - end - - --local function _walker(root,bottom_up,attrib) -- local dirs,files = _dirfiles(root,attrib) -- if not bottom_up then yield(root,dirs,files) end -- for i,d in ipairs(dirs) do -- _walker(root..path.sep..d,bottom_up,attrib) -- end -- if bottom_up then yield(root,dirs,files) end --end -- - --- return an iterator which walks through a directory tree starting at root. - -- The iterator returns (root,dirs,files) - -- Note that dirs and files are lists of names (i.e. you must say path.join(root,d) -@@ -299,11 +294,33 @@ function dir.walk(root,bottom_up,follow_links) - else - attrib = path.link_attrib - end -- return wrap(function () _walker(root,bottom_up,attrib) end) -+ -+ local to_scan = { root } -+ local to_return = {} -+ local iter = function() -+ while #to_scan > 0 do -+ local current_root = table.remove(to_scan) -+ local dirs,files = _dirfiles(current_root, attrib) -+ for _, d in ipairs(dirs) do -+ table.insert(to_scan, current_root..path.sep..d) -+ end -+ if not bottom_up then -+ return current_root, dirs, files -+ else -+ table.insert(to_return, { current_root, dirs, files }) -+ end -+ end -+ if #to_return > 0 then -+ return utils.unpack(table.remove(to_return)) -+ end -+ end -+ -+ return iter - end - - --- remove a whole directory tree. ---- @string fullpath A directory path -+-- Symlinks in the tree will be deleted without following them. -+-- @string fullpath A directory path (must be an actual directory, not a symlink) - -- @return true or nil - -- @return error if failed - -- @raise fullpath must be a string -@@ -311,50 +328,70 @@ function dir.rmtree(fullpath) - assert_dir(1,fullpath) - if path.islink(fullpath) then return false,'will not follow symlink' end - for root,dirs,files in dir.walk(fullpath,true) do -- for i,f in ipairs(files) do -- local res, err = remove(path.join(root,f)) -- if not res then return nil,err end -+ if path.islink(root) then -+ -- sub dir is a link, remove link, do not follow -+ if is_windows then -+ -- Windows requires using "rmdir". Deleting the link like a file -+ -- will instead delete all files from the target directory!! -+ local res, err = rmdir(root) -+ if not res then return nil,err .. ": " .. root end -+ else -+ local res, err = remove(root) -+ if not res then return nil,err .. ": " .. root end -+ end -+ else -+ for i,f in ipairs(files) do -+ local res, err = remove(path.join(root,f)) -+ if not res then return nil,err .. ": " .. path.join(root,f) end -+ end -+ local res, err = rmdir(root) -+ if not res then return nil,err .. ": " .. root end - end -- local res, err = rmdir(root) -- if not res then return nil,err end - end - return true - end - --local dirpat --if path.is_windows then -- dirpat = '(.+)\\[^\\]+$' --else -- dirpat = '(.+)/[^/]+$' --end -- --local _makepath --function _makepath(p) -- -- windows root drive case -- if p:find '^%a:[\\]*$' then -- return true -- end -- if not path.isdir(p) then -- local subp = p:match(dirpat) -- local ok, err = _makepath(subp) -- if not ok then return nil, err end -- return mkdir(p) -- else -- return true -- end --end - ----- create a directory path. ---- This will create subdirectories as necessary! ---- @string p A directory path ---- @return true on success, nil + errormsg on failure ---- @raise failure to create --function dir.makepath (p) -- assert_string(1,p) -- return _makepath(path.normcase(path.abspath(p))) -+do -+ local dirpat -+ if path.is_windows then -+ dirpat = '(.+)\\[^\\]+$' -+ else -+ dirpat = '(.+)/[^/]+$' -+ end -+ -+ local _makepath -+ function _makepath(p) -+ -- windows root drive case -+ if p:find '^%a:[\\]*$' then -+ return true -+ end -+ if not path.isdir(p) then -+ local subp = p:match(dirpat) -+ if subp then -+ local ok, err = _makepath(subp) -+ if not ok then return nil, err end -+ end -+ return mkdir(p) -+ else -+ return true -+ end -+ end -+ -+ --- create a directory path. -+ -- This will create subdirectories as necessary! -+ -- @string p A directory path -+ -- @return true on success, nil + errormsg on failure -+ -- @raise failure to create -+ function dir.makepath (p) -+ assert_string(1,p) -+ if path.is_windows then -+ p = p:gsub("/", "\\") -+ end -+ return _makepath(path.abspath(p)) -+ end - end - -- - --- clone a directory tree. Will always try to create a new directory structure - -- if necessary. - -- @string path1 the base path of the source tree -@@ -379,7 +416,7 @@ function dir.clonetree (path1,path2,file_fun,verbose) - if verbose then verbose('normalized:',path1,path2) end - -- particularly NB that the new path isn't fully contained in the old path - if path1 == path2 then return raise "paths are the same" end -- local i1,i2 = path2:find(path1,1,true) -+ local _,i2 = path2:find(path1,1,true) - if i2 == #path1 and path2:sub(i2+1,i2+1) == path.sep then - return raise 'destination is a subdirectory of the source' - end -@@ -411,46 +448,65 @@ function dir.clonetree (path1,path2,file_fun,verbose) - return true,faildirs,failfiles - end - -+ -+-- each entry of the stack is an array with three items: -+-- 1. the name of the directory -+-- 2. the lfs iterator function -+-- 3. the lfs iterator userdata -+local function treeiter(iterstack) -+ local diriter = iterstack[#iterstack] -+ if not diriter then -+ return -- done -+ end -+ -+ local dirname = diriter[1] -+ local entry = diriter[2](diriter[3]) -+ if not entry then -+ table.remove(iterstack) -+ return treeiter(iterstack) -- tail-call to try next -+ end -+ -+ if entry ~= "." and entry ~= ".." then -+ entry = dirname .. sep .. entry -+ if exists(entry) then -- Just in case a symlink is broken. -+ local is_dir = isdir(entry) -+ if is_dir then -+ table.insert(iterstack, { entry, ldir(entry) }) -+ end -+ return entry, is_dir -+ end -+ end -+ -+ return treeiter(iterstack) -- tail-call to try next -+end -+ -+ - --- return an iterator over all entries in a directory tree - -- @string d a directory - -- @return an iterator giving pathname and mode (true for dir, false otherwise) - -- @raise d must be a non-empty string - function dir.dirtree( d ) - assert( d and d ~= "", "directory parameter is missing or empty" ) -- local exists, isdir = path.exists, path.isdir -- local sep = path.sep - - local last = sub ( d, -1 ) - if last == sep or last == '/' then - d = sub( d, 1, -2 ) - end - -- local function yieldtree( dir ) -- for entry in ldir( dir ) do -- if entry ~= "." and entry ~= ".." then -- entry = dir .. sep .. entry -- if exists(entry) then -- Just in case a symlink is broken. -- local is_dir = isdir(entry) -- yield( entry, is_dir ) -- if is_dir then -- yieldtree( entry ) -- end -- end -- end -- end -- end -+ local iterstack = { {d, ldir(d)} } - -- return wrap( function() yieldtree( d ) end ) -+ return treeiter, iterstack - end - - ----- Recursively returns all the file starting at _path_. It can optionally take a shell pattern and ---- only returns files that match _shell_pattern_. If a pattern is given it will do a case insensitive search. ---- @string start_path A directory. If not given, all files in current directory are returned. ---- @string shell_pattern A shell pattern. If not given, all files are returned. ---- @treturn List(string) containing all the files found recursively starting at _path_ and filtered by _shell_pattern_. -+--- Recursively returns all the file starting at 'path'. It can optionally take a shell pattern and -+-- only returns files that match 'shell_pattern'. If a pattern is given it will do a case insensitive search. -+-- @string[opt='.'] start_path A directory. -+-- @string[opt='*'] shell_pattern A shell pattern (see `fnmatch`). -+-- @treturn List(string) containing all the files found recursively starting at 'path' and filtered by 'shell_pattern'. - -- @raise start_path must be a directory - function dir.getallfiles( start_path, shell_pattern ) -+ start_path = start_path or '.' - assert_dir(1,start_path) - shell_pattern = shell_pattern or "*" - -diff --git a/extra/penlight/lua/pl/file.lua b/extra/penlight/lua/pl/file.lua -index 572d7fc..b8058c4 100644 ---- a/extra/penlight/lua/pl/file.lua -+++ b/extra/penlight/lua/pl/file.lua -@@ -1,5 +1,8 @@ - --- File manipulation functions: reading, writing, moving and copying. - -- -+-- This module wraps a number of functions from other modules into a -+-- file related module for convenience. -+-- - -- Dependencies: `pl.utils`, `pl.dir`, `pl.path` - -- @module pl.file - local os = os -@@ -7,56 +10,46 @@ local utils = require 'pl.utils' - local dir = require 'pl.dir' - local path = require 'pl.path' - ----[[ --module ('pl.file',utils._module) --]] - local file = {} - ----- return the contents of a file as a string -+--- return the contents of a file as a string. -+-- This function is a copy of `utils.readfile`. - -- @function file.read ---- @string filename The file path ---- @return file contents - file.read = utils.readfile - ----- write a string to a file -+--- write a string to a file. -+-- This function is a copy of `utils.writefile`. - -- @function file.write ---- @string filename The file path ---- @string str The string - file.write = utils.writefile - - --- copy a file. -+-- This function is a copy of `dir.copyfile`. - -- @function file.copy ---- @string src source file ---- @string dest destination file ---- @bool flag true if you want to force the copy (default) ---- @return true if operation succeeded - file.copy = dir.copyfile - - --- move a file. -+-- This function is a copy of `dir.movefile`. - -- @function file.move ---- @string src source file ---- @string dest destination file ---- @return true if operation succeeded, else false and the reason for the error. - file.move = dir.movefile - - --- Return the time of last access as the number of seconds since the epoch. -+-- This function is a copy of `path.getatime`. - -- @function file.access_time ---- @string path A file path - file.access_time = path.getatime - - ---Return when the file was created. -+-- This function is a copy of `path.getctime`. - -- @function file.creation_time ---- @string path A file path - file.creation_time = path.getctime - ----- Return the time of last modification -+--- Return the time of last modification. -+-- This function is a copy of `path.getmtime`. - -- @function file.modified_time ---- @string path A file path - file.modified_time = path.getmtime - ----- Delete a file -+--- Delete a file. -+-- This function is a copy of `os.remove`. - -- @function file.delete ---- @string path A file path - file.delete = os.remove - - return file -diff --git a/extra/penlight/lua/pl/func.lua b/extra/penlight/lua/pl/func.lua -index 28c01d8..4fdfb46 100644 ---- a/extra/penlight/lua/pl/func.lua -+++ b/extra/penlight/lua/pl/func.lua -@@ -21,7 +21,7 @@ local type,setmetatable,getmetatable,rawset = type,setmetatable,getmetatable,raw - local concat,append = table.concat,table.insert - local tostring = tostring - local utils = require 'pl.utils' --local pairs,rawget,unpack = pairs,rawget,utils.unpack -+local pairs,rawget,unpack,pack = pairs,rawget,utils.unpack,utils.pack - local tablex = require 'pl.tablex' - local map = tablex.map - local _DEBUG = rawget(_G,'_DEBUG') -@@ -88,7 +88,7 @@ function _PEMT.__tostring (e) - end - - function _PEMT.__unm(arg) -- return P{op='-',arg} -+ return P{op='unm',arg} - end - - function func.Not (arg) -@@ -159,16 +159,22 @@ function func.Args (...) - return P{op='()',_arg,...} - end - ---- binary and unary operators, with their precedences (see 2.5.6) --local operators = { -+-- binary operators with their precedences (see Lua manual) -+-- precedences might be incremented by one before use depending on -+-- left- or right-associativity, space them out -+local binary_operators = { - ['or'] = 0, -- ['and'] = 1, -- ['=='] = 2, ['~='] = 2, ['<'] = 2, ['>'] = 2, ['<='] = 2, ['>='] = 2, -- ['..'] = 3, -- ['+'] = 4, ['-'] = 4, -- ['*'] = 5, ['/'] = 5, ['%'] = 5, -- ['not'] = 6, ['#'] = 6, ['-'] = 6, -- ['^'] = 7 -+ ['and'] = 2, -+ ['=='] = 4, ['~='] = 4, ['<'] = 4, ['>'] = 4, ['<='] = 4, ['>='] = 4, -+ ['..'] = 6, -+ ['+'] = 8, ['-'] = 8, -+ ['*'] = 10, ['/'] = 10, ['%'] = 10, -+ ['^'] = 14 -+} -+ -+-- unary operators with their precedences -+local unary_operators = { -+ ['not'] = 12, ['#'] = 12, ['unm'] = 12 - } - - -- comparisons (as prefix functions) -@@ -196,19 +202,31 @@ end - function repr (e,lastpred) - local tail = func.tail - if isPE(e) then -- local pred = operators[e.op] -- local ls = map(repr,e,pred) -- if pred then --unary or binary operator -- if #ls ~= 1 then -- local s = concat(ls,' '..e.op..' ') -- if lastpred and lastpred > pred then -- s = '('..s..')' -+ local pred = binary_operators[e.op] or unary_operators[e.op] -+ if pred then -+ -- binary or unary operator -+ local s -+ if binary_operators[e.op] then -+ local left_pred = pred -+ local right_pred = pred -+ if e.op == '..' or e.op == '^' then -+ left_pred = left_pred + 1 -+ else -+ right_pred = right_pred + 1 - end -- return s -+ local left_arg = repr(e[1], left_pred) -+ local right_arg = repr(e[2], right_pred) -+ s = left_arg..' '..e.op..' '..right_arg - else -- return e.op..' '..ls[1] -+ local op = e.op == 'unm' and '-' or e.op -+ s = op..' '..repr(e[1], pred) -+ end -+ if lastpred and lastpred > pred then -+ s = '('..s..')' - end -+ return s - else -- either postfix, or a placeholder -+ local ls = map(repr,e) - if e.op == '[]' then - return ls[1]..'['..ls[2]..']' - elseif e.op == '()' then -@@ -234,7 +252,7 @@ function repr (e,lastpred) - end - func.repr = repr - ---- collect all the non-PE values in this PE into vlist, and replace each occurence -+-- collect all the non-PE values in this PE into vlist, and replace each occurrence - -- with a constant PH (_C1, etc). Return the maximum placeholder index found. - local collect_values - function collect_values (e,vlist) -@@ -318,13 +336,20 @@ utils.add_function_factory(_PEMT,func.I) - func.bind1 = utils.bind1 - func.curry = func.bind1 - ----- create a function which chains two functions. -+--- create a function which chains multiple functions. - -- @func f a function of at least one argument - -- @func g a function of at least one argument -+-- @param ... additional functions to compose - -- @return a function ---- @usage printf = compose(io.write,string.format) --function func.compose (f,g) -- return function(...) return f(g(...)) end -+-- @usage printf = compose(io.write, string.format) -+-- @usage printf = compose(io.write, string.lower, string.format) -+function func.compose (...) -+ local args = pack(...) -+ return tablex.reduce(function(f, g) -+ return function(...) -+ return f(g(...)) -+ end -+ end, args) - end - - --- bind the arguments of a function to given values. -@@ -335,7 +360,7 @@ end - -- @usage (bind(f,_1,a))(b) == f(a,b) - -- @usage (bind(f,_2,_1))(a,b) == f(b,a) - function func.bind(fn,...) -- local args = table.pack(...) -+ local args = pack(...) - local holders,parms,bvalues,values = {},{},{'fn'},{} - local nv,maxplace,varargs = 1,0,false - for i = 1,args.n do -@@ -365,7 +390,7 @@ return function (%s) - end - ]]):format(bvalues,parms,holders) - if _DEBUG then print(fstr) end -- local res,err = utils.load(fstr) -+ local res = utils.load(fstr) - res = res() - return res(fn,unpack(values)) - end -diff --git a/extra/penlight/lua/pl/import_into.lua b/extra/penlight/lua/pl/import_into.lua -index 7ef54f8..214b0f8 100644 ---- a/extra/penlight/lua/pl/import_into.lua -+++ b/extra/penlight/lua/pl/import_into.lua -@@ -1,6 +1,6 @@ - -------------- - -- PL loader, for loading all PL libraries, only on demand. ---- Whenever a module is implicitly accesssed, the table will have the module automatically injected. -+-- Whenever a module is implicitly accessed, the table will have the module automatically injected. - -- (e.g. `_ENV.tablex`) - -- then that module is dynamically loaded. The submodules are all brought into - -- the table that is provided as the argument, or returned in a new table. -@@ -17,64 +17,66 @@ return function(env) - mod = {} - env = {} - end -- local env = env or {} -+ local env = env or {} - -- local modules = { -- utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true, -- input=true,seq=true,lexer=true,stringx=true, -- config=true,pretty=true,data=true,func=true,text=true, -- operator=true,lapp=true,array2d=true, -- comprehension=true,xml=true,types=true, -- test = true, app = true, file = true, class = true, List = true, -- Map = true, Set = true, OrderedMap = true, MultiMap = true, -- Date = true, -- -- classes -- -- } -- rawset(env,'utils',require 'pl.utils') -+ local modules = { -+ utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true, -+ input=true,seq=true,lexer=true,stringx=true, -+ config=true,pretty=true,data=true,func=true,text=true, -+ operator=true,lapp=true,array2d=true, -+ comprehension=true,xml=true,types=true, -+ test = true, app = true, file = true, class = true, -+ luabalanced = true, permute = true, template = true, -+ url = true, compat = true, -+ -- classes -- -+ List = true, Map = true, Set = true, -+ OrderedMap = true, MultiMap = true, Date = true, -+ } -+ rawset(env,'utils',require 'pl.utils') - -- for name,klass in pairs(env.utils.stdmt) do -- klass.__index = function(t,key) -- return require ('pl.'..name)[key] -- end; -- end -+ for name,klass in pairs(env.utils.stdmt) do -+ klass.__index = function(t,key) -+ return require ('pl.'..name)[key] -+ end; -+ end - -- -- ensure that we play nice with libraries that also attach a metatable -- -- to the global table; always forward to a custom __index if we don't -- -- match -+ -- ensure that we play nice with libraries that also attach a metatable -+ -- to the global table; always forward to a custom __index if we don't -+ -- match - -- local _hook,_prev_index -- local gmt = {} -- local prevenvmt = getmetatable(env) -- if prevenvmt then -- _prev_index = prevenvmt.__index -- if prevenvmt.__newindex then -- gmt.__index = prevenvmt.__newindex -- end -- end -+ local _hook,_prev_index -+ local gmt = {} -+ local prevenvmt = getmetatable(env) -+ if prevenvmt then -+ _prev_index = prevenvmt.__index -+ if prevenvmt.__newindex then -+ gmt.__newindex = prevenvmt.__newindex -+ end -+ end - -- function gmt.hook(handler) -- _hook = handler -- end -+ function gmt.hook(handler) -+ _hook = handler -+ end - -- function gmt.__index(t,name) -- local found = modules[name] -- -- either true, or the name of the module containing this class. -- -- either way, we load the required module and make it globally available. -- if found then -- -- e..g pretty.dump causes pl.pretty to become available as 'pretty' -- rawset(env,name,require('pl.'..name)) -- return env[name] -- else -- local res -- if _hook then -- res = _hook(t,name) -- if res then return res end -- end -- if _prev_index then -- return _prev_index(t,name) -- end -- end -- end -+ function gmt.__index(t,name) -+ local found = modules[name] -+ -- either true, or the name of the module containing this class. -+ -- either way, we load the required module and make it globally available. -+ if found then -+ -- e..g pretty.dump causes pl.pretty to become available as 'pretty' -+ rawset(env,name,require('pl.'..name)) -+ return env[name] -+ else -+ local res -+ if _hook then -+ res = _hook(t,name) -+ if res then return res end -+ end -+ if _prev_index then -+ return _prev_index(t,name) -+ end -+ end -+ end - - if mod then - function gmt.__newindex(t,name,value) -@@ -83,7 +85,7 @@ return function(env) - end - end - -- setmetatable(env,gmt) -+ setmetatable(env,gmt) - -- return env,mod or env -+ return env,mod or env - end -diff --git a/extra/penlight/lua/pl/init.lua b/extra/penlight/lua/pl/init.lua -index c27a890..2c97b70 100644 ---- a/extra/penlight/lua/pl/init.lua -+++ b/extra/penlight/lua/pl/init.lua -@@ -1,6 +1,6 @@ - -------------- - -- Entry point for loading all PL libraries only on demand, into the global space. ---- Requiring 'pl' means that whenever a module is implicitly accesssed -+-- Requiring 'pl' means that whenever a module is implicitly accessed - -- (e.g. `utils.split`) - -- then that module is dynamically loaded. The submodules are all brought into - -- the global space. -diff --git a/extra/penlight/lua/pl/lapp.lua b/extra/penlight/lua/pl/lapp.lua -index 5ef42e9..3bd752d 100644 ---- a/extra/penlight/lua/pl/lapp.lua -+++ b/extra/penlight/lua/pl/lapp.lua -@@ -33,11 +33,10 @@ local function lines(s) return s:gmatch('([^\n]*)\n') end - local function lstrip(str) return str:gsub('^%s+','') end - local function strip(str) return lstrip(str):gsub('%s+$','') end - local function at(s,k) return s:sub(k,k) end --local function isdigit(s) return s:find('^%d+$') == 1 end - - local lapp = {} - --local open_files,parms,aliases,parmlist,usage,windows,script -+local open_files,parms,aliases,parmlist,usage,script - - lapp.callback = false -- keep Strict happy - -@@ -182,7 +181,6 @@ end - -- @return a table with parameter-value pairs - function lapp.process_options_string(str,args) - local results = {} -- local opts = {at_start=true} - local varargs - local arg = args or _G.arg - open_files = {} -@@ -219,7 +217,7 @@ function lapp.process_options_string(str,args) - - for line in lines(str) do - local res = {} -- local optspec,optparm,i1,i2,defval,vtype,constraint,rest -+ local optparm,defval,vtype,constraint,rest - line = lstrip(line) - local function check(str) - return match(str,line,res) -@@ -239,7 +237,8 @@ function lapp.process_options_string(str,args) - elseif check '$<{name} $' then -- is it ? - -- so becomes input_file ... - optparm,rest = res.name:match '([^%.]+)(.*)' -- optparm = optparm:gsub('%A','_') -+ -- follow lua legal variable names -+ optparm = optparm:sub(1,1):gsub('%A','_') .. optparm:sub(2):gsub('%W', '_') - varargs = rest == '...' - append(parmlist,optparm) - end -@@ -248,6 +247,7 @@ function lapp.process_options_string(str,args) - line = res.rest - res = {} - local optional -+ local defval_str - -- do we have ([optional] [] [default ])? - if match('$({def} $',line,res) or match('$({def}',line,res) then - local typespec = strip(res.def) -@@ -279,7 +279,7 @@ function lapp.process_options_string(str,args) - local enump = '|' .. enums .. '|' - vtype = 'string' - constraint = function(s) -- lapp.assert(enump:match('|'..s..'|'), -+ lapp.assert(enump:find('|'..s..'|', 1, true), - "value '"..s.."' not in "..enums - ) - end -@@ -290,6 +290,7 @@ function lapp.process_options_string(str,args) - -- optional 'default value' clause. Type is inferred as - -- 'string' or 'number' if there's no explicit type - if default or match('default $r{rest}',typespec,res) then -+ defval_str = res.rest - defval,vtype = process_default(res.rest,vtype) - end - else -- must be a plain flag, no extra parameter required -@@ -299,6 +300,7 @@ function lapp.process_options_string(str,args) - local ps = { - type = vtype, - defval = defval, -+ defval_str = defval_str, - required = defval == nil and not optional, - comment = res.rest or optparm, - constraint = constraint, -@@ -314,7 +316,7 @@ function lapp.process_options_string(str,args) - end - ps.constraint = types[vtype].constraint - elseif not builtin_types[vtype] and vtype then -- lapp.error(vtype.." is unknown type") -+ lapp.error(vtype.." is unknown type") - end - parms[optparm] = ps - end -@@ -428,6 +430,9 @@ function lapp.process_options_string(str,args) - if not ps.used then - if ps.required then lapp.error("missing required parameter: "..parm) end - set_result(ps,parm,ps.defval) -+ if builtin_types[ps.type] == "file" then -+ set_result(ps, parm .. "_name", ps.defval_str) -+ end - end - end - return results -diff --git a/extra/penlight/lua/pl/lexer.lua b/extra/penlight/lua/pl/lexer.lua -index 071a62b..9219716 100644 ---- a/extra/penlight/lua/pl/lexer.lua -+++ b/extra/penlight/lua/pl/lexer.lua -@@ -20,11 +20,11 @@ - -- See the Guide for further @{06-data.md.Lexical_Scanning|discussion} - -- @module pl.lexer - --local yield,wrap = coroutine.yield,coroutine.wrap - local strfind = string.find - local strsub = string.sub - local append = table.insert - -+ - local function assert_arg(idx,val,tp) - if type(val) ~= tp then - error("argument "..idx.." must be "..tp, 2) -@@ -33,11 +33,15 @@ end - - local lexer = {} - --local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' --local NUMBER2 = '^[%+%-]?%d+%.?%d*' --local NUMBER3 = '^0x[%da-fA-F]+' --local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' --local NUMBER5 = '^%d+%.?%d*' -+local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' -+local NUMBER1a = '^[%+%-]?%d*%.%d+[eE][%+%-]?%d+' -+local NUMBER2 = '^[%+%-]?%d+%.?%d*' -+local NUMBER2a = '^[%+%-]?%d*%.%d+' -+local NUMBER3 = '^0x[%da-fA-F]+' -+local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' -+local NUMBER4a = '^%d*%.%d+[eE][%+%-]?%d+' -+local NUMBER5 = '^%d+%.?%d*' -+local NUMBER5a = '^%d*%.%d+' - local IDEN = '^[%a_][%w_]*' - local WSPACE = '^%s+' - local STRING1 = "^(['\"])%1" -- empty string -@@ -51,14 +55,14 @@ local PREPRO = '^#.-[^\\]\n' - local plain_matches,lua_matches,cpp_matches,lua_keyword,cpp_keyword - - local function tdump(tok) -- return yield(tok,tok) -+ return tok,tok - end - - local function ndump(tok,options) - if options and options.number then - tok = tonumber(tok) - end -- return yield("number",tok) -+ return "number",tok - end - - -- regular strings, single or double quotes; usually we want them -@@ -67,7 +71,7 @@ local function sdump(tok,options) - if options and options.string then - tok = tok:sub(2,-2) - end -- return yield("string",tok) -+ return "string",tok - end - - -- long Lua strings need extra work to get rid of the quotes -@@ -82,45 +86,45 @@ local function sdump_l(tok,options,findres) - tok = tok:sub(2) - end - end -- return yield("string",tok) -+ return "string",tok - end - - local function chdump(tok,options) - if options and options.string then - tok = tok:sub(2,-2) - end -- return yield("char",tok) -+ return "char",tok - end - - local function cdump(tok) -- return yield('comment',tok) -+ return "comment",tok - end - - local function wsdump (tok) -- return yield("space",tok) -+ return "space",tok - end - - local function pdump (tok) -- return yield('prepro',tok) -+ return "prepro",tok - end - - local function plain_vdump(tok) -- return yield("iden",tok) -+ return "iden",tok - end - - local function lua_vdump(tok) - if lua_keyword[tok] then -- return yield("keyword",tok) -+ return "keyword",tok - else -- return yield("iden",tok) -+ return "iden",tok - end - end - - local function cpp_vdump(tok) - if cpp_keyword[tok] then -- return yield("keyword",tok) -+ return "keyword",tok - else -- return yield("iden",tok) -+ return "iden",tok - end - end - -@@ -149,7 +153,9 @@ function lexer.scan(s,matches,filter,options) - {NUMBER3,ndump}, - {IDEN,plain_vdump}, - {NUMBER1,ndump}, -+ {NUMBER1a,ndump}, - {NUMBER2,ndump}, -+ {NUMBER2a,ndump}, - {STRING1,sdump}, - {STRING2,sdump}, - {STRING3,sdump}, -@@ -158,45 +164,60 @@ function lexer.scan(s,matches,filter,options) - end - matches = plain_matches - end -- local function lex(first_arg) -- local line_nr = 0 -- local next_line = file and file:read() -- local sz = file and 0 or #s -- local idx = 1 -- -- -- res is the value used to resume the coroutine. -- local function handle_requests(res) -- while res do -- local tp = type(res) -- -- insert a token list -- if tp == 'table' then -- res = yield('','') -- for _,t in ipairs(res) do -- res = yield(t[1],t[2]) -- end -- elseif tp == 'string' then -- or search up to some special pattern -- local i1,i2 = strfind(s,res,idx) -- if i1 then -- local tok = strsub(s,i1,i2) -- idx = i2 + 1 -- res = yield('',tok) -- else -- res = yield('','') -- idx = sz + 1 -- end -- else -- res = yield(line_nr,idx) -- end -+ -+ local line_nr = 0 -+ local next_line = file and file:read() -+ local sz = file and 0 or #s -+ local idx = 1 -+ -+ local tlist_i -+ local tlist -+ -+ local first_hit = true -+ -+ local function iter(res) -+ local tp = type(res) -+ -+ if tlist then -- returning the inserted token list -+ local cur = tlist[tlist_i] -+ if cur then -+ tlist_i = tlist_i + 1 -+ return cur[1], cur[2] -+ else -+ tlist = nil - end - end - -- handle_requests(first_arg) -- if not file then line_nr = 1 end -+ if tp == 'string' then -- search up to some special pattern -+ local i1,i2 = strfind(s,res,idx) -+ if i1 then -+ local tok = strsub(s,i1,i2) -+ idx = i2 + 1 -+ return '', tok -+ else -+ idx = sz + 1 -+ return '', '' -+ end -+ -+ elseif tp == 'table' then -- insert a token list -+ tlist_i = 1 -+ tlist = res -+ return '', '' -+ -+ elseif tp ~= 'nil' then -- return position -+ return line_nr, idx -+ -+ else -- look for next token -+ if first_hit then -+ if not file then line_nr = 1 end -+ first_hit = false -+ end - -- while true do - if idx > sz then - if file then -- if not next_line then return end -+ if not next_line then -+ return -- past the end of file, done -+ end - s = next_line - line_nr = line_nr + 1 - next_line = file:read() -@@ -205,9 +226,7 @@ function lexer.scan(s,matches,filter,options) - end - idx, sz = 1, #s - else -- while true do -- handle_requests(yield()) -- end -+ return -- past the end of input, done - end - end - -@@ -219,23 +238,27 @@ function lexer.scan(s,matches,filter,options) - if i1 then - local tok = strsub(s,i1,i2) - idx = i2 + 1 -- local res -+ local ret1, ret2 - if not (filter and filter[fun]) then - lexer.finished = idx > sz -- res = fun(tok, options, findres) -+ ret1, ret2 = fun(tok, options, findres) - end - if not file and tok:find("\n") then - -- Update line number. - local _, newlines = tok:gsub("\n", {}) - line_nr = line_nr + newlines - end -- handle_requests(res) -- break -+ if ret1 then -+ return ret1, ret2 -- found a match -+ else -+ return iter() -- tail-call to try again -+ end - end - end - end - end -- return wrap(lex) -+ -+ return iter - end - - local function isstring (s) -@@ -267,7 +290,7 @@ end - -- @param tok a token stream - -- @return a string - function lexer.getline (tok) -- local t,v = tok('.-\n') -+ local _,v = tok('.-\n') - return v - end - -@@ -284,7 +307,7 @@ end - -- @param tok a token stream - -- @return a string - function lexer.getrest (tok) -- local t,v = tok('.+') -+ local _,v = tok('.+') - return v - end - -@@ -321,7 +344,9 @@ function lexer.lua(s,filter,options) - {NUMBER3,ndump}, - {IDEN,lua_vdump}, - {NUMBER4,ndump}, -+ {NUMBER4a,ndump}, - {NUMBER5,ndump}, -+ {NUMBER5a,ndump}, - {STRING1,sdump}, - {STRING2,sdump}, - {STRING3,sdump}, -@@ -371,7 +396,9 @@ function lexer.cpp(s,filter,options) - {NUMBER3,ndump}, - {IDEN,cpp_vdump}, - {NUMBER4,ndump}, -+ {NUMBER4a,ndump}, - {NUMBER5,ndump}, -+ {NUMBER5a,ndump}, - {CHAR1,chdump}, - {CHAR2,chdump}, - {CHAR3,chdump}, -diff --git a/extra/penlight/lua/pl/luabalanced.lua b/extra/penlight/lua/pl/luabalanced.lua -index a75f6fd..a1f7dc6 100644 ---- a/extra/penlight/lua/pl/luabalanced.lua -+++ b/extra/penlight/lua/pl/luabalanced.lua -@@ -130,6 +130,7 @@ local wordop = {['and']=true, ['or']=true, ['not']=true} - local is_compare = {['>']=true, ['<']=true, ['~']=true} - local function match_expression(s, pos) - pos = pos or 1 -+ local _ - local posa = pos - local lastident - local poscs, posce -@@ -149,7 +150,7 @@ local function match_expression(s, pos) - posce = pos - end - elseif c == '(' or c == '{' or c == '[' then -- local part; part, pos = match_bracketed(s, pos) -+ _, pos = match_bracketed(s, pos) - elseif c == '=' and s:sub(pos+1,pos+1) == '=' then - pos = pos + 2 -- skip over two-char op containing '=' - elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then -diff --git a/extra/penlight/lua/pl/path.lua b/extra/penlight/lua/pl/path.lua -index 7c2fe31..011b979 100644 ---- a/extra/penlight/lua/pl/path.lua -+++ b/extra/penlight/lua/pl/path.lua -@@ -2,6 +2,12 @@ - -- - -- This is modelled after Python's os.path library (10.1); see @{04-paths.md|the Guide}. - -- -+-- NOTE: the functions assume the paths being dealt with to originate -+-- from the OS the application is running on. Windows drive letters are not -+-- to be used when running on a Unix system for example. The one exception -+-- is Windows paths to allow both forward and backward slashes (since Lua -+-- also accepts those) -+-- - -- Dependencies: `pl.utils`, `lfs` - -- @module pl.path - -@@ -10,66 +16,125 @@ local _G = _G - local sub = string.sub - local getenv = os.getenv - local tmpnam = os.tmpname --local attributes, currentdir, link_attrib - local package = package - local append, concat, remove = table.insert, table.concat, table.remove - local utils = require 'pl.utils' - local assert_string,raise = utils.assert_string,utils.raise - --local attrib --local path = {} -- - local res,lfs = _G.pcall(_G.require,'lfs') --if res then -- attributes = lfs.attributes -- currentdir = lfs.currentdir -- link_attrib = lfs.symlinkattributes --else -+if not res then - error("pl.path requires LuaFileSystem") - end - --attrib = attributes --path.attrib = attrib --path.link_attrib = link_attrib -+local attrib = lfs.attributes -+local currentdir = lfs.currentdir -+local link_attrib = lfs.symlinkattributes -+ -+local path = {} -+ -+local function err_func(name, param, err, code) -+ local ret = ("%s failed"):format(tostring(name)) -+ if param ~= nil then -+ ret = ret .. (" for '%s'"):format(tostring(param)) -+ end -+ ret = ret .. (": %s"):format(tostring(err)) -+ if code ~= nil then -+ ret = ret .. (" (code %s)"):format(tostring(code)) -+ end -+ return ret -+end - - --- Lua iterator over the entries of a given directory. ---- Behaves like `lfs.dir` -+-- Implicit link to [`luafilesystem.dir`](https://lunarmodules.github.io/luafilesystem/manual.html#dir) -+-- @function dir - path.dir = lfs.dir - - --- Creates a directory. --path.mkdir = lfs.mkdir -+-- Implicit link to [`luafilesystem.mkdir`](https://lunarmodules.github.io/luafilesystem/manual.html#mkdir) -+-- @function mkdir -+path.mkdir = function(d) -+ local ok, err, code = lfs.mkdir(d) -+ if not ok then -+ return ok, err_func("mkdir", d, err, code), code -+ end -+ return ok, err, code -+end - - --- Removes a directory. --path.rmdir = lfs.rmdir -+-- Implicit link to [`luafilesystem.rmdir`](https://lunarmodules.github.io/luafilesystem/manual.html#rmdir) -+-- @function rmdir -+path.rmdir = function(d) -+ local ok, err, code = lfs.rmdir(d) -+ if not ok then -+ return ok, err_func("rmdir", d, err, code), code -+ end -+ return ok, err, code -+end - ------ Get the working directory. --path.currentdir = currentdir -+--- Gets attributes. -+-- Implicit link to [`luafilesystem.attributes`](https://lunarmodules.github.io/luafilesystem/manual.html#attributes) -+-- @function attrib -+path.attrib = function(d, r) -+ local ok, err, code = attrib(d, r) -+ if not ok then -+ return ok, err_func("attrib", d, err, code), code -+ end -+ return ok, err, code -+end - ----- Changes the working directory. --path.chdir = lfs.chdir -+--- Get the working directory. -+-- Implicit link to [`luafilesystem.currentdir`](https://lunarmodules.github.io/luafilesystem/manual.html#currentdir) -+-- @function currentdir -+path.currentdir = function() -+ local ok, err, code = currentdir() -+ if not ok then -+ return ok, err_func("currentdir", nil, err, code), code -+ end -+ return ok, err, code -+end - -+--- Gets symlink attributes. -+-- Implicit link to [`luafilesystem.symlinkattributes`](https://lunarmodules.github.io/luafilesystem/manual.html#symlinkattributes) -+-- @function link_attrib -+path.link_attrib = function(d, r) -+ local ok, err, code = link_attrib(d, r) -+ if not ok then -+ return ok, err_func("link_attrib", d, err, code), code -+ end -+ return ok, err, code -+end -+ -+--- Changes the working directory. -+-- On Windows, if a drive is specified, it also changes the current drive. If -+-- only specifying the drive, it will only switch drive, but not modify the path. -+-- Implicit link to [`luafilesystem.chdir`](https://lunarmodules.github.io/luafilesystem/manual.html#chdir) -+-- @function chdir -+path.chdir = function(d) -+ local ok, err, code = lfs.chdir(d) -+ if not ok then -+ return ok, err_func("chdir", d, err, code), code -+ end -+ return ok, err, code -+end - - --- is this a directory? - -- @string P A file path - function path.isdir(P) -- assert_string(1,P) -- if P:match("\\$") then -- P = P:sub(1,-2) -- end -+ assert_string(1,P) - return attrib(P,'mode') == 'directory' - end - ----- is this a file?. -+--- is this a file? - -- @string P A file path - function path.isfile(P) -- assert_string(1,P) -+ assert_string(1,P) - return attrib(P,'mode') == 'file' - end - - -- is this a symbolic link? - -- @string P A file path - function path.islink(P) -- assert_string(1,P) -+ assert_string(1,P) - if link_attrib then - return link_attrib(P,'mode')=='link' - else -@@ -80,35 +145,36 @@ end - --- return size of a file. - -- @string P A file path - function path.getsize(P) -- assert_string(1,P) -+ assert_string(1,P) - return attrib(P,'size') - end - ----- does a path exist?. -+--- does a path exist? - -- @string P A file path ---- @return the file path if it exists, nil otherwise -+-- @return the file path if it exists (either as file, directory, socket, etc), false otherwise - function path.exists(P) -- assert_string(1,P) -+ assert_string(1,P) - return attrib(P,'mode') ~= nil and P - end - - --- Return the time of last access as the number of seconds since the epoch. - -- @string P A file path - function path.getatime(P) -- assert_string(1,P) -+ assert_string(1,P) - return attrib(P,'access') - end - ----- Return the time of last modification -+--- Return the time of last modification as the number of seconds since the epoch. - -- @string P A file path - function path.getmtime(P) -+ assert_string(1,P) - return attrib(P,'modification') - end - -----Return the system's ctime. -+---Return the system's ctime as the number of seconds since the epoch. - -- @string P A file path - function path.getctime(P) -- assert_string(1,P) -+ assert_string(1,P) - return path.attrib(P,'change') - end - -@@ -119,16 +185,19 @@ end - - path.is_windows = utils.is_windows - --local other_sep ---- !constant sep is the directory separator for this platform. -+local sep, other_sep, seps -+-- constant sep is the directory separator for this platform. -+-- constant dirsep is the separator in the PATH environment variable - if path.is_windows then - path.sep = '\\'; other_sep = '/' - path.dirsep = ';' -+ seps = { ['/'] = true, ['\\'] = true } - else - path.sep = '/' - path.dirsep = ':' -+ seps = { ['/'] = true } - end --local sep,dirsep = path.sep,path.dirsep -+sep = path.sep - - --- are we running Windows? - -- @class field -@@ -145,6 +214,20 @@ local sep,dirsep = path.sep,path.dirsep - --- given a path, return the directory part and a file part. - -- if there's no directory part, the first value will be empty - -- @string P A file path -+-- @return directory part -+-- @return file part -+-- @usage -+-- local dir, file = path.splitpath("some/dir/myfile.txt") -+-- assert(dir == "some/dir") -+-- assert(file == "myfile.txt") -+-- -+-- local dir, file = path.splitpath("some/dir/") -+-- assert(dir == "some/dir") -+-- assert(file == "") -+-- -+-- local dir, file = path.splitpath("some_dir") -+-- assert(dir == "") -+-- assert(file == "some_dir") - function path.splitpath(P) - assert_string(1,P) - local i = #P -@@ -165,9 +248,9 @@ end - -- @string[opt] pwd optional start path to use (default is current dir) - function path.abspath(P,pwd) - assert_string(1,P) -- if pwd then assert_string(2,pwd) end - local use_pwd = pwd ~= nil -- if not use_pwd and not currentdir then return P end -+ if use_pwd then assert_string(2,pwd) end -+ if not use_pwd and not currentdir() then return P end - P = P:gsub('[\\/]$','') - pwd = pwd or currentdir() - if not path.isabs(P) then -@@ -181,14 +264,22 @@ end - --- given a path, return the root part and the extension part. - -- if there's no extension part, the second value will be empty - -- @string P A file path ---- @treturn string root part ---- @treturn string extension part (maybe empty) -+-- @treturn string root part (everything upto the "."", maybe empty) -+-- @treturn string extension part (including the ".", maybe empty) -+-- @usage -+-- local file_path, ext = path.splitext("/bonzo/dog_stuff/cat.txt") -+-- assert(file_path == "/bonzo/dog_stuff/cat") -+-- assert(ext == ".txt") -+-- -+-- local file_path, ext = path.splitext("") -+-- assert(file_path == "") -+-- assert(ext == "") - function path.splitext(P) - assert_string(1,P) - local i = #P - local ch = at(P,i) - while i > 0 and ch ~= '.' do -- if ch == sep or ch == other_sep then -+ if seps[ch] then - return P,'' - end - i = i - 1 -@@ -203,37 +294,59 @@ end - - --- return the directory part of a path - -- @string P A file path -+-- @treturn string everything before the last dir-separator -+-- @see splitpath -+-- @usage -+-- path.dirname("/some/path/file.txt") -- "/some/path" -+-- path.dirname("file.txt") -- "" (empty string) - function path.dirname(P) - assert_string(1,P) -- local p1,p2 = path.splitpath(P) -+ local p1 = path.splitpath(P) - return p1 - end - - --- return the file part of a path - -- @string P A file path -+-- @treturn string -+-- @see splitpath -+-- @usage -+-- path.basename("/some/path/file.txt") -- "file.txt" -+-- path.basename("/some/path/file/") -- "" (empty string) - function path.basename(P) - assert_string(1,P) -- local p1,p2 = path.splitpath(P) -+ local _,p2 = path.splitpath(P) - return p2 - end - - --- get the extension part of a path. - -- @string P A file path -+-- @treturn string -+-- @see splitext -+-- @usage -+-- path.extension("/some/path/file.txt") -- ".txt" -+-- path.extension("/some/path/file_txt") -- "" (empty string) - function path.extension(P) - assert_string(1,P) -- local p1,p2 = path.splitext(P) -+ local _,p2 = path.splitext(P) - return p2 - end - ----- is this an absolute path?. -+--- is this an absolute path? - -- @string P A file path -+-- @usage -+-- path.isabs("hello/path") -- false -+-- path.isabs("/hello/path") -- true -+-- -- Windows; -+-- path.isabs("hello\path") -- false -+-- path.isabs("\hello\path") -- true -+-- path.isabs("C:\hello\path") -- true -+-- path.isabs("C:hello\path") -- false - function path.isabs(P) - assert_string(1,P) -- if path.is_windows then -- return at(P,1) == '/' or at(P,1)=='\\' or at(P,2)==':' -- else -- return at(P,1) == '/' -+ if path.is_windows and at(P,2) == ":" then -+ return seps[at(P,3)] ~= nil - end -+ return seps[at(P,1)] ~= nil - end - - --- return the path resulting from combining the individual paths. -@@ -242,6 +355,11 @@ end - -- @string p1 A file path - -- @string p2 A file path - -- @string ... more file paths -+-- @treturn string the combined path -+-- @usage -+-- path.join("/first","second","third") -- "/first/second/third" -+-- path.join("first","second/third") -- "first/second/third" -+-- path.join("/first","/second","third") -- "/second/third" - function path.join(p1,p2,...) - assert_string(1,p1) - assert_string(2,p2) -@@ -262,21 +380,28 @@ function path.join(p1,p2,...) - return p1..p2 - end - ----- normalize the case of a pathname. On Unix, this returns the path unchanged; ---- for Windows, it converts the path to lowercase, and it also converts forward slashes ---- to backward slashes. -+--- normalize the case of a pathname. On Unix, this returns the path unchanged, -+-- for Windows it converts; -+-- -+-- * the path to lowercase -+-- * forward slashes to backward slashes - -- @string P A file path -+-- @usage path.normcase("/Some/Path/File.txt") -+-- -- Windows: "\some\path\file.txt" -+-- -- Others : "/Some/Path/File.txt" - function path.normcase(P) - assert_string(1,P) - if path.is_windows then -- return (P:lower():gsub('/','\\')) -+ return P:gsub('/','\\'):lower() - else - return P - end - end - - --- normalize a path name. ---- A//B, A/./B and A/foo/../B all become A/B. -+-- `A//B`, `A/./B`, and `A/foo/../B` all become `A/B`. -+-- -+-- An empty path results in '.'. - -- @string P a file path - function path.normpath(P) - assert_string(1,P) -@@ -286,13 +411,13 @@ function path.normpath(P) - if P:match '^\\\\' then -- UNC - anchor = '\\\\' - P = P:sub(3) -- elseif at(P, 1) == '/' or at(P, 1) == '\\' then -+ elseif seps[at(P, 1)] then - anchor = '\\' - P = P:sub(2) - elseif at(P, 2) == ':' then - anchor = P:sub(1, 2) - P = P:sub(3) -- if at(P, 1) == '/' or at(P, 1) == '\\' then -+ if seps[at(P, 1)] then - anchor = anchor..'\\' - P = P:sub(2) - end -@@ -326,23 +451,23 @@ function path.normpath(P) - return P - end - --local function ATS (P) -- if at(P,#P) ~= path.sep then -- P = P..path.sep -- end -- return path.normcase(P) --end -- - --- relative path from current directory or optional start point - -- @string P a path - -- @string[opt] start optional start point (default current directory) - function path.relpath (P,start) - assert_string(1,P) -- if start then assert_string(2,start) end -- local split,normcase,min,append = utils.split, path.normcase, math.min, table.insert -- P = normcase(path.abspath(P,start)) -+ if start then assert_string(2,start) end -+ local split,min,append = utils.split, math.min, table.insert -+ P = path.abspath(P,start) - start = start or currentdir() -- start = normcase(start) -+ local compare -+ if path.is_windows then -+ P = P:gsub("/","\\") -+ start = start:gsub("/","\\") -+ compare = function(v) return v:lower() end -+ else -+ compare = function(v) return v end -+ end - local startl, Pl = split(start,sep), split(P,sep) - local n = min(#startl,#Pl) - if path.is_windows and n > 0 and at(Pl[1],2) == ':' and Pl[1] ~= startl[1] then -@@ -350,7 +475,7 @@ function path.relpath (P,start) - end - local k = n+1 -- default value if this loop doesn't bail out! - for i = 1,n do -- if startl[i] ~= Pl[i] then -+ if compare(startl[i]) ~= compare(Pl[i]) then - k = i - break - end -@@ -368,22 +493,40 @@ end - -- In windows, if HOME isn't set, then USERPROFILE is used in preference to - -- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. - -- @string P A file path -+-- @treturn[1] string The file path with the `~` prefix substituted, or the input path if it had no prefix. -+-- @treturn[2] nil -+-- @treturn[2] string Error message if the environment variables were unavailable. - function path.expanduser(P) - assert_string(1,P) -- if at(P,1) == '~' then -- local home = getenv('HOME') -- if not home then -- has to be Windows -- home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH') -- end -- return home..sub(P,2) -- else -+ if P:sub(1,1) ~= '~' then - return P - end -+ -+ local home = getenv('HOME') -+ if (not home) and (not path.is_windows) then -+ -- no more options to try on Nix -+ return nil, "failed to expand '~' (HOME not set)" -+ end -+ -+ if (not home) then -+ -- try alternatives on Windows -+ home = getenv 'USERPROFILE' -+ if not home then -+ local hd = getenv 'HOMEDRIVE' -+ local hp = getenv 'HOMEPATH' -+ if not (hd and hp) then -+ return nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" -+ end -+ home = hd..hp -+ end -+ end -+ -+ return home..sub(P,2) - end - - - ---Return a suitable full path to a new temporary file name. ---- unlike os.tmpnam(), it always gives you a writeable path (uses TEMP environment variable on Windows) -+-- unlike os.tmpname(), it always gives you a writeable path (uses TEMP environment variable on Windows) - function path.tmpname () - local res = tmpnam() - -- On Windows if Lua is compiled using MSVC14 os.tmpname -@@ -398,15 +541,22 @@ end - --- return the largest common prefix path of two paths. - -- @string path1 a file path - -- @string path2 a file path -+-- @return the common prefix (Windows: separators will be normalized, casing will be original) - function path.common_prefix (path1,path2) - assert_string(1,path1) - assert_string(2,path2) -- path1, path2 = path.normcase(path1), path.normcase(path2) - -- get them in order! - if #path1 > #path2 then path2,path1 = path1,path2 end -+ local compare -+ if path.is_windows then -+ path1 = path1:gsub("/", "\\") -+ path2 = path2:gsub("/", "\\") -+ compare = function(v) return v:lower() end -+ else -+ compare = function(v) return v end -+ end - for i = 1,#path1 do -- local c1 = at(path1,i) -- if c1 ~= at(path2,i) then -+ if compare(at(path1,i)) ~= compare(at(path2,i)) then - local cp = path1:sub(1,i-1) - if at(path1,i-1) ~= sep then - cp = path.dirname(cp) -@@ -424,16 +574,15 @@ end - -- either be a Lua file or a shared library. - -- @string mod name of the module - -- @return on success: path of module, lua or binary ---- @return on error: nil,error string -+-- @return on error: nil, error string listing paths tried - function path.package_path(mod) - assert_string(1,mod) -- local res -- mod = mod:gsub('%.',sep) -- res = package.searchpath(mod,package.path) -+ local res, err1, err2 -+ res, err1 = package.searchpath(mod,package.path) - if res then return res,true end -- res = package.searchpath(mod,package.cpath) -+ res, err2 = package.searchpath(mod,package.cpath) - if res then return res,false end -- return raise 'cannot find module on path' -+ return raise ('cannot find module on path\n' .. err1 .. "\n" .. err2) - end - - -diff --git a/extra/penlight/lua/pl/permute.lua b/extra/penlight/lua/pl/permute.lua -index 56d1a8d..1df9dd2 100644 ---- a/extra/penlight/lua/pl/permute.lua -+++ b/extra/penlight/lua/pl/permute.lua -@@ -6,58 +6,191 @@ local tablex = require 'pl.tablex' - local utils = require 'pl.utils' - local copy = tablex.deepcopy - local append = table.insert --local coroutine = coroutine --local resume = coroutine.resume - local assert_arg = utils.assert_arg - - - local permute = {} - ---- PiL, 9.3 - --local permgen --permgen = function (a, n, fn) -- if n == 0 then -- fn(a) -- else -- for i=1,n do -- -- put i-th element as the last one -- a[n], a[i] = a[i], a[n] -- -- -- generate all permutations of the other elements -- permgen(a, n - 1, fn) -- -- -- restore i-th element -- a[n], a[i] = a[i], a[n] -- -- end -- end --end -- ----- an iterator over all permutations of the elements of a list. -+--- an iterator over all order-permutations of the elements of a list. - -- Please note that the same list is returned each time, so do not keep references! - -- @param a list-like table - -- @return an iterator which provides the next permutation as a list --function permute.iter (a) -+function permute.order_iter(a) - assert_arg(1,a,'table') -- local n = #a -- local co = coroutine.create(function () permgen(a, n, coroutine.yield) end) -- return function () -- iterator -- local code, res = resume(co) -- return res -+ -+ local t = #a -+ local stack = { 1 } -+ local function iter() -+ local h = #stack -+ local n = t - h + 1 -+ -+ local i = stack[h] -+ if i > t then -+ return -+ end -+ -+ if n == 0 then -+ table.remove(stack) -+ h = h - 1 -+ -+ stack[h] = stack[h] + 1 -+ return a -+ -+ elseif i <= n then -+ -+ -- put i-th element as the last one -+ a[n], a[i] = a[i], a[n] -+ -+ -- generate all permutations of the other elements -+ table.insert(stack, 1) -+ -+ else -+ -+ table.remove(stack) -+ h = h - 1 -+ -+ n = n + 1 -+ i = stack[h] -+ -+ -- restore i-th element -+ a[n], a[i] = a[i], a[n] -+ -+ stack[h] = stack[h] + 1 -+ end -+ return iter() -- tail-call - end -+ -+ return iter - end - ----- construct a table containing all the permutations of a list. -+ -+--- construct a table containing all the order-permutations of a list. - -- @param a list-like table - -- @return a table of tables ---- @usage permute.table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}} --function permute.table (a) -+-- @usage permute.order_table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}} -+function permute.order_table (a) - assert_arg(1,a,'table') - local res = {} -- local n = #a -- permgen(a,n,function(t) append(res,copy(t)) end) -+ for t in permute.iter(a) do -+ append(res,copy(t)) -+ end - return res - end - -+ -+ -+--- an iterator over all permutations of the elements of the given lists. -+-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided (see `utils.pack`) -+-- @return an iterator which provides the next permutation as return values in the same order as the provided lists, preceded by an index -+-- @usage -+-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety -+-- local bools = utils.pack(true, false) -+-- local iter = permute.list_iter(strs, bools) -+-- -+-- print(iter()) --> 1, one, true -+-- print(iter()) --> 2, nil, true -+-- print(iter()) --> 3, three, true -+-- print(iter()) --> 4, one, false -+-- print(iter()) --> 5, nil, false -+-- print(iter()) --> 6, three, false -+function permute.list_iter(...) -+ local elements = {...} -+ local pointers = {} -+ local sizes = {} -+ local size = #elements -+ for i, list in ipairs(elements) do -+ assert_arg(i,list,'table') -+ pointers[i] = 1 -+ sizes[i] = list.n or #list -+ end -+ local count = 0 -+ -+ return function() -+ if pointers[size] > sizes[size] then return end -- we're done -+ count = count + 1 -+ local r = { n = #elements } -+ local cascade_up = true -+ for i = 1, size do -+ r[i] = elements[i][pointers[i]] -+ if cascade_up then -+ pointers[i] = pointers[i] + 1 -+ if pointers[i] <= sizes[i] then -+ -- this list is not done yet, stop cascade -+ cascade_up = false -+ else -+ -- this list is done -+ if i ~= size then -+ -- reset pointer -+ pointers[i] = 1 -+ end -+ end -+ end -+ end -+ return count, utils.unpack(r) -+ end -+end -+ -+ -+ -+--- construct a table containing all the permutations of a set of lists. -+-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided -+-- @return a list of lists, the sub-lists have an 'n' field for nil-safety -+-- @usage -+-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety -+-- local bools = utils.pack(true, false) -+-- local results = permute.list_table(strs, bools) -+-- -- results = { -+-- -- { "one, true, n = 2 } -+-- -- { nil, true, n = 2 }, -+-- -- { "three, true, n = 2 }, -+-- -- { "one, false, n = 2 }, -+-- -- { nil, false, n = 2 }, -+-- -- { "three", false, n = 2 }, -+-- -- } -+function permute.list_table(...) -+ local iter = permute.list_iter(...) -+ local results = {} -+ local i = 1 -+ while true do -+ local values = utils.pack(iter()) -+ if values[1] == nil then return results end -+ for i = 1, values.n do values[i] = values[i+1] end -+ values.n = values.n - 1 -+ results[i] = values -+ i = i + 1 -+ end -+end -+ -+ -+-- backward compat, to be deprecated -+ -+--- deprecated. -+-- @param ... -+-- @see permute.order_iter -+function permute.iter(...) -+ utils.raise_deprecation { -+ source = "Penlight " .. utils._VERSION, -+ message = "function 'iter' was renamed to 'order_iter'", -+ version_removed = "2.0.0", -+ deprecated_after = "1.9.2", -+ } -+ -+ return permute.order_iter(...) -+end -+ -+--- deprecated. -+-- @param ... -+-- @see permute.order_iter -+function permute.table(...) -+ utils.raise_deprecation { -+ source = "Penlight " .. utils._VERSION, -+ message = "function 'table' was renamed to 'order_table'", -+ version_removed = "2.0.0", -+ deprecated_after = "1.9.2", -+ } -+ -+ return permute.order_table(...) -+end -+ - return permute -diff --git a/extra/penlight/lua/pl/pretty.lua b/extra/penlight/lua/pl/pretty.lua -index 9de158d..df9a923 100644 ---- a/extra/penlight/lua/pl/pretty.lua -+++ b/extra/penlight/lua/pl/pretty.lua -@@ -7,7 +7,8 @@ - - local append = table.insert - local concat = table.concat --local mfloor, mhuge, mtype = math.floor, math.huge, math.type -+local mfloor, mhuge = math.floor, math.huge -+local mtype = math.type - local utils = require 'pl.utils' - local lexer = require 'pl.lexer' - local debug = require 'debug' -@@ -16,6 +17,42 @@ local assert_arg = utils.assert_arg - - local original_tostring = tostring - -+-- Calculate min and max integer supported by lua_Number -+-- Assumptions: -+-- 1. max_int = 2 ^ n - 1 -+-- 2. min_int = -max_int -+-- 3. if n > max_int versions with integer support will have -+-- integer overflow and versions without integers will lose least significant bit -+-- Note: if lua_Integer is smaller than lua_Number mantissa string.format('%d') -+-- can throw runtime error -+local max_int, min_int -+local next_cand = 1 -+while next_cand > 0 and next_cand % 2 == 1 do -+ max_int = next_cand -+ min_int = -next_cand -+ next_cand = next_cand * 2 + 1 -+end -+ -+local function is_integer(value) -+ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then -+ return mtype(value) == "integer" -+ end -+ if value < min_int or value > max_int then -+ return false -+ end -+ return math.floor(value) == value -+end -+ -+local function is_float(value) -+ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then -+ return mtype(value) == "float" -+ end -+ if value < min_int or value > max_int then -+ return true -+ end -+ return mfloor(value) == value -+end -+ - -- Patch tostring to format numbers with better precision - -- and to produce cross-platform results for - -- infinite values and NaN. -@@ -28,11 +65,11 @@ local function tostring(value) - return "Inf" - elseif value == -mhuge then - return "-Inf" -- elseif (_VERSION ~= "Lua 5.3" or mtype(value) == "integer") and mfloor(value) == value then -+ elseif is_integer(value) then - return ("%d"):format(value) - else - local res = ("%.14g"):format(value) -- if _VERSION == "Lua 5.3" and mtype(value) == "float" and not res:find("%.") then -+ if is_float(value) and not res:find("%.") then - -- Number is internally a float but looks like an integer. - -- Insert ".0" after first run of digits. - res = res:gsub("%d+", "%0.0", 1) -@@ -46,12 +83,12 @@ local pretty = {} - local function save_global_env() - local env = {} - env.hook, env.mask, env.count = debug.gethook() -- -+ - -- env.hook is "external hook" if is a C hook function -- if env.hook~="external hook" then -- debug.sethook() -- end -- -+ if env.hook~="external hook" then -+ debug.sethook() -+ end -+ - env.string_mt = getmetatable("") - debug.setmetatable("", nil) - return env -@@ -164,11 +201,17 @@ local function index (numkey,key) - end - - ----- Create a string representation of a Lua table. -+--- Create a string representation of a Lua table. - -- This function never fails, but may complain by returning an - -- extra value. Normally puts out one item per line, using - -- the provided indent; set the second parameter to an empty string - -- if you want output on one line. -+-- -+-- *NOTE:* this is NOT a serialization function, not a full blown -+-- debug function. Checkout out respectively the -+-- [serpent](https://github.com/pkulchenko/serpent) -+-- or [inspect](https://github.com/kikito/inspect.lua) -+-- Lua modules for that if you need them. - -- @tab tbl Table to serialize to a string. - -- @string[opt] space The indent to use. - -- Defaults to two spaces; pass an empty string for no indentation. -@@ -210,7 +253,7 @@ function pretty.write (tbl,space,not_clever) - end - - local function eat_last_comma () -- local n,lastch = #lines -+ local n = #lines - local lastch = lines[n]:sub(-1,-1) - if lastch == ',' then - lines[n] = lines[n]:sub(1,-2) -@@ -218,6 +261,29 @@ function pretty.write (tbl,space,not_clever) - end - - -+ -- safe versions for iterators since 5.3+ honors metamethods that can throw -+ -- errors -+ local ipairs = function(t) -+ local i = 0 -+ local ok, v -+ local getter = function() return t[i] end -+ return function() -+ i = i + 1 -+ ok, v = pcall(getter) -+ if v == nil or not ok then return end -+ return i, t[i] -+ end -+ end -+ local pairs = function(t) -+ local k, v, ok -+ local getter = function() return next(t, k) end -+ return function() -+ ok, k, v = pcall(getter) -+ if not ok then return end -+ return k, v -+ end -+ end -+ - local writeit - writeit = function (t,oldindent,indent) - local tp = type(t) -@@ -247,7 +313,20 @@ function pretty.write (tbl,space,not_clever) - used[i] = true - end - end -- for key,val in pairs(t) do -+ local ordered_keys = {} -+ for k,v in pairs(t) do -+ if type(k) ~= 'number' then -+ ordered_keys[#ordered_keys + 1] = k -+ end -+ end -+ table.sort(ordered_keys, function (a, b) -+ if type(a) == type(b) then -+ return tostring(a) < tostring(b) -+ else -+ return type(a) < type(b) -+ end -+ end) -+ local function write_entry (key, val) - local tkey = type(key) - local numkey = tkey == 'number' - if not_clever then -@@ -267,6 +346,16 @@ function pretty.write (tbl,space,not_clever) - end - end - end -+ for i = 1, #ordered_keys do -+ local key = ordered_keys[i] -+ local val = t[key] -+ write_entry(key, val) -+ end -+ for key,val in pairs(t) do -+ if type(key) == 'number' then -+ write_entry(key, val) -+ end -+ end - tables[t] = nil - eat_last_comma() - putln(oldindent..'},') -@@ -292,6 +381,49 @@ function pretty.dump (t, filename) - end - end - -+--- Dump a series of arguments to stdout for debug purposes. -+-- This function is attached to the module table `__call` method, to make it -+-- extra easy to access. So the full: -+-- -+-- print(require("pl.pretty").write({...})) -+-- -+-- Can be shortened to: -+-- -+-- require"pl.pretty" (...) -+-- -+-- Any `nil` entries will be printed as `""` to make them explicit. -+-- @param ... the parameters to dump to stdout. -+-- @usage -+-- -- example debug output -+-- require"pl.pretty" ("hello", nil, "world", { bye = "world", true} ) -+-- -+-- -- output: -+-- { -+-- ["arg 1"] = "hello", -+-- ["arg 2"] = "", -+-- ["arg 3"] = "world", -+-- ["arg 4"] = { -+-- true, -+-- bye = "world" -+-- } -+-- } -+function pretty.debug(...) -+ local n = select("#", ...) -+ local t = { ... } -+ for i = 1, n do -+ local value = t[i] -+ if value == nil then -+ value = "" -+ end -+ t[i] = nil -+ t["arg " .. i] = value -+ end -+ -+ print(pretty.write(t)) -+ return true -+end -+ -+ - local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'} - - local function comma (val) -@@ -335,4 +467,8 @@ function pretty.number (num,kind,prec) - end - end - --return pretty -+return setmetatable(pretty, { -+ __call = function(self, ...) -+ return self.debug(...) -+ end -+}) -diff --git a/extra/penlight/lua/pl/seq.lua b/extra/penlight/lua/pl/seq.lua -index 238aa7a..1c08d20 100644 ---- a/extra/penlight/lua/pl/seq.lua -+++ b/extra/penlight/lua/pl/seq.lua -@@ -79,9 +79,9 @@ end - -- @return iterator over keys - function seq.keys(t) - assert_arg(1,t,'table') -- local key,value -+ local key - return function() -- key,value = next(t,key) -+ key = next(t,key) - return key - end - end -@@ -434,15 +434,12 @@ end - -- @param iter a sequence - function seq.last (iter) - iter = default_iter(iter) -- local l = iter() -- if l == nil then return nil end -+ local val, l = iter(), nil -+ if val == nil then return list{} end - return function () -- local val,ll -- val = iter() -+ val,l = iter(),val - if val == nil then return nil end -- ll = l -- l = val -- return val,ll -+ return val,l - end - end - -@@ -514,7 +511,6 @@ setmetatable(seq,{ - -- @param ... for Lua 5.2 only, optional format specifiers, as in `io.read`. - -- @return a sequence wrapper - function seq.lines (f,...) -- local n = select('#',...) - local iter,obj - if f == 'STDIN' then - f = io.stdin -diff --git a/extra/penlight/lua/pl/strict.lua b/extra/penlight/lua/pl/strict.lua -index d4e0fc7..67cbf5b 100644 ---- a/extra/penlight/lua/pl/strict.lua -+++ b/extra/penlight/lua/pl/strict.lua -@@ -1,7 +1,7 @@ - --- Checks uses of undeclared global variables. - -- All global variables must be 'declared' through a regular assignment - -- (even assigning `nil` will do) in a main chunk before being used ---- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index` -+-- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index` - -- metamethods are respected. - -- - -- You can set any table to have strict behaviour using `strict.module`. Creating a new -@@ -23,15 +23,25 @@ local function what () - end - - --- make an existing table strict. ---- @string name name of table (optional) ---- @tab[opt] mod table - if `nil` then we'll return a new table -+-- @string[opt] name name of table -+-- @tab[opt] mod the table to protect - if `nil` then we'll return a new table - -- @tab[opt] predeclared - table of variables that are to be considered predeclared. - -- @return the given table, or a new table -+-- @usage -+-- local M = { hello = "world" } -+-- strict.module ("Awesome_Module", M, { -+-- Lua = true, -- defines allowed keys -+-- }) -+-- -+-- assert(M.hello == "world") -+-- assert(M.Lua == nil) -- access allowed, but has no value yet -+-- M.Lua = "Rocks" -+-- assert(M.Lua == "Rocks") -+-- M.not_allowed = "bad boy" -- throws an error - function strict.module (name,mod,predeclared) -- local mt, old_newindex, old_index, old_index_type, global, closed -+ local mt, old_newindex, old_index, old_index_type, global - if predeclared then - global = predeclared.__global -- closed = predeclared.__closed - end - if type(mod) == 'table' then - mt = getmetatable(mod) -@@ -71,17 +81,17 @@ function strict.module (name,mod,predeclared) - local fallback = old_index[n] - if fallback ~= nil then - return fallback -- end -+ end - else - local res = old_index(t, n) - if res ~= nil then - return res - end -- end -+ end - end - local msg = "variable '"..n.."' is not declared" - if name then -- msg = msg .. " in '"..name.."'" -+ msg = msg .. " in '"..tostring(name).."'" - end - error(msg, 2) - end -@@ -93,7 +103,7 @@ end - --- make all tables in a table strict. - -- So `strict.make_all_strict(_G)` prevents monkey-patching - -- of any global table ---- @tab T -+-- @tab T the table containing the tables to protect. Table `T` itself will NOT be protected. - function strict.make_all_strict (T) - for k,v in pairs(T) do - if type(v) == 'table' and v ~= T then -@@ -103,7 +113,11 @@ function strict.make_all_strict (T) - end - - --- make a new module table which is closed to further changes. -+-- @tab mod module table -+-- @string name module name - function strict.closed_module (mod,name) -+ -- No clue to what this is useful for? see tests -+ -- Deprecate this and remove??? - local M = {} - mod = mod or {} - local mt = getmetatable(mod) -@@ -118,8 +132,7 @@ function strict.closed_module (mod,name) - end - - if not rawget(_G,'PENLIGHT_NO_GLOBAL_STRICT') then -- strict.module(nil,_G,{_PROMPT=true,__global=true}) -+ strict.module(nil,_G,{_PROMPT=true,_PROMPT2=true,__global=true}) - end - - return strict -- -diff --git a/extra/penlight/lua/pl/stringio.lua b/extra/penlight/lua/pl/stringio.lua -index 10fd102..666f415 100644 ---- a/extra/penlight/lua/pl/stringio.lua -+++ b/extra/penlight/lua/pl/stringio.lua -@@ -72,7 +72,7 @@ function SR:_read(fmt) - res = str:sub(i) - self.i = sz - elseif fmt == '*n' then -- local _,i2,i2,idx -+ local _,i2,idx - _,idx = str:find ('%s*%d+',i) - _,i2 = str:find ('^%.%d+',idx+1) - if i2 then idx = i2 end -diff --git a/extra/penlight/lua/pl/stringx.lua b/extra/penlight/lua/pl/stringx.lua -index f5a27d4..3fb8a75 100644 ---- a/extra/penlight/lua/pl/stringx.lua -+++ b/extra/penlight/lua/pl/stringx.lua -@@ -6,9 +6,10 @@ - -- - -- See @{03-strings.md|the Guide} - -- ---- Dependencies: `pl.utils` -+-- Dependencies: `pl.utils`, `pl.types` - -- @module pl.stringx - local utils = require 'pl.utils' -+local is_callable = require 'pl.types'.is_callable - local string = string - local find = string.find - local type,setmetatable,ipairs = type,setmetatable,ipairs -@@ -16,12 +17,16 @@ local error = error - local gsub = string.gsub - local rep = string.rep - local sub = string.sub -+local reverse = string.reverse - local concat = table.concat - local append = table.insert -+local remove = table.remove - local escape = utils.escape - local ceil, max = math.ceil, math.max - local assert_arg,usplit = utils.assert_arg,utils.split - local lstrip -+local unpack = utils.unpack -+local pack = utils.pack - - local function assert_string (n,s) - assert_arg(n,s,'string') -@@ -66,7 +71,8 @@ function stringx.isalnum(s) - return find(s,'^%w+$') == 1 - end - ----- does s only contain spaces? -+--- does s only contain whitespace? -+-- Matches on pattern '%s' so matches space, newline, tabs, etc. - -- @string s a string - function stringx.isspace(s) - assert_string(1,s) -@@ -128,9 +134,10 @@ end - -- @section lists - - --- concatenate the strings using this string as a delimiter. -+-- Note that the arguments are reversed from `string.concat`. - -- @string s the string - -- @param seq a table of strings or numbers ---- @usage (' '):join {1,2,3} == '1 2 3' -+-- @usage stringx.join(' ', {1,2,3}) == '1 2 3' - function stringx.join(s,seq) - assert_string(1,s) - return concat(seq,s) -@@ -143,6 +150,7 @@ end - -- Splitting an empty string results in an empty list. - -- @string s the string. - -- @bool[opt] keep_ends include line ends. -+-- @return List of lines - function stringx.splitlines(s, keep_ends) - assert_string(1, s) - local res = {} -@@ -178,9 +186,10 @@ end - -- @string s the string - -- @string[opt] re a delimiter (defaults to whitespace) - -- @int[opt] n maximum number of results ---- @usage #(('one two'):split()) == 2 ---- @usage ('one,two,three'):split(',') == List{'one','two','three'} ---- @usage ('one,two,three'):split(',',2) == List{'one','two,three'} -+-- @return List -+-- @usage #(stringx.split('one two')) == 2 -+-- @usage stringx.split('one,two,three', ',') == List{'one','two','three'} -+-- @usage stringx.split('one,two,three', ',', 2) == List{'one','two,three'} - function stringx.split(s,re,n) - assert_string(1,s) - local plain = true -@@ -189,29 +198,37 @@ function stringx.split(s,re,n) - plain = false - end - local res = usplit(s,re,plain,n) -- if re and re ~= '' and find(s,re,-#re,true) then -+ if re and re ~= '' and -+ find(s,re,-#re,true) and -+ (n or math.huge) > #res then - res[#res+1] = "" - end -- return makelist(res) -+ return makelist(res) - end - - --- replace all tabs in s with tabsize spaces. If not specified, tabsize defaults to 8. ---- with 0.9.5 this now correctly expands to the next tab stop (if you really ---- want to just replace tabs, use :gsub('\t',' ') etc) -+-- Tab stops will be honored. - -- @string s the string - -- @int tabsize[opt=8] number of spaces to expand each tab -+-- @return expanded string -+-- @usage stringx.expandtabs('\tone,two,three', 4) == ' one,two,three' -+-- @usage stringx.expandtabs(' \tone,two,three', 4) == ' one,two,three' - function stringx.expandtabs(s,tabsize) -- assert_string(1,s) -- tabsize = tabsize or 8 -- return (s:gsub("([^\t\r\n]*)\t", function(before_tab) -+ assert_string(1,s) -+ tabsize = tabsize or 8 -+ return (s:gsub("([^\t\r\n]*)\t", function(before_tab) -+ if tabsize == 0 then -+ return before_tab -+ else - return before_tab .. (" "):rep(tabsize - #before_tab % tabsize) -+ end - end)) - end - - --- Finding and Replacing - -- @section find - --local function _find_all(s,sub,first,last) -+local function _find_all(s,sub,first,last,allow_overlap) - first = first or 1 - last = last or #s - if sub == '' then return last+1,last-first+1 end -@@ -222,7 +239,11 @@ local function _find_all(s,sub,first,last) - if last and i2 > last then break end - res = i1 - k = k + 1 -- i1,i2 = find(s,sub,i2+1,true) -+ if allow_overlap then -+ i1,i2 = find(s,sub,i1+1,true) -+ else -+ i1,i2 = find(s,sub,i2+1,true) -+ end - end - return res,k - end -@@ -232,6 +253,7 @@ end - -- @string sub substring - -- @int[opt] first first index - -- @int[opt] last last index -+-- @return start index, or nil if not found - function stringx.lfind(s,sub,first,last) - assert_string(1,s) - assert_string(2,sub) -@@ -249,14 +271,15 @@ end - -- @string sub substring - -- @int[opt] first first index - -- @int[opt] last last index -+-- @return start index, or nil if not found - function stringx.rfind(s,sub,first,last) - assert_string(1,s) - assert_string(2,sub) -- return (_find_all(s,sub,first,last)) -+ return (_find_all(s,sub,first,last,true)) - end - - --- replace up to n instances of old by new in the string s. ---- if n is not present, replace all instances. -+-- If n is not present, replace all instances. - -- @string s the string - -- @string old the target substring - -- @string new the substitution -@@ -272,9 +295,13 @@ end - --- count all instances of substring in string. - -- @string s the string - -- @string sub substring --function stringx.count(s,sub) -+-- @bool[opt] allow_overlap allow matches to overlap -+-- @usage -+-- assert(stringx.count('banana', 'ana') == 1) -+-- assert(stringx.count('banana', 'ana', true) == 2) -+function stringx.count(s,sub,allow_overlap) - assert_string(1,s) -- local i,k = _find_all(s,sub,1) -+ local _,k = _find_all(s,sub,1,false,allow_overlap) - return k - end - -@@ -308,6 +335,7 @@ end - -- @string s the string - -- @int w width of justification - -- @string[opt=' '] ch padding character -+-- @usage stringx.ljust('hello', 10, '*') == '*****hello' - function stringx.ljust(s,w,ch) - assert_string(1,s) - assert_arg(2,w,'number') -@@ -318,6 +346,7 @@ end - -- @string s the string - -- @int w width of justification - -- @string[opt=' '] ch padding character -+-- @usage stringx.rjust('hello', 10, '*') == 'hello*****' - function stringx.rjust(s,w,ch) - assert_string(1,s) - assert_arg(2,w,'number') -@@ -328,6 +357,7 @@ end - -- @string s the string - -- @int w width of justification - -- @string[opt=' '] ch padding character -+-- @usage stringx.center('hello', 10, '*') == '**hello***' - function stringx.center(s,w,ch) - assert_string(1,s) - assert_arg(2,w,'number') -@@ -340,63 +370,75 @@ local function _strip(s,left,right,chrs) - else - chrs = '['..escape(chrs)..']' - end -+ local f = 1 -+ local t - if left then - local i1,i2 = find(s,'^'..chrs..'*') - if i2 >= i1 then -- s = sub(s,i2+1) -+ f = i2+1 - end - end - if right then -- local i1,i2 = find(s,chrs..'*$') -- if i2 >= i1 then -- s = sub(s,1,i1-1) -+ if #s < 200 then -+ local i1,i2 = find(s,chrs..'*$',f) -+ if i2 >= i1 then -+ t = i1-1 -+ end -+ else -+ local rs = reverse(s) -+ local i1,i2 = find(rs, '^'..chrs..'*') -+ if i2 >= i1 then -+ t = -i2-1 -+ end - end - end -- return s -+ return sub(s,f,t) - end - ----- trim any whitespace on the left of s. -+--- trim any characters on the left of s. - -- @string s the string - -- @string[opt='%s'] chrs default any whitespace character, ---- but can be a string of characters to be trimmed -+-- but can be a string of characters to be trimmed - function stringx.lstrip(s,chrs) - assert_string(1,s) - return _strip(s,true,false,chrs) - end - lstrip = stringx.lstrip - ----- trim any whitespace on the right of s. -+--- trim any characters on the right of s. - -- @string s the string - -- @string[opt='%s'] chrs default any whitespace character, ---- but can be a string of characters to be trimmed -+-- but can be a string of characters to be trimmed - function stringx.rstrip(s,chrs) - assert_string(1,s) - return _strip(s,false,true,chrs) - end - ----- trim any whitespace on both left and right of s. -+--- trim any characters on both left and right of s. - -- @string s the string - -- @string[opt='%s'] chrs default any whitespace character, ---- but can be a string of characters to be trimmed -+-- but can be a string of characters to be trimmed -+-- @usage stringx.strip(' --== Hello ==-- ', "- =") --> 'Hello' - function stringx.strip(s,chrs) - assert_string(1,s) - return _strip(s,true,true,chrs) - end - ----- Partioning Strings ---- @section partioning -+--- Partitioning Strings -+-- @section partitioning - - --- split a string using a pattern. Note that at least one value will be returned! - -- @string s the string - -- @string[opt='%s'] re a Lua string pattern (defaults to whitespace) - -- @return the parts of the string - -- @usage a,b = line:splitv('=') -+-- @see utils.splitv - function stringx.splitv(s,re) - assert_string(1,s) - return utils.splitv(s,re) - end - ---- The partition functions split a string using a delimiter into three parts: -+-- The partition functions split a string using a delimiter into three parts: - -- the part before, the delimiter itself, and the part afterwards - local function _partition(p,delim,fn) - local i1,i2 = fn(p,delim) -@@ -408,28 +450,36 @@ local function _partition(p,delim,fn) - end - end - ----- partition the string using first occurance of a delimiter -+--- partition the string using first occurrence of a delimiter - -- @string s the string ---- @string ch delimiter -+-- @string ch delimiter (match as plain string, no patterns) - -- @return part before ch - -- @return ch - -- @return part after ch -+-- @usage {stringx.partition('a,b,c', ','))} == {'a', ',', 'b,c'} -+-- @usage {stringx.partition('abc', 'x'))} == {'abc', '', ''} - function stringx.partition(s,ch) - assert_string(1,s) - assert_nonempty_string(2,ch) - return _partition(s,ch,stringx.lfind) - end - ----- partition the string p using last occurance of a delimiter -+--- partition the string p using last occurrence of a delimiter - -- @string s the string ---- @string ch delimiter -+-- @string ch delimiter (match as plain string, no patterns) - -- @return part before ch - -- @return ch - -- @return part after ch -+-- @usage {stringx.rpartition('a,b,c', ','))} == {'a,b', ',', 'c'} -+-- @usage {stringx.rpartition('abc', 'x'))} == {'', '', 'abc'} - function stringx.rpartition(s,ch) - assert_string(1,s) - assert_nonempty_string(2,ch) -- return _partition(s,ch,stringx.rfind) -+ local a,b,c = _partition(s,ch,stringx.rfind) -+ if a == s then -- no match found -+ return c,b,a -+ end -+ return a,b,c - end - - --- return the 'character' at the index. -@@ -442,22 +492,273 @@ function stringx.at(s,idx) - return sub(s,idx,idx) - end - ----- Miscelaneous -+ -+--- Text handling -+-- @section text -+ -+ -+--- indent a multiline string. -+-- @tparam string s the (multiline) string -+-- @tparam integer n the size of the indent -+-- @tparam[opt=' '] string ch the character to use when indenting -+-- @return indented string -+function stringx.indent (s,n,ch) -+ assert_arg(1,s,'string') -+ assert_arg(2,n,'number') -+ local lines = usplit(s ,'\n') -+ local prefix = string.rep(ch or ' ',n) -+ for i, line in ipairs(lines) do -+ lines[i] = prefix..line -+ end -+ return concat(lines,'\n')..'\n' -+end -+ -+ -+--- dedent a multiline string by removing any initial indent. -+-- useful when working with [[..]] strings. -+-- Empty lines are ignored. -+-- @tparam string s the (multiline) string -+-- @return a string with initial indent zero. -+-- @usage -+-- local s = dedent [[ -+-- One -+-- -+-- Two -+-- -+-- Three -+-- ]] -+-- assert(s == [[ -+-- One -+-- -+-- Two -+-- -+-- Three -+-- ]]) -+function stringx.dedent (s) -+ assert_arg(1,s,'string') -+ local lst = usplit(s,'\n') -+ if #lst>0 then -+ local ind_size = math.huge -+ for i, line in ipairs(lst) do -+ local i1, i2 = lst[i]:find('^%s*[^%s]') -+ if i1 and i2 < ind_size then -+ ind_size = i2 -+ end -+ end -+ for i, line in ipairs(lst) do -+ lst[i] = lst[i]:sub(ind_size, -1) -+ end -+ end -+ return concat(lst,'\n')..'\n' -+end -+ -+ -+ -+do -+ local buildline = function(words, size, breaklong) -+ -- if overflow is set, a word longer than size, will overflow the size -+ -- otherwise it will be chopped in line-length pieces -+ local line = {} -+ if #words[1] > size then -+ -- word longer than line -+ if not breaklong then -+ line[1] = words[1] -+ remove(words, 1) -+ else -+ line[1] = words[1]:sub(1, size) -+ words[1] = words[1]:sub(size + 1, -1) -+ end -+ else -+ local len = 0 -+ while words[1] and (len + #words[1] <= size) or -+ (len == 0 and #words[1] == size) do -+ if words[1] ~= "" then -+ line[#line+1] = words[1] -+ len = len + #words[1] + 1 -+ end -+ remove(words, 1) -+ end -+ end -+ return stringx.strip(concat(line, " ")), words -+ end -+ -+ --- format a paragraph into lines so that they fit into a line width. -+ -- It will not break long words by default, so lines can be over the length -+ -- to that extent. -+ -- @tparam string s the string to format -+ -- @tparam[opt=70] integer width the margin width -+ -- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split. -+ -- @return a list of lines (List object), use `fill` to return a string instead of a `List`. -+ -- @see pl.List -+ -- @see fill -+ stringx.wrap = function(s, width, breaklong) -+ s = s:gsub('\n',' ') -- remove line breaks -+ s = stringx.strip(s) -- remove leading/trailing whitespace -+ if s == "" then -+ return { "" } -+ end -+ width = width or 70 -+ local out = {} -+ local words = usplit(s, "%s") -+ while words[1] do -+ out[#out+1], words = buildline(words, width, breaklong) -+ end -+ return makelist(out) -+ end -+end -+ -+--- format a paragraph so that it fits into a line width. -+-- @tparam string s the string to format -+-- @tparam[opt=70] integer width the margin width -+-- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split. -+-- @return a string, use `wrap` to return a list of lines instead of a string. -+-- @see wrap -+function stringx.fill (s,width,breaklong) -+ return concat(stringx.wrap(s,width,breaklong),'\n') .. '\n' -+end -+ -+--- Template -+-- @section Template -+ -+ -+local function _substitute(s,tbl,safe) -+ local subst -+ if is_callable(tbl) then -+ subst = tbl -+ else -+ function subst(f) -+ local s = tbl[f] -+ if not s then -+ if safe then -+ return f -+ else -+ error("not present in table "..f) -+ end -+ else -+ return s -+ end -+ end -+ end -+ local res = gsub(s,'%${([%w_]+)}',subst) -+ return (gsub(res,'%$([%w_]+)',subst)) -+end -+ -+ -+ -+local Template = {} -+stringx.Template = Template -+Template.__index = Template -+setmetatable(Template, { -+ __call = function(obj,tmpl) -+ return Template.new(tmpl) -+ end -+}) -+ -+--- Creates a new Template class. -+-- This is a shortcut to `Template.new(tmpl)`. -+-- @tparam string tmpl the template string -+-- @function Template -+-- @treturn Template -+function Template.new(tmpl) -+ assert_arg(1,tmpl,'string') -+ local res = {} -+ res.tmpl = tmpl -+ setmetatable(res,Template) -+ return res -+end -+ -+--- substitute values into a template, throwing an error. -+-- This will throw an error if no name is found. -+-- @tparam table tbl a table of name-value pairs. -+-- @return string with place holders substituted -+function Template:substitute(tbl) -+ assert_arg(1,tbl,'table') -+ return _substitute(self.tmpl,tbl,false) -+end -+ -+--- substitute values into a template. -+-- This version just passes unknown names through. -+-- @tparam table tbl a table of name-value pairs. -+-- @return string with place holders substituted -+function Template:safe_substitute(tbl) -+ assert_arg(1,tbl,'table') -+ return _substitute(self.tmpl,tbl,true) -+end -+ -+--- substitute values into a template, preserving indentation.
    -+-- If the value is a multiline string _or_ a template, it will insert -+-- the lines at the correct indentation.
    -+-- Furthermore, if a template, then that template will be substituted -+-- using the same table. -+-- @tparam table tbl a table of name-value pairs. -+-- @return string with place holders substituted -+function Template:indent_substitute(tbl) -+ assert_arg(1,tbl,'table') -+ if not self.strings then -+ self.strings = usplit(self.tmpl,'\n') -+ end -+ -+ -- the idea is to substitute line by line, grabbing any spaces as -+ -- well as the $var. If the value to be substituted contains newlines, -+ -- then we split that into lines and adjust the indent before inserting. -+ local function subst(line) -+ return line:gsub('(%s*)%$([%w_]+)',function(sp,f) -+ local subtmpl -+ local s = tbl[f] -+ if not s then error("not present in table "..f) end -+ if getmetatable(s) == Template then -+ subtmpl = s -+ s = s.tmpl -+ else -+ s = tostring(s) -+ end -+ if s:find '\n' then -+ local lines = usplit(s, '\n') -+ for i, line in ipairs(lines) do -+ lines[i] = sp..line -+ end -+ s = concat(lines, '\n') .. '\n' -+ end -+ if subtmpl then -+ return _substitute(s, tbl) -+ else -+ return s -+ end -+ end) -+ end -+ -+ local lines = {} -+ for i, line in ipairs(self.strings) do -+ lines[i] = subst(line) -+ end -+ return concat(lines,'\n')..'\n' -+end -+ -+ -+ -+--- Miscellaneous - -- @section misc - - --- return an iterator over all lines in a string - -- @string s the string - -- @return an iterator -+-- @usage -+-- local line_no = 1 -+-- for line in stringx.lines(some_text) do -+-- print(line_no, line) -+-- line_no = line_no + 1 -+-- end - function stringx.lines(s) - assert_string(1,s) - if not s:find '\n$' then s = s..'\n' end - return s:gmatch('([^\n]*)\n') - end - ----- iniital word letters uppercase ('title case'). -+--- initial word letters uppercase ('title case'). - -- Here 'words' mean chunks of non-space characters. - -- @string s the string - -- @return a string with each word's first letter uppercase -+-- @usage stringx.title("hello world") == "Hello World") - function stringx.title(s) - assert_string(1,s) - return (s:gsub('(%S)(%S*)',function(f,r) -@@ -467,80 +768,148 @@ end - - stringx.capitalize = stringx.title - --local ellipsis = '...' --local n_ellipsis = #ellipsis -- ----- Return a shortened version of a string. ---- Fits string within w characters. Removed characters are marked with ellipsis. ---- @string s the string ---- @int w the maxinum size allowed ---- @bool tail true if we want to show the end of the string (head otherwise) ---- @usage ('1234567890'):shorten(8) == '12345...' ---- @usage ('1234567890'):shorten(8, true) == '...67890' ---- @usage ('1234567890'):shorten(20) == '1234567890' --function stringx.shorten(s,w,tail) -- assert_string(1,s) -- if #s > w then -- if w < n_ellipsis then return ellipsis:sub(1,w) end -- if tail then -- local i = #s - w + 1 + n_ellipsis -- return ellipsis .. s:sub(i) -- else -- return s:sub(1,w-n_ellipsis) .. ellipsis -- end -+do -+ local ellipsis = '...' -+ local n_ellipsis = #ellipsis -+ -+ --- Return a shortened version of a string. -+ -- Fits string within w characters. Removed characters are marked with ellipsis. -+ -- @string s the string -+ -- @int w the maximum size allowed -+ -- @bool tail true if we want to show the end of the string (head otherwise) -+ -- @usage ('1234567890'):shorten(8) == '12345...' -+ -- @usage ('1234567890'):shorten(8, true) == '...67890' -+ -- @usage ('1234567890'):shorten(20) == '1234567890' -+ function stringx.shorten(s,w,tail) -+ assert_string(1,s) -+ if #s > w then -+ if w < n_ellipsis then return ellipsis:sub(1,w) end -+ if tail then -+ local i = #s - w + 1 + n_ellipsis -+ return ellipsis .. s:sub(i) -+ else -+ return s:sub(1,w-n_ellipsis) .. ellipsis -+ end -+ end -+ return s -+ end -+end -+ -+ -+do -+ -- Utility function that finds any patterns that match a long string's an open or close. -+ -- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. -+ -- Right now, it simply returns the greatest number of them found. -+ -- @param s The string -+ -- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. -+ local function has_lquote(s) -+ local lstring_pat = '([%[%]])(=*)%1' -+ local equals, new_equals, _ -+ local finish = 1 -+ repeat -+ _, finish, _, new_equals = s:find(lstring_pat, finish) -+ if new_equals then -+ equals = max(equals or 0, #new_equals) -+ end -+ until not new_equals -+ -+ return equals -+ end -+ -+ --- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. -+ -- @param s The string to be quoted. -+ -- @return The quoted string. -+ function stringx.quote_string(s) -+ assert_string(1,s) -+ -- Find out if there are any embedded long-quote sequences that may cause issues. -+ -- This is important when strings are embedded within strings, like when serializing. -+ -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. -+ local equal_signs = has_lquote(s .. "]") -+ -+ -- Note that strings containing "\r" can't be quoted using long brackets -+ -- as Lua lexer converts all newlines to "\n" within long strings. -+ if (s:find("\n") or equal_signs) and not s:find("\r") then -+ -- If there is an embedded sequence that matches a long quote, then -+ -- find the one with the maximum number of = signs and add one to that number. -+ equal_signs = ("="):rep((equal_signs or -1) + 1) -+ -- Long strings strip out leading newline. We want to retain that, when quoting. -+ if s:find("^\n") then s = "\n" .. s end -+ local lbracket, rbracket = -+ "[" .. equal_signs .. "[", -+ "]" .. equal_signs .. "]" -+ s = lbracket .. s .. rbracket -+ else -+ -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. -+ s = ("%q"):format(s):gsub("\r", "\\r") -+ end -+ return s -+ end -+end -+ -+ -+--- Python-style formatting operator. -+-- Calling `text.format_operator()` overloads the % operator for strings to give -+-- Python/Ruby style formatted output. -+-- This is extended to also do template-like substitution for map-like data. -+-- -+-- Note this goes further than the original, and will allow these cases: -+-- -+-- 1. a single value -+-- 2. a list of values -+-- 3. a map of var=value pairs -+-- 4. a function, as in gsub -+-- -+-- For the second two cases, it uses $-variable substitution. -+-- -+-- When called, this function will monkey-patch the global `string` metatable by -+-- adding a `__mod` method. -+-- -+-- See the lua-users wiki -+-- -+-- @usage -+-- require 'pl.text'.format_operator() -+-- local out1 = '%s = %5.3f' % {'PI',math.pi} --> 'PI = 3.142' -+-- local out2 = '$name = $value' % {name='dog',value='Pluto'} --> 'dog = Pluto' -+function stringx.format_operator() -+ -+ local format = string.format -+ -+ -- a more forgiving version of string.format, which applies -+ -- tostring() to any value with a %s format. -+ local function formatx (fmt,...) -+ local args = pack(...) -+ local i = 1 -+ for p in fmt:gmatch('%%.') do -+ if p == '%s' and type(args[i]) ~= 'string' then -+ args[i] = tostring(args[i]) -+ end -+ i = i + 1 - end -- return s --end -- ----- Utility function that finds any patterns that match a long string's an open or close. ---- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. ---- Right now, it simply returns the greatest number of them found. ---- @param s The string ---- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. --local function has_lquote(s) -- local lstring_pat = '([%[%]])(=*)%1' -- local equals -- local start, finish, bracket, new_equals = nil, 1, nil, nil -- -- repeat -- start, finish, bracket, new_equals = s:find(lstring_pat, finish) -- if new_equals then -- equals = max(equals or 0, #new_equals) -- end -- until not new_equals -- -- return equals --end -- ----- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. ---- @param s The string to be quoted. ---- @return The quoted string. --function stringx.quote_string(s) -- assert_string(1,s) -- -- Find out if there are any embedded long-quote sequences that may cause issues. -- -- This is important when strings are embedded within strings, like when serializing. -- -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. -- local equal_signs = has_lquote(s .. "]") -- -- -- Note that strings containing "\r" can't be quoted using long brackets -- -- as Lua lexer converts all newlines to "\n" within long strings. -- if (s:find("\n") or equal_signs) and not s:find("\r") then -- -- If there is an embedded sequence that matches a long quote, then -- -- find the one with the maximum number of = signs and add one to that number. -- equal_signs = ("="):rep((equal_signs or -1) + 1) -- -- Long strings strip out leading newline. We want to retain that, when quoting. -- if s:find("^\n") then s = "\n" .. s end -- local lbracket, rbracket = -- "[" .. equal_signs .. "[", -- "]" .. equal_signs .. "]" -- s = lbracket .. s .. rbracket -+ return format(fmt,unpack(args)) -+ end -+ -+ local function basic_subst(s,t) -+ return (s:gsub('%$([%w_]+)',t)) -+ end -+ -+ getmetatable("").__mod = function(a, b) -+ if b == nil then -+ return a -+ elseif type(b) == "table" and getmetatable(b) == nil then -+ if #b == 0 then -- assume a map-like table -+ return _substitute(a,b,true) -+ else -+ return formatx(a,unpack(b)) -+ end -+ elseif type(b) == 'function' then -+ return basic_subst(a,b) - else -- -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. -- s = ("%q"):format(s):gsub("\r", "\\r") -+ return formatx(a,b) - end -- return s -+ end - end - -+--- import the stringx functions into the global string (meta)table - function stringx.import() - utils.import(stringx,string) - end -diff --git a/extra/penlight/lua/pl/tablex.lua b/extra/penlight/lua/pl/tablex.lua -index 03ecba9..3606912 100644 ---- a/extra/penlight/lua/pl/tablex.lua -+++ b/extra/penlight/lua/pl/tablex.lua -@@ -95,37 +95,36 @@ function tablex.copy (t) - return res - end - ----- make a deep copy of a table, recursively copying all the keys and fields. ---- This will also set the copied table's metatable to that of the original. ---- @within Copying ---- @tab t A table ---- @return new table --function tablex.deepcopy(t) -+local function cycle_aware_copy(t, cache) - if type(t) ~= 'table' then return t end -+ if cache[t] then return cache[t] end - assert_arg_iterable(1,t) -- local mt = getmetatable(t) - local res = {} -+ cache[t] = res -+ local mt = getmetatable(t) - for k,v in pairs(t) do -- if type(v) == 'table' then -- v = tablex.deepcopy(v) -- end -+ k = cycle_aware_copy(k, cache) -+ v = cycle_aware_copy(v, cache) - res[k] = v - end - setmetatable(res,mt) - return res - end - --local abs, deepcompare = math.abs -+--- make a deep copy of a table, recursively copying all the keys and fields. -+-- This supports cycles in tables; cycles will be reproduced in the copy. -+-- This will also set the copied table's metatable to that of the original. -+-- @within Copying -+-- @tab t A table -+-- @return new table -+function tablex.deepcopy(t) -+ return cycle_aware_copy(t,{}) -+end - ----- compare two values. ---- if they are tables, then compare their keys and fields recursively. ---- @within Comparing ---- @param t1 A value ---- @param t2 A value ---- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false) ---- @number[opt] eps if defined, then used for any number comparisons ---- @return true or false --function tablex.deepcompare(t1,t2,ignore_mt,eps) -+local abs = math.abs -+ -+local function cycle_aware_compare(t1,t2,ignore_mt,eps,cache) -+ if cache[t1] and cache[t1][t2] then return true end - local ty1 = type(t1) - local ty2 = type(t2) - if ty1 ~= ty2 then return false end -@@ -143,21 +142,39 @@ function tablex.deepcompare(t1,t2,ignore_mt,eps) - for k2 in pairs(t2) do - if t1[k2]==nil then return false end - end -+ cache[t1] = cache[t1] or {} -+ cache[t1][t2] = true - for k1,v1 in pairs(t1) do - local v2 = t2[k1] -- if not deepcompare(v1,v2,ignore_mt,eps) then return false end -+ if not cycle_aware_compare(v1,v2,ignore_mt,eps,cache) then return false end - end -- - return true - end - --deepcompare = tablex.deepcompare -+--- compare two values. -+-- if they are tables, then compare their keys and fields recursively. -+-- @within Comparing -+-- @param t1 A value -+-- @param t2 A value -+-- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false) -+-- @number[opt] eps if defined, then used for any number comparisons -+-- @return true or false -+function tablex.deepcompare(t1,t2,ignore_mt,eps) -+ return cycle_aware_compare(t1,t2,ignore_mt,eps,{}) -+end - - --- compare two arrays using a predicate. - -- @within Comparing - -- @array t1 an array - -- @array t2 an array ---- @func cmp A comparison function -+-- @func cmp A comparison function; `bool = cmp(t1_value, t2_value)` -+-- @return true or false -+-- @usage -+-- assert(tablex.compare({ 1, 2, 3 }, { 1, 2, 3 }, "==")) -+-- -+-- assert(tablex.compare( -+-- {1,2,3, hello = "world"}, -- fields are not compared! -+-- {1,2,3}, function(v1, v2) return v1 == v2 end) - function tablex.compare (t1,t2,cmp) - assert_arg_indexable(1,t1) - assert_arg_indexable(2,t2) -@@ -183,14 +200,16 @@ function tablex.compare_no_order (t1,t2,cmp) - for i = 1,#t1 do - local val = t1[i] - local gotcha -- for j = 1,#t2 do if not visited[j] then -- local match -- if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end -- if match then -- gotcha = j -- break -+ for j = 1,#t2 do -+ if not visited[j] then -+ local match -+ if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end -+ if match then -+ gotcha = j -+ break -+ end - end -- end end -+ end - if not gotcha then return false end - visited[gotcha] = true - end -@@ -224,7 +243,7 @@ end - -- @within Finding - -- @array t A list-like table - -- @param val A value ---- @param idx index to start; -1 means last element,etc (default 1) -+-- @param idx index to start; -1 means last element,etc (default `#t`) - -- @return index of value or nil if not found - -- @usage rfind({10,10,10},10) == 3 - function tablex.rfind(t,val,idx) -@@ -239,12 +258,38 @@ end - - - --- return the index (or key) of a value in a table using a comparison function. -+-- -+-- *NOTE*: the 2nd return value of this function, the value returned -+-- by the comparison function, has a limitation that it cannot be `false`. -+-- Because if it is, then it indicates the comparison failed, and the -+-- function will continue the search. See examples. - -- @within Finding - -- @tab t A table - -- @func cmp A comparison function - -- @param arg an optional second argument to the function - -- @return index of value, or nil if not found ---- @return value returned by comparison function -+-- @return value returned by comparison function (cannot be `false`!) -+-- @usage -+-- -- using an operator -+-- local lst = { "Rudolph", true, false, 15 } -+-- local idx, cmp_result = tablex.rfind(lst, "==", "Rudolph") -+-- assert(idx == 1) -+-- assert(cmp_result == true) -+-- -+-- local idx, cmp_result = tablex.rfind(lst, "==", false) -+-- assert(idx == 3) -+-- assert(cmp_result == true) -- looking up 'false' works! -+-- -+-- -- using a function returning the value looked up -+-- local cmp = function(v1, v2) return v1 == v2 and v2 end -+-- local idx, cmp_result = tablex.rfind(lst, cmp, "Rudolph") -+-- assert(idx == 1) -+-- assert(cmp_result == "Rudolph") -- the value is returned -+-- -+-- -- NOTE: this fails, since 'false' cannot be returned! -+-- local idx, cmp_result = tablex.rfind(lst, cmp, false) -+-- assert(idx == nil) -- looking up 'false' failed! -+-- assert(cmp_result == nil) - function tablex.find_if(t,cmp,arg) - assert_arg_iterable(1,t) - cmp = function_arg(2,cmp) -@@ -313,6 +358,28 @@ end - -- @string name the method name - -- @array t a list-like table - -- @param ... any extra arguments to the method -+-- @return a `List` with the results of the method (1st result only) -+-- @usage -+-- local Car = {} -+-- Car.__index = Car -+-- function Car.new(car) -+-- return setmetatable(car or {}, Car) -+-- end -+-- Car.speed = 0 -+-- function Car:faster(increase) -+-- self.speed = self.speed + increase -+-- return self.speed -+-- end -+-- -+-- local ferrari = Car.new{ name = "Ferrari" } -+-- local lamborghini = Car.new{ name = "Lamborghini", speed = 50 } -+-- local cars = { ferrari, lamborghini } -+-- -+-- assert(ferrari.speed == 0) -+-- assert(lamborghini.speed == 50) -+-- tablex.map_named_method("faster", cars, 10) -+-- assert(ferrari.speed == 10) -+-- assert(lamborghini.speed == 60) - function tablex.map_named_method (name,t,...) - utils.assert_string(1,name) - assert_arg_indexable(2,t) -@@ -329,7 +396,8 @@ end - -- Any extra arguments are passed to the function. - -- @func fun A function that takes at least one argument - -- @tab t a table ---- @param ... extra arguments -+-- @param ... extra arguments passed to `fun` -+-- @see tablex.foreach - function tablex.transform (fun,t,...) - assert_arg_iterable(1,t) - fun = function_arg(1,fun) -@@ -422,8 +490,9 @@ end - -- Note that the Lua 5.0 function table.foreach passed the _key_ first. - -- @within Iterating - -- @tab t a table ---- @func fun a function with at least one argument ---- @param ... extra arguments -+-- @func fun a function on the elements; `function(value, key, ...)` -+-- @param ... extra arguments passed to `fun` -+-- @see tablex.transform - function tablex.foreach(t,fun,...) - assert_arg_iterable(1,t) - fun = function_arg(2,fun) -@@ -455,7 +524,7 @@ end - -- @func fun a function of n arguments - -- @tab ... n tables - -- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333} ---- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300} -+-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300} - -- @param fun A function that takes as many arguments as there are tables - function tablex.mapn(fun,...) - fun = function_arg(1,fun) -@@ -493,15 +562,15 @@ function tablex.pairmap(fun,t,...) - for k,v in pairs(t) do - local rv,rk = fun(k,v,...) - if rk then -- if res[rk] then -- if type(res[rk]) == 'table' then -- table.insert(res[rk],rv) -- else -- res[rk] = {res[rk], rv} -- end -- else -- res[rk] = rv -- end -+ if res[rk] then -+ if type(res[rk]) == 'table' then -+ table.insert(res[rk],rv) -+ else -+ res[rk] = {res[rk], rv} -+ end -+ else -+ res[rk] = rv -+ end - else - res[#res+1] = rv - end -@@ -513,7 +582,7 @@ local function keys_op(i,v) return i end - - --- return all the keys of a table in arbitrary order. - -- @within Extraction ---- @tab t A table -+-- @tab t A list-like table where the values are the keys of the input table - function tablex.keys(t) - assert_arg_iterable(1,t) - return makelist(tablex.pairmap(keys_op,t)) -@@ -523,7 +592,7 @@ local function values_op(i,v) return v end - - --- return all the values of the table in arbitrary order - -- @within Extraction ---- @tab t A table -+-- @tab t A list-like table where the values are the values of the input table - function tablex.values(t) - assert_arg_iterable(1,t) - return makelist(tablex.pairmap(values_op,t)) -@@ -861,8 +930,8 @@ end - -- @tab t the table - -- @param value the value - -- @array[opt] exclude any tables to avoid searching ---- @usage search(_G,math.sin,{package.path}) == 'math.sin' - -- @return a fieldspec, e.g. 'a.b' or 'math.sin' -+-- @usage search(_G,math.sin,{package.path}) == 'math.sin' - function tablex.search (t,value,exclude) - assert_arg_iterable(1,t) - local tables = {[t]=true} -@@ -910,8 +979,11 @@ end - --- modifies a table to be read only. - -- This only offers weak protection. Tables can still be modified with - -- `table.insert` and `rawset`. -+-- -+-- *NOTE*: for Lua 5.1 length, pairs and ipairs will not work, since the -+-- equivalent metamethods are only available in Lua 5.2 and newer. - -- @tab t the table ---- @return the table read only. -+-- @return the table read only (a proxy). - function tablex.readonly(t) - local mt = { - __index=t, -diff --git a/extra/penlight/lua/pl/template.lua b/extra/penlight/lua/pl/template.lua -index 3de5eda..c10a1f6 100644 ---- a/extra/penlight/lua/pl/template.lua -+++ b/extra/penlight/lua/pl/template.lua -@@ -30,57 +30,72 @@ - - local utils = require 'pl.utils' - --local append,format,strsub,strfind,strgsub = table.insert,string.format,string.sub,string.find,string.gsub -+local append, concat = table.insert, table.concat -+local format, strsub, strfind, strgsub, strrep = string.format, string.sub, string.find, string.gsub, string.rep - --local APPENDER = "\n__R_size = __R_size + 1; __R_table[__R_size] = " -+local APPENDER = " __R_size = __R_size + 1; __R_table[__R_size] = " - -+-- When this function returns, `pieces` is guaranteed to hold a complete Lua -+-- statement, meaning that new statements can be appended without creating -+-- invalid Lua code. - local function parseDollarParen(pieces, chunk, exec_pat, newline) - local s = 1 - for term, executed, e in chunk:gmatch(exec_pat) do -- executed = '('..strsub(executed,2,-2)..')' -- append(pieces, APPENDER..format("%q", strsub(chunk,s, term - 1))) -- append(pieces, APPENDER..format("(%s or '')", executed)) -+ executed = '(' .. strsub(executed, 2, -2) .. ')' -+ append(pieces, APPENDER .. format("%q;", strsub(chunk, s, term - 1))) -+ append(pieces, APPENDER .. format("__tostring(%s or '');", executed)) - s = e - end -- local r -+ local remainder, newlines_removed - if newline then -- r = format("%q", strgsub(strsub(chunk,s),"\n","")) -+ remainder, newlines_removed = strgsub(strsub(chunk, s), "\n", "") - else -- r = format("%q", strsub(chunk,s)) -+ remainder, newlines_removed = strsub(chunk, s), 0 - end -- if r ~= '""' then -- append(pieces, APPENDER..r) -+ if remainder ~= "" then -+ append(pieces, APPENDER .. format("%q;", remainder)) -+ end -+ if newlines_removed > 0 then -+ append(pieces, strrep("\n", newlines_removed)) - end - end - --local function parseHashLines(chunk,inline_escape,brackets,esc,newline) -- local exec_pat = "()"..inline_escape.."(%b"..brackets..")()" -+local function parseHashLines(chunk, inline_escape, brackets, esc, newline) -+ -- Escape special characters to avoid invalid expressions -+ inline_escape = utils.escape(inline_escape) -+ esc = utils.escape(esc) -+ -+ local exec_pat = "()" .. inline_escape .. "(%b" .. brackets .. ")()" - -- local esc_pat = esc.."+([^\n]*\n?)" -- local esc_pat1, esc_pat2 = "^"..esc_pat, "\n"..esc_pat -- local pieces, s = {"return function()\nlocal __R_size, __R_table = 0, {}", n = 1}, 1 -+ local esc_pat = esc .. "+([^\n]*\n?)" -+ local esc_pat1, esc_pat2 = "^" .. esc_pat, "\n" .. esc_pat -+ local pieces, s = {"return function() local __R_size, __R_table, __tostring = 0, {}, __tostring; "}, 1 - while true do -- local ss, e, lua = strfind(chunk,esc_pat1, s) -+ local _, e, lua = strfind(chunk, esc_pat1, s) - if not e then -- ss, e, lua = strfind(chunk,esc_pat2, s) -- parseDollarParen(pieces, strsub(chunk,s, ss), exec_pat, newline) -+ local ss -+ ss, e, lua = strfind(chunk, esc_pat2, s) -+ parseDollarParen(pieces, strsub(chunk, s, ss), exec_pat, newline) - if not e then break end - end -- append(pieces, "\n"..lua) -+ if strsub(lua, -1, -1) ~= "\n" then lua = lua .. "\n" end -- Ensure trailing newline -+ append(pieces, lua) -+ -- since `lua` ends with a newline, there is no danger of subsequent -+ -- statements being gobbled up by comments or being altered - s = e + 1 - end -- append(pieces, "\nreturn __R_table\nend") -- -+ append(pieces, "return __R_table; end") -+ - -- let's check for a special case where there is nothing to template, but it's - -- just a single static string - local short = false -- if (#pieces == 3) and (pieces[2]:find(APPENDER, 1, true) == 1) then -- pieces = { "return " .. pieces[2]:sub(#APPENDER+1,-1) } -+ if (#pieces == 3) and (strfind(pieces[2], APPENDER, 1, true) == 1) then -+ pieces = { "return " .. strsub(pieces[2], #APPENDER + 1, -1) } - short = true - end - -- if short == true, the generated function will not return a table of strings, - -- but a single string -- return table.concat(pieces), short -+ return concat(pieces), short - end - - local template = {} -@@ -88,33 +103,34 @@ local template = {} - --- expand the template using the specified environment. - -- This function will compile and render the template. For more performant - -- recurring usage use the two step approach by using `compile` and `ct:render`. ---- There are six special fields in the environment table `env` ---- ---- * `_parent`: continue looking up in this table (e.g. `_parent=_G`). ---- * `_brackets`: bracket pair that wraps inline Lua expressions, default is '()'. ---- * `_escape`: character marking Lua lines, default is '#' ---- * `_inline_escape`: character marking inline Lua expression, default is '$'. ---- * `_chunk_name`: chunk name for loaded templates, used if there ---- is an error in Lua code. Default is 'TMP'. ---- * `_debug`: if truthy, the generated code will be printed upon a render error ---- - -- @string str the template string ---- @tab[opt] env the environment ---- @return `rendered template + nil + source_code`, or `nil + error + source_code`. The last ---- return value (`source_code`) is only returned if the debug option is used. --function template.substitute(str,env) -+-- @tparam[opt] table env the environment. This table has the following special fields: -+-- @tparam[opt=nil] table env._parent continue looking up in this table (e.g. `_parent=_G`). -+-- @tparam[opt="()"] string env._brackets bracket pair that wraps inline Lua expressions. -+-- @tparam[opt="#"] string env._escape character marking Lua lines. -+-- @tparam[opt="$"] string env._inline_escape character marking inline Lua expression. -+-- @tparam[opt="TMP"] string env._chunk_name chunk name for loaded templates, used if there -+-- is an error in Lua code. -+-- @tparam[opt=false] boolean env._debug if truthy, the generated code will be printed upon a render error. -+-- @treturn[1] string render result -+-- @treturn[1] nil -+-- @treturn[1] string source_code (only if '`env._debug`' was truthy). -+-- @treturn[2] nil -+-- @treturn[2] string error message -+-- @treturn[2] string source_code (only if '`env._debug`' was truthy). -+function template.substitute(str, env) - env = env or {} - local t, err = template.compile(str, { -- chunk_name = rawget(env,"_chunk_name"), -- escape = rawget(env,"_escape"), -- inline_escape = rawget(env,"_inline_escape"), -- inline_brackets = rawget(env,"_brackets"), -- newline = nil, -- debug = rawget(env,"_debug") -+ chunk_name = rawget(env, "_chunk_name"), -+ escape = rawget(env, "_escape"), -+ inline_escape = rawget(env, "_inline_escape"), -+ inline_brackets = rawget(env, "_brackets"), -+ newline = false, -+ debug = rawget(env, "_debug") - }) - if not t then return t, err end -- -- return t:render(env, rawget(env,"_parent"), rawget(env,"_debug")) -+ -+ return t:render(env, rawget(env, "_parent"), rawget(env, "_debug")) - end - - --- executes the previously compiled template and renders it. -@@ -123,12 +139,16 @@ end - -- @tab[opt] parent continue looking up in this table (e.g. `parent=_G`). - -- @bool[opt] db if thruthy, it will print the code upon a render error - -- (provided the template was compiled with the debug option). ---- @return `rendered template + nil + source_code`, or `nil + error + source_code`. The last return value ---- (`source_code`) is only returned if the template was compiled with the debug option. -+-- @treturn[1] string render result -+-- @treturn[1] nil -+-- @treturn[1] string source_code (only if '`env._debug`' was truthy). -+-- @treturn[2] nil -+-- @treturn[2] string error message -+-- @treturn[2] string source_code (only if '`env._debug`' was truthy). - -- @usage - -- local ct, err = template.compile(my_template) - -- local rendered , err = ct:render(my_env, parent) --local render = function(self, env, parent, db) -+local function render(self, env, parent, db) - env = env or {} - if parent then -- parent is a bit silly, but for backward compatibility retained - setmetatable(env, {__index = parent}) -@@ -140,25 +160,27 @@ local render = function(self, env, parent, db) - if self.code and db then print(self.code) end - return nil, out, self.code - end -- return table.concat(out), nil, self.code -+ return concat(out), nil, self.code - end - - --- compiles the template. - -- Returns an object that can repeatedly be rendered without parsing/compiling ---- the template again. ---- The options passed in the `opts` table support the following options: ---- ---- * `chunk_name`: chunk name for loaded templates, used if there ---- is an error in Lua code. Default is 'TMP'. ---- * `escape`: character marking Lua lines, default is '#' ---- * `inline_escape`: character marking inline Lua expression, default is '$'. ---- * `inline_brackets`: bracket pair that wraps inline Lua expressions, default is '()'. ---- * `newline`: string to replace newline characters, default is `nil` (not replacing newlines). ---- * `debug`: if truthy, the generated source code will be retained within the compiled template object, default is `nil`. ---- ---- @string str the template string ---- @tab[opt] opts the compilation options to use ---- @return template object, or `nil + error + source_code` -+-- the template again. Preserves the line layout of the template so that line -+-- numbers in error messages should point to the correct lines in the source -+-- string. -+-- @tparam string str the template string -+-- @tparam[opt] table opts the compilation options to use. This table supports the following options: -+-- @tparam[opt="TMP"] string opts.chunk_name chunk name for loaded templates, used if there -+-- is an error in Lua code. -+-- @tparam[opt="#"] string opts.escape character marking Lua lines. -+-- @tparam[opt="$"] string opts.inline_escape character marking inline Lua expression. -+-- @tparam[opt="()"] string opts.inline_brackets bracket pair that wraps inline Lua expressions. -+-- @tparam[opt=false] boolean opts.newline if truthy, newlines will be stripped from text in the template. -+-- @tparam[opt=false] boolean opts.debug if truthy, the generated code will be printed upon a render error. -+-- @treturn[1] ct compiled template object -+-- @treturn[2] nil -+-- @treturn[2] string error message -+-- @treturn[2] string source_code - -- @usage - -- local ct, err = template.compile(my_template) - -- local rendered , err = ct:render(my_env, parent) -@@ -168,10 +190,10 @@ function template.compile(str, opts) - local escape = opts.escape or '#' - local inline_escape = opts.inline_escape or '$' - local inline_brackets = opts.inline_brackets or '()' -- -- local code, short = parseHashLines(str,inline_escape,inline_brackets,escape,opts.newline) -- local env = {} -- local fn, err = utils.load(code, chunk_name,'t',env) -+ -+ local code, short = parseHashLines(str, inline_escape, inline_brackets, escape, opts.newline) -+ local env = { __tostring = tostring } -+ local fn, err = utils.load(code, chunk_name, 't', env) - if not fn then return nil, err, code end - - if short then -diff --git a/extra/penlight/lua/pl/test.lua b/extra/penlight/lua/pl/test.lua -index c77eba7..694bbc5 100644 ---- a/extra/penlight/lua/pl/test.lua -+++ b/extra/penlight/lua/pl/test.lua -@@ -12,7 +12,7 @@ local tablex = require 'pl.tablex' - local utils = require 'pl.utils' - local pretty = require 'pl.pretty' - local path = require 'pl.path' --local type,unpack = type,utils.pack -+local type,unpack,pack = type,utils.unpack,utils.pack - local clock = os.clock - local debug = require 'debug' - local io = io -@@ -91,7 +91,7 @@ function test.assertraise(fn,e,where) - else - ok, err = pcall(fn) - end -- if not err or err:match(e)==nil then -+ if ok or err:match(e)==nil then - complain (err,e,"these errors did not match",where) - end - end -@@ -112,7 +112,7 @@ end - -- tuple type -- - - local tuple_mt = { -- unpack = table.unpack -+ unpack = unpack - } - tuple_mt.__index = tuple_mt - -@@ -146,7 +146,7 @@ end - -- and there is an `unpack` method. - -- @usage asserteq(tuple( ('ab'):find 'a'), tuple(1,1)) - function test.tuple(...) -- return setmetatable(table.pack(...), tuple_mt) -+ return setmetatable(pack(...), tuple_mt) - end - - --- Time a function. Call the function a given number of times, and report the number of seconds taken, -diff --git a/extra/penlight/lua/pl/text.lua b/extra/penlight/lua/pl/text.lua -index 7e0e933..8b6a53f 100644 ---- a/extra/penlight/lua/pl/text.lua -+++ b/extra/penlight/lua/pl/text.lua -@@ -4,245 +4,23 @@ - -- libraries, see string.Template). It also provides similar functions to those - -- found in the textwrap module. - -- ---- See @{03-strings.md.String_Templates|the Guide}. ---- ---- Calling `text.format_operator()` overloads the % operator for strings to give Python/Ruby style formated output. ---- This is extended to also do template-like substitution for map-like data. -+-- IMPORTANT: this module has been deprecated and will be removed in a future -+-- version (2.0). The contents of this module have moved to the `pl.stringx` -+-- module. - -- ---- > require 'pl.text'.format_operator() ---- > = '%s = %5.3f' % {'PI',math.pi} ---- PI = 3.142 ---- > = '$name = $value' % {name='dog',value='Pluto'} ---- dog = Pluto -+-- See @{03-strings.md.String_Templates|the Guide}. - -- ---- Dependencies: `pl.utils`, `pl.types` -+-- Dependencies: `pl.stringx`, `pl.utils` - -- @module pl.text - --local gsub = string.gsub --local concat,append = table.concat,table.insert --local utils = require 'pl.utils' --local bind1,usplit,assert_arg = utils.bind1,utils.split,utils.assert_arg --local is_callable = require 'pl.types'.is_callable --local unpack = utils.unpack -- --local function makelist(l) -- return setmetatable(l, require('pl.List')) --end -- --local function lstrip(str) return (str:gsub('^%s+','')) end --local function strip(str) return (lstrip(str):gsub('%s+$','')) end --local function split(s,delim) return makelist(usplit(s,delim)) end -- --local function imap(f,t,...) -- local res = {} -- for i = 1,#t do res[i] = f(t[i],...) end -- return res --end -- ----[[ --module ('pl.text',utils._module) --]] -- --local text = {} -- --local function _indent (s,sp) -- local sl = split(s,'\n') -- return concat(imap(bind1('..',sp),sl),'\n')..'\n' --end -- ----- indent a multiline string. ---- @param s the string ---- @param n the size of the indent ---- @param ch the character to use when indenting (default ' ') ---- @return indented string --function text.indent (s,n,ch) -- assert_arg(1,s,'string') -- assert_arg(2,n,'number') -- return _indent(s,string.rep(ch or ' ',n)) --end -- ----- dedent a multiline string by removing any initial indent. ---- useful when working with [[..]] strings. ---- @param s the string ---- @return a string with initial indent zero. --function text.dedent (s) -- assert_arg(1,s,'string') -- local sl = split(s,'\n') -- local i1,i2 = (#sl>0 and sl[1] or ''):find('^%s*') -- sl = imap(string.sub,sl,i2+1) -- return concat(sl,'\n')..'\n' --end -- ----- format a paragraph into lines so that they fit into a line width. ---- It will not break long words, so lines can be over the length ---- to that extent. ---- @param s the string ---- @param width the margin width, default 70 ---- @return a list of lines --function text.wrap (s,width) -- assert_arg(1,s,'string') -- width = width or 70 -- s = s:gsub('\n',' ') -- local i,nxt = 1 -- local lines,line = {} -- while i < #s do -- nxt = i+width -- if s:find("[%w']",nxt) then -- inside a word -- nxt = s:find('%W',nxt+1) -- so find word boundary -- end -- line = s:sub(i,nxt) -- i = i + #line -- append(lines,strip(line)) -- end -- return makelist(lines) --end -- ----- format a paragraph so that it fits into a line width. ---- @param s the string ---- @param width the margin width, default 70 ---- @return a string ---- @see wrap --function text.fill (s,width) -- return concat(text.wrap(s,width),'\n') .. '\n' --end -- --local Template = {} --text.Template = Template --Template.__index = Template --setmetatable(Template, { -- __call = function(obj,tmpl) -- return Template.new(tmpl) -- end}) -- --function Template.new(tmpl) -- assert_arg(1,tmpl,'string') -- local res = {} -- res.tmpl = tmpl -- setmetatable(res,Template) -- return res --end -- --local function _substitute(s,tbl,safe) -- local subst -- if is_callable(tbl) then -- subst = tbl -- else -- function subst(f) -- local s = tbl[f] -- if not s then -- if safe then -- return f -- else -- error("not present in table "..f) -- end -- else -- return s -- end -- end -- end -- local res = gsub(s,'%${([%w_]+)}',subst) -- return (gsub(res,'%$([%w_]+)',subst)) --end -- ----- substitute values into a template, throwing an error. ---- This will throw an error if no name is found. ---- @param tbl a table of name-value pairs. --function Template:substitute(tbl) -- assert_arg(1,tbl,'table') -- return _substitute(self.tmpl,tbl,false) --end -- ----- substitute values into a template. ---- This version just passes unknown names through. ---- @param tbl a table of name-value pairs. --function Template:safe_substitute(tbl) -- assert_arg(1,tbl,'table') -- return _substitute(self.tmpl,tbl,true) --end -- ----- substitute values into a template, preserving indentation.
    ---- If the value is a multiline string _or_ a template, it will insert ---- the lines at the correct indentation.
    ---- Furthermore, if a template, then that template will be subsituted ---- using the same table. ---- @param tbl a table of name-value pairs. --function Template:indent_substitute(tbl) -- assert_arg(1,tbl,'table') -- if not self.strings then -- self.strings = split(self.tmpl,'\n') -- end -- -- the idea is to substitute line by line, grabbing any spaces as -- -- well as the $var. If the value to be substituted contains newlines, -- -- then we split that into lines and adjust the indent before inserting. -- local function subst(line) -- return line:gsub('(%s*)%$([%w_]+)',function(sp,f) -- local subtmpl -- local s = tbl[f] -- if not s then error("not present in table "..f) end -- if getmetatable(s) == Template then -- subtmpl = s -- s = s.tmpl -- else -- s = tostring(s) -- end -- if s:find '\n' then -- s = _indent(s,sp) -- end -- if subtmpl then return _substitute(s,tbl) -- else return s -- end -- end) -- end -- local lines = imap(subst,self.strings) -- return concat(lines,'\n')..'\n' --end -- --------- Python-style formatting operator ------ ---- (see the lua-users wiki) -- -- --function text.format_operator() -- -- local format = string.format -- -- -- a more forgiving version of string.format, which applies -- -- tostring() to any value with a %s format. -- local function formatx (fmt,...) -- local args = {...} -- local i = 1 -- for p in fmt:gmatch('%%.') do -- if p == '%s' and type(args[i]) ~= 'string' then -- args[i] = tostring(args[i]) -- end -- i = i + 1 -- end -- return format(fmt,unpack(args)) -- end -- -- local function basic_subst(s,t) -- return (s:gsub('%$([%w_]+)',t)) -- end -+local utils = require("pl.utils") - -- -- Note this goes further than the original, and will allow these cases: -- -- 1. a single value -- -- 2. a list of values -- -- 3. a map of var=value pairs -- -- 4. a function, as in gsub -- -- For the second two cases, it uses $-variable substituion. -- getmetatable("").__mod = function(a, b) -- if b == nil then -- return a -- elseif type(b) == "table" and getmetatable(b) == nil then -- if #b == 0 then -- assume a map-like table -- return _substitute(a,b,true) -- else -- return formatx(a,unpack(b)) -- end -- elseif type(b) == 'function' then -- return basic_subst(a,b) -- else -- return formatx(a,b) -- end -- end --end -+utils.raise_deprecation { -+ source = "Penlight " .. utils._VERSION, -+ message = "the contents of module 'pl.text' has moved into 'pl.stringx'", -+ version_removed = "2.0.0", -+ deprecated_after = "1.11.0", -+ no_trace = true, -+} - --return text -+return require "pl.stringx" -diff --git a/extra/penlight/lua/pl/types.lua b/extra/penlight/lua/pl/types.lua -index f4ab236..ce82efa 100644 ---- a/extra/penlight/lua/pl/types.lua -+++ b/extra/penlight/lua/pl/types.lua -@@ -4,28 +4,47 @@ - -- @module pl.types - - local utils = require 'pl.utils' -+local math_ceil = math.ceil -+local assert_arg = utils.assert_arg - local types = {} - ----- is the object either a function or a callable object?. ---- @param obj Object to check. --function types.is_callable (obj) -- return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call and true -+do -+ -- we prefer debug.getmetatable, but only if available -+ local gmt = (debug or {}).getmetatable or getmetatable -+ -+ --- is the object either a function or a callable object?. -+ -- @param obj Object to check. -+ function types.is_callable (obj) -+ if type(obj) == 'function' then -+ return true -+ end -+ local mt = gmt(obj) -+ if not mt then -+ return false -+ end -+ return type(rawget(mt, "__call")) == "function" -+ end - end - - --- is the object of the specified type?. ---- If the type is a string, then use type, otherwise compare with metatable -+-- If the type is a string, then use type, otherwise compare with metatable. -+-- -+-- NOTE: this function is imported from `utils.is_type`. - -- @param obj An object to check ---- @param tp String of what type it should be -+-- @param tp The expected type - -- @function is_type -+-- @see utils.is_type - types.is_type = utils.is_type - - local fileMT = getmetatable(io.stdout) - - --- a string representation of a type. ---- For tables with metatables, we assume that the metatable has a `_name` ---- field. Knows about Lua file objects. -+-- For tables and userdata with metatables, we assume that the metatable has a `_name` -+-- field. If the field is not present it will return 'unknown table' or -+-- 'unknown userdata'. -+-- Lua file objects return the type 'file'. - -- @param obj an object ---- @return a string like 'number', 'table' or 'List' -+-- @return a string like 'number', 'table', 'file' or 'List' - function types.type (obj) - local t = type(obj) - if t == 'table' or t == 'userdata' then -@@ -35,6 +54,7 @@ function types.type (obj) - elseif mt == nil then - return t - else -+ -- TODO: the "unknown" is weird, it should just return the type - return mt._name or "unknown "..t - end - else -@@ -45,22 +65,32 @@ end - --- is this number an integer? - -- @param x a number - -- @raise error if x is not a number -+-- @return boolean - function types.is_integer (x) -- return math.ceil(x)==x -+ return math_ceil(x)==x - end - - --- Check if the object is "empty". ---- An object is considered empty if it is nil, a table with out any items (key, ---- value pairs or indexes), or a string with no content (""). -+-- An object is considered empty if it is: -+-- -+-- - `nil` -+-- - a table without any items (key-value pairs or indexes) -+-- - a string with no content ("") -+-- - not a nil/table/string - -- @param o The object to check if it is empty. - -- @param ignore_spaces If the object is a string and this is true the string is ---- considered empty is it only contains spaces. ---- @return true if the object is empty, otherwise false. -+-- considered empty if it only contains spaces. -+-- @return `true` if the object is empty, otherwise a falsy value. - function types.is_empty(o, ignore_spaces) -- if o == nil or (type(o) == "table" and not next(o)) or (type(o) == "string" and (o == "" or (ignore_spaces and o:match("^%s+$")))) then -+ if o == nil then -+ return true -+ elseif type(o) == "table" then -+ return next(o) == nil -+ elseif type(o) == "string" then -+ return o == "" or (not not ignore_spaces and (not not o:find("^%s+$"))) -+ else - return true - end -- return false - end - - local function check_meta (val) -@@ -69,7 +99,14 @@ local function check_meta (val) - end - - --- is an object 'array-like'? -+-- An object is array like if: -+-- -+-- - it is a table, or -+-- - it has a metatable with `__len` and `__index` methods -+-- -+-- NOTE: since `__len` is 5.2+, on 5.1 is usually returns `false` for userdata - -- @param val any value. -+-- @return `true` if the object is array-like, otherwise a falsy value. - function types.is_indexable (val) - local mt = check_meta(val) - if mt == true then return true end -@@ -77,7 +114,14 @@ function types.is_indexable (val) - end - - --- can an object be iterated over with `pairs`? -+-- An object is iterable if: -+-- -+-- - it is a table, or -+-- - it has a metatable with a `__pairs` meta method -+-- -+-- NOTE: since `__pairs` is 5.2+, on 5.1 is usually returns `false` for userdata - -- @param val any value. -+-- @return `true` if the object is iterable, otherwise a falsy value. - function types.is_iterable (val) - local mt = check_meta(val) - if mt == true then return true end -@@ -85,20 +129,27 @@ function types.is_iterable (val) - end - - --- can an object accept new key/pair values? -+-- An object is iterable if: -+-- -+-- - it is a table, or -+-- - it has a metatable with a `__newindex` meta method -+-- - -- @param val any value. -+-- @return `true` if the object is writeable, otherwise a falsy value. - function types.is_writeable (val) - local mt = check_meta(val) - if mt == true then return true end - return mt and mt.__newindex and true - end - ---- Strings that should evaluate to true. -+-- Strings that should evaluate to true. -- TODO: add on/off ??? - local trues = { yes=true, y=true, ["true"]=true, t=true, ["1"]=true } - -- Conditions types should evaluate to true. - local true_types = { - boolean=function(o, true_strs, check_objs) return o end, - string=function(o, true_strs, check_objs) -- if trues[o:lower()] then -+ o = o:lower() -+ if trues[o] then - return true - end - -- Check alternative user provided strings. -@@ -119,18 +170,17 @@ local true_types = { - -- * string: 'yes', 'y', 'true', 't', '1' or additional strings specified by `true_strs`. - -- * number: Any non-zero value. - -- * table: Is not empty and `check_objs` is true. ---- * object: Is not `nil` and `check_objs` is true. -+-- * everything else: Is not `nil` and `check_objs` is true. - -- - -- @param o The object to evaluate. - -- @param[opt] true_strs optional Additional strings that when matched should evaluate to true. Comparison is case insensitive. - -- This should be a List of strings. E.g. "ja" to support German. ---- @param[opt] check_objs True if objects should be evaluated. Default is to evaluate objects as true if not nil ---- or if it is a table and it is not empty. -+-- @param[opt] check_objs True if objects should be evaluated. - -- @return true if the input evaluates to true, otherwise false. - function types.to_bool(o, true_strs, check_objs) - local true_func - if true_strs then -- utils.assert_arg(2, true_strs, "table") -+ assert_arg(2, true_strs, "table") - end - true_func = true_types[type(o)] - if true_func then -diff --git a/extra/penlight/lua/pl/url.lua b/extra/penlight/lua/pl/url.lua -index 9628871..8c7cfeb 100644 ---- a/extra/penlight/lua/pl/url.lua -+++ b/extra/penlight/lua/pl/url.lua -@@ -11,6 +11,7 @@ end - --- Quote the url, replacing special characters using the '%xx' escape. - -- @string s the string - -- @bool quote_plus Also escape slashes and replace spaces by plus signs. -+-- @return The quoted string, or if `s` wasn't a string, just plain unaltered `s`. - function url.quote(s, quote_plus) - if type(s) ~= "string" then - return s -@@ -34,6 +35,7 @@ end - - --- Unquote the url, replacing '%xx' escapes and plus signs. - -- @string s the string -+-- @return The unquoted string, or if `s` wasn't a string, just plain unaltered `s`. - function url.unquote(s) - if type(s) ~= "string" then - return s -diff --git a/extra/penlight/lua/pl/utils.lua b/extra/penlight/lua/pl/utils.lua -index 5fd11a2..ca2a022 100644 ---- a/extra/penlight/lua/pl/utils.lua -+++ b/extra/penlight/lua/pl/utils.lua -@@ -1,120 +1,529 @@ - --- Generally useful routines. - -- See @{01-introduction.md.Generally_useful_functions|the Guide}. - -- ---- Dependencies: `pl.compat` -+-- Dependencies: `pl.compat`, all exported fields and functions from -+-- `pl.compat` are also available in this module. - -- - -- @module pl.utils - local format = string.format - local compat = require 'pl.compat' - local stdout = io.stdout - local append = table.insert --local unpack = rawget(_G,'unpack') or rawget(table,'unpack') -- --local utils = { -- _VERSION = "1.5.2", -- lua51 = compat.lua51, -- setfenv = compat.setfenv, -- getfenv = compat.getfenv, -- load = compat.load, -- execute = compat.execute, -- dir_separator = compat.dir_separator, -- is_windows = compat.is_windows, -- unpack = unpack -+local concat = table.concat -+local _unpack = table.unpack -- always injected by 'compat' -+local find = string.find -+local sub = string.sub -+local next = next -+local floor = math.floor -+ -+local is_windows = compat.is_windows -+local err_mode = 'default' -+local raise -+local operators -+local _function_factories = {} -+ -+ -+local utils = { _VERSION = "1.14.0" } -+ -+for k, v in pairs(compat) do utils[k] = v end -+ -+--- Some standard patterns -+-- @table patterns -+utils.patterns = { -+ FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', -- floating point number -+ INTEGER = '[+%-%d]%d*', -- integer number -+ IDEN = '[%a_][%w_]*', -- identifier -+ FILE = '[%a%.\\][:%][%w%._%-\\]*', -- file - } - ----- end this program gracefully. ---- @param code The exit code or a message to be printed ---- @param ... extra arguments for message's format' ---- @see utils.fprintf --function utils.quit(code,...) -- if type(code) == 'string' then -- utils.fprintf(io.stderr,code,...) -- code = -1 -- else -- utils.fprintf(io.stderr,...) -- end -- io.stderr:write('\n') -- os.exit(code) -+ -+--- Standard meta-tables as used by other Penlight modules -+-- @table stdmt -+-- @field List the List metatable -+-- @field Map the Map metatable -+-- @field Set the Set metatable -+-- @field MultiMap the MultiMap metatable -+utils.stdmt = { -+ List = {_name='List'}, -+ Map = {_name='Map'}, -+ Set = {_name='Set'}, -+ MultiMap = {_name='MultiMap'}, -+} -+ -+ -+--- pack an argument list into a table. -+-- @param ... any arguments -+-- @return a table with field `n` set to the length -+-- @function utils.pack -+-- @see compat.pack -+-- @see utils.npairs -+-- @see utils.unpack -+utils.pack = table.pack -- added here to be symmetrical with unpack -+ -+--- unpack a table and return its contents. -+-- -+-- NOTE: this implementation differs from the Lua implementation in the way -+-- that this one DOES honor the `n` field in the table `t`, such that it is 'nil-safe'. -+-- @param t table to unpack -+-- @param[opt] i index from which to start unpacking, defaults to 1 -+-- @param[opt] j index of the last element to unpack, defaults to `t.n` or else `#t` -+-- @return multiple return values from the table -+-- @function utils.unpack -+-- @see compat.unpack -+-- @see utils.pack -+-- @see utils.npairs -+-- @usage -+-- local t = table.pack(nil, nil, nil, 4) -+-- local a, b, c, d = table.unpack(t) -- this `unpack` is NOT nil-safe, so d == nil -+-- -+-- local a, b, c, d = utils.unpack(t) -- this is nil-safe, so d == 4 -+function utils.unpack(t, i, j) -+ return _unpack(t, i or 1, j or t.n or #t) - end - - --- print an arbitrary number of arguments using a format. ---- @param fmt The format (see string.format) -+-- Output will be sent to `stdout`. -+-- @param fmt The format (see `string.format`) - -- @param ... Extra arguments for format --function utils.printf(fmt,...) -- utils.assert_string(1,fmt) -- utils.fprintf(stdout,fmt,...) -+function utils.printf(fmt, ...) -+ utils.assert_string(1, fmt) -+ utils.fprintf(stdout, fmt, ...) - end - - --- write an arbitrary number of arguments to a file using a format. - -- @param f File handle to write to. ---- @param fmt The format (see string.format). -+-- @param fmt The format (see `string.format`). - -- @param ... Extra arguments for format - function utils.fprintf(f,fmt,...) - utils.assert_string(2,fmt) - f:write(format(fmt,...)) - end - --local function import_symbol(T,k,v,libname) -- local key = rawget(T,k) -- -- warn about collisions! -- if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then -- utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k) -- return -+do -+ local function import_symbol(T,k,v,libname) -+ local key = rawget(T,k) -+ -- warn about collisions! -+ if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then -+ utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k) -+ return -+ end -+ rawset(T,k,v) -+ end -+ -+ local function lookup_lib(T,t) -+ for k,v in pairs(T) do -+ if v == t then return k end -+ end -+ return '?' -+ end -+ -+ local already_imported = {} -+ -+ --- take a table and 'inject' it into the local namespace. -+ -- @param t The table (table), or module name (string), defaults to this `utils` module table -+ -- @param T An optional destination table (defaults to callers environment) -+ function utils.import(t,T) -+ T = T or _G -+ t = t or utils -+ if type(t) == 'string' then -+ t = require (t) -+ end -+ local libname = lookup_lib(T,t) -+ if already_imported[t] then return end -+ already_imported[t] = libname -+ for k,v in pairs(t) do -+ import_symbol(T,k,v,libname) -+ end - end -- rawset(T,k,v) - end - --local function lookup_lib(T,t) -- for k,v in pairs(T) do -- if v == t then return k end -+--- return either of two values, depending on a condition. -+-- @param cond A condition -+-- @param value1 Value returned if cond is truthy -+-- @param value2 Value returned if cond is falsy -+function utils.choose(cond, value1, value2) -+ if cond then -+ return value1 -+ else -+ return value2 -+ end -+end -+ -+--- convert an array of values to strings. -+-- @param t a list-like table -+-- @param[opt] temp (table) buffer to use, otherwise allocate -+-- @param[opt] tostr custom tostring function, called with (value,index). Defaults to `tostring`. -+-- @return the converted buffer -+function utils.array_tostring (t,temp,tostr) -+ temp, tostr = temp or {}, tostr or tostring -+ for i = 1,#t do -+ temp[i] = tostr(t[i],i) - end -- return '?' -+ return temp -+end -+ -+ -+ -+--- is the object of the specified type? -+-- If the type is a string, then use type, otherwise compare with metatable -+-- @param obj An object to check -+-- @param tp String of what type it should be -+-- @return boolean -+-- @usage utils.is_type("hello world", "string") --> true -+-- -- or check metatable -+-- local my_mt = {} -+-- local my_obj = setmetatable(my_obj, my_mt) -+-- utils.is_type(my_obj, my_mt) --> true -+function utils.is_type (obj,tp) -+ if type(tp) == 'string' then return type(obj) == tp end -+ local mt = getmetatable(obj) -+ return tp == mt - end - --local already_imported = {} - ----- take a table and 'inject' it into the local namespace. ---- @param t The Table ---- @param T An optional destination table (defaults to callers environment) --function utils.import(t,T) -- T = T or _G -- t = t or utils -- if type(t) == 'string' then -- t = require (t) -+ -+--- an iterator with indices, similar to `ipairs`, but with a range. -+-- This is a nil-safe index based iterator that will return `nil` when there -+-- is a hole in a list. To be safe ensure that table `t.n` contains the length. -+-- @tparam table t the table to iterate over -+-- @tparam[opt=1] integer i_start start index -+-- @tparam[opt=t.n or #t] integer i_end end index -+-- @tparam[opt=1] integer step step size -+-- @treturn integer index -+-- @treturn any value at index (which can be `nil`!) -+-- @see utils.pack -+-- @see utils.unpack -+-- @usage -+-- local t = utils.pack(nil, 123, nil) -- adds an `n` field when packing -+-- -+-- for i, v in utils.npairs(t, 2) do -- start at index 2 -+-- t[i] = tostring(t[i]) -+-- end -+-- -+-- -- t = { n = 3, [2] = "123", [3] = "nil" } -+function utils.npairs(t, i_start, i_end, step) -+ step = step or 1 -+ if step == 0 then -+ error("iterator step-size cannot be 0", 2) -+ end -+ local i = (i_start or 1) - step -+ i_end = i_end or t.n or #t -+ if step < 0 then -+ return function() -+ i = i + step -+ if i < i_end then -+ return nil -+ end -+ return i, t[i] - end -- local libname = lookup_lib(T,t) -- if already_imported[t] then return end -- already_imported[t] = libname -- for k,v in pairs(t) do -- import_symbol(T,k,v,libname) -+ -+ else -+ return function() -+ i = i + step -+ if i > i_end then -+ return nil -+ end -+ return i, t[i] - end -+ end - end - --utils.patterns = { -- FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', -- INTEGER = '[+%-%d]%d*', -- IDEN = '[%a_][%w_]*', -- FILE = '[%a%.\\][:%][%w%._%-\\]*' --} - ----- escape any 'magic' characters in a string ---- @param s The input string --function utils.escape(s) -- utils.assert_string(1,s) -- return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) -+ -+--- an iterator over all non-integer keys (inverse of `ipairs`). -+-- It will skip any key that is an integer number, so negative indices or an -+-- array with holes will not return those either (so it returns slightly less than -+-- 'the inverse of `ipairs`'). -+-- -+-- This uses `pairs` under the hood, so any value that is iterable using `pairs` -+-- will work with this function. -+-- @tparam table t the table to iterate over -+-- @treturn key -+-- @treturn value -+-- @usage -+-- local t = { -+-- "hello", -+-- "world", -+-- hello = "hallo", -+-- world = "Welt", -+-- } -+-- -+-- for k, v in utils.kpairs(t) do -+-- print("German: ", v) -+-- end -+-- -+-- -- output; -+-- -- German: hallo -+-- -- German: Welt -+function utils.kpairs(t) -+ local index -+ return function() -+ local value -+ while true do -+ index, value = next(t, index) -+ if type(index) ~= "number" or floor(index) ~= index then -+ break -+ end -+ end -+ return index, value -+ end - end - ----- return either of two values, depending on a condition. ---- @param cond A condition ---- @param value1 Value returned if cond is true ---- @param value2 Value returned if cond is false (can be optional) --function utils.choose(cond,value1,value2) -- if cond then return value1 -- else return value2 -+ -+ -+--- Error handling -+-- @section Error-handling -+ -+--- assert that the given argument is in fact of the correct type. -+-- @param n argument index -+-- @param val the value -+-- @param tp the type -+-- @param verify an optional verification function -+-- @param msg an optional custom message -+-- @param lev optional stack position for trace, default 2 -+-- @return the validated value -+-- @raise if `val` is not the correct type -+-- @usage -+-- local param1 = assert_arg(1,"hello",'table') --> error: argument 1 expected a 'table', got a 'string' -+-- local param4 = assert_arg(4,'!@#$%^&*','string',path.isdir,'not a directory') -+-- --> error: argument 4: '!@#$%^&*' not a directory -+function utils.assert_arg (n,val,tp,verify,msg,lev) -+ if type(val) ~= tp then -+ error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2) - end -+ if verify and not verify(val) then -+ error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) -+ end -+ return val - end - --local raise -+--- creates an Enum or constants lookup table for improved error handling. -+-- This helps prevent magic strings in code by throwing errors for accessing -+-- non-existing values, and/or converting strings/identifiers to other values. -+-- -+-- Calling on the object does the same, but returns a soft error; `nil + err`, if -+-- the call is successful (the key exists), it will return the value. -+-- -+-- When calling with varargs or an array the values will be equal to the keys. -+-- The enum object is read-only. -+-- @tparam table|vararg ... the input for the Enum. If varargs or an array then the -+-- values in the Enum will be equal to the names (must be strings), if a hash-table -+-- then values remain (any type), and the keys must be strings. -+-- @return Enum object (read-only table/object) -+-- @usage -- Enum access at runtime -+-- local obj = {} -+-- obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT") -+-- -+-- if current_movement == obj.MOVEMENT.FORWARD then -+-- -- do something -+-- -+-- elseif current_movement == obj.MOVEMENT.REVERES then -+-- -- throws error due to typo 'REVERES', so a silent mistake becomes a hard error -+-- -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')" -+-- -+-- end -+-- @usage -- standardized error codes -+-- local obj = { -+-- ERR = utils.enum { -+-- NOT_FOUND = "the item was not found", -+-- OUT_OF_BOUNDS = "the index is outside the allowed range" -+-- }, -+-- -+-- some_method = function(self) -+-- return nil, self.ERR.OUT_OF_BOUNDS -+-- end, -+-- } -+-- -+-- local result, err = obj:some_method() -+-- if not result then -+-- if err == obj.ERR.NOT_FOUND then -+-- -- check on error code, not magic strings -+-- -+-- else -+-- -- return the error description, contained in the constant -+-- return nil, "error: "..err -- "error: the index is outside the allowed range" -+-- end -+-- end -+-- @usage -- validating/converting user-input -+-- local color = "purple" -+-- local ansi_colors = utils.enum { -+-- black = 30, -+-- red = 31, -+-- green = 32, -+-- } -+-- local color_code, err = ansi_colors(color) -- calling on the object, returns the value from the enum -+-- if not color_code then -+-- print("bad 'color', " .. err) -+-- -- "bad 'color', 'purple' is not a valid value (expected one of: 'black', 'red', 'green')" -+-- os.exit(1) -+-- end -+function utils.enum(...) -+ local first = select(1, ...) -+ local enum = {} -+ local lst -+ -+ if type(first) ~= "table" then -+ -- vararg with strings -+ lst = utils.pack(...) -+ for i, value in utils.npairs(lst) do -+ utils.assert_arg(i, value, "string") -+ enum[value] = value -+ end -+ -+ else -+ -- table/array with values -+ utils.assert_arg(1, first, "table") -+ lst = {} -+ -- first add array part -+ for i, value in ipairs(first) do -+ if type(value) ~= "string" then -+ error(("expected 'string' but got '%s' at index %d"):format(type(value), i), 2) -+ end -+ lst[i] = value -+ enum[value] = value -+ end -+ -- add key-ed part -+ for key, value in utils.kpairs(first) do -+ if type(key) ~= "string" then -+ error(("expected key to be 'string' but got '%s'"):format(type(key)), 2) -+ end -+ if enum[key] then -+ error(("duplicate entry in array and hash part: '%s'"):format(key), 2) -+ end -+ enum[key] = value -+ lst[#lst+1] = key -+ end -+ end -+ -+ if not lst[1] then -+ error("expected at least 1 entry", 2) -+ end -+ -+ local valid = "(expected one of: '" .. concat(lst, "', '") .. "')" -+ setmetatable(enum, { -+ __index = function(self, key) -+ error(("'%s' is not a valid value %s"):format(tostring(key), valid), 2) -+ end, -+ __newindex = function(self, key, value) -+ error("the Enum object is read-only", 2) -+ end, -+ __call = function(self, key) -+ if type(key) == "string" then -+ local v = rawget(self, key) -+ if v ~= nil then -+ return v -+ end -+ end -+ return nil, ("'%s' is not a valid value %s"):format(tostring(key), valid) -+ end -+ }) -+ -+ return enum -+end -+ -+ -+--- process a function argument. -+-- This is used throughout Penlight and defines what is meant by a function: -+-- Something that is callable, or an operator string as defined by pl.operator, -+-- such as '>' or '#'. If a function factory has been registered for the type, it will -+-- be called to get the function. -+-- @param idx argument index -+-- @param f a function, operator string, or callable object -+-- @param msg optional error message -+-- @return a callable -+-- @raise if idx is not a number or if f is not callable -+function utils.function_arg (idx,f,msg) -+ utils.assert_arg(1,idx,'number') -+ local tp = type(f) -+ if tp == 'function' then return f end -- no worries! -+ -- ok, a string can correspond to an operator (like '==') -+ if tp == 'string' then -+ if not operators then operators = require 'pl.operator'.optable end -+ local fn = operators[f] -+ if fn then return fn end -+ local fn, err = utils.string_lambda(f) -+ if not fn then error(err..': '..f) end -+ return fn -+ elseif tp == 'table' or tp == 'userdata' then -+ local mt = getmetatable(f) -+ if not mt then error('not a callable object',2) end -+ local ff = _function_factories[mt] -+ if not ff then -+ if not mt.__call then error('not a callable object',2) end -+ return f -+ else -+ return ff(f) -- we have a function factory for this type! -+ end -+ end -+ if not msg then msg = " must be callable" end -+ if idx > 0 then -+ error("argument "..idx..": "..msg,2) -+ else -+ error(msg,2) -+ end -+end -+ -+ -+--- assert the common case that the argument is a string. -+-- @param n argument index -+-- @param val a value that must be a string -+-- @return the validated value -+-- @raise val must be a string -+-- @usage -+-- local val = 42 -+-- local param2 = utils.assert_string(2, val) --> error: argument 2 expected a 'string', got a 'number' -+function utils.assert_string (n, val) -+ return utils.assert_arg(n,val,'string',nil,nil,3) -+end -+ -+--- control the error strategy used by Penlight. -+-- This is a global setting that controls how `utils.raise` behaves: -+-- -+-- - 'default': return `nil + error` (this is the default) -+-- - 'error': throw a Lua error -+-- - 'quit': exit the program -+-- -+-- @param mode either 'default', 'quit' or 'error' -+-- @see utils.raise -+function utils.on_error (mode) -+ mode = tostring(mode) -+ if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then -+ err_mode = mode -+ else -+ -- fail loudly -+ local err = "Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'" -+ if err_mode == 'default' then -+ error(err, 2) -- even in 'default' mode fail loud in this case -+ end -+ raise(err) -+ end -+end -+ -+--- used by Penlight functions to return errors. Its global behaviour is controlled -+-- by `utils.on_error`. -+-- To use this function you MUST use it in conjunction with `return`, since it might -+-- return `nil + error`. -+-- @param err the error string. -+-- @see utils.on_error -+-- @usage -+-- if some_condition then -+-- return utils.raise("some condition was not met") -- MUST use 'return'! -+-- end -+function utils.raise (err) -+ if err_mode == 'default' then -+ return nil, err -+ elseif err_mode == 'quit' then -+ return utils.quit(err) -+ else -+ error(err, 2) -+ end -+end -+raise = utils.raise -+ -+ -+ -+--- File handling -+-- @section files - - --- return the contents of a file as a string - -- @param filename The file path -@@ -124,7 +533,7 @@ function utils.readfile(filename,is_bin) - local mode = is_bin and 'b' or '' - utils.assert_string(1,filename) - local f,open_err = io.open(filename,'r'..mode) -- if not f then return utils.raise (open_err) end -+ if not f then return raise (open_err) end - local res,read_err = f:read('*a') - f:close() - if not res then -@@ -148,15 +557,20 @@ function utils.writefile(filename,str,is_bin) - utils.assert_string(2,str) - local f,err = io.open(filename,'w'..mode) - if not f then return raise(err) end -- f:write(str) -+ local ok, write_err = f:write(str) - f:close() -+ if not ok then -+ -- Errors in io.open have "filename: " prefix, -+ -- error in file:write don't, add it. -+ return raise (filename..": "..write_err) -+ end - return true - end - - --- return the contents of a file as a list of lines - -- @param filename The file path - -- @return file contents as a table ---- @raise errror if filename is not a string -+-- @raise error if filename is not a string - function utils.readlines(filename) - utils.assert_string(1,filename) - local f,err = io.open(filename,'r') -@@ -169,71 +583,64 @@ function utils.readlines(filename) - return res - end - ----- split a string into a list of strings separated by a delimiter. ---- @param s The input string ---- @param re A Lua string pattern; defaults to '%s+' ---- @param plain don't use Lua patterns ---- @param n optional maximum number of splits ---- @return a list-like table ---- @raise error if s is not a string --function utils.split(s,re,plain,n) -- utils.assert_string(1,s) -- local find,sub,append = string.find, string.sub, table.insert -- local i1,ls = 1,{} -- if not re then re = '%s+' end -- if re == '' then return {s} end -- while true do -- local i2,i3 = find(s,re,i1,plain) -- if not i2 then -- local last = sub(s,i1) -- if last ~= '' then append(ls,last) end -- if #ls == 1 and ls[1] == '' then -- return {} -- else -- return ls -- end -- end -- append(ls,sub(s,i1,i2-1)) -- if n and #ls == n then -- ls[#ls] = sub(s,i1) -- return ls -- end -- i1 = i3+1 -- end --end -+--- OS functions -+-- @section OS-functions - ----- split a string into a number of values. ---- @param s the string ---- @param re the delimiter, default space ---- @return n values ---- @usage first,next = splitv('jane:doe',':') ---- @see split --function utils.splitv (s,re) -- return unpack(utils.split(s,re)) --end -+--- Execute a shell command. -+-- This function is a copy of `compat.execute`. -+-- @class function -+-- @name utils.execute - ----- convert an array of values to strings. ---- @param t a list-like table ---- @param temp buffer to use, otherwise allocate ---- @param tostr custom tostring function, called with (value,index). ---- Otherwise use `tostring` ---- @return the converted buffer --function utils.array_tostring (t,temp,tostr) -- temp, tostr = temp or {}, tostr or tostring -- for i = 1,#t do -- temp[i] = tostr(t[i],i) -+--- execute a shell command and return the output. -+-- This function redirects the output to tempfiles and returns the content of those files. -+-- @param cmd a shell command -+-- @param bin boolean, if true, read output as binary file -+-- @return true if successful -+-- @return actual return code -+-- @return stdout output (string) -+-- @return errout output (string) -+function utils.executeex(cmd, bin) -+ local outfile = os.tmpname() -+ local errfile = os.tmpname() -+ -+ if is_windows and not outfile:find(':') then -+ outfile = os.getenv('TEMP')..outfile -+ errfile = os.getenv('TEMP')..errfile - end -- return temp --end -+ cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile) - --local is_windows = utils.is_windows -+ local success, retcode = utils.execute(cmd) -+ local outcontent = utils.readfile(outfile, bin) -+ local errcontent = utils.readfile(errfile, bin) -+ os.remove(outfile) -+ os.remove(errfile) -+ return success, retcode, (outcontent or ""), (errcontent or "") -+end - ----- Quote an argument of a command. ---- Quotes a single argument of a command to be passed -+--- Quote and escape an argument of a command. -+-- Quotes a single (or list of) argument(s) of a command to be passed - -- to `os.execute`, `pl.utils.execute` or `pl.utils.executeex`. ---- @string argument the argument. ---- @return quoted argument. -+-- @param argument (string or table/list) the argument to quote. If a list then -+-- all arguments in the list will be returned as a single string quoted. -+-- @return quoted and escaped argument. -+-- @usage -+-- local options = utils.quote_arg { -+-- "-lluacov", -+-- "-e", -+-- "utils = print(require('pl.utils')._VERSION", -+-- } -+-- -- returns: -lluacov -e 'utils = print(require('\''pl.utils'\'')._VERSION' - function utils.quote_arg(argument) -+ if type(argument) == "table" then -+ -- encode an entire table -+ local r = {} -+ for i, arg in ipairs(argument) do -+ r[i] = utils.quote_arg(arg) -+ end -+ -+ return concat(r, " ") -+ end -+ -- only a single argument - if is_windows then - if argument == "" or argument:find('[ \f\t\v]') then - -- Need to quote the argument. -@@ -259,39 +666,97 @@ function utils.quote_arg(argument) - end - end - ----- execute a shell command and return the output. ---- This function redirects the output to tempfiles and returns the content of those files. ---- @param cmd a shell command ---- @param bin boolean, if true, read output as binary file ---- @return true if successful ---- @return actual return code ---- @return stdout output (string) ---- @return errout output (string) --function utils.executeex(cmd, bin) -- local mode -- local outfile = os.tmpname() -- local errfile = os.tmpname() -+--- error out of this program gracefully. -+-- @param[opt] code The exit code, defaults to -`1` if omitted -+-- @param msg The exit message will be sent to `stderr` (will be formatted with the extra parameters) -+-- @param ... extra arguments for message's format' -+-- @see utils.fprintf -+-- @usage utils.quit(-1, "Error '%s' happened", "42") -+-- -- is equivalent to -+-- utils.quit("Error '%s' happened", "42") --> Error '42' happened -+function utils.quit(code, msg, ...) -+ if type(code) == 'string' then -+ utils.fprintf(io.stderr, code, msg, ...) -+ io.stderr:write('\n') -+ code = -1 -- TODO: this is odd, see the test. Which returns 255 as exit code -+ elseif msg then -+ utils.fprintf(io.stderr, msg, ...) -+ io.stderr:write('\n') -+ end -+ os.exit(code, true) -+end - -- if is_windows and not outfile:find(':') then -- outfile = os.getenv('TEMP')..outfile -- errfile = os.getenv('TEMP')..errfile -+ -+--- String functions -+-- @section string-functions -+ -+--- escape any Lua 'magic' characters in a string -+-- @param s The input string -+function utils.escape(s) -+ utils.assert_string(1,s) -+ return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) -+end -+ -+--- split a string into a list of strings separated by a delimiter. -+-- @param s The input string -+-- @param re optional A Lua string pattern; defaults to '%s+' -+-- @param plain optional If truthy don't use Lua patterns -+-- @param n optional maximum number of elements (if there are more, the last will remain un-split) -+-- @return a list-like table -+-- @raise error if s is not a string -+-- @see splitv -+function utils.split(s,re,plain,n) -+ utils.assert_string(1,s) -+ local i1,ls = 1,{} -+ if not re then re = '%s+' end -+ if re == '' then return {s} end -+ while true do -+ local i2,i3 = find(s,re,i1,plain) -+ if not i2 then -+ local last = sub(s,i1) -+ if last ~= '' then append(ls,last) end -+ if #ls == 1 and ls[1] == '' then -+ return {} -+ else -+ return ls -+ end -+ end -+ append(ls,sub(s,i1,i2-1)) -+ if n and #ls == n then -+ ls[#ls] = sub(s,i1) -+ return ls -+ end -+ i1 = i3+1 - end -- cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile) -+end - -- local success, retcode = utils.execute(cmd) -- local outcontent = utils.readfile(outfile, bin) -- local errcontent = utils.readfile(errfile, bin) -- os.remove(outfile) -- os.remove(errfile) -- return success, retcode, (outcontent or ""), (errcontent or "") -+--- split a string into a number of return values. -+-- Identical to `split` but returns multiple sub-strings instead of -+-- a single list of sub-strings. -+-- @param s the string -+-- @param re A Lua string pattern; defaults to '%s+' -+-- @param plain don't use Lua patterns -+-- @param n optional maximum number of splits -+-- @return n values -+-- @usage first,next = splitv('user=jane=doe','=', false, 2) -+-- assert(first == "user") -+-- assert(next == "jane=doe") -+-- @see split -+function utils.splitv (s,re, plain, n) -+ return _unpack(utils.split(s,re, plain, n)) - end - -+ -+--- Functional -+-- @section functional -+ -+ - --- 'memoize' a function (cache returned value for next call). - -- This is useful if you have a function which is relatively expensive, - -- but you don't know in advance what values will be required, so - -- building a table upfront is wasteful/impossible. ---- @param func a function of at least one argument ---- @return a function with at least one argument, which is used as the key. -+-- @param func a function that takes exactly one argument (which later serves as the cache key) and returns a single value -+-- @return a function taking one argument and returning a single value either from the cache or by running the original input function - function utils.memoize(func) - local cache = {} - return function(k) -@@ -305,13 +770,6 @@ function utils.memoize(func) - end - - --utils.stdmt = { -- List = {_name='List'}, Map = {_name='Map'}, -- Set = {_name='Set'}, MultiMap = {_name='MultiMap'} --} -- --local _function_factories = {} -- - --- associate a function factory with a type. - -- A function factory takes an object of the given type and - -- returns a function for evaluating it -@@ -322,7 +780,6 @@ function utils.add_function_factory (mt,fun) - end - - local function _string_lambda(f) -- local raise = utils.raise - if f:find '^|' or f:find '_' then - local args,body = f:match '|([^|]*)|(.+)' - if f:find '_' then -@@ -336,61 +793,22 @@ local function _string_lambda(f) - if not fn then return raise(err) end - fn = fn() - return fn -- else return raise 'not a string lambda' -+ else -+ return raise 'not a string lambda' - end - end - -+ - --- an anonymous function as a string. This string is either of the form - -- '|args| expression' or is a function of one argument, '_' - -- @param lf function as a string - -- @return a function ---- @usage string_lambda '|x|x+1' (2) == 3 ---- @usage string_lambda '_+1' (2) == 3 - -- @function utils.string_lambda -+-- @usage -+-- string_lambda '|x|x+1' (2) == 3 -+-- string_lambda '_+1' (2) == 3 - utils.string_lambda = utils.memoize(_string_lambda) - --local ops -- ----- process a function argument. ---- This is used throughout Penlight and defines what is meant by a function: ---- Something that is callable, or an operator string as defined by pl.operator, ---- such as '>' or '#'. If a function factory has been registered for the type, it will ---- be called to get the function. ---- @param idx argument index ---- @param f a function, operator string, or callable object ---- @param msg optional error message ---- @return a callable ---- @raise if idx is not a number or if f is not callable --function utils.function_arg (idx,f,msg) -- utils.assert_arg(1,idx,'number') -- local tp = type(f) -- if tp == 'function' then return f end -- no worries! -- -- ok, a string can correspond to an operator (like '==') -- if tp == 'string' then -- if not ops then ops = require 'pl.operator'.optable end -- local fn = ops[f] -- if fn then return fn end -- local fn, err = utils.string_lambda(f) -- if not fn then error(err..': '..f) end -- return fn -- elseif tp == 'table' or tp == 'userdata' then -- local mt = getmetatable(f) -- if not mt then error('not a callable object',2) end -- local ff = _function_factories[mt] -- if not ff then -- if not mt.__call then error('not a callable object',2) end -- return f -- else -- return ff(f) -- we have a function factory for this type! -- end -- end -- if not msg then msg = " must be callable" end -- if idx > 0 then -- error("argument "..idx..": "..msg,2) -- else -- error(msg,2) -- end --end - - --- bind the first argument of the function to a value. - -- @param fn a function of at least two values (may be an operator string) -@@ -398,119 +816,141 @@ end - -- @return a function such that f(x) is fn(p,x) - -- @raise same as @{function_arg} - -- @see func.bind1 -+-- @usage local function f(msg, name) -+-- print(msg .. " " .. name) -+-- end -+-- -+-- local hello = utils.bind1(f, "Hello") -+-- -+-- print(hello("world")) --> "Hello world" -+-- print(hello("sunshine")) --> "Hello sunshine" - function utils.bind1 (fn,p) - fn = utils.function_arg(1,fn) - return function(...) return fn(p,...) end - end - -+ - --- bind the second argument of the function to a value. - -- @param fn a function of at least two values (may be an operator string) - -- @param p a value - -- @return a function such that f(x) is fn(x,p) - -- @raise same as @{function_arg} -+-- @usage local function f(a, b, c) -+-- print(a .. " " .. b .. " " .. c) -+-- end -+-- -+-- local hello = utils.bind1(f, "world") -+-- -+-- print(hello("Hello", "!")) --> "Hello world !" -+-- print(hello("Bye", "?")) --> "Bye world ?" - function utils.bind2 (fn,p) - fn = utils.function_arg(1,fn) - return function(x,...) return fn(x,p,...) end - end - - ----- assert that the given argument is in fact of the correct type. ---- @param n argument index ---- @param val the value ---- @param tp the type ---- @param verify an optional verification function ---- @param msg an optional custom message ---- @param lev optional stack position for trace, default 2 ---- @raise if the argument n is not the correct type ---- @usage assert_arg(1,t,'table') ---- @usage assert_arg(n,val,'string',path.isdir,'not a directory') --function utils.assert_arg (n,val,tp,verify,msg,lev) -- if type(val) ~= tp then -- error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2) -- end -- if verify and not verify(val) then -- error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) -- end --end - ----- assert the common case that the argument is a string. ---- @param n argument index ---- @param val a value that must be a string ---- @raise val must be a string --function utils.assert_string (n,val) -- utils.assert_arg(n,val,'string',nil,nil,3) --end - --local err_mode = 'default' -+--- Deprecation -+-- @section deprecation - ----- control the error strategy used by Penlight. ---- Controls how utils.raise works; the default is for it ---- to return nil and the error string, but if the mode is 'error' then ---- it will throw an error. If mode is 'quit' it will immediately terminate ---- the program. ---- @param mode - either 'default', 'quit' or 'error' ---- @see utils.raise --function utils.on_error (mode) -- if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then -- err_mode = mode -+do -+ -- the default implementation -+ local deprecation_func = function(msg, trace) -+ if trace then -+ warn(msg, "\n", trace) -- luacheck: ignore - else -- -- fail loudly -- if err_mode == 'default' then err_mode = 'error' end -- utils.raise("Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'") -+ warn(msg) -- luacheck: ignore -+ end -+ end -+ -+ --- Sets a deprecation warning function. -+ -- An application can override this function to support proper output of -+ -- deprecation warnings. The warnings can be generated from libraries or -+ -- functions by calling `utils.raise_deprecation`. The default function -+ -- will write to the 'warn' system (introduced in Lua 5.4, or the compatibility -+ -- function from the `compat` module for earlier versions). -+ -- -+ -- Note: only applications should set/change this function, libraries should not. -+ -- @param func a callback with signature: `function(msg, trace)` both arguments are strings, the latter being optional. -+ -- @see utils.raise_deprecation -+ -- @usage -+ -- -- write to the Nginx logs with OpenResty -+ -- utils.set_deprecation_func(function(msg, trace) -+ -- ngx.log(ngx.WARN, msg, (trace and (" " .. trace) or nil)) -+ -- end) -+ -- -+ -- -- disable deprecation warnings -+ -- utils.set_deprecation_func() -+ function utils.set_deprecation_func(func) -+ if func == nil then -+ deprecation_func = function() end -+ else -+ utils.assert_arg(1, func, "function") -+ deprecation_func = func -+ end -+ end -+ -+ --- raises a deprecation warning. -+ -- For options see the usage example below. -+ -- -+ -- Note: the `opts.deprecated_after` field is the last version in which -+ -- a feature or option was NOT YET deprecated! Because when writing the code it -+ -- is quite often not known in what version the code will land. But the last -+ -- released version is usually known. -+ -- @param opts options table -+ -- @see utils.set_deprecation_func -+ -- @usage -+ -- warn("@on") -- enable Lua warnings, they are usually off by default -+ -- -+ -- function stringx.islower(str) -+ -- raise_deprecation { -+ -- source = "Penlight " .. utils._VERSION, -- optional -+ -- message = "function 'islower' was renamed to 'is_lower'", -- required -+ -- version_removed = "2.0.0", -- optional -+ -- deprecated_after = "1.2.3", -- optional -+ -- no_trace = true, -- optional -+ -- } -+ -- return stringx.is_lower(str) -+ -- end -+ -- -- output: "[Penlight 1.9.2] function 'islower' was renamed to 'is_lower' (deprecated after 1.2.3, scheduled for removal in 2.0.0)" -+ function utils.raise_deprecation(opts) -+ utils.assert_arg(1, opts, "table") -+ if type(opts.message) ~= "string" then -+ error("field 'message' of the options table must be a string", 2) -+ end -+ local trace -+ if not opts.no_trace then -+ trace = debug.traceback("", 2):match("[\n%s]*(.-)$") -+ end -+ local msg -+ if opts.deprecated_after and opts.version_removed then -+ msg = (" (deprecated after %s, scheduled for removal in %s)"):format( -+ tostring(opts.deprecated_after), tostring(opts.version_removed)) -+ elseif opts.deprecated_after then -+ msg = (" (deprecated after %s)"):format(tostring(opts.deprecated_after)) -+ elseif opts.version_removed then -+ msg = (" (scheduled for removal in %s)"):format(tostring(opts.version_removed)) -+ else -+ msg = "" - end --end - ----- used by Penlight functions to return errors. Its global behaviour is controlled ---- by utils.on_error ---- @param err the error string. ---- @see utils.on_error --function utils.raise (err) -- if err_mode == 'default' then return nil,err -- elseif err_mode == 'quit' then utils.quit(err) -- else error(err,2) -+ msg = opts.message .. msg -+ -+ if opts.source then -+ msg = "[" .. opts.source .."] " .. msg -+ else -+ if msg:sub(1,1) == "@" then -+ -- in Lua 5.4 "@" prefixed messages are control messages to the warn system -+ error("message cannot start with '@'", 2) -+ end - end --end - ----- is the object of the specified type?. ---- If the type is a string, then use type, otherwise compare with metatable ---- @param obj An object to check ---- @param tp String of what type it should be --function utils.is_type (obj,tp) -- if type(tp) == 'string' then return type(obj) == tp end -- local mt = getmetatable(obj) -- return tp == mt --end -+ deprecation_func(msg, trace) -+ end - --raise = utils.raise -+end - ----- load a code string or bytecode chunk. ---- @param code Lua code as a string or bytecode ---- @param name for source errors ---- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default) ---- @param env the environment for the new chunk (default nil) ---- @return compiled chunk ---- @return error message (chunk is nil) ---- @function utils.load -- ----------------- ---- Get environment of a function. ---- With Lua 5.2, may return nil for a function with no global references! ---- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html) ---- @param f a function or a call stack reference ---- @function utils.getfenv -- ----------------- ---- Set environment of a function ---- @param f a function or a call stack reference ---- @param env a table that becomes the new environment of `f` ---- @function utils.setfenv -- ----- execute a shell command. ---- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 ---- @param cmd a shell command ---- @return true if successful ---- @return actual return code ---- @function utils.execute - - return utils - -diff --git a/extra/penlight/lua/pl/xml.lua b/extra/penlight/lua/pl/xml.lua -index c4382e6..3f49c49 100644 ---- a/extra/penlight/lua/pl/xml.lua -+++ b/extra/penlight/lua/pl/xml.lua -@@ -2,7 +2,7 @@ - -- - -- This implements some useful things on [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) documents, such as returned by `lxp.lom.parse`. - -- In particular, it can convert LOM back into XML text, with optional pretty-printing control. ---- It is s based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua) -+-- It is based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua) - -- - -- > d = xml.parse "alice" - -- > = d -@@ -30,518 +30,902 @@ - -- @module pl.xml - - local utils = require 'pl.utils' --local split = utils.split; --local t_insert = table.insert; --local t_concat = table.concat; --local t_remove = table.remove; --local s_match = string.match; --local tostring = tostring; --local setmetatable = setmetatable; --local getmetatable = getmetatable; --local pairs = pairs; --local ipairs = ipairs; --local type = type; --local next = next; --local print = print; --local unpack = utils.unpack; --local s_gsub = string.gsub; --local s_find = string.find; --local pcall,require,io = pcall,require,io -+local split = utils.split -+local t_insert = table.insert -+local t_concat = table.concat -+local t_remove = table.remove -+local s_match = string.match -+local tostring = tostring -+local setmetatable = setmetatable -+local getmetatable = getmetatable -+local pairs = pairs -+local ipairs = ipairs -+local type = type -+local next = next -+local print = print -+local unpack = utils.unpack -+local s_gsub = string.gsub -+local s_sub = string.sub -+local s_find = string.find -+local pcall = pcall -+local require = require -+ -+ -+utils.raise_deprecation { -+ source = "Penlight " .. utils._VERSION, -+ message = "the contents of module 'pl.xml' has been deprecated, please use a more specialized library instead", -+ version_removed = "2.0.0", -+ deprecated_after = "1.11.0", -+ no_trace = true, -+} -+ -+ - - local _M = {} - local Doc = { __type = "doc" }; - Doc.__index = Doc; - -+ -+local function is_text(s) return type(s) == 'string' end -+local function is_tag(d) return type(d) == 'table' and is_text(d.tag) end -+ -+ -+ - --- create a new document node. ---- @param tag the tag name ---- @param attr optional attributes (table of name-value pairs) -+-- @tparam string tag the tag name -+-- @tparam[opt={}] table attr attributes (table of name-value pairs) -+-- @return the Node object -+-- @see xml.elem -+-- @usage -+-- local doc = xml.new("main", { hello = "world", answer = "42" }) -+-- print(doc) -->
    - function _M.new(tag, attr) -- local doc = { tag = tag, attr = attr or {}, last_add = {}}; -- return setmetatable(doc, Doc); -+ if type(tag) ~= "string" then -+ error("expected 'tag' to be a string value, got: " .. type(tag), 2) -+ end -+ attr = attr or {} -+ if type(attr) ~= "table" then -+ error("expected 'attr' to be a table value, got: " .. type(attr), 2) -+ end -+ -+ local doc = { tag = tag, attr = attr, last_add = {}}; -+ return setmetatable(doc, Doc); - end - ----- parse an XML document. By default, this uses lxp.lom.parse, but ---- falls back to basic_parse, or if use_basic is true ---- @param text_or_file file or string representation -+ -+--- parse an XML document. By default, this uses lxp.lom.parse, but -+-- falls back to basic_parse, or if `use_basic` is truthy -+-- @param text_or_filename file or string representation - -- @param is_file whether text_or_file is a file name or not - -- @param use_basic do a basic parse - -- @return a parsed LOM document with the document metatatables set - -- @return nil, error the error can either be a file error or a parse error --function _M.parse(text_or_file, is_file, use_basic) -- local parser,status,lom -- if use_basic then parser = _M.basic_parse -+function _M.parse(text_or_filename, is_file, use_basic) -+ local parser,status,lom -+ if use_basic then -+ parser = _M.basic_parse -+ else -+ status,lom = pcall(require,'lxp.lom') -+ if not status then -+ parser = _M.basic_parse - else -- status,lom = pcall(require,'lxp.lom') -- if not status then parser = _M.basic_parse else parser = lom.parse end -- end -- if is_file then -- local f,err = io.open(text_or_file) -- if not f then return nil,err end -- text_or_file = f:read '*a' -- f:close() -- end -- local doc,err = parser(text_or_file) -- if not doc then return nil,err end -- if lom then -- _M.walk(doc,false,function(_,d) -- setmetatable(d,Doc) -- end) -+ parser = lom.parse -+ end -+ end -+ -+ if is_file then -+ local text, err = utils.readfile(text_or_filename) -+ if not text then -+ return nil, err -+ end -+ text_or_filename = text -+ end -+ -+ local doc, err = parser(text_or_filename) -+ if not doc then -+ return nil, err -+ end -+ -+ if lom then -+ _M.walk(doc, false, function(_, d) -+ setmetatable(d, Doc) -+ end) -+ end -+ return doc -+end -+ -+ -+--- Create a Node with a set of children (text or Nodes) and attributes. -+-- @tparam string tag a tag name -+-- @tparam table|string items either a single child (text or Node), or a table where the hash -+-- part is the attributes and the list part is the children (text or Nodes). -+-- @return the new Node -+-- @see xml.new -+-- @see xml.tags -+-- @usage -+-- local doc = xml.elem("top", "hello world") -- hello world -+-- local doc = xml.elem("main", xml.new("child")) --
    -+-- local doc = xml.elem("main", { "this ", "is ", "nice" }) --
    this is nice
    -+-- local doc = xml.elem("main", { xml.new "this", -+-- xml.new "is", -+-- xml.new "nice" }) --
    -+-- local doc = xml.elem("main", { hello = "world" }) --
    -+-- local doc = xml.elem("main", { -+-- "prefix", -+-- xml.elem("child", { "this ", "is ", "nice"}), -+-- "postfix", -+-- attrib = "value" -+-- }) --
    prefixthis is nicepostfix
    " -+function _M.elem(tag, items) -+ local s = _M.new(tag) -+ if is_text(items) then items = {items} end -+ if is_tag(items) then -+ t_insert(s,items) -+ elseif type(items) == 'table' then -+ for k,v in pairs(items) do -+ if is_text(k) then -+ s.attr[k] = v -+ t_insert(s.attr,k) -+ else -+ s[k] = v -+ end -+ end -+ end -+ return s -+end -+ -+ -+--- given a list of names, return a number of element constructors. -+-- If passing a comma-separated string, then whitespace surrounding the values -+-- will be stripped. -+-- -+-- The returned constructor functions are a shortcut to `xml.elem` where you -+-- no longer provide the tag-name, but only the `items` table. -+-- @tparam string|table list a list of names, or a comma-separated string. -+-- @return (multiple) constructor functions; `function(items)`. For the `items` -+-- parameter see `xml.elem`. -+-- @see xml.elem -+-- @usage -+-- local new_parent, new_child = xml.tags 'mom, kid' -+-- doc = new_parent {new_child 'Bob', new_child 'Annie'} -+-- -- BobAnnie -+function _M.tags(list) -+ local ctors = {} -+ if is_text(list) then -+ list = split(list:match("^%s*(.-)%s*$"),'%s*,%s*') -+ end -+ for i,tag in ipairs(list) do -+ local function ctor(items) -+ return _M.elem(tag,items) - end -- return doc -+ ctors[i] = ctor -+ end -+ return unpack(ctors) - end - ------ convenient function to add a document node, This updates the last inserted position. ---- @param tag a tag name ---- @param attrs optional set of attributes (name-string pairs) -+ -+--- Adds a document Node, at current position. -+-- This updates the last inserted position to the new Node. -+-- @tparam string tag the tag name -+-- @tparam[opt={}] table attrs attributes (table of name-value pairs) -+-- @return the current node (`self`) -+-- @usage -+-- local doc = xml.new("main") -+-- doc:addtag("penlight", { hello = "world"}) -+-- doc:addtag("expat") -- added to 'penlight' since position moved -+-- print(doc) -->
    - function Doc:addtag(tag, attrs) -- local s = _M.new(tag, attrs); -- (self.last_add[#self.last_add] or self):add_direct_child(s); -- t_insert(self.last_add, s); -- return self; -+ local s = _M.new(tag, attrs) -+ self:add_child(s) -+ t_insert(self.last_add, s) -+ return self - end - ----- convenient function to add a text node. This updates the last inserted position. ---- @param text a string -+ -+--- Adds a text node, at current position. -+-- @tparam string text a string -+-- @return the current node (`self`) -+-- @usage -+-- local doc = xml.new("main") -+-- doc:text("penlight") -+-- doc:text("expat") -+-- print(doc) -->
    - function Doc:text(text) -- (self.last_add[#self.last_add] or self):add_direct_child(text); -- return self; -+ self:add_child(text) -+ return self - end - ------ go up one level in a document -+ -+--- Moves current position up one level. -+-- @return the current node (`self`) - function Doc:up() -- t_remove(self.last_add); -- return self; -+ t_remove(self.last_add) -+ return self - end - -+ -+--- Resets current position to top level. -+-- Resets to the `self` node. -+-- @return the current node (`self`) - function Doc:reset() -- local last_add = self.last_add; -- for i = 1,#last_add do -- last_add[i] = nil; -- end -- return self; -+ local last_add = self.last_add -+ for i = 1,#last_add do -+ last_add[i] = nil -+ end -+ return self - end - ----- append a child to a document directly. -+ -+--- Append a child to the current Node (ignoring current position). - -- @param child a child node (either text or a document) -+-- @return the current node (`self`) -+-- @usage -+-- local doc = xml.new("main") -+-- doc:add_direct_child("dog") -+-- doc:add_direct_child(xml.new("child")) -+-- doc:add_direct_child("cat") -+-- print(doc) -->
    dogcat
    - function Doc:add_direct_child(child) -- t_insert(self, child); -+ t_insert(self, child) -+ return self - end - ----- append a child to a document at the last element added -+ -+--- Append a child at the current position (without changing position). - -- @param child a child node (either text or a document) -+-- @return the current node (`self`) -+-- @usage -+-- local doc = xml.new("main") -+-- doc:addtag("one") -+-- doc:add_child(xml.new("item1")) -+-- doc:add_child(xml.new("item2")) -+-- doc:add_child(xml.new("item3")) -+-- print(doc) -->
    - function Doc:add_child(child) -- (self.last_add[#self.last_add] or self):add_direct_child(child); -- return self; -+ (self.last_add[#self.last_add] or self):add_direct_child(child) -+ return self - end - -+ - --accessing attributes: useful not to have to expose implementation (attr) - --but also can allow attr to be nil in any future optimizations - ----- set attributes of a document node. ---- @param t a table containing attribute/value pairs --function Doc:set_attribs (t) -- for k,v in pairs(t) do -- self.attr[k] = v -- end -+ -+--- Set attributes of a document node. -+-- Will add/overwrite values, but will not remove existing ones. -+-- Operates on the Node itself, will not take position into account. -+-- @tparam table t a table containing attribute/value pairs -+-- @return the current node (`self`) -+function Doc:set_attribs(t) -+ -- TODO: keep array part in sync -+ for k,v in pairs(t) do -+ self.attr[k] = v -+ end -+ return self - end - ----- set a single attribute of a document node. -+ -+--- Set a single attribute of a document node. -+-- Operates on the Node itself, will not take position into account. - -- @param a attribute ---- @param v its value -+-- @param v its value, pass in `nil` to delete the attribute -+-- @return the current node (`self`) - function Doc:set_attrib(a,v) -- self.attr[a] = v -+ -- TODO: keep array part in sync -+ self.attr[a] = v -+ return self - end - ----- access the attributes of a document node. -+ -+--- Gets the attributes of a document node. -+-- Operates on the Node itself, will not take position into account. -+-- @return table with attributes (attribute/value pairs) - function Doc:get_attribs() -- return self.attr -+ return self.attr - end - --local function is_text(s) return type(s) == 'string' end - ----- function to create an element with a given tag name and a set of children. ---- @param tag a tag name ---- @param items either text or a table where the hash part is the attributes and the list part is the children. --function _M.elem(tag,items) -- local s = _M.new(tag) -- if is_text(items) then items = {items} end -- if _M.is_tag(items) then -- t_insert(s,items) -- elseif type(items) == 'table' then -- for k,v in pairs(items) do -- if is_text(k) then -- s.attr[k] = v -- t_insert(s.attr,k) -- else -- s[k] = v -- end -- end -- end -- return s --end - ----- given a list of names, return a number of element constructors. ---- @param list a list of names, or a comma-separated string. ---- @usage local parent,children = doc.tags 'parent,children'
    ---- doc = parent {child 'one', child 'two'} --function _M.tags(list) -- local ctors = {} -- local elem = _M.elem -- if is_text(list) then list = split(list,'%s*,%s*') end -- for _,tag in ipairs(list) do -- local ctor = function(items) return _M.elem(tag,items) end -- t_insert(ctors,ctor) -- end -- return unpack(ctors) --end -- --local templ_cache = {} -+local template_cache do -+ local templ_cache = {} - --local function template_cache (templ) -+ -- @param templ a template, a string being valid xml to be parsed, or a Node object -+ function template_cache(templ) - if is_text(templ) then -- if templ_cache[templ] then -- templ = templ_cache[templ] -- else -- local str,err = templ -- templ,err = _M.parse(str,false,true) -- if not templ then return nil,err end -- templ_cache[str] = templ -+ if templ_cache[templ] then -+ -- cache hit -+ return templ_cache[templ] -+ -+ else -+ -- parse and cache -+ local ptempl, err = _M.parse(templ,false,true) -+ if not ptempl then -+ return nil, err - end -- elseif not _M.is_tag(templ) then -- return nil, "template is not a document" -+ templ_cache[templ] = ptempl -+ return ptempl -+ end -+ end -+ -+ if is_tag(templ) then -+ return templ - end -- return templ -+ -+ return nil, "template is not a document" -+ end - end - --local function is_data(data) -+ -+do -+ local function is_data(data) - return #data == 0 or type(data[1]) ~= 'table' --end -+ end -+ - --local function prepare_data(data) -+ local function prepare_data(data) - -- a hack for ensuring that $1 maps to first element of data, etc. - -- Either this or could change the gsub call just below. - for i,v in ipairs(data) do -- data[tostring(i)] = v -+ data[tostring(i)] = v -+ end -+ end -+ -+ --- create a substituted copy of a document, -+ -- @param template may be a document or a string representation which will be parsed and cached -+ -- @param data a table of name-value pairs or a list of such tables -+ -- @return an XML document -+ function Doc.subst(template, data) -+ if type(data) ~= 'table' or not next(data) then -+ return nil, "data must be a non-empty table" - end --end - ----- create a substituted copy of a document, ---- @param templ may be a document or a string representation which will be parsed and cached ---- @param data a table of name-value pairs or a list of such tables ---- @return an XML document --function Doc.subst(templ, data) -- local err -- if type(data) ~= 'table' or not next(data) then return nil, "data must be a non-empty table" end - if is_data(data) then -- prepare_data(data) -+ prepare_data(data) -+ end -+ -+ local templ, err = template_cache(template) -+ if err then -+ return nil, err - end -- templ,err = template_cache(templ) -- if err then return nil, err end -+ - local function _subst(item) -- return _M.clone(templ,function(s) -- return s:gsub('%$(%w+)',item) -- end) -+ return _M.clone(templ, function(s) -+ return s:gsub('%$(%w+)', item) -+ end) - end -- if is_data(data) then return _subst(data) end -+ -+ if is_data(data) then -+ return _subst(data) -+ end -+ - local list = {} -- for _,item in ipairs(data) do -- prepare_data(item) -- t_insert(list,_subst(item)) -+ for _, item in ipairs(data) do -+ prepare_data(item) -+ t_insert(list, _subst(item)) - end -+ - if data.tag then -- list = _M.elem(data.tag,list) -+ list = _M.elem(data.tag,list) - end - return list -+ end - end - - ----- get the first child with a given tag name. -+--- Return the first child with a given tag name (non-recursive). - -- @param tag the tag name -+-- @return the child Node found or `nil` if not found - function Doc:child_with_name(tag) -- for _, child in ipairs(self) do -- if child.tag == tag then return child; end -+ for _, child in ipairs(self) do -+ if child.tag == tag then -+ return child - end -+ end - end - --local _children_with_name --function _children_with_name(self,tag,list,recurse) -- for _, child in ipairs(self) do if type(child) == 'table' then -- if child.tag == tag then t_insert(list,child) end -- if recurse then _children_with_name(child,tag,list,recurse) end -- end end --end - ----- get all elements in a document that have a given tag. ---- @param tag a tag name ---- @param dont_recurse optionally only return the immediate children with this tag name ---- @return a list of elements --function Doc:get_elements_with_name(tag,dont_recurse) -+do -+ -- @param self document node to traverse -+ -- @param tag tag-name to look for -+ -- @param list array table to add the matching ones to -+ -- @param recurse if truthy, recursively search the node -+ local function _children_with_name(self, tag, list, recurse) -+ -- TODO: protect against recursion -+ for _, child in ipairs(self) do -+ if type(child) == 'table' then -+ if child.tag == tag then -+ t_insert(list, child) -+ end -+ if recurse then -+ _children_with_name(child, tag, list, recurse) -+ end -+ end -+ end -+ end -+ -+ --- Returns all elements in a document that have a given tag. -+ -- @tparam string tag a tag name -+ -- @tparam[opt=false] boolean dont_recurse optionally only return the immediate children with this tag name -+ -- @return a list of elements found, list will be empty if none was found. -+ function Doc:get_elements_with_name(tag, dont_recurse) - local res = {} -- _children_with_name(self,tag,res,not dont_recurse) -+ _children_with_name(self, tag, res, not dont_recurse) - return res -+ end - end - ---- iterate over all children of a document node, including text nodes. -+ -+ -+--- Iterator over all children of a document node, including text nodes. -+-- This function is not recursive, so returns only direct child nodes. -+-- @return iterator that returns a single Node per iteration. - function Doc:children() -- local i = 0; -- return function (a) -- i = i + 1 -- return a[i]; -- end, self, i; -+ local i = 0; -+ return function (a) -+ i = i + 1 -+ return a[i]; -+ end, self, i; - end - ---- return the first child element of a node, if it exists. -+ -+--- Return the first child element of a node, if it exists. -+-- This will skip text nodes. -+-- @return first child Node or `nil` if there is none. - function Doc:first_childtag() -- if #self == 0 then return end -- for _,t in ipairs(self) do -- if type(t) == 'table' then return t end -+ if #self == 0 then -+ return -+ end -+ for _, t in ipairs(self) do -+ if is_tag(t) then -+ return t - end -+ end - end - -+ -+--- Iterator that matches tag names, and a namespace (non-recursive). -+-- @tparam[opt=nil] string tag tag names to return. Returns all tags if not provided. -+-- @tparam[opt=nil] string xmlns the namespace value ('xmlns' attribute) to return. If not -+-- provided will match all namespaces. -+-- @return iterator that returns a single Node per iteration. - function Doc:matching_tags(tag, xmlns) -- xmlns = xmlns or self.attr.xmlns; -- local tags = self; -- local start_i, max_i, v = 1, #tags; -- return function () -- for i=start_i,max_i do -- v = tags[i]; -- if (not tag or v.tag == tag) -- and (not xmlns or xmlns == v.attr.xmlns) then -- start_i = i+1; -- return v; -- end -- end -- end, tags, start_i; -+ -- TODO: this doesn't make sense??? namespaces are not "xmnls", as matched below -+ -- but "xmlns:name"... so should be a string-prefix match if anything... -+ xmlns = xmlns or self.attr.xmlns; -+ local tags = self -+ local next_i = 1 -+ local max_i = #tags -+ local node -+ return function () -+ for i = next_i, max_i do -+ node = tags[i]; -+ if (not tag or node.tag == tag) and -+ (not xmlns or xmlns == node.attr.xmlns) then -+ next_i = i + 1 -+ return node -+ end -+ end -+ end, tags, next_i - end - ----- iterate over all child elements of a document node. -+ -+--- Iterator over all child tags of a document node. This will skip over -+-- text nodes. -+-- @return iterator that returns a single Node per iteration. - function Doc:childtags() -- local i = 0; -- return function (a) -- local v -- repeat -- i = i + 1 -- v = self[i] -- if v and type(v) == 'table' then return v; end -- until not v -- end, self[1], i; --end -- ----- visit child element of a node and call a function, possibility modifying the document. ---- @param callback a function passed the node (text or element). If it returns nil, that node will be removed. ---- If it returns a value, that will replace the current node. --function Doc:maptags(callback) -- local is_tag = _M.is_tag -- local i = 1; -- while i <= #self do -- if is_tag(self[i]) then -- local ret = callback(self[i]); -- if ret == nil then -- t_remove(self, i); -- else -- self[i] = ret; -- i = i + 1; -- end -+ local i = 0; -+ return function (a) -+ local v -+ repeat -+ i = i + 1 -+ v = self[i] -+ if v and type(v) == 'table' then -+ return v - end -+ until not v -+ end, self[1], i; -+end -+ -+ -+--- Visit child Nodes of a node and call a function, possibly modifying the document. -+-- Text elements will be skipped. -+-- This is not recursive, so only direct children will be passed. -+-- @tparam function callback a function with signature `function(node)`, passed the node. -+-- The element will be updated with the returned value, or deleted if it returns `nil`. -+function Doc:maptags(callback) -+ local i = 1; -+ -+ while i <= #self do -+ if is_tag(self[i]) then -+ local ret = callback(self[i]); -+ if ret == nil then -+ -- remove it -+ t_remove(self, i); -+ -+ else -+ -- update it -+ self[i] = ret; -+ i = i + 1; -+ end -+ else -+ i = i + 1 - end -- return self; -+ end -+ -+ return self; -+end -+ -+ -+do -+ local escape_table = { -+ ["'"] = "'", -+ ['"'] = """, -+ ["<"] = "<", -+ [">"] = ">", -+ ["&"] = "&", -+ } -+ -+ --- Escapes a string for safe use in xml. -+ -- Handles quotes(single+double), less-than, greater-than, and ampersand. -+ -- @tparam string str string value to escape -+ -- @return escaped string -+ -- @usage -+ -- local esc = xml.xml_escape([["'<>&]]) --> ""'<>&" -+ function _M.xml_escape(str) -+ return (s_gsub(str, "['&<>\"]", escape_table)) -+ end - end -+local xml_escape = _M.xml_escape - --local xml_escape - do -- local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; -- function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end -- _M.xml_escape = xml_escape; -+ local escape_table = { -+ quot = '"', -+ apos = "'", -+ lt = "<", -+ gt = ">", -+ amp = "&", -+ } -+ -+ --- Unescapes a string from xml. -+ -- Handles quotes(single+double), less-than, greater-than, and ampersand. -+ -- @tparam string str string value to unescape -+ -- @return unescaped string -+ -- @usage -+ -- local unesc = xml.xml_escape(""'<>&") --> [["'<>&]] -+ function _M.xml_unescape(str) -+ return (str:gsub( "&(%a+);", escape_table)) -+ end - end -+local xml_unescape = _M.xml_unescape - - -- pretty printing - -- if indent, then put each new tag on its own line - -- if attr_indent, put each new attribute on its own line --local function _dostring(t, buf, self, xml_escape, parentns, idn, indent, attr_indent) -- local nsid = 0; -- local tag = t.tag -- local lf,alf = ""," " -- if indent then lf = '\n'..idn end -- if attr_indent then alf = '\n'..idn..attr_indent end -- t_insert(buf, lf.."<"..tag); -- local function write_attr(k,v) -- if s_find(k, "\1", 1, true) then -- local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); -- nsid = nsid + 1; -- t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'"); -- elseif not(k == "xmlns" and v == parentns) then -- t_insert(buf, alf..k.."='"..xml_escape(v).."'"); -- end -+local function _dostring(t, buf, parentns, block_indent, tag_indent, attr_indent) -+ local nsid = 0 -+ local tag = t.tag -+ -+ local lf = "" -+ if tag_indent then -+ lf = '\n'..block_indent -+ end -+ -+ local alf = " " -+ if attr_indent then -+ alf = '\n'..block_indent..attr_indent -+ end -+ -+ t_insert(buf, lf.."<"..tag) -+ -+ local function write_attr(k,v) -+ if s_find(k, "\1", 1, true) then -+ nsid = nsid + 1 -+ local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$") -+ t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'") -+ -+ elseif not (k == "xmlns" and v == parentns) then -+ t_insert(buf, alf..k.."='"..xml_escape(v).."'"); - end -- -- it's useful for testing to have predictable attribute ordering, if available -- if #t.attr > 0 then -- for _,k in ipairs(t.attr) do -- write_attr(k,t.attr[k]) -- end -- else -- for k, v in pairs(t.attr) do -- write_attr(k,v) -- end -+ end -+ -+ -- it's useful for testing to have predictable attribute ordering, if available -+ if #t.attr > 0 then -+ -- TODO: the key-value list is leading, what if they are not in-sync -+ for _,k in ipairs(t.attr) do -+ write_attr(k,t.attr[k]) - end -- local len,has_children = #t; -- if len == 0 then -- local out = "/>" -- if attr_indent then out = '\n'..idn..out end -- t_insert(buf, out); -+ else -+ for k, v in pairs(t.attr) do -+ write_attr(k,v) -+ end -+ end -+ -+ local len = #t -+ local has_children -+ -+ if len == 0 then -+ t_insert(buf, attr_indent and '\n'..block_indent.."/>" or "/>") -+ -+ else -+ t_insert(buf, ">"); -+ -+ for n = 1, len do -+ local child = t[n] -+ -+ if child.tag then -+ has_children = true -+ _dostring(child, buf, t.attr.xmlns, block_indent and block_indent..tag_indent, tag_indent, attr_indent) -+ -+ else -+ -- text element -+ t_insert(buf, xml_escape(child)) -+ end -+ end -+ -+ t_insert(buf, (has_children and lf or '')..""); -+ end -+end -+ -+--- Function to pretty-print an XML document. -+-- @param doc an XML document -+-- @tparam[opt] string|int b_ind an initial block-indent (required when `t_ind` is set) -+-- @tparam[opt] string|int t_ind an tag-indent for each level (required when `a_ind` is set) -+-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line -+-- @tparam[opt] string|bool xml_preface force prefacing with default or custom , if truthy then `<?xml version='1.0'?>` will be used as default. -+-- @return a string representation -+-- @see Doc:tostring -+function _M.tostring(doc, b_ind, t_ind, a_ind, xml_preface) -+ local buf = {} -+ -+ if type(b_ind) == "number" then b_ind = (" "):rep(b_ind) end -+ if type(t_ind) == "number" then t_ind = (" "):rep(t_ind) end -+ if type(a_ind) == "number" then a_ind = (" "):rep(a_ind) end -+ -+ if xml_preface then -+ if type(xml_preface) == "string" then -+ buf[1] = xml_preface - else -- t_insert(buf, ">"); -- for n=1,len do -- local child = t[n]; -- if child.tag then -- self(child, buf, self, xml_escape, t.attr.xmlns,idn and idn..indent, indent, attr_indent ); -- has_children = true -- else -- text element -- t_insert(buf, xml_escape(child)); -- end -- end -- t_insert(buf, (has_children and lf or '')..""); -- end --end -- ------ pretty-print an XML document ----- @param t an XML document ----- @param idn an initial indent (indents are all strings) ----- @param indent an indent for each level ----- @param attr_indent if given, indent each attribute pair and put on a separate line ----- @param xml force prefacing with default or custom ----- @return a string representation --function _M.tostring(t,idn,indent, attr_indent, xml) -- local buf = {}; -- if xml then -- if type(xml) == "string" then -- buf[1] = xml -- else -- buf[1] = "" -- end -+ buf[1] = "" - end -- _dostring(t, buf, _dostring, xml_escape, nil,idn,indent, attr_indent); -- return t_concat(buf); -+ end -+ -+ _dostring(doc, buf, nil, b_ind, t_ind, a_ind, xml_preface) -+ -+ return t_concat(buf) - end - -+ - Doc.__tostring = _M.tostring - ----- get the full text value of an element -+ -+--- Method to pretty-print an XML document. -+-- Invokes `xml.tostring`. -+-- @tparam[opt] string|int b_ind an initial indent (required when `t_ind` is set) -+-- @tparam[opt] string|int t_ind an indent for each level (required when `a_ind` is set) -+-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line -+-- @tparam[opt="<?xml version='1.0'?>"] string xml_preface force prefacing with default or custom -+-- @return a string representation -+-- @see xml.tostring -+function Doc:tostring(b_ind, t_ind, a_ind, xml_preface) -+ return _M.tostring(self, b_ind, t_ind, a_ind, xml_preface) -+end -+ -+ -+--- get the full text value of an element. -+-- @return a single string with all text elements concatenated -+-- @usage -+-- local doc = xml.new("main") -+-- doc:text("one") -+-- doc:add_child(xml.elem "two") -+-- doc:text("three") -+-- -+-- local t = doc:get_text() --> "onethree" - function Doc:get_text() -- local res = {} -- for i,el in ipairs(self) do -- if is_text(el) then t_insert(res,el) end -- end -- return t_concat(res); -+ local res = {} -+ for i,el in ipairs(self) do -+ if is_text(el) then t_insert(res,el) end -+ end -+ return t_concat(res); - end - ----- make a copy of a document ---- @param doc the original document ---- @param strsubst an optional function for handling string copying which could do substitution, etc. --function _M.clone(doc, strsubst) -- local lookup_table = {}; -- local function _copy(object,kind,parent) -- if type(object) ~= "table" then -- if strsubst and is_text(object) then return strsubst(object,kind,parent) -- else return object -- end -- elseif lookup_table[object] then -- return lookup_table[object] -- end -- local new_table = {}; -- lookup_table[object] = new_table -- local tag = object.tag -- new_table.tag = _copy(tag,'*TAG',parent) -- if object.attr then -- local res = {} -- for attr,value in pairs(object.attr) do -- res[attr] = _copy(value,attr,object) -- end -- new_table.attr = res -- end -- for index = 1,#object do -- local v = _copy(object[index],'*TEXT',object) -- t_insert(new_table,v) -+ -+do -+ local function _copy(object, kind, parent, strsubst, lookup_table) -+ if type(object) ~= "table" then -+ if strsubst and is_text(object) then -+ return strsubst(object, kind, parent) -+ else -+ return object -+ end -+ end -+ -+ if lookup_table[object] then -+ error("recursion detected") -+ end -+ lookup_table[object] = true -+ -+ local new_table = {} -+ lookup_table[object] = new_table -+ -+ local tag = object.tag -+ new_table.tag = _copy(tag, '*TAG', parent, strsubst, lookup_table) -+ -+ if object.attr then -+ local res = {} -+ for attr, value in pairs(object.attr) do -+ if type(attr) == "string" then -+ res[attr] = _copy(value, attr, object, strsubst, lookup_table) - end -- return setmetatable(new_table, getmetatable(object)) -+ end -+ new_table.attr = res - end - -- return _copy(doc) -+ for index = 1, #object do -+ local v = _copy(object[index], '*TEXT', object, strsubst, lookup_table) -+ t_insert(new_table,v) -+ end -+ -+ return setmetatable(new_table, getmetatable(object)) -+ end -+ -+ --- Returns a copy of a document. -+ -- The `strsubst` parameter is a callback with signature `function(object, kind, parent)`. -+ -- -+ -- Param `kind` has the following values, and parameters: -+ -- -+ -- - `"*TAG"`: `object` is the tag-name, `parent` is the Node object. Returns the new tag name. -+ -- -+ -- - `"*TEXT"`: `object` is the text-element, `parent` is the Node object. Returns the new text value. -+ -- -+ -- - other strings not prefixed with `*`: `kind` is the attribute name, `object` is the -+ -- attribute value, `parent` is the Node object. Returns the new attribute value. -+ -- -+ -- @tparam Node|string doc a Node object or string (text node) -+ -- @tparam[opt] function strsubst an optional function for handling string copying -+ -- which could do substitution, etc. -+ -- @return copy of the document -+ -- @see Doc:filter -+ function _M.clone(doc, strsubst) -+ return _copy(doc, nil, nil, strsubst, {}) -+ end - end - -+ -+--- Returns a copy of a document. -+-- This is the method version of `xml.clone`. -+-- @see xml.clone -+-- @name Doc:filter -+-- @tparam[opt] function strsubst an optional function for handling string copying - Doc.filter = _M.clone -- also available as method - ----- compare two documents. ---- @param t1 any value ---- @param t2 any value --function _M.compare(t1,t2) -+do -+ local function _compare(t1, t2, recurse_check) -+ - local ty1 = type(t1) - local ty2 = type(t2) -- if ty1 ~= ty2 then return false, 'type mismatch' end -+ -+ if ty1 ~= ty2 then -+ return false, 'type mismatch' -+ end -+ - if ty1 == 'string' then -- return t1 == t2 and true or 'text '..t1..' ~= text '..t2 -+ if t1 == t2 then -+ return true -+ else -+ return false, 'text '..t1..' ~= text '..t2 -+ end -+ end -+ -+ if ty1 ~= 'table' or ty2 ~= 'table' then -+ return false, 'not a document' -+ end -+ -+ if recurse_check[t1] then -+ return false, "recursive document" -+ end -+ recurse_check[t1] = true -+ -+ if t1.tag ~= t2.tag then -+ return false, 'tag '..t1.tag..' ~= tag '..t2.tag -+ end -+ -+ if #t1 ~= #t2 then -+ return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag - end -- if ty1 ~= 'table' or ty2 ~= 'table' then return false, 'not a document' end -- if t1.tag ~= t2.tag then return false, 'tag '..t1.tag..' ~= tag '..t2.tag end -- if #t1 ~= #t2 then return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag end -+ - -- compare attributes - for k,v in pairs(t1.attr) do -- if t2.attr[k] ~= v then return false, 'mismatch attrib' end -+ local t2_value = t2.attr[k] -+ if type(k) == "string" then -+ if t2_value ~= v then return false, 'mismatch attrib' end -+ else -+ if t2_value ~= nil and t2_value ~= v then return false, "mismatch attrib order" end -+ end - end - for k,v in pairs(t2.attr) do -- if t1.attr[k] ~= v then return false, 'mismatch attrib' end -+ local t1_value = t1.attr[k] -+ if type(k) == "string" then -+ if t1_value ~= v then return false, 'mismatch attrib' end -+ else -+ if t1_value ~= nil and t1_value ~= v then return false, "mismatch attrib order" end -+ end - end -+ - -- compare children -- for i = 1,#t1 do -- local yes,err = _M.compare(t1[i],t2[i]) -- if not yes then return err end -+ for i = 1, #t1 do -+ local ok, err = _compare(t1[i], t2[i], recurse_check) -+ if not ok then -+ return ok, err -+ end - end - return true -+ end -+ -+ --- Compare two documents or elements. -+ -- Equality is based on tag, child nodes (text and tags), attributes and order -+ -- of those (order only fails if both are given, and not equal). -+ -- @tparam Node|string t1 a Node object or string (text node) -+ -- @tparam Node|string t2 a Node object or string (text node) -+ -- @treturn boolean `true` when the Nodes are equal. -+ function _M.compare(t1,t2) -+ return _compare(t1, t2, {}) -+ end - end - -+ - --- is this value a document element? - -- @param d any value --function _M.is_tag(d) -- return type(d) == 'table' and is_text(d.tag) --end -+-- @treturn boolean `true` if it is a `table` with property `tag` being a string value. -+-- @name is_tag -+_M.is_tag = is_tag - ----- call the desired function recursively over the document. ---- @param doc the document ---- @param depth_first visit child notes first, then the current node ---- @param operation a function which will receive the current tag name and current node. --function _M.walk (doc, depth_first, operation) -- if not depth_first then operation(doc.tag,doc) end -+ -+do -+ local function _walk(doc, depth_first, operation, recurse_check) -+ if not depth_first then operation(doc.tag, doc) end - for _,d in ipairs(doc) do -- if _M.is_tag(d) then -- _M.walk(d,depth_first,operation) -- end -+ if is_tag(d) then -+ assert(not recurse_check[d], "recursion detected") -+ recurse_check[d] = true -+ _walk(d, depth_first, operation, recurse_check) -+ end - end -- if depth_first then operation(doc.tag,doc) end -+ if depth_first then operation(doc.tag, doc) end -+ end -+ -+ --- Calls a function recursively over Nodes in the document. -+ -- Will only call on tags, it will skip text nodes. -+ -- The function signature for `operation` is `function(tag_name, Node)`. -+ -- @tparam Node|string doc a Node object or string (text node) -+ -- @tparam boolean depth_first visit child nodes first, then the current node -+ -- @tparam function operation a function which will receive the current tag name and current node. -+ function _M.walk(doc, depth_first, operation) -+ return _walk(doc, depth_first, operation, {}) -+ end - end - -+ - local html_empty_elements = { --lists all HTML empty (void) elements -- br = true, -- img = true, -- meta = true, -- frame = true, -- area = true, -- hr = true, -- base = true, -- col = true, -- link = true, -- input = true, -- option = true, -- param = true, -+ br = true, -+ img = true, -+ meta = true, -+ frame = true, -+ area = true, -+ hr = true, -+ base = true, -+ col = true, -+ link = true, -+ input = true, -+ option = true, -+ param = true, - isindex = true, - embed = true, - } - --local escapes = { quot = "\"", apos = "'", lt = "<", gt = ">", amp = "&" } --local function unescape(str) return (str:gsub( "&(%a+);", escapes)); end -- - --- Parse a well-formed HTML file as a string. ---- Tags are case-insenstive, DOCTYPE is ignored, and empty elements can be .. empty. -+-- Tags are case-insensitive, DOCTYPE is ignored, and empty elements can be .. empty. - -- @param s the HTML --function _M.parsehtml (s) -+function _M.parsehtml(s) - return _M.basic_parse(s,false,true) - end - -@@ -549,9 +933,7 @@ end - -- @param s the XML document to be parsed. - -- @param all_text if true, preserves all whitespace. Otherwise only text containing non-whitespace is included. - -- @param html if true, uses relaxed HTML rules for parsing --function _M.basic_parse(s,all_text,html) -- local t_insert,t_remove = table.insert,table.remove -- local s_find,s_sub = string.find,string.sub -+function _M.basic_parse(s, all_text, html) - local stack = {} - local top = {} - -@@ -559,12 +941,12 @@ function _M.basic_parse(s,all_text,html) - local arg = {} - s:gsub("([%w:%-_]+)%s*=%s*([\"'])(.-)%2", function (w, _, a) - if html then w = w:lower() end -- arg[w] = unescape(a) -+ arg[w] = xml_unescape(a) - end) - if html then - s:gsub("([%w:%-_]+)%s*=%s*([^\"']+)%s*", function (w, a) - w = w:lower() -- arg[w] = unescape(a) -+ arg[w] = xml_unescape(a) - end) - end - return arg -@@ -572,9 +954,10 @@ function _M.basic_parse(s,all_text,html) - - t_insert(stack, top) - local ni,c,label,xarg, empty, _, istart -- local i, j = 1, 1 -+ local i = 1 -+ local j - -- we're not interested in -- _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*') -+ _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*') - if not istart then -- or - _,istart = s_find(s,'^%s*%s*') - end -@@ -596,11 +979,9 @@ function _M.basic_parse(s,all_text,html) - if html then - label = label:lower() - if html_empty_elements[label] then empty = "/" end -- if label == 'script' then -- end - end - if all_text or not s_find(text, "^%s*$") then -- t_insert(top, unescape(text)) -+ t_insert(top, xml_unescape(text)) - end - if empty == "/" then -- empty element tag - t_insert(top, setmetatable({tag=label, attr=parseargs(xarg), empty=1},Doc)) -@@ -619,11 +1000,11 @@ function _M.basic_parse(s,all_text,html) - t_insert(top, toclose) - end - end -- i = j+1 -+ i = j+1 - end - local text = s_sub(s, i) - if all_text or not s_find(text, "^%s*$") then -- t_insert(stack[#stack], unescape(text)) -+ t_insert(stack[#stack], xml_unescape(text)) - end - if #stack > 1 then - error("unclosed "..stack[#stack].tag) -@@ -632,145 +1013,151 @@ function _M.basic_parse(s,all_text,html) - return is_text(res[1]) and res[2] or res[1] - end - --local function empty(attr) return not attr or not next(attr) end --local function is_element(d) return type(d) == 'table' and d.tag ~= nil end -+do -+ local match do - ---- returns the key,value pair from a table if it has exactly one entry --local function has_one_element(t) -- local key,value = next(t) -- if next(t,key) ~= nil then return false end -- return key,value --end -+ local function empty(attr) return not attr or not next(attr) end - --local function append_capture(res,tbl) -- if not empty(tbl) then -- no point in capturing empty tables... -- local key -- if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table -- key = tbl._ -- tbl._ = nil -- if empty(tbl) then return end -- end -- -- a table with only one pair {[0]=value} shall be reduced to that value -- local numkey,val = has_one_element(tbl) -- if numkey == 0 then tbl = val end -- if key then -- res[key] = tbl -- else -- otherwise, we append the captured table -- t_insert(res,tbl) -- end -+ local append_capture do -+ -- returns the key,value pair from a table if it has exactly one entry -+ local function has_one_element(t) -+ local key,value = next(t) -+ if next(t,key) ~= nil then return false end -+ return key,value -+ end -+ -+ function append_capture(res,tbl) -+ if not empty(tbl) then -- no point in capturing empty tables... -+ local key -+ if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table -+ key = tbl._ -+ tbl._ = nil -+ if empty(tbl) then return end -+ end -+ -- a table with only one pair {[0]=value} shall be reduced to that value -+ local numkey,val = has_one_element(tbl) -+ if numkey == 0 then tbl = val end -+ if key then -+ res[key] = tbl -+ else -- otherwise, we append the captured table -+ t_insert(res,tbl) -+ end -+ end -+ end - end --end - --local function make_number(pat) -- if pat:find '^%d+$' then -- $1 etc means use this as an array location -- pat = tonumber(pat) -+ local function make_number(pat) -+ if pat:find '^%d+$' then -- $1 etc means use this as an array location -+ pat = tonumber(pat) -+ end -+ return pat - end -- return pat --end - --local function capture_attrib(res,pat,value) -- pat = make_number(pat:sub(2)) -- res[pat] = value -- return true --end -+ local function capture_attrib(res,pat,value) -+ pat = make_number(pat:sub(2)) -+ res[pat] = value -+ return true -+ end - --local match --function match(d,pat,res,keep_going) -- local ret = true -- if d == nil then d = '' end --return false end -- -- attribute string matching is straight equality, except if the pattern is a $ capture, -- -- which always succeeds. -- if is_text(d) then -- if not is_text(pat) then return false end -- if _M.debug then print(d,pat) end -- if pat:find '^%$' then -- return capture_attrib(res,pat,d) -+ function match(d,pat,res,keep_going) -+ local ret = true -+ if d == nil then d = '' end --return false end -+ -- attribute string matching is straight equality, except if the pattern is a $ capture, -+ -- which always succeeds. -+ if is_text(d) then -+ if not is_text(pat) then return false end -+ if _M.debug then print(d,pat) end -+ if pat:find '^%$' then -+ return capture_attrib(res,pat,d) -+ else -+ return d == pat -+ end - else -- return d == pat -- end -- else -- if _M.debug then print(d.tag,pat.tag) end -- -- this is an element node. For a match to succeed, the attributes must -- -- match as well. -- -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute -- local tagpat = pat.tag:match '^(.-)%-$' -- if tagpat then -- tagpat = make_number(tagpat) -- res[tagpat] = d.tag -- end -- if d.tag == pat.tag or tagpat then -- -- if not empty(pat.attr) then -- if empty(d.attr) then ret = false -- else -- for prop,pval in pairs(pat.attr) do -- local dval = d.attr[prop] -- if not match(dval,pval,res) then ret = false; break end -- end -- end -+ if _M.debug then print(d.tag,pat.tag) end -+ -- this is an element node. For a match to succeed, the attributes must -+ -- match as well. -+ -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute -+ local tagpat = pat.tag:match '^(.-)%-$' -+ if tagpat then -+ tagpat = make_number(tagpat) -+ res[tagpat] = d.tag - end -- -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..} -- if ret and #pat > 0 then -- local i,j = 1,1 -- local function next_elem() -- j = j + 1 -- next child element of data -- if is_text(d[j]) then j = j + 1 end -- return j <= #d -- end -- repeat -- local p = pat[i] -- -- repeated {{<...>}} patterns shall match one or more elements -- -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X} -- if is_element(p) and p.repeated then -- local found -- repeat -- local tbl = {} -- ret = match(d[j],p,tbl,false) -- if ret then -- found = false --true -- append_capture(res,tbl) -- end -- until not next_elem() or (found and not ret) -- i = i + 1 -+ if d.tag == pat.tag or tagpat then -+ -+ if not empty(pat.attr) then -+ if empty(d.attr) then ret = false - else -- ret = match(d[j],p,res,false) -- if ret then i = i + 1 end -+ for prop,pval in pairs(pat.attr) do -+ local dval = d.attr[prop] -+ if not match(dval,pval,res) then ret = false; break end -+ end -+ end -+ end -+ -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..} -+ if ret and #pat > 0 then -+ local i,j = 1,1 -+ local function next_elem() -+ j = j + 1 -- next child element of data -+ if is_text(d[j]) then j = j + 1 end -+ return j <= #d - end -- until not next_elem() or i > #pat -- run out of elements or patterns to match -- -- if every element in our pattern matched ok, then it's been a successful match -- if i > #pat then return true end -+ repeat -+ local p = pat[i] -+ -- repeated {{<...>}} patterns shall match one or more elements -+ -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X} -+ if is_tag(p) and p.repeated then -+ local found -+ repeat -+ local tbl = {} -+ ret = match(d[j],p,tbl,false) -+ if ret then -+ found = false --true -+ append_capture(res,tbl) -+ end -+ until not next_elem() or (found and not ret) -+ i = i + 1 -+ else -+ ret = match(d[j],p,res,false) -+ if ret then i = i + 1 end -+ end -+ until not next_elem() or i > #pat -- run out of elements or patterns to match -+ -- if every element in our pattern matched ok, then it's been a successful match -+ if i > #pat then return true end -+ end -+ if ret then return true end -+ else -+ ret = false - end -- if ret then return true end -- else -- ret = false -- end -- -- keep going anyway - look at the children! -- if keep_going then -- for child in d:childtags() do -- ret = match(child,pat,res,keep_going) -- if ret then break end -+ -- keep going anyway - look at the children! -+ if keep_going then -+ for child in d:childtags() do -+ ret = match(child,pat,res,keep_going) -+ if ret then break end -+ end - end - end -+ return ret - end -- return ret --end -+ end - --function Doc:match(pat) -- local err -- pat,err = template_cache(pat) -- if not pat then return nil, err end -- _M.walk(pat,false,function(_,d) -- if is_text(d[1]) and is_element(d[2]) and is_text(d[3]) and -- d[1]:find '%s*{{' and d[3]:find '}}%s*' then -- t_remove(d,1) -- t_remove(d,2) -- d[1].repeated = true -- end -- end) -+ --- does something... -+ function Doc:match(pat) -+ local err -+ pat,err = template_cache(pat) -+ if not pat then return nil, err end -+ _M.walk(pat,false,function(_,d) -+ if is_text(d[1]) and is_tag(d[2]) and is_text(d[3]) and -+ d[1]:find '%s*{{' and d[3]:find '}}%s*' then -+ t_remove(d,1) -+ t_remove(d,2) -+ d[1].repeated = true -+ end -+ end) - -- local res = {} -- local ret = match(self,pat,res,true) -- return res,ret -+ local res = {} -+ local ret = match(self,pat,res,true) -+ return res,ret -+ end - end - - -diff --git a/extra/penlight/penlight-dev-1.rockspec b/extra/penlight/penlight-dev-1.rockspec -new file mode 100644 -index 0000000..c632001 ---- /dev/null -+++ b/extra/penlight/penlight-dev-1.rockspec -@@ -0,0 +1,89 @@ -+local package_name = "penlight" -+local package_version = "dev" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+rockspec_format = "3.0" -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ detailed = [[ -+ Penlight is a set of pure Lua libraries focusing on input data handling -+ (such as reading configuration files), functional programming -+ (such as map, reduce, placeholder expressions,etc), and OS path management. -+ Much of the functionality is inspired by the Python standard libraries. -+ ]], -+ license = "MIT/X11", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ issues_url = "https://github.com/"..github_account_name.."/"..github_repo_name.."/issues", -+ maintainer = "thijs@thijsschreijer.nl", -+} -+ -+dependencies = { -+ "lua >= 5.1", -+ "luafilesystem" -+} -+ -+test_dependencies = { -+ "busted", -+} -+ -+test = { -+ type = "busted", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl"] = "lua/pl/init.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.compat"] = "lua/pl/compat.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.template"] = "lua/pl/template.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -diff --git a/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec -new file mode 100644 -index 0000000..6bcc6ab ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.10.0-1.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.10.0" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec -new file mode 100644 -index 0000000..622a219 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.10.0-2.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.10.0" -+local rockspec_revision = "2" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec -new file mode 100644 -index 0000000..253f8e6 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.11.0-1.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.11.0" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec -new file mode 100644 -index 0000000..23a6a11 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.11.0-2.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.11.0" -+local rockspec_revision = "2" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec -new file mode 100644 -index 0000000..9ebefa2 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.12.0-1.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.12.0" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec -new file mode 100644 -index 0000000..39ddf10 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.12.0-2.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.12.0" -+local rockspec_revision = "2" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec -new file mode 100644 -index 0000000..8aeb7d4 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.13.0-1.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.13.0" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec -new file mode 100644 -index 0000000..4451a66 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.13.1-1.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.13.1" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec -new file mode 100644 -index 0000000..b00255f ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.14.0-1.rockspec -@@ -0,0 +1,89 @@ -+local package_name = "penlight" -+local package_version = "1.14.0" -+local rockspec_revision = "1" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+rockspec_format = "3.0" -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ detailed = [[ -+ Penlight is a set of pure Lua libraries focusing on input data handling -+ (such as reading configuration files), functional programming -+ (such as map, reduce, placeholder expressions,etc), and OS path management. -+ Much of the functionality is inspired by the Python standard libraries. -+ ]], -+ license = "MIT/X11", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ issues_url = "https://github.com/"..github_account_name.."/"..github_repo_name.."/issues", -+ maintainer = "thijs@thijsschreijer.nl", -+} -+ -+dependencies = { -+ "lua >= 5.1", -+ "luafilesystem" -+} -+ -+test_dependencies = { -+ "busted", -+} -+ -+test = { -+ type = "busted", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl"] = "lua/pl/init.lua", -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.compat"] = "lua/pl/compat.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua" -+ }, -+ copy_directories = {"docs", "tests"} -+} -diff --git a/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec -new file mode 100644 -index 0000000..0ff4f87 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.14.0-2.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.14.0" -+local rockspec_revision = "2" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec b/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec -new file mode 100644 -index 0000000..e2ca7d0 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.14.0-3.rockspec -@@ -0,0 +1,78 @@ -+local package_name = "penlight" -+local package_version = "1.14.0" -+local rockspec_revision = "3" -+local github_account_name = "lunarmodules" -+local github_repo_name = package_name -+local git_checkout = package_version == "dev" and "master" or package_version -+ -+ -+package = package_name -+version = package_version .. "-" .. rockspec_revision -+ -+source = { -+ url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", -+ branch = git_checkout -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://"..github_account_name..".github.io/"..github_repo_name, -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.compat"] = "lua/pl/compat.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/penlight-scm-1.rockspec b/extra/penlight/rockspecs/penlight-1.6.0-1.rockspec -similarity index 83% -rename from penlight-scm-1.rockspec -rename to rockspecs/penlight-1.6.0-1.rockspec -index 35a834b..760cbf5 100644 ---- a/extra/penlight/penlight-scm-1.rockspec -+++ b/extra/penlight/rockspecs/penlight-1.6.0-1.rockspec -@@ -1,15 +1,16 @@ - package = "penlight" --version = "scm-1" -+version = "1.6.0-1" - - source = { -- url = "git://github.com/stevedonovan/Penlight.git", -+ url = "git://github.com/Tieske/Penlight.git", -+ branch = "1.6.0" - } - - description = { - summary = "Lua utility libraries loosely based on the Python standard libraries", -- homepage = "http://stevedonovan.github.com/Penlight", -+ homepage = "http://tieske.github.io/Penlight", - license = "MIT/X11", -- maintainer = "steve.j.donovan@gmail.com", -+ maintainer = "thijs@thijsschreijer.nl", - detailed = [[ - Penlight is a set of pure Lua libraries for making it easier to work with common tasks like - iterating over directories, reading configuration files and the like. Provides functional operations -@@ -18,18 +19,18 @@ on tables and sequences. - } - - dependencies = { -- "luafilesystem" -+ "luafilesystem", - } - - build = { - type = "builtin", - modules = { -- ["pl"] = "lua/pl/init.lua", - ["pl.strict"] = "lua/pl/strict.lua", - ["pl.dir"] = "lua/pl/dir.lua", - ["pl.operator"] = "lua/pl/operator.lua", - ["pl.input"] = "lua/pl/input.lua", - ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", - ["pl.seq"] = "lua/pl/seq.lua", - ["pl.stringio"] = "lua/pl/stringio.lua", - ["pl.text"] = "lua/pl/text.lua", -@@ -39,7 +40,6 @@ build = { - ["pl.stringx"] = "lua/pl/stringx.lua", - ["pl.lexer"] = "lua/pl/lexer.lua", - ["pl.utils"] = "lua/pl/utils.lua", -- ["pl.compat"] = "lua/pl/compat.lua", - ["pl.sip"] = "lua/pl/sip.lua", - ["pl.permute"] = "lua/pl/permute.lua", - ["pl.pretty"] = "lua/pl/pretty.lua", -@@ -47,6 +47,7 @@ build = { - ["pl.List"] = "lua/pl/List.lua", - ["pl.data"] = "lua/pl/data.lua", - ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", - ["pl.luabalanced"] = "lua/pl/luabalanced.lua", - ["pl.comprehension"] = "lua/pl/comprehension.lua", - ["pl.path"] = "lua/pl/path.lua", -@@ -61,8 +62,9 @@ build = { - ["pl.Set"] = "lua/pl/Set.lua", - ["pl.xml"] = "lua/pl/xml.lua", - ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", - ["pl.types"] = "lua/pl/types.lua", -- ["pl.import_into"] = "lua/pl/import_into.lua" - }, -- copy_directories = {"doc", "tests"} -+ copy_directories = {"docs", "tests"} - } -+ -diff --git a/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec -new file mode 100644 -index 0000000..b8fbfa7 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.6.0-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.6.0-2" -+ -+source = { -+ url = "git+https://github.com/Tieske/Penlight.git", -+ branch = "1.6.0" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "http://tieske.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec -new file mode 100644 -index 0000000..9b19f39 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.7.0-1.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.7.0-1" -+ -+source = { -+ url = "git://github.com/Tieske/Penlight.git", -+ branch = "1.7.0" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "http://tieske.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec -new file mode 100644 -index 0000000..5013503 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.7.0-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.7.0-2" -+ -+source = { -+ url = "git+https://github.com/Tieske/Penlight.git", -+ branch = "1.7.0" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "http://tieske.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec b/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec -new file mode 100644 -index 0000000..7ee49d3 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.8.0-1.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.8.0-1" -+ -+source = { -+ url = "git://github.com/Tieske/Penlight.git", -+ branch = "1.8.0" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "http://tieske.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec b/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec -new file mode 100644 -index 0000000..d48571e ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.8.0-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.8.0-2" -+ -+source = { -+ url = "git+https://github.com/Tieske/Penlight.git", -+ branch = "1.8.0" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "http://tieske.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec -new file mode 100644 -index 0000000..45919d1 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.8.1-1.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.8.1-1" -+ -+source = { -+ url = "git://github.com/lunarmodules/Penlight.git", -+ tag = "1.8.1" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec b/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec -new file mode 100644 -index 0000000..7f05877 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.8.1-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.8.1-2" -+ -+source = { -+ url = "git+https://github.com/lunarmodules/Penlight.git", -+ tag = "1.8.1" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec b/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec -new file mode 100644 -index 0000000..e546a16 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.9.1-1.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.9.1-1" -+ -+source = { -+ url = "git://github.com/lunarmodules/Penlight.git", -+ tag = "1.9.1" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec b/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec -new file mode 100644 -index 0000000..f0704ff ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.9.1-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.9.1-2" -+ -+source = { -+ url = "git+https://github.com/lunarmodules/Penlight.git", -+ tag = "1.9.1" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec b/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec -new file mode 100644 -index 0000000..5145b38 ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.9.2-1.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.9.2-1" -+ -+source = { -+ url = "git://github.com/lunarmodules/Penlight.git", -+ tag = "1.9.2" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec b/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec -new file mode 100644 -index 0000000..0caf00e ---- /dev/null -+++ b/extra/penlight/rockspecs/penlight-1.9.2-2.rockspec -@@ -0,0 +1,70 @@ -+package = "penlight" -+version = "1.9.2-2" -+ -+source = { -+ url = "git+https://github.com/lunarmodules/Penlight.git", -+ tag = "1.9.2" -+} -+ -+description = { -+ summary = "Lua utility libraries loosely based on the Python standard libraries", -+ homepage = "https://lunarmodules.github.io/Penlight", -+ license = "MIT/X11", -+ maintainer = "thijs@thijsschreijer.nl", -+ detailed = [[ -+Penlight is a set of pure Lua libraries for making it easier to work with common tasks like -+iterating over directories, reading configuration files and the like. Provides functional operations -+on tables and sequences. -+]] -+} -+ -+dependencies = { -+ "luafilesystem", -+} -+ -+build = { -+ type = "builtin", -+ modules = { -+ ["pl.strict"] = "lua/pl/strict.lua", -+ ["pl.dir"] = "lua/pl/dir.lua", -+ ["pl.operator"] = "lua/pl/operator.lua", -+ ["pl.input"] = "lua/pl/input.lua", -+ ["pl.config"] = "lua/pl/config.lua", -+ ["pl.compat"] = "lua/pl/config.lua", -+ ["pl.seq"] = "lua/pl/seq.lua", -+ ["pl.stringio"] = "lua/pl/stringio.lua", -+ ["pl.text"] = "lua/pl/text.lua", -+ ["pl.test"] = "lua/pl/test.lua", -+ ["pl.tablex"] = "lua/pl/tablex.lua", -+ ["pl.app"] = "lua/pl/app.lua", -+ ["pl.stringx"] = "lua/pl/stringx.lua", -+ ["pl.lexer"] = "lua/pl/lexer.lua", -+ ["pl.utils"] = "lua/pl/utils.lua", -+ ["pl.sip"] = "lua/pl/sip.lua", -+ ["pl.permute"] = "lua/pl/permute.lua", -+ ["pl.pretty"] = "lua/pl/pretty.lua", -+ ["pl.class"] = "lua/pl/class.lua", -+ ["pl.List"] = "lua/pl/List.lua", -+ ["pl.data"] = "lua/pl/data.lua", -+ ["pl.Date"] = "lua/pl/Date.lua", -+ ["pl.init"] = "lua/pl/init.lua", -+ ["pl.luabalanced"] = "lua/pl/luabalanced.lua", -+ ["pl.comprehension"] = "lua/pl/comprehension.lua", -+ ["pl.path"] = "lua/pl/path.lua", -+ ["pl.array2d"] = "lua/pl/array2d.lua", -+ ["pl.func"] = "lua/pl/func.lua", -+ ["pl.lapp"] = "lua/pl/lapp.lua", -+ ["pl.file"] = "lua/pl/file.lua", -+ ['pl.template'] = "lua/pl/template.lua", -+ ["pl.Map"] = "lua/pl/Map.lua", -+ ["pl.MultiMap"] = "lua/pl/MultiMap.lua", -+ ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", -+ ["pl.Set"] = "lua/pl/Set.lua", -+ ["pl.xml"] = "lua/pl/xml.lua", -+ ["pl.url"] = "lua/pl/url.lua", -+ ["pl.import_into"] = "lua/pl/import_into.lua", -+ ["pl.types"] = "lua/pl/types.lua", -+ }, -+ copy_directories = {"docs", "tests"} -+} -+ -diff --git a/extra/penlight/run.lua b/extra/penlight/run.lua -old mode 100644 -new mode 100755 -index 98c3587..4d0764b ---- a/extra/penlight/run.lua -+++ b/extra/penlight/run.lua -@@ -1,3 +1,5 @@ -+#!/usr/bin/env lua -+ - -- Running tests and/or examples. - local lfs = require "lfs" - -@@ -32,7 +34,7 @@ end - - local dir_sep = package.config:sub(1, 1) - local quote = dir_sep == "/" and "'" or '"' --local pl_src = "lua" .. dir_sep .. "?.lua" -+local pl_src = "lua/?.lua;lua/?/init.lua" - lua = lua .. " -e " .. quote .. "package.path=[[" .. pl_src .. ";]]..package.path" .. quote - - local function run_directory(dir) -diff --git a/extra/penlight/spec/app_spec.lua b/extra/penlight/spec/app_spec.lua -new file mode 100644 -index 0000000..3b1f290 ---- /dev/null -+++ b/extra/penlight/spec/app_spec.lua -@@ -0,0 +1,27 @@ -+local app = require("pl.app") -+ -+describe("pl.app.lua", function () -+ -+ local invocation = app.lua() -+ -+ it("should pick up the arguments used to run this test", function () -+ assert.is.truthy(invocation:match("lua.+package.+busted")) -+ end) -+ -+ it("should be reusable to invoke Lua", function () -+ assert.is.truthy(os.execute(app.lua()..' -e "n=1;os.exit(n-1)"')) -+ end) -+ -+end) -+ -+describe("pl.app.platform", function () -+ -+ -- TODO: Find a reliable alternate way to determine platform to check that -+ -- this is returning the right answer, not just any old answer. -+ it("should at least return a valid platform", function () -+ local platforms = { Linux = true, OSX = true, Windows = true } -+ local detected = app.platform() -+ assert.is.truthy(platforms[detected]) -+ end) -+ -+end) -diff --git a/extra/penlight/spec/array2d_spec.lua b/extra/penlight/spec/array2d_spec.lua -new file mode 100644 -index 0000000..95122de ---- /dev/null -+++ b/extra/penlight/spec/array2d_spec.lua -@@ -0,0 +1,557 @@ -+local array2d = require("pl.array2d") -+ -+describe("pl.array2d", function() -+ -+ describe("new()", function() -+ it("creates an empty 2d array", function() -+ assert.same({{},{},{}}, array2d.new(3,3,nil)) -+ end) -+ -+ it("creates a value-filled 2d array", function() -+ assert.same({{99,99,99}, -+ {99,99,99}, -+ {99,99,99}}, array2d.new(3,3,99)) -+ end) -+ -+ it("creates a function-filled 2d array", function() -+ assert.same({{2,3,4}, -+ {3,4,5}, -+ {4,5,6}}, array2d.new(3,3,function(i,j) return i+j end)) -+ end) -+ end) -+ -+ describe("size()", function() -+ it("returns array size", function() -+ local a = array2d.new(3,5,99) -+ assert.same({3,5}, {array2d.size(a)}) -+ end) -+ -+ it("returns 0 columns for nil arrays", function() -+ local a = array2d.new(3,5,nil) -+ assert.same({3,0}, {array2d.size(a)}) -+ end) -+ end) -+ -+ describe("column()", function() -+ it("returns a column copy", function() -+ local a = {{1,2}, -+ {3,4}, -+ {5,6}} -+ assert.same({1,3,5}, array2d.column(a,1)) -+ assert.same({2,4,6}, array2d.column(a,2)) -+ end) -+ end) -+ -+ describe("row()", function() -+ it("returns a row copy", function() -+ local a = {{1,2}, -+ {3,4}, -+ {5,6}} -+ assert.same({1,2}, array2d.row(a,1)) -+ -- next test: need to remove the metatable to prevent comparison by -+ -- metamethods in Lua 5.3 and 5.4 -+ assert.not_equal(a[1], setmetatable(array2d.row(a,1),nil)) -+ assert.same({3,4}, array2d.row(a,2)) -+ assert.same({5,6}, array2d.row(a,3)) -+ end) -+ end) -+ -+ describe("map()", function() -+ it("maps a function on an array", function() -+ local a1 = array2d.new(2,3,function(i,j) return i+j end) -+ local a2 = array2d.map(function(a,b) return a .. b end, a1, "x") -+ assert.same({{"2x","3x","4x"}, -+ {"3x","4x","5x"}}, a2) -+ end) -+ end) -+ -+ describe("reduce_rows()", function() -+ it("reduces rows", function() -+ local a = {{ 1, 2, 3, 4}, -+ { 10, 20, 30, 40}, -+ { 100, 200, 300, 400}, -+ {1000,2000,3000,4000}} -+ assert.same({10,100,1000,10000},array2d.reduce_rows('+',a)) -+ end) -+ end) -+ -+ describe("reduce_cols()", function() -+ it("reduces columns", function() -+ local a = {{ 1, 2, 3, 4}, -+ { 10, 20, 30, 40}, -+ { 100, 200, 300, 400}, -+ {1000,2000,3000,4000}} -+ assert.same({1111,2222,3333,4444},array2d.reduce_cols('+',a)) -+ end) -+ end) -+ -+ describe("reduce2()", function() -+ it("recuces array to scalar", function() -+ local a = {{1,10}, -+ {2,10}, -+ {3,10}} -+ assert.same(60, array2d.reduce2('+','*',a)) -+ end) -+ end) -+ -+ describe("map2()", function() -+ it("maps over 2 arrays", function() -+ local b = {{10,20}, -+ {30,40}} -+ local a = {{1,2}, -+ {3,4}} -+ -- 2 2d arrays -+ assert.same({{11,22},{33,44}}, array2d.map2('+',2,2,a,b)) -+ -- 1d, 2d -+ assert.same({{11,102},{13,104}}, array2d.map2('+',1,2,{10,100},a)) -+ -- 2d, 1d -+ assert.same({{1,-2},{3,-4}},array2d.map2('*',2,1,a,{1,-1})) -+ end) -+ end) -+ -+ describe("product()", function() -+ it("creates a product array", function() -+ local a = array2d.product('..',{1,2,3},{'a','b','c'}) -+ assert.same({{'1a','2a','3a'},{'1b','2b','3b'},{'1c','2c','3c'}}, a) -+ -+ local a = array2d.product('{}',{1,2},{'a','b','c'}) -+ assert.same({{{1,'a'},{2,'a'}},{{1,'b'},{2,'b'}},{{1,'c'},{2,'c'}}}, a) -+ end) -+ end) -+ -+ describe("flatten()", function() -+ it("flattens a 2darray", function() -+ local a = {{1,2}, -+ {3,4}, -+ {5,6}} -+ assert.same( {1,2,3,4,5,6}, array2d.flatten(a)) -+ end) -+ -+ it("keeps a nil-array 'square'", function() -+ local a = {{ 1,2}, -+ {nil,4}, -+ {nil,6}} -+ assert.same( {1,2,nil,4,nil,6}, array2d.flatten(a)) -+ end) -+ end) -+ -+ describe("reshape()", function() -+ it("reshapes array in new nr of rows", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}, -+ {10,11,12}} -+ local b = array2d.reshape(a, 2, false) -+ assert.same({{ 1, 2, 3, 4, 5, 6}, -+ { 7, 8, 9,10,11,12}}, b) -+ local c = array2d.reshape(b, 4, false) -+ assert.same(a, c) -+ end) -+ it("reshapes array in new nr of rows, column order", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}} -+ local b = array2d.reshape(a, 3, true) -+ assert.same({{ 1, 4, 7}, -+ { 2, 5, 8}, -+ { 3, 6, 9}}, b) -+ end) -+ end) -+ -+ describe("transpose()", function() -+ it("transposes a 2d array", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}} -+ local b = array2d.transpose(a) -+ assert.same({{ 1, 4, 7}, -+ { 2, 5, 8}, -+ { 3, 6, 9}}, b) -+ -+ local a = {{ 1, 2, 3, 4, 5}, -+ { 6, 7, 8, 9, 10}} -+ local b = array2d.transpose(a) -+ assert.same({{ 1, 6}, -+ { 2, 7}, -+ { 3, 8}, -+ { 4, 9}, -+ { 5,10}}, b) -+ end) -+ end) -+ -+ describe("swap_rows()", function() -+ it("swaps 2 rows, in-place", function() -+ local a = {{1,2}, -+ {3,4}, -+ {5,6}} -+ local b = array2d.swap_rows(a, 1, 3) -+ assert.same({{5,6}, -+ {3,4}, -+ {1,2}}, b) -+ assert.equal(a, b) -+ end) -+ end) -+ -+ describe("swap_cols()", function() -+ it("swaps 2 columns, in-place", function() -+ local a = {{1,2,3}, -+ {4,5,6}, -+ {7,8,9}} -+ local b = array2d.swap_cols(a, 1, 3) -+ assert.same({{3,2,1}, -+ {6,5,4}, -+ {9,8,7}}, b) -+ assert.equal(a, b) -+ end) -+ end) -+ -+ describe("extract_rows()", function() -+ it("extracts rows", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}, -+ {10,11,12}} -+ local b = array2d.extract_rows(a, {1, 3}) -+ assert.same({{1,2,3}, -+ {7,8,9}}, b) -+ end) -+ end) -+ -+ describe("extract_cols()", function() -+ it("extracts columns", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}, -+ {10,11,12}} -+ local b = array2d.extract_cols(a, {1, 2}) -+ assert.same({{ 1, 2}, -+ { 4, 5}, -+ { 7, 8}, -+ {10,11}}, b) -+ end) -+ end) -+ -+ describe("remove_row()", function() -+ it("removes a row", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}, -+ {10,11,12}} -+ array2d.remove_row(a, 2) -+ assert.same({{ 1, 2, 3}, -+ { 7, 8, 9}, -+ {10,11,12}}, a) -+ end) -+ end) -+ -+ describe("remove_col()", function() -+ it("removes a colum", function() -+ local a = {{ 1, 2, 3}, -+ { 4, 5, 6}, -+ { 7, 8, 9}, -+ {10,11,12}} -+ array2d.remove_col(a, 2) -+ assert.same({{ 1, 3}, -+ { 4, 6}, -+ { 7, 9}, -+ {10,12}}, a) -+ end) -+ end) -+ -+ describe("parse_range()", function() -+ it("parses A1:B2 format", function() -+ assert.same({4,11,7,12},{array2d.parse_range("K4:L7")}) -+ assert.same({4,28,7,54},{array2d.parse_range("AB4:BB7")}) -+ -- test Col R since it might be mixed up with RxCx format -+ assert.same({4,18,7,18},{array2d.parse_range("R4:R7")}) -+ end) -+ -+ it("parses A1 format", function() -+ assert.same({4,11},{array2d.parse_range("K4")}) -+ -- test Col R since it might be mixed up with RxCx format -+ assert.same({4,18},{array2d.parse_range("R4")}) -+ end) -+ -+ it("parses R1C1:R2C2 format", function() -+ assert.same({4,11,7,12},{array2d.parse_range("R4C11:R7C12")}) -+ end) -+ -+ it("parses R1C1 format", function() -+ assert.same({4,11},{array2d.parse_range("R4C11")}) -+ end) -+ end) -+ -+ describe("range()", function() -+ it("returns a range", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local b = array2d.range(a, "B3:C4") -+ assert.same({{ 8, 9}, -+ {11,12}}, b) -+ end) -+ end) -+ -+ describe("default_range()", function() -+ it("returns the default range", function() -+ local a = array2d.new(4,6,1) -+ assert.same({1,1,4,6}, {array2d.default_range(a, nil, nil, nil, nil)}) -+ end) -+ -+ it("accepts negative indices", function() -+ local a = array2d.new(4,6,1) -+ assert.same({2,2,3,5}, {array2d.default_range(a, -3, -5, -2, -2)}) -+ end) -+ -+ it("corrects out of bounds indices", function() -+ local a = array2d.new(4,6,1) -+ assert.same({1,1,4,6}, {array2d.default_range(a, -100, -100, 100, 100)}) -+ end) -+ end) -+ -+ describe("slice()", function() -+ it("returns a slice", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local b = array2d.slice(a,3,2,4,3) -+ assert.same({{ 8, 9}, -+ {11,12}}, b) -+ end) -+ -+ it("returns a single row if rows are equal", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local b = array2d.slice(a,4,1,4,3) -+ assert.same({10,11,12}, b) -+ end) -+ -+ it("returns a single column if columns are equal", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local b = array2d.slice(a,1,3,4,3) -+ assert.same({3,6,9,12}, b) -+ end) -+ -+ it("returns a single value if rows and columns are equal", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local b = array2d.slice(a,2,2,2,2) -+ assert.same(5, b) -+ end) -+ end) -+ -+ describe("set()", function() -+ it("sets a range to a value", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ array2d.set(a,0,2,2,3,3) -+ assert.same({{1 ,2 ,3}, -+ {4 ,0 ,0}, -+ {7 ,0 ,0}, -+ {10,11,12}}, a) -+ end) -+ -+ it("sets a range to a function value", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local x = 10 -+ local args = {} -+ local f = function(r,c) -+ args[#args+1] = {r,c} -+ x = x + 1 -+ return x -+ end -+ array2d.set(a,f,3,1,4,3) -+ assert.same({{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {11,12,13}, -+ {14,15,16}}, a) -+ -- validate args used to call the function -+ assert.same({{3,1},{3,2},{3,3},{4,1},{4,2},{4,3}}, args) -+ end) -+ end) -+ -+ describe("write()", function() -+ it("writes array to a file", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local f = setmetatable({}, { -+ __index = { -+ write = function(self,str) -+ self[#self+1] = str -+ end -+ } -+ }) -+ array2d.write(a,f,"(%s)") -+ f = table.concat(f) -+ assert.equal([[(1)(2)(3) -+(4)(5)(6) -+(7)(8)(9) -+(10)(11)(12) -+]],f) -+ end) -+ -+ it("writes partial array to a file", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local f = setmetatable({}, { -+ __index = { -+ write = function(self,str) -+ self[#self+1] = str -+ end -+ } -+ }) -+ array2d.write(a,f,"(%s)", 1,1,2,2) -+ f = table.concat(f) -+ assert.equal([[(1)(2) -+(4)(5) -+]],f) -+ end) -+ end) -+ -+ describe("forall()", function() -+ it("runs all value and row functions", function() -+ local r = {} -+ local t = 0 -+ local fval = function(row, j) t = t + row[j] end -+ local frow = function(i) r[#r+1] = t; t = 0 end -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ array2d.forall(a, fval, frow) -+ assert.same({6, 15, 24, 33}, r) -+ r = {} -+ array2d.forall(a, fval, frow, 2,2,4,3) -+ assert.same({11, 17, 23}, r) -+ end) -+ -+ end) -+ -+ describe("move()", function() -+ it("moves block to destination array", function() -+ local a = array2d.new(4,4,0) -+ local b = array2d.new(3,3,1) -+ array2d.move(a,2,2,b) -+ assert.same({{0,0,0,0}, -+ {0,1,1,1}, -+ {0,1,1,1}, -+ {0,1,1,1}}, a) -+ end) -+ end) -+ -+ describe("iter()", function() -+ it("iterates all values", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ for v, i, j in array2d.iter(a) do -+ r[#r+1] = v -+ assert.is_nil(i) -+ assert.is_nil(j) -+ end -+ assert.same({1,2,3,4,5,6,7,8,9,10,11,12}, r) -+ end) -+ -+ it("iterates all values and indices", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ local ri = {} -+ local rj = {} -+ for i, j, v in array2d.iter(a,true) do -+ r[#r+1] = v -+ ri[#ri+1] = i -+ rj[#rj+1] = j -+ end -+ assert.same({1,2,3,4,5,6,7,8,9,10,11,12}, r) -+ assert.same({1,1,1,2,2,2,3,3,3,4,4,4}, ri) -+ assert.same({1,2,3,1,2,3,1,2,3,1,2,3}, rj) -+ end) -+ -+ it("iterates all values of a 2d array part", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ for v, i, j in array2d.iter(a,false,2,2,4,3) do -+ r[#r+1] = v -+ assert.is_nil(i) -+ assert.is_nil(j) -+ end -+ assert.same({5,6,8,9,11,12}, r) -+ end) -+ -+ it("iterates all values and indices of a 2d array part", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ local ri = {} -+ local rj = {} -+ for i, j, v in array2d.iter(a,true,2,2,4,3) do -+ r[#r+1] = v -+ ri[#ri+1] = i -+ rj[#rj+1] = j -+ end -+ assert.same({5,6,8,9,11,12}, r) -+ assert.same({2,2,3,3,4,4}, ri) -+ assert.same({2,3,2,3,2,3}, rj) -+ end) -+ end) -+ -+ describe("columns()", function() -+ it("iterates all columns", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ for col, idx in array2d.columns(a) do -+ r[#r+1] = col -+ col.idx = idx -+ end -+ assert.same({{1,4,7,10, idx=1},{2,5,8,11, idx=2},{3,6,9,12, idx=3}}, r) -+ end) -+ end) -+ -+ describe("rows()", function() -+ it("iterates all columns", function() -+ local a = {{1 ,2 ,3}, -+ {4 ,5 ,6}, -+ {7 ,8 ,9}, -+ {10,11,12}} -+ local r = {} -+ for row, idx in array2d.rows(a) do -+ r[#r+1] = row -+ row.idx = idx -+ end -+ assert.same({{1,2,3, idx=1},{4,5,6, idx=2}, -+ {7,8,9, idx=3},{10,11,12, idx=4}}, r) -+ end) -+ end) -+ -+end) -diff --git a/extra/penlight/spec/date_spec.lua b/extra/penlight/spec/date_spec.lua -new file mode 100644 -index 0000000..1032de2 ---- /dev/null -+++ b/extra/penlight/spec/date_spec.lua -@@ -0,0 +1,48 @@ -+local Date = require("pl.Date") -+ -+describe("pl.Date", function () -+ -+ describe("function", function () -+ -+ describe("Format()", function () -+ -+ it("should output parsable inputs", function () -+ local function assert_date_format(expected, format) -+ local df = Date.Format(format) -+ local d = df:parse(expected) -+ assert.is.equal(expected, df:tostring(d)) -+ end -+ assert_date_format('02/04/10', 'dd/mm/yy') -+ assert_date_format('04/02/2010', 'mm/dd/yyyy') -+ assert_date_format('2011-02-20', 'yyyy-mm-dd') -+ assert_date_format('20070320', 'yyyymmdd') -+ assert_date_format('23:10', 'HH:MM') -+ end) -+ -+ it("should parse 'slack' fields", function () -+ local df = Date.Format("m/d/yy") -+ -- TODO: Re-enable when issue #359 fixed -+ -- assert.is.equal('01/05/99', df:tostring(df:parse('1/5/99'))) -+ assert.is.equal('01/05/01', df:tostring(df:parse('1/5/01'))) -+ assert.is.equal('01/05/32', df:tostring(df:parse('1/5/32'))) -+ end) -+ -+ end) -+ -+ end) -+ -+ describe("meta method", function () -+ -+ describe("__tostring()", function () -+ -+ it("should be suitable for serialization", function () -+ local df = Date.Format() -+ local du = df:parse("2008-07-05") -+ assert.is.equal(du, du:toUTC()) -+ end) -+ -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/func_spec.lua b/extra/penlight/spec/func_spec.lua -new file mode 100644 -index 0000000..2b3a438 ---- /dev/null -+++ b/extra/penlight/spec/func_spec.lua -@@ -0,0 +1,54 @@ -+local func = require("pl.func") -+ -+describe("pl.func", function () -+ -+ describe("compose", function () -+ -+ it("compose(f)(x) == f(x)", function () -+ local f = function(x) return x + 1 end -+ assert.equals(func.compose(f)(1), f(1)) -+ end) -+ -+ it("compose(f, g)(x) == f(g(x))", function () -+ local f = function(x) return x + 1 end -+ local g = function(x) return x + 2 end -+ assert.equals(func.compose(f, g)(1), f(g(1))) -+ end) -+ -+ it("compose(f, g, h)(x) == f(g(h(x)))", function () -+ local f = function(x) return x + 1 end -+ local g = function(x) return x + 2 end -+ local h = function(x) return x + 3 end -+ assert.equals(func.compose(f, g, h)(1), f(g(h(1)))) -+ end) -+ -+ it("compose(f)(x, y) == f(x, y)", function () -+ local f = function(x, y) return x + 1, y + 1 end -+ local ax, ay = func.compose(f)(1, 2) -+ local bx, by = f(1, 2) -+ assert.equals(ax, bx) -+ assert.equals(ay, by) -+ end) -+ -+ it("compose(f, g)(x, y) == f(g(x, y))", function () -+ local f = function(x, y) return x + 1, y + 1 end -+ local g = function(x, y) return x + 2, y + 2 end -+ local ax, ay = func.compose(f, g)(1, 2) -+ local bx, by = f(g(1, 2)) -+ assert.equals(ax, bx) -+ assert.equals(ay, by) -+ end) -+ -+ it("compose(f, g, h)(x, y) == f(g(h(x, y)))", function () -+ local f = function(x, y) return x + 1, y + 1 end -+ local g = function(x, y) return x + 2, y + 2 end -+ local h = function(x, y) return x + 3, y + 3 end -+ local ax, ay = func.compose(f, g, h)(1, 2) -+ local bx, by = f(g(h(1, 2))) -+ assert.equals(ax, bx) -+ assert.equals(ay, by) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/multimap_spec.lua b/extra/penlight/spec/multimap_spec.lua -new file mode 100644 -index 0000000..a83d150 ---- /dev/null -+++ b/extra/penlight/spec/multimap_spec.lua -@@ -0,0 +1,14 @@ -+local MultiMap = require("pl.MultiMap") -+ -+describe("pl.MultiMap", function () -+ -+ it("should hold multiple values per key", function () -+ local map = MultiMap() -+ map:set('foo', 1) -+ map:set('bar', 3) -+ map:set('foo', 2) -+ local expected = { foo = { 1, 2 }, bar = { 3 } } -+ assert.is.same(expected, map) -+ end) -+ -+end) -diff --git a/extra/penlight/spec/path_spec.lua b/extra/penlight/spec/path_spec.lua -new file mode 100644 -index 0000000..7f18f29 ---- /dev/null -+++ b/extra/penlight/spec/path_spec.lua -@@ -0,0 +1,106 @@ -+ -+-- conditional it/pending blocks per platform -+local function nix_it(desc, ...) -+ if package.config:sub(1,1) == "\\" then -+ pending("Skip test on Windows: " .. desc, ...) -+ else -+ it(desc, ...) -+ end -+end -+local function win_it(desc, ...) -+ if package.config:sub(1,1) == "\\" then -+ it(desc, ...) -+ else -+ pending("Skip test on Unix: " .. desc, ...) -+ end -+end -+ -+ -+ -+describe("pl.path", function() -+ -+ local path -+ local mock_envs -+ local old_get_env -+ -+ before_each(function() -+ mock_envs = {} -+ old_get_env = os.getenv -+ os.getenv = function(name) -- luacheck: ignore -+ return mock_envs[name] -+ end -+ package.loaded["pl.path"] = nil -+ path = require "pl.path" -+ end) -+ -+ after_each(function() -+ package.loaded["pl.path"] = nil -+ os.getenv = old_get_env -- luacheck: ignore -+ end) -+ -+ -+ -+ describe("expanduser()", function() -+ -+ it("should expand ~ to the user's home directory", function() -+ mock_envs = { -+ HOME = "/home/user", -+ } -+ assert.equal("/home/user/file", path.expanduser("~/file")) -+ end) -+ -+ -+ nix_it("returns an error if expansion fails: HOME not set", function() -+ mock_envs = {} -+ assert.same( -+ { nil, "failed to expand '~' (HOME not set)" }, -+ { path.expanduser("~/file")} -+ ) -+ end) -+ -+ -+ win_it("returns an error if expansion fails: all Windows vars", function() -+ mock_envs = {} -+ assert.same( -+ { nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" }, -+ { path.expanduser("~/file")} -+ ) -+ end) -+ -+ -+ win_it("HOME is first in line", function() -+ mock_envs = { -+ HOME = "\\home\\user1", -+ USERPROFILE = "\\home\\user2", -+ HOMEDRIVE = "C:", -+ HOMEPATH = "\\home\\user3", -+ } -+ assert.equal("\\home\\user1\\file", path.expanduser("~\\file")) -+ end) -+ -+ -+ win_it("USERPROFILE is second in line", function() -+ mock_envs = { -+ --HOME = "\\home\\user1", -+ USERPROFILE = "\\home\\user2", -+ HOMEDRIVE = "C:", -+ HOMEPATH = "\\home\\user3", -+ } -+ assert.equal("\\home\\user2\\file", path.expanduser("~\\file")) -+ end) -+ -+ -+ win_it("HOMEDRIVE/PATH is third in line", function() -+ mock_envs = { -+ -- HOME = "\\home\\user1", -+ -- USERPROFILE = "\\home\\user2", -+ HOMEDRIVE = "C:", -+ HOMEPATH = "\\home\\user3", -+ } -+ assert.equal("C:\\home\\user3\\file", path.expanduser("~\\file")) -+ end) -+ -+ end) -+ -+end) -+ -diff --git a/extra/penlight/spec/permute_spec.lua b/extra/penlight/spec/permute_spec.lua -new file mode 100644 -index 0000000..49143c0 ---- /dev/null -+++ b/extra/penlight/spec/permute_spec.lua -@@ -0,0 +1,213 @@ -+local permute = require("pl.permute") -+local tcopy = require("pl.tablex").copy -+local utils = require("pl.utils") -+ -+describe("pl.permute", function() -+ -+ describe("order_iter", function() -+ -+ it("returns all order combinations", function() -+ local result = {} -+ for list in permute.order_iter({"one", "two", "three"}) do -+ result[#result+1] = tcopy(list) -+ end -+ assert.same({ -+ [1] = { -+ [1] = 'two', -+ [2] = 'three', -+ [3] = 'one' }, -+ [2] = { -+ [1] = 'three', -+ [2] = 'two', -+ [3] = 'one' }, -+ [3] = { -+ [1] = 'three', -+ [2] = 'one', -+ [3] = 'two' }, -+ [4] = { -+ [1] = 'one', -+ [2] = 'three', -+ [3] = 'two' }, -+ [5] = { -+ [1] = 'two', -+ [2] = 'one', -+ [3] = 'three' }, -+ [6] = { -+ [1] = 'one', -+ [2] = 'two', -+ [3] = 'three' } }, result) -+ end) -+ -+ -+ it("returns nil on empty list", function() -+ local result = {} -+ for list in permute.order_iter({}) do -+ result[#result+1] = tcopy(list) -+ end -+ assert.equal(0, #result) -+ end) -+ -+ end) -+ -+ -+ -+ describe("order_table", function() -+ -+ it("returns all order combinations", function() -+ local result = permute.order_table({"one", "two", "three"}) -+ assert.same({ -+ [1] = { -+ [1] = 'two', -+ [2] = 'three', -+ [3] = 'one' }, -+ [2] = { -+ [1] = 'three', -+ [2] = 'two', -+ [3] = 'one' }, -+ [3] = { -+ [1] = 'three', -+ [2] = 'one', -+ [3] = 'two' }, -+ [4] = { -+ [1] = 'one', -+ [2] = 'three', -+ [3] = 'two' }, -+ [5] = { -+ [1] = 'two', -+ [2] = 'one', -+ [3] = 'three' }, -+ [6] = { -+ [1] = 'one', -+ [2] = 'two', -+ [3] = 'three' } }, result) -+ end) -+ -+ -+ it("returns empty table on empty input list", function() -+ local result = permute.order_table({}) -+ assert.same({}, result) -+ end) -+ -+ end) -+ -+ -+ -+ describe("list_iter", function() -+ -+ it("returns all combinations from sub-lists", function() -+ local result = {} -+ local strs = {"one", "two", "three"} -+ local ints = { 1,2,3 } -+ local bools = { true, false } -+ for count, str, int, bool in permute.list_iter(strs, ints, bools) do -+ result[#result+1] = {count, str, int, bool} -+ end -+ assert.same({ -+ [1] = {1, 'one', 1, true }, -+ [2] = {2, 'two', 1, true }, -+ [3] = {3, 'three', 1, true }, -+ [4] = {4, 'one', 2, true }, -+ [5] = {5, 'two', 2, true }, -+ [6] = {6, 'three', 2, true }, -+ [7] = {7, 'one', 3, true }, -+ [8] = {8, 'two', 3, true }, -+ [9] = {9, 'three', 3, true }, -+ [10] = {10, 'one', 1, false }, -+ [11] = {11, 'two', 1, false }, -+ [12] = {12, 'three', 1, false }, -+ [13] = {13, 'one', 2, false }, -+ [14] = {14, 'two', 2, false }, -+ [15] = {15, 'three', 2, false }, -+ [16] = {16, 'one', 3, false }, -+ [17] = {17, 'two', 3, false }, -+ [18] = {18, 'three', 3, false }, -+ }, result) -+ end) -+ -+ -+ it("is nil-safe, given 'n' is set", function() -+ local result = {} -+ local bools = utils.pack(nil, true, false) -+ local strs = utils.pack("one", "two", nil) -+ for count, bool, str in permute.list_iter(bools, strs) do -+ result[#result+1] = {count, bool, str} -+ end -+ assert.same({ -+ [1] = {1, nil, 'one' }, -+ [2] = {2, true, 'one' }, -+ [3] = {3, false, 'one' }, -+ [4] = {4, nil, 'two' }, -+ [5] = {5, true, 'two' }, -+ [6] = {6, false, 'two' }, -+ [7] = {7, nil, nil }, -+ [8] = {8, true, nil }, -+ [9] = {9, false, nil }, -+ }, result) -+ end) -+ -+ -+ it("returns nil on empty list", function() -+ local count = 0 -+ for list in permute.list_iter({}) do -+ count = count + 1 -+ end -+ assert.equal(0, count) -+ end) -+ -+ end) -+ -+ -+ -+ describe("list_table", function() -+ -+ it("returns all combinations from sub-lists", function() -+ local strs = {"one", "two", "three"} -+ local ints = { 1,2,3 } -+ local bools = { true, false } -+ assert.same({ -+ [1] = {'one', 1, true, n = 3 }, -+ [2] = {'two', 1, true, n = 3 }, -+ [3] = {'three', 1, true, n = 3 }, -+ [4] = {'one', 2, true, n = 3 }, -+ [5] = {'two', 2, true, n = 3 }, -+ [6] = {'three', 2, true, n = 3 }, -+ [7] = {'one', 3, true, n = 3 }, -+ [8] = {'two', 3, true, n = 3 }, -+ [9] = {'three', 3, true, n = 3 }, -+ [10] = {'one', 1, false, n = 3 }, -+ [11] = {'two', 1, false, n = 3 }, -+ [12] = {'three', 1, false, n = 3 }, -+ [13] = {'one', 2, false, n = 3 }, -+ [14] = {'two', 2, false, n = 3 }, -+ [15] = {'three', 2, false, n = 3 }, -+ [16] = {'one', 3, false, n = 3 }, -+ [17] = {'two', 3, false, n = 3 }, -+ [18] = {'three', 3, false, n = 3 }, -+ }, permute.list_table(strs, ints, bools)) -+ end) -+ -+ -+ it("is nil-safe, given 'n' is set", function() -+ local bools = utils.pack(nil, true, false) -+ local strs = utils.pack("one", "two", nil) -+ assert.same({ -+ [1] = {nil, 'one', n = 2 }, -+ [2] = {true, 'one', n = 2 }, -+ [3] = {false, 'one', n = 2 }, -+ [4] = {nil, 'two', n = 2 }, -+ [5] = {true, 'two', n = 2 }, -+ [6] = {false, 'two', n = 2 }, -+ [7] = {nil, nil, n = 2 }, -+ [8] = {true, nil, n = 2 }, -+ [9] = {false, nil, n = 2 }, -+ }, permute.list_table(bools, strs)) -+ end) -+ -+ -+ it("returns nil on empty list", function() -+ assert.same({}, permute.list_table({})) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/pretty_spec.lua b/extra/penlight/spec/pretty_spec.lua -new file mode 100644 -index 0000000..85e3770 ---- /dev/null -+++ b/extra/penlight/spec/pretty_spec.lua -@@ -0,0 +1,40 @@ -+local pretty = require("pl.pretty") -+ -+describe("pl.pretty.number", function () -+ -+ it("should format memory", function () -+ local function assert_memory (expected, input) -+ assert.is.equal(expected, pretty.number(input, "M")) -+ end -+ assert_memory("123B", 123) -+ assert_memory("1.2KiB", 1234) -+ assert_memory("10.0KiB", 10*1024) -+ assert_memory("1.0MiB", 1024*1024) -+ assert_memory("1.0GiB", 1024*1024*1024) -+ end) -+ -+ it("should format postfixes", function () -+ local function assert_postfix(expected, input) -+ assert.is.equal(expected, pretty.number(input, "N", 2)) -+ end -+ assert_postfix("123", 123) -+ assert_postfix("1.23K", 1234) -+ assert_postfix("10.24K", 10*1024) -+ assert_postfix("1.05M", 1024*1024) -+ assert_postfix("1.07B", 1024*1024*1024) -+ end) -+ -+ it("should format postfixes", function () -+ local function assert_separator(expected, input) -+ assert.is.equal(expected, pretty.number(input, "T")) -+ end -+ assert_separator('123', 123) -+ assert_separator('1,234', 1234) -+ assert_separator('12,345', 12345) -+ assert_separator('123,456', 123456) -+ assert_separator('1,234,567', 1234567) -+ assert_separator('12,345,678', 12345678) -+ end) -+ -+ -+end) -diff --git a/extra/penlight/spec/set_spec.lua b/extra/penlight/spec/set_spec.lua -new file mode 100644 -index 0000000..02febca ---- /dev/null -+++ b/extra/penlight/spec/set_spec.lua -@@ -0,0 +1,84 @@ -+local Set = require("pl.Set") -+ -+describe("pl.Set", function () -+ -+ local s = Set() -+ local s1_2 = Set({ 1, 2 }) -+ local s1_2_3 = Set({ 1, 2, 3 }) -+ local s1_3 = Set({ 1, 3 }) -+ local s2 = Set({ 2 }) -+ local s2_1 = Set({ 2, 1 }) -+ local s2_3 = Set({ 2, 3 }) -+ local s3 = Set({ 3 }) -+ local sm = Set({ "foo", "bar" }) -+ -+ it("should produce a set object", function () -+ assert.is.same({ true, true }, s1_2) -+ end) -+ -+ it("should produce identical sets for any ordered input", function () -+ assert.is.same(s1_2, s2_1) -+ end) -+ -+ describe("should have an operator for", function () -+ -+ it("union", function () -+ assert.is.same(s1_2_3, s1_2 + s3) -+ assert.is.same(s1_2_3, s1_2 + 3) -+ end) -+ -+ it("intersection", function () -+ assert.is.same(s2, s1_2 * s2_3) -+ end) -+ -+ it("difference", function () -+ assert.is.same(s2_1, s1_2_3 - s3) -+ assert.is.same(s2_3, s1_2_3 - 1) -+ end) -+ -+ it("symmetric difference", function () -+ assert.is.same(s1_3, s1_2 ^ s2_3) -+ end) -+ -+ it("tostring", function () -+ -- Cannot test multi-entry sets because of non-deterministic key order -+ assert.is.same('[2]', tostring(s2)) -+ end) -+ -+ end) -+ -+ describe("should provide functions", function () -+ -+ it("isempty", function () -+ assert.is.truthy(Set.isempty(s)) -+ assert.is.falsy(Set.isempty(s3)) -+ end) -+ -+ it("set", function () -+ local m = Set() -+ Set.set(m, 'foo', true) -+ m.bar = true -+ assert.is.same(m, sm) -+ assert.is_not.same(m, s1_2) -+ end) -+ -+ end) -+ -+ describe("should have a comparison operator for", function () -+ -+ it("supersets/subsets than", function () -+ assert.is.truthy(s1_2 > s2) -+ assert.is.falsy(s1_3 > s2) -+ assert.is.falsy(s1_2 > s2_3) -+ assert.is.truthy(s1_2 < s1_2_3) -+ assert.is.falsy(s1_2_3 < s1_2) -+ end) -+ -+ it("equality", function () -+ assert.is.truthy(s1_2 == s2_1) -+ assert.is.falsy(s1_2 == s2_3) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/stringx_spec.lua b/extra/penlight/spec/stringx_spec.lua -new file mode 100644 -index 0000000..1738bde ---- /dev/null -+++ b/extra/penlight/spec/stringx_spec.lua -@@ -0,0 +1,740 @@ -+describe("stringx", function() -+ -+ local stringx = require "pl.stringx" -+ -+ -+ it("isalpha()", function() -+ assert.equal(false, stringx.isalpha '') -+ assert.equal(false, stringx.isalpha' ') -+ assert.equal(false, stringx.isalpha'0') -+ assert.equal(false, stringx.isalpha'\0') -+ assert.equal(true, stringx.isalpha'azAZ') -+ assert.equal(false, stringx.isalpha'az9AZ') -+ end) -+ -+ -+ it("isdigit()", function() -+ assert.equal(false, stringx.isdigit'') -+ assert.equal(false, stringx.isdigit' ') -+ assert.equal(false, stringx.isdigit'a') -+ assert.equal(true, stringx.isdigit'0123456789') -+ end) -+ -+ -+ it("isalnum()", function() -+ assert.equal(false, stringx.isalnum'') -+ assert.equal(false, stringx.isalnum' ') -+ assert.equal(true, stringx.isalnum'azAZ01234567890') -+ end) -+ -+ -+ it("isspace()", function() -+ assert.equal(false, stringx.isspace'') -+ assert.equal(true, stringx.isspace' ') -+ assert.equal(true, stringx.isspace' \r\n\f\t') -+ assert.equal(false, stringx.isspace' \r\n-\f\t') -+ end) -+ -+ -+ it("islower()", function() -+ assert.equal(false, stringx.islower'') -+ assert.equal(true, stringx.islower'az') -+ assert.equal(false, stringx.islower'aMz') -+ assert.equal(true, stringx.islower'a z') -+ end) -+ -+ -+ it("isupper()", function() -+ assert.equal(false, stringx.isupper'') -+ assert.equal(true, stringx.isupper'AZ') -+ assert.equal(false, stringx.isupper'AmZ') -+ assert.equal(true, stringx.isupper'A Z') -+ end) -+ -+ -+ it("startswith()", function() -+ local startswith = stringx.startswith -+ assert.equal(true, startswith('', '')) -+ assert.equal(false, startswith('', 'a')) -+ assert.equal(true, startswith('a', '')) -+ assert.equal(true, startswith('a', 'a')) -+ assert.equal(false, startswith('a', 'b')) -+ assert.equal(false, startswith('a', 'ab')) -+ assert.equal(true, startswith('abc', 'ab')) -+ assert.equal(false, startswith('abc', 'bc')) -- off by one -+ assert.equal(false, startswith('abc', '.')) -- Lua pattern char -+ assert.equal(true, startswith('a\0bc', 'a\0b')) -- '\0' -+ -+ assert.equal(true, startswith('abcfoo',{'abc','def'})) -+ assert.equal(true, startswith('deffoo',{'abc','def'})) -+ assert.equal(false, startswith('cdefoo',{'abc','def'})) -+ end) -+ -+ -+ it("endswith()", function() -+ local endswith = stringx.endswith -+ assert.equal(true, endswith("", "")) -+ assert.equal(false, endswith("", "a")) -+ assert.equal(true, endswith("a", "")) -+ assert.equal(true, endswith("a", "a")) -+ assert.equal(false, endswith("a", "A")) -- case sensitive -+ assert.equal(false, endswith("a", "aa")) -+ assert.equal(true, endswith("abc", "")) -+ assert.equal(false, endswith("abc", "ab")) -- off by one -+ assert.equal(true, endswith("abc", "c")) -+ assert.equal(true, endswith("abc", "bc")) -+ assert.equal(true, endswith("abc", "abc")) -+ assert.equal(false, endswith("abc", " abc")) -+ assert.equal(false, endswith("abc", "a")) -+ assert.equal(false, endswith("abc", ".")) -- Lua pattern char -+ assert.equal(true, endswith("ab\0c", "b\0c")) -- \0 -+ assert.equal(false, endswith("ab\0c", "b\0d")) -- \0 -+ -+ assert.equal(true, endswith('dollar.dot',{'.dot','.txt'})) -+ assert.equal(true, endswith('dollar.txt',{'.dot','.txt'})) -+ assert.equal(false, endswith('dollar.rtxt',{'.dot','.txt'})) -+ end) -+ -+ -+ it("join()", function() -+ assert.equal('1 2 3', stringx.join(' ', {1,2,3})) -+ end) -+ -+ -+ it("splitlines", function() -+ assert.same({}, stringx.splitlines('')) -+ assert.same({'a'}, stringx.splitlines('a')) -+ assert.same({''}, stringx.splitlines('\n')) -+ assert.same({'', ''}, stringx.splitlines('\n\n')) -+ assert.same({'', ''}, stringx.splitlines('\r\r')) -+ assert.same({''}, stringx.splitlines('\r\n')) -+ assert.same({'ab', 'cd'}, stringx.splitlines('ab\ncd\n')) -+ assert.same({'ab\n', 'cd\n'}, stringx.splitlines('ab\ncd\n', true)) -+ assert.same({'\n', 'ab\r', '\r\n', 'cd\n'}, stringx.splitlines('\nab\r\r\ncd\n', true)) -+ end) -+ -+ -+ it("split()", function() -+ local split = stringx.split -+ assert.same({''}, split('', '')) -+ assert.same({}, split('', 'z')) --FIX:intended and specified behavior? -+ assert.same({'a'}, split('a', '')) --FIX:intended and specified behavior? -+ assert.same({''}, split('a', 'a')) -+ -- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. -+ -- If you need to split on a pattern, use utils.split() -+ -- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars -+ -- note that leading space is ignored by the default -+ assert.same({'1','2','3'}, split(' 1 2 3 ')) -+ assert.same({'a','bb','c','ddd'}, split('a*bb*c*ddd','*')) -+ assert.same({'dog','fred','bonzo:alice'}, split('dog:fred:bonzo:alice',':',3)) -+ assert.same({'dog','fred','bonzo:alice:'}, split('dog:fred:bonzo:alice:',':',3)) -+ assert.same({'','','',''}, split('///','/')) -+ end) -+ -+ -+ it("expandtabs()", function() -+ assert.equal('', stringx.expandtabs('',0)) -+ assert.equal('', stringx.expandtabs('',1)) -+ assert.equal(' ', stringx.expandtabs(' ',1)) -+ assert.equal((' '):rep(1+8), stringx.expandtabs(' \t ')) -+ assert.equal((' '):rep(3), stringx.expandtabs(' \t ',2)) -+ assert.equal((' '):rep(2), stringx.expandtabs(' \t ',0)) -+ assert.equal(' hi there folks!', stringx.expandtabs('\thi\tthere\tfolks!')) -+ end) -+ -+ -+ it("lfind()", function() -+ assert.equal(1, stringx.lfind('', '')) -+ assert.equal(1, stringx.lfind('a', '')) -+ assert.equal(2, stringx.lfind('ab', 'b')) -+ assert.is_nil(stringx.lfind('abc', 'cd')) -+ assert.equal(2, stringx.lfind('abcbc', 'bc')) -+ assert.equal(3, stringx.lfind('ab..cd', '.')) -- pattern char -+ assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3)) -+ assert.is_nil(stringx.lfind('abcbcbbc', 'bc', 3, 4)) -+ assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3, 5)) -+ assert.equal(2, stringx.lfind('abcbcbbc', 'bc', nil, 5)) -+ end) -+ -+ -+ it("rfind()", function() -+ assert.equal(1, stringx.rfind('', '')) -+ assert.equal(3, stringx.rfind('ab', '')) -+ assert.is_nil(stringx.rfind('abc', 'cd')) -+ assert.equal(4, stringx.rfind('abcbc', 'bc')) -+ assert.equal(4, stringx.rfind('abcbcb', 'bc')) -+ assert.equal(4, stringx.rfind('ab..cd', '.')) -- pattern char -+ assert.equal(7, stringx.rfind('abcbcbbc', 'bc', 3)) -+ assert.is_nil(stringx.rfind('abcbcbbc', 'bc', 3, 4)) -+ assert.equal(4, stringx.rfind('abcbcbbc', 'bc', 3, 5)) -+ assert.equal(4, stringx.rfind('abcbcbbc', 'bc', nil, 5)) -+ assert.equal(4, stringx.rfind('banana', 'ana')) -+ end) -+ -+ -+ it("replace()", function() -+ assert.equal('', stringx.replace('', '', '')) -+ assert.equal(' ', stringx.replace(' ', '', '')) -+ assert.equal(' ', stringx.replace(' ', '', ' ')) -+ assert.equal('', stringx.replace(' ', ' ', '')) -+ assert.equal('aBCaBCaBC', stringx.replace('abcabcabc', 'bc', 'BC')) -+ assert.equal('aBCabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 1)) -+ assert.equal('abcabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 0)) -+ assert.equal('abc', stringx.replace('abc', 'd', 'e')) -+ assert.equal('a%db', stringx.replace('a.b', '.', '%d')) -+ end) -+ -+ -+ it("count()", function() -+ assert.equal(0, stringx.count('', '')) --infinite loop]] -+ assert.equal(2, stringx.count(' ', '')) --infinite loop]] -+ assert.equal(2, stringx.count('a..c', '.')) -- pattern chars -+ assert.equal(0, stringx.count('a1c', '%d')) -- pattern chars -+ assert.equal(3, stringx.count('Anna Anna Anna', 'Anna')) -- no overlap -+ assert.equal(1, stringx.count('banana', 'ana', false)) -- no overlap -+ assert.equal(2, stringx.count('banana', 'ana', true)) -- overlap -+ end) -+ -+ -+ it("ljust()", function() -+ assert.equal('', stringx.ljust('', 0)) -+ assert.equal(' ', stringx.ljust('', 2)) -+ assert.equal('ab ', stringx.ljust('ab', 3)) -+ assert.equal('ab%', stringx.ljust('ab', 3, '%')) -+ assert.equal('abcd', stringx.ljust('abcd', 3)) -- agrees with Python -+ end) -+ -+ -+ it("rjust()", function() -+ assert.equal('', stringx.rjust('', 0)) -+ assert.equal(' ', stringx.rjust('', 2)) -+ assert.equal(' ab', stringx.rjust('ab', 3)) -+ assert.equal('%ab', stringx.rjust('ab', 3, '%')) -+ assert.equal('abcd', stringx.rjust('abcd', 3)) -- agrees with Python -+ end) -+ -+ -+ it("center()", function() -+ assert.equal('', stringx.center('', 0)) -+ assert.equal(' ', stringx.center('', 1)) -+ assert.equal(' ', stringx.center('', 2)) -+ assert.equal('a', stringx.center('a', 1)) -+ assert.equal('a ', stringx.center('a', 2)) -+ assert.equal(' a ', stringx.center('a', 3)) -+ end) -+ -+ -+ it("lstrip()", function() -+ local trim = stringx.lstrip -+ assert.equal('', trim'') -+ assert.equal('', trim' ') -+ assert.equal('', trim' ') -+ assert.equal('a', trim'a') -+ assert.equal('a', trim' a') -+ assert.equal('a ', trim'a ') -+ assert.equal('a ', trim' a ') -+ assert.equal('a ', trim' a ') -+ assert.equal('ab cd ', trim' ab cd ') -+ assert.equal('a\000b \r\t\n\f\v', trim' \t\r\n\f\va\000b \r\t\n\f\v') -+ assert.equal('hello] -- - ', trim(' - -- [hello] -- - ','-[] ')) -+ end) -+ -+ -+ it("rstrip()", function() -+ local trim = stringx.rstrip -+ assert.equal('', trim'') -+ assert.equal('', trim' ') -+ assert.equal('', trim' ') -+ assert.equal('a', trim'a') -+ assert.equal(' a', trim' a') -+ assert.equal('a', trim'a ') -+ assert.equal(' a', trim' a ') -+ assert.equal(' a', trim' a ') -+ assert.equal(' ab cd', trim' ab cd ') -+ assert.equal(' \t\r\n\f\va\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') -+ assert.equal(' - -- [hello', trim(' - -- [hello] -- - ','-[] ')) -+ end) -+ -+ -+ it("strip()", function() -+ local trim = stringx.strip -+ assert.equal('', trim'') -+ assert.equal('', trim' ') -+ assert.equal('', trim' ') -+ assert.equal('a', trim'a') -+ assert.equal('a', trim' a') -+ assert.equal('a', trim'a ') -+ assert.equal('a', trim' a ') -+ assert.equal('a', trim' a ') -+ assert.equal('ab cd', trim' ab cd ') -+ assert.equal('a\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') -+ assert.equal('hello', trim(' - -- [hello] -- - ','-[] ')) -+ local long = 'a' .. string.rep(' ', 200000) .. 'a' -+ assert.equal(long, trim(long)) -+ end) -+ -+ it("splitv()", function() -+ -- is actually 'utils.splitv' -+ assert.same({"hello", "dolly"}, {stringx.splitv("hello dolly")}) -+ end) -+ -+ -+ it("partition()", function() -+ assert.has.error(function() -+ stringx.partition('a', '') -+ end) -+ assert.same({'', 'a', ''}, {stringx.partition('a', 'a')}) -+ assert.same({'a', 'b', 'c'}, {stringx.partition('abc', 'b')}) -+ assert.same({'abc','',''}, {stringx.partition('abc', '.+')}) -+ assert.same({'ab','.','c'}, {stringx.partition('ab.c', '.')}) -+ assert.same({'a',',','b,c'}, {stringx.partition('a,b,c', ',')}) -+ assert.same({'abc', '', ''}, {stringx.partition('abc', '/')}) -+ end) -+ -+ -+ it("rpartition()", function() -+ assert.has.error(function() -+ stringx.rpartition('a', '') -+ end) -+ assert.same({'a/b', '/', 'c'}, {stringx.rpartition('a/b/c', '/')}) -+ assert.same({'a', 'b', 'c'}, {stringx.rpartition('abc', 'b')}) -+ assert.same({'', 'a', ''}, {stringx.rpartition('a', 'a')}) -+ assert.same({'', '', 'abc'}, {stringx.rpartition('abc', '/')}) -+ end) -+ -+ -+ it("at()", function() -+ -- at (works like s:sub(idx,idx), so negative indices allowed -+ assert.equal('a', stringx.at('a', 1)) -+ assert.equal('b', stringx.at('ab', 2)) -+ assert.equal('d', stringx.at('abcd', -1)) -+ assert.equal('', stringx.at('abcd', 10)) -- not found -+ end) -+ -+ -+ -+ describe("indent()", function() -+ -+ it("adds an indent", function() -+ local t = "a whole lot\nof love" -+ -+ assert.equal([[ -+ a whole lot -+ of love -+]], stringx.indent(t, 4)) -+ -+ assert.equal([[ -+**easy -+** -+**enough! -+]], stringx.indent("easy\n\nenough!", 2 ,'*')) -+ end) -+ -+ it("appends a newline if not present", function() -+ assert.equal(" hello\n world\n", stringx.indent("hello\nworld", 2)) -+ assert.equal(" hello\n world\n", stringx.indent("hello\nworld\n", 2)) -+ end) -+ -+ end) -+ -+ -+ -+ describe("dedent()", function() -+ -+ it("removes prefixed whitespace", function() -+ assert.equal([[ -+one -+two -+three -+]], stringx.dedent [[ -+ one -+ two -+ three -+]]) -+ end) -+ -+ it("removes prefixed whitespace, retains structure", function() -+ assert.equal([[ -+ one -+ -+ two -+ -+three -+]], stringx.dedent [[ -+ one -+ -+ two -+ -+ three -+]]) -+ end) -+ -+ it("appends a newline if not present", function() -+ assert.equal("hello\nworld\n", stringx.dedent(" hello\n world")) -+ assert.equal("hello\nworld\n", stringx.dedent(" hello\n world\n")) -+ end) -+ -+ end) -+ -+ -+ -+ -+ describe("fill()/wrap()", function() -+ -+ it("wraps width over limit", function() -+ assert.same({ -+ "abc", -+ "def" -+ }, stringx.wrap("abc def", 2)) -+ end) -+ -+ it("wraps width at limit", function() -+ assert.same({ -+ "abc", -+ "def" -+ }, stringx.wrap("abc def", 3)) -+ assert.same({ -+ "a c", -+ "d f" -+ }, stringx.wrap("a c d f", 3)) -+ end) -+ -+ it("wraps single letters", function() -+ assert.same({"a"}, stringx.wrap("a")) -+ end) -+ -+ it("wraps empty strings", function() -+ assert.same({""}, stringx.wrap("")) -+ assert.same({""}, stringx.wrap(" ")) -+ end) -+ -+ it("handles leading/trailing whitespace", function() -+ assert.same({"hello"}, stringx.wrap(" hello ", 10)) -+ assert.same({"hello"}, stringx.wrap(" hello ", 2)) -+ assert.same({"he", "ll", "o"}, stringx.wrap(" hello ", 2, true)) -+ end) -+ -+ it("handles line-breaks", function() -+ assert.same({"Hello", "Dolly"}, stringx.wrap("Hello\nDolly", 10)) -+ assert.same({"Hello Dolly"}, stringx.wrap("Hello\nDolly", 20)) -+ end) -+ -+ it("doesn't split on accented characters", function() -+ assert.same({"àbcdéfghîj"}, stringx.wrap("àbcdéfghîj")) -+ end) -+ -+ it("word-wraps a text", function() -+ -- local binstring = require("luassert.formatters.binarystring") -+ -- assert:add_formatter(binstring) -+ assert.equal([[ -+It is often said of -+Lua that it does not -+include batteries. -+That is because the -+goal of Lua is to -+produce a lean -+expressive language -+that will be used on -+all sorts of -+machines, (some of -+which don't even -+have hierarchical -+filesystems). The -+Lua language is the -+equivalent of an -+operating system -+kernel; the creators -+of Lua do not see it -+as their -+responsibility to -+create a full -+software ecosystem -+around the language. -+That is the role of -+the community. -+]], stringx.fill("It is often said of Lua that it does not include batteries. That is because the goal of Lua is to produce a lean expressive language that will be used on all sorts of machines, (some of which don't even have hierarchical filesystems). The Lua language is the equivalent of an operating system kernel; the creators of Lua do not see it as their responsibility to create a full software ecosystem around the language. That is the role of the community.", 20)) -+ end) -+ -+ -+ it("generic wrap test", function() -+ local t = [[ -+hello "world" 'this' -is- a bb ccc dddd test... but wouldn't it pass??? final. word-that-can-be-broken -+]] -+ -+ assert.same({ -+ "hello", -+ '"world"', -+ "'this'", -+ "-is-", -+ "a", -+ "bb", -+ "ccc", -+ "dddd", -+ "test...", -+ "but", -+ "wouldn't", -+ "it", -+ "pass???", -+ "final.", -+ "word-that-can-be-broken", -+ }, stringx.wrap(t, 3)) -+ end) -+ -+ it("generic wrap test, with overflow breaking", function() -+ local t = [[ -+hello "world" 'this' -is- a bb ccc dddd test... but wouldn't it pass??? final. word-that-can-be-broken -+]] -+ -+ assert.same({ -+ "hel", -+ "lo", -+ '"wo', -+ 'rld', -+ '"', -+ "'th", -+ "is'", -+ "-is", -+ "- a", -+ "bb", -+ "ccc", -+ "ddd", -+ "d", -+ "tes", -+ "t..", -+ ".", -+ "but", -+ "wou", -+ "ldn", -+ "'t", -+ "it", -+ "pas", -+ "s??", -+ "?", -+ "fin", -+ "al.", -+ "wor", -+ "d-t", -+ "hat", -+ "-ca", -+ "n-b", -+ "e-b", -+ "rok", -+ "en", -+ }, stringx.wrap(t, 3, true)) -+ end) -+ -+ end) -+ -+ -+ -+ describe("Template", function() -+ -+ local Template = stringx.Template -+ -+ -+ it("substitute() replaces placeholders", function() -+ local t1 = Template [[ -+while true do -+ $contents -+end -+]] -+ -+ assert.equal([[ -+while true do -+ print "hello" -+end -+]], t1:substitute {contents = 'print "hello"'}) -+ end) -+ -+ -+ it("substitute() replaces multiple placeholders", function () -+ local template = Template("${here} is the $answer") -+ local out = template:substitute({ here = 'one', answer = 'two' }) -+ assert.is.equal('one is the two', out) -+ end) -+ -+ -+ it("indent_substitute() indents replaced multi-lines", function() -+ local t1 = Template [[ -+while true do -+ $contents -+end -+]] -+ -+ assert.equal( -+"while true do\n".. -+" for i = 1,10 do\n".. -+" gotcha(i)\n".. -+" end\n".. -+"\n".. -+"end\n" -+, t1:indent_substitute {contents = [[ -+for i = 1,10 do -+ gotcha(i) -+end -+]]}) -+ end) -+ -+ end) -+ -+ -+ -+ it("lines()", function() -+ local function merge(it, ...) -+ assert(select('#', ...) == 0) -+ local ts = {} -+ for val in it do ts[#ts+1] = val end -+ return ts -+ end -+ assert.same({''}, merge(stringx.lines(''))) -+ assert.same({'ab'}, merge(stringx.lines('ab'))) -+ assert.same({'ab', 'cd'}, merge(stringx.lines('ab\ncd'))) -+ end) -+ -+ -+ it("title()", function() -+ assert.equal('', stringx.title('')) -+ assert.equal('Abc Def1', stringx.title('abC deF1')) -- Python behaviour -+ assert.equal('Hello World', stringx.capitalize('hello world')) -+ end) -+ -+ -+ it("capitalize()", function() -+ -- old name for 'title' -+ assert.equal(stringx.title, stringx.capitalize) -+ end) -+ -+ -+ it("shorten()", function() -+ assert.equal('', stringx.shorten('', 0)) -+ assert.equal('a', stringx.shorten('a', 1)) -+ assert.equal('.', stringx.shorten('ab', 1)) --FIX:ok? -+ assert.equal('abc', stringx.shorten('abc', 3)) -+ assert.equal('...', stringx.shorten('abcd', 3)) -+ assert.equal('abcde', stringx.shorten('abcde', 5)) -+ assert.equal('a...', stringx.shorten('abcde', 4)) -+ assert.equal('...', stringx.shorten('abcde', 3)) -+ assert.equal('..', stringx.shorten('abcde', 2)) -+ assert.equal('', stringx.shorten('abcde', 0)) -+ assert.equal('', stringx.shorten('', 0, true)) -+ assert.equal('a', stringx.shorten('a', 1, true)) -+ assert.equal('.', stringx.shorten('ab', 1, true)) -+ assert.equal('abcde', stringx.shorten('abcde', 5, true)) -+ assert.equal('...e', stringx.shorten('abcde', 4, true)) -+ assert.equal('...', stringx.shorten('abcde', 3, true)) -+ assert.equal('..', stringx.shorten('abcde', 2, true)) -+ assert.equal('', stringx.shorten('abcde', 0, true)) -+ end) -+ -+ -+ it("quote_string()", function() -+ local assert_str_round_trip = function(s) -+ -+ local qs = stringx.quote_string(s) -+ local compiled, err = require("pl.utils").load("return "..qs) -+ -+ if not compiled then -+ print( -+ ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): -+ format(s, qs, err) -+ ) -+ error() -+ else -+ compiled = compiled() -+ end -+ -+ if compiled ~= s then -+ print("stringx.quote_string assert Failed: String compiled but did not round trip.") -+ print("input string:\t\t",s, #s) -+ print("compiled string:\t", compiled, #compiled) -+ print("output string:\t\t",qs, #qs) -+ error() -+ -- else -+ -- print("input string:\t\t",s) -+ -- print("compiled string:\t", compiled) -+ -- print("output string:\t\t",qs) -+ end -+ end -+ -+ assert_str_round_trip( "normal string with nothing weird.") -+ assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") -+ -+ assert_str_round_trip( "Unescapped quote \" in the middle") -+ assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") -+ assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) -+ assert_str_round_trip( "[[Completely normal\n long quote. ]]") -+ assert_str_round_trip( "String with a newline\nending with a closing bracket]") -+ assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") -+ assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") -+ assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') -+ assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") -+ assert_str_round_trip( "This\tincludes\ttabs.") -+ assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") -+ assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") -+ -+ assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") -+ assert_str_round_trip('"A quoted string looks like what?"') -+ assert_str_round_trip( "'I think that it should be quoted, anyway.'") -+ assert_str_round_trip( "[[Even if they're long quoted.]]") -+ assert_str_round_trip( "]=]==]") -+ -+ assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") -+ assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") -+ assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") -+ assert_str_round_trip( "") -+ assert_str_round_trip( " ") -+ assert_str_round_trip( "\n") --tricky. -+ assert_str_round_trip( "\r") -+ assert_str_round_trip( "\r\n") -+ assert_str_round_trip( "\r1\n") -+ assert_str_round_trip( "[[") -+ assert_str_round_trip( "''") -+ assert_str_round_trip( '""') -+ end) -+ -+ -+ -+ describe("format_operator()", function() -+ -+ setup(function() -+ stringx.format_operator() -+ end) -+ -+ -+ it("handles plain substitutions", function() -+ assert.equal('[home]', '[%s]' % 'home') -+ assert.equal('fred = 42', '%s = %d' % {'fred',42}) -+ end) -+ -+ -+ it("invokes tostring on %s formats", function() -+ -- mostly works like string.format, except that %s forces use of tostring() -+ -- rather than throwing an error -+ local List = require 'pl.List' -+ assert.equal('TBL:{1,2,3}', 'TBL:%s' % List{1,2,3}) -+ end) -+ -+ -+ it("replaces '$field' references", function() -+ -- table with keys and format with $ -+ assert.equal('<1>', '<$one>' % {one=1}) -+ end) -+ -+ -+ it("accepts replacement functions", function() -+ local function subst(k) -+ if k == 'A' then -+ return 'ay' -+ elseif k == 'B' then -+ return 'bee' -+ else -+ return '?' -+ end -+ end -+ assert.equal('ay & bee', '$A & $B' % subst) -+ end) -+ -+ end) -+ -+end) -+ -diff --git a/extra/penlight/spec/text_spec.lua b/extra/penlight/spec/text_spec.lua -new file mode 100644 -index 0000000..2254aa0 ---- /dev/null -+++ b/extra/penlight/spec/text_spec.lua -@@ -0,0 +1,10 @@ -+describe("pl.text", function() -+ -+ it("forwarded to stringx", function() -+ assert.equal( -+ require "pl.stringx", -+ require "pl.text" -+ ) -+ end) -+ -+end) -diff --git a/extra/penlight/spec/utils-choose_spec.lua b/extra/penlight/spec/utils-choose_spec.lua -new file mode 100644 -index 0000000..ee4b674 ---- /dev/null -+++ b/extra/penlight/spec/utils-choose_spec.lua -@@ -0,0 +1,21 @@ -+local utils = require("pl.utils") -+ -+describe("pl.utils", function() -+ -+ describe("choose", function () -+ -+ it("handles normal values", function() -+ assert.equal(utils.choose(true, 1, 2), 1) -+ assert.equal(utils.choose(false, 1, 2), 2) -+ end) -+ -+ it("handles nils", function() -+ assert.equal(utils.choose(true, nil, 2), nil) -+ assert.equal(utils.choose(false, nil, 2), 2) -+ assert.equal(utils.choose(true, 1, nil), 1) -+ assert.equal(utils.choose(false, 1, nil), nil) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/utils-deprecate_spec.lua b/extra/penlight/spec/utils-deprecate_spec.lua -new file mode 100644 -index 0000000..1a6e352 ---- /dev/null -+++ b/extra/penlight/spec/utils-deprecate_spec.lua -@@ -0,0 +1,128 @@ -+local utils = require("pl.utils") -+ -+describe("pl.utils", function () -+ -+ local old_fn, last_msg, last_trace -+ -+ before_each(function() -+ old_fn = function() end -+ last_msg = nil -+ last_trace = nil -+ utils.set_deprecation_func(function(msg, trace) -+ last_msg = msg -+ last_trace = trace -+ end) -+ end) -+ -+ -+ after_each(function() -+ utils.deprecation_warning = old_fn -+ end) -+ -+ -+ -+ describe("set_deprecation_func", function () -+ -+ it("accepts nil as callback", function() -+ assert.has.no.error(function() -+ utils.set_deprecation_func() -+ end) -+ end) -+ -+ -+ it("accepts function as callback", function() -+ assert.has.no.error(function() -+ utils.set_deprecation_func(function() end) -+ end) -+ end) -+ -+ -+ it("fails on non-functions", function() -+ assert.has.error(function() -+ utils.set_deprecation_func("not a function") -+ end, "argument 1 expected a 'function', got a 'string'") -+ end) -+ -+ end) -+ -+ -+ -+ describe("raise_deprecation", function () -+ -+ it("requires the opts table", function() -+ assert.has.error(function() utils.raise_deprecation(nil) end, -+ "argument 1 expected a 'table', got a 'nil'") -+ end) -+ -+ -+ it("requires the opts.message field", function() -+ assert.has.error(function() utils.raise_deprecation({}) end, -+ "field 'message' of the options table must be a string") -+ end) -+ -+ -+ it("should output the message", function () -+ utils.raise_deprecation { -+ message = "hello world" -+ } -+ assert.equal("hello world", last_msg) -+ end) -+ -+ -+ it("should output the deprecated version", function () -+ utils.raise_deprecation { -+ message = "hello world", -+ deprecated_after = "2.0.0", -+ } -+ assert.equal("hello world (deprecated after 2.0.0)", last_msg) -+ end) -+ -+ -+ it("should output the removal version", function () -+ utils.raise_deprecation { -+ message = "hello world", -+ version_removed = "3.0.0", -+ } -+ assert.equal("hello world (scheduled for removal in 3.0.0)", last_msg) -+ end) -+ -+ -+ it("should output the deprecated and removal versions", function () -+ utils.raise_deprecation { -+ message = "hello world", -+ deprecated_after = "2.0.0", -+ version_removed = "3.0.0", -+ } -+ assert.equal("hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) -+ end) -+ -+ -+ it("should output the application/module name", function () -+ utils.raise_deprecation { -+ source = "MyApp 1.2.3", -+ message = "hello world", -+ deprecated_after = "2.0.0", -+ version_removed = "3.0.0", -+ } -+ assert.equal("[MyApp 1.2.3] hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) -+ end) -+ -+ -+ it("should add a stracktrace", function () -+ local function my_function_name() -+ utils.raise_deprecation { -+ source = "MyApp 1.2.3", -+ message = "hello world", -+ deprecated_after = "2.0.0", -+ version_removed = "3.0.0", -+ } -+ end -+ my_function_name() -+ -+ assert.Not.match("raise_deprecation", last_trace) -+ assert.match("my_function_name", last_trace) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/utils-enum_spec.lua b/extra/penlight/spec/utils-enum_spec.lua -new file mode 100644 -index 0000000..dbe6954 ---- /dev/null -+++ b/extra/penlight/spec/utils-enum_spec.lua -@@ -0,0 +1,174 @@ -+describe("pl.utils", function () -+ -+ describe("enum()", function () -+ local enum, t -+ -+ before_each(function() -+ enum = require("pl.utils").enum -+ end) -+ -+ -+ describe("creating", function() -+ -+ it("accepts a vararg", function() -+ t = enum("ONE", "two", "THREE") -+ assert.same({ -+ ONE = "ONE", -+ two = "two", -+ THREE = "THREE", -+ }, t) -+ end) -+ -+ -+ it("vararg entries must be strings", function() -+ assert.has.error(function() -+ t = enum("hello", true, "world") -+ end, "argument 2 expected a 'string', got a 'boolean'") -+ -- no holes -+ assert.has.error(function() -+ t = enum("hello", nil, "world") -+ end, "argument 2 expected a 'string', got a 'nil'") -+ end) -+ -+ -+ it("vararg requires at least 1 entry", function() -+ assert.has.error(function() -+ t = enum() -+ end, "expected at least 1 entry") -+ end) -+ -+ -+ it("accepts an array", function() -+ t = enum { "ONE", "two", "THREE" } -+ assert.same({ -+ ONE = "ONE", -+ two = "two", -+ THREE = "THREE", -+ }, t) -+ end) -+ -+ -+ it("array entries must be strings", function() -+ assert.has.error(function() -+ t = enum { "ONE", 999, "THREE" } -+ end, "expected 'string' but got 'number' at index 2") -+ end) -+ -+ -+ it("array requires at least 1 entry", function() -+ assert.has.error(function() -+ t = enum {} -+ end, "expected at least 1 entry") -+ end) -+ -+ -+ it("accepts a hash-table", function() -+ t = enum { -+ FILE_NOT_FOUND = "The file was not found in the filesystem", -+ FILE_READ_ONLY = "The file is read-only", -+ } -+ assert.same({ -+ FILE_NOT_FOUND = "The file was not found in the filesystem", -+ FILE_READ_ONLY = "The file is read-only", -+ }, t) -+ end) -+ -+ -+ it("hash-table keys must be strings", function() -+ assert.has.error(function() -+ t = enum { [{}] = "ONE" } -+ end, "expected key to be 'string' but got 'table'") -+ end) -+ -+ -+ it("hash-table requires at least 1 entry", function() -+ assert.has.error(function() -+ t = enum {} -+ end, "expected at least 1 entry") -+ end) -+ -+ -+ it("accepts a combined array/hash-table", function() -+ t = enum { -+ "BAD_FD", -+ FILE_NOT_FOUND = "The file was not found in the filesystem", -+ FILE_READ_ONLY = "The file is read-only", -+ } -+ assert.same({ -+ BAD_FD = "BAD_FD", -+ FILE_NOT_FOUND = "The file was not found in the filesystem", -+ FILE_READ_ONLY = "The file is read-only", -+ }, t) -+ end) -+ -+ -+ it("keys must be unique with combined array/has-table", function() -+ assert.has.error(function() -+ t = enum { -+ "FILE_NOT_FOUND", -+ FILE_NOT_FOUND = "The file was not found in the filesystem", -+ } -+ end, "duplicate entry in array and hash part: 'FILE_NOT_FOUND'") -+ end) -+ -+ end) -+ -+ -+ -+ describe("accessing", function() -+ -+ before_each(function() -+ t = enum("ONE", "two", "THREE") -+ end) -+ -+ -+ it("errors on unknown values", function() -+ assert.has.error(function() -+ print(t.four) -+ end, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')") -+ end) -+ -+ -+ it("errors on setting new keys", function() -+ assert.has.error(function() -+ t.four = "four" -+ end, "the Enum object is read-only") -+ end) -+ -+ -+ it("keys can have 'format' placeholders", function() -+ t = enum("hello", "contains: %s") -+ assert.has.error(function() -+ print(t["%s"]) -- should still format error properly -+ end, "'%s' is not a valid value (expected one of: 'hello', 'contains: %s')") -+ end) -+ -+ end) -+ -+ -+ -+ describe("calling", function() -+ -+ before_each(function() -+ t = enum("ONE", "two", "THREE") -+ end) -+ -+ -+ it("returns error on unknown values", function() -+ local ok, err = t("four") -+ assert.equal(err, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')") -+ assert.equal(nil, ok) -+ end) -+ -+ -+ it("returns value on success", function() -+ local ok, err = t("THREE") -+ assert.equal(nil, err) -+ assert.equal("THREE", ok) -+ end) -+ -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/utils-kpairs_spec.lua b/extra/penlight/spec/utils-kpairs_spec.lua -new file mode 100644 -index 0000000..7fac079 ---- /dev/null -+++ b/extra/penlight/spec/utils-kpairs_spec.lua -@@ -0,0 +1,38 @@ -+local utils = require("pl.utils") -+ -+describe("pl.utils", function () -+ -+ describe("kpairs", function () -+ local kpairs -+ -+ before_each(function() -+ kpairs = utils.kpairs -+ end) -+ -+ -+ it("iterates over non-integers", function() -+ local func = function() end -+ local bool = true -+ local string = "a string" -+ local float = 123.45 -+ local r = {} -+ for k, v in kpairs { -+ [func] = 1, -+ [bool] = 2, -+ [string] = 3, -+ [float] = 4, -+ 5, 6, 7, -+ } do -+ r[k] = v -+ end -+ -+ assert.same({ -+ [func] = 1, -+ [bool] = 2, -+ [string] = 3, -+ [float] = 4 }, r) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/utils-npairs_spec.lua b/extra/penlight/spec/utils-npairs_spec.lua -new file mode 100644 -index 0000000..7e3c5d4 ---- /dev/null -+++ b/extra/penlight/spec/utils-npairs_spec.lua -@@ -0,0 +1,105 @@ -+local utils = require("pl.utils") -+ -+describe("pl.utils", function () -+ -+ describe("npairs", function () -+ local npairs = utils.npairs -+ -+ it("start index defaults to 1", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, nil, 2) do t2[i] = v end -+ assert.are.same({ 1, 2 }, t2) -+ end) -+ -+ -+ it("end index defaults to `t.n`", function() -+ local t1 = { n = 2, 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1) do t2[i] = v end -+ assert.are.same({1, 2}, t2) -+ end) -+ -+ -+ it("step size defaults to 1", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1) do t2[i] = v end -+ assert.are.same({1, 2, 3}, t2) -+ end) -+ -+ -+ it("step size cannot be 0", function() -+ local t1 = { 1, 2, 3 } -+ assert.has.error(function() -+ npairs(t1, nil, nil, 0) -+ end, "iterator step-size cannot be 0") -+ end) -+ -+ -+ it("end index defaults to `#t` if there is no `t.n`", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1) do t2[i] = v end -+ assert.are.same({1, 2, 3}, t2) -+ end) -+ -+ -+ it("returns nothing if start index is beyond end index", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, 5, 3) do t2[i] = v end -+ assert.are.same({}, t2) -+ end) -+ -+ -+ it("returns nothing if start index is beyond end index, with negative step size", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, 3, 1, -1) do t2[#t2+1] = v end -+ assert.are.same({ 3, 2, 1}, t2) -+ end) -+ -+ -+ it("returns 1 key/value if end == start index", function() -+ local t1 = { 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, 2, 2) do t2[i] = v end -+ assert.are.same({ [2] = 2 }, t2) -+ end) -+ -+ -+ it("returns negative to positive ranges", function() -+ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, -4, 1) do t2[i] = v end -+ assert.are.same({ [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1 }, t2) -+ end) -+ -+ -+ it("returns nil values with the range", function() -+ local t1 = { n = 3 } -+ local t2 = {} -+ for i, v in npairs(t1) do t2[i] = tostring(v) end -+ assert.are.same({ "nil", "nil", "nil" }, t2) -+ end) -+ -+ -+ it("honours positive step size", function() -+ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, -4, 1, 2) do t2[#t2+1] = v end -+ assert.are.same({ -4, -2, 0}, t2) -+ end) -+ -+ -+ it("honours negative step size", function() -+ local t1 = { [-5] = -5, [-4] = -4, [-3] = -3, [-2] = -2, [-1] = -1, [0] = 0, 1, 2, 3 } -+ local t2 = {} -+ for i, v in npairs(t1, 0, -5, -2) do t2[#t2+1] = v end -+ assert.are.same({ 0, -2, -4 }, t2) -+ end) -+ -+ end) -+ -+end) -diff --git a/extra/penlight/spec/xml_spec.lua b/extra/penlight/spec/xml_spec.lua -new file mode 100644 -index 0000000..67c9a3d ---- /dev/null -+++ b/extra/penlight/spec/xml_spec.lua -@@ -0,0 +1,960 @@ -+local xml = require "pl.xml" -+ -+describe("xml", function() -+ -+ describe("new()", function() -+ -+ it("creates a new xml-document", function() -+ local doc = xml.new("main") -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("fails without a tag", function() -+ assert.has.error(function() -+ xml.new() -+ end, "expected 'tag' to be a string value, got: nil") -+ end) -+ -+ -+ it("fails with bad attributes", function() -+ assert.has.error(function() -+ xml.new("tag", "not a table...") -+ end, "expected 'attr' to be a table value, got: string") -+ end) -+ -+ -+ it("adds attributes if given", function() -+ local doc = xml.new("main", { hello = "world" }) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("parse()", function() -+ -+ pending("todo", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+ -+ -+ describe("elem()", function() -+ -+ it("creates a node", function() -+ local doc = xml.elem("main") -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with single text element", function() -+ local doc = xml.elem("main", "oh my") -+ assert.equal("
    oh my
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with single child tag/Node", function() -+ local doc = xml.elem("main", xml.new("child")) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with multiple text elements", function() -+ local doc = xml.elem("main", { "this ", "is ", "nice" }) -+ assert.equal("
    this is nice
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with multiple child tags/Nodes", function() -+ local doc = xml.elem("main", { xml.new "this", xml.new "is", xml.new "nice" }) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with attributes", function() -+ local doc = xml.elem("main", { hello = "world" }) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with text/Node children and attributes", function() -+ local doc = xml.elem("main", { -+ "prefix", -+ xml.elem("child", { "this ", "is ", "nice"}), -+ "postfix", -+ attrib = "value" -+ }) -+ assert.equal("
    prefixthis is nicepostfix
    ", doc:tostring()) -+ end) -+ -+ -+ it("creates a node, with text/Node nested children and attributes", function() -+ local doc = xml.elem("main", { -+ "prefix", -+ xml.elem("child", { -+ "this", -+ xml.elem "is", -+ "nice", -+ }), -+ "postfix", -+ attrib = "value" -+ }) -+ assert.equal("
    prefixthisnicepostfix
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("tags()", function() -+ -+ it("creates constructors", function() -+ local parent, child = xml.tags({ "mom" , "kid" }) -+ local doc = parent {child 'Bob', child 'Annie'} -+ assert.equal("BobAnnie", doc:tostring()) -+ end) -+ -+ -+ it("creates constructors from CSV values", function() -+ local parent, child = xml.tags("mom,kid" ) -+ local doc = parent {child 'Bob', child 'Annie'} -+ assert.equal("BobAnnie", doc:tostring()) -+ end) -+ -+ -+ it("creates constructors from CSV values, ignores surrounding whitespace", function() -+ local parent, child = xml.tags(" mom , kid " ) -+ local doc = parent {child 'Bob', child 'Annie'} -+ assert.equal("BobAnnie", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("addtag()", function() -+ -+ it("adds a Node", function() -+ local doc = xml.new("main") -+ doc:addtag("penlight", { hello = "world" }) -+ assert.equal("
    ", doc:tostring()) -+ -+ -- moves position -+ doc:addtag("expat") -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("text()", function() -+ -+ it("adds text", function() -+ local doc = xml.new("main") -+ doc:text("penlight") -+ assert.equal("
    penlight
    ", doc:tostring()) -+ -+ -- moves position -+ doc:text("expat") -+ assert.equal("
    penlightexpat
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("up()", function() -+ -+ it("moves position up 1 level", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -+ doc:addtag("two-a") -+ doc:up() -+ doc:addtag("two-b") -+ assert.equal("
    ", doc:tostring()) -+ -+ -- doesn't move beyond top level -+ for i = 1, 10 do -+ doc:up() -+ end -+ doc:addtag("solong") -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("reset()", function() -+ -+ it("resets position to top Node", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -+ doc:addtag("two") -+ doc:addtag("three") -+ doc:reset() -+ doc:addtag("solong") -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("add_direct_child", function() -+ -+ it("adds a child node", function() -+ local doc = xml.new("main") -+ doc:add_direct_child(xml.new("child")) -+ assert.equal("
    ", doc:tostring()) -+ -+ doc:add_direct_child(xml.new("child")) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ -+ it("adds a text node", function() -+ local doc = xml.new("main") -+ doc:add_direct_child("child") -+ assert.equal("
    child
    ", doc:tostring()) -+ -+ doc:add_direct_child("child") -+ assert.equal("
    childchild
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("add_child()", function() -+ -+ it("adds a child at the current position", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -+ doc:add_child(xml.new("item1")) -+ doc:add_child(xml.new("item2")) -+ doc:add_child(xml.new("item3")) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("set_attribs()", function() -+ -+ it("sets attributes on the Node", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -- moves position -+ -+ -+ doc:set_attribs( { one = "a" }) -+ assert.equal("
    ", doc:tostring()) -+ -+ -- overwrites and adds -+ doc:set_attribs( { one = "1", two = "2" }) -+ assert.matches("one='1'", doc:tostring()) -+ assert.matches("two='2'", doc:tostring()) -+ -+ -- 'two' doesn't get removed -+ doc:set_attribs( { one = "a" }) -+ assert.matches("one='a'", doc:tostring()) -+ assert.matches("two='2'", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("set_attrib()", function() -+ -+ it("sets/deletes a single attribute on the Node", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -- moves position -+ -+ -+ doc:set_attrib("one", "a") -+ assert.equal("
    ", doc:tostring()) -+ -+ -- deletes -+ doc:set_attrib("one", nil) -+ assert.equal("
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("get_attribs()", function() -+ -+ it("gets attributes on the Node", function() -+ local doc = xml.new("main") -+ doc:addtag("one") -- moves position -+ -+ doc:set_attribs( { one = "1", two = "2" }) -+ assert.same({ one = "1", two = "2" }, doc:get_attribs()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("subst()", function() -+ -+ pending("todo", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+ -+ -+ describe("child_with_name()", function() -+ -+ it("returns the first child", function() -+ local doc = xml.new("main") -+ doc:add_child(xml.elem "one") -+ doc:text("hello") -+ doc:add_child(xml.elem "two") -+ doc:text("goodbye") -+ doc:add_child(xml.elem "three") -+ -+ local child = doc:child_with_name("two") -+ assert.not_nil(child) -+ assert.equal(doc[3], child) -+ end) -+ -+ end) -+ -+ -+ -+ describe("tostring()", function() -+ -+ pending("todo still...", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+ -+ -+ describe("get_elements_with_name()", function() -+ -+ it("returns matching nodes", function() -+ local doc = assert(xml.parse[[ -+ -+ John -+ -+ -+ Bob -+ -+ -+ Bob junior -+ -+ -+ -+ -+ Annie -+ -+ -+ Melissa -+ -+ -+ Noel -+ -+ -+ -+ -+ -+ ]]) -+ -+ local list = doc:get_elements_with_name("name") -+ for i, entry in ipairs(list) do -+ list[i] = entry:get_text() -+ end -+ assert.same({"John", "Bob", "Bob junior", "Annie", "Melissa", "Noel"}, list) -+ -+ -- if tag not found, returns empty table -+ local list = doc:get_elements_with_name("unknown") -+ assert.same({}, list) -+ end) -+ -+ end) -+ -+ -+ -+ describe("children()", function() -+ -+ it("iterates over all children", function() -+ local doc = xml.elem("main", { -+ "prefix", -+ xml.elem("child"), -+ "postfix", -+ attrib = "value" -+ }) -+ -+ local lst = {} -+ for node in doc:children() do -+ lst[#lst+1] = tostring(node) -+ end -+ assert.same({ "prefix", "", "postfix"}, lst) -+ end) -+ -+ -+ it("doesn't fail on empty node", function() -+ local doc = xml.elem("main") -+ local lst = {} -+ for node in doc:children() do -+ lst[#lst+1] = tostring(node) -+ end -+ assert.same({}, lst) -+ end) -+ -+ end) -+ -+ -+ -+ describe("first_childtag()", function() -+ -+ it("returns first non-text tag", function() -+ local doc = xml.elem("main", { -+ "prefix", -+ xml.elem("child"), -+ "postfix", -+ attrib = "value" -+ }) -+ -+ local node = doc:first_childtag() -+ assert.same("", tostring(node)) -+ end) -+ -+ -+ it("returns nil if there is none", function() -+ local doc = xml.elem("main", { -+ "prefix", -+ "postfix", -+ attrib = "value" -+ }) -+ -+ local node = doc:first_childtag() -+ assert.is_nil(node) -+ end) -+ -+ end) -+ -+ -+ -+ describe("matching_tags()", function() -+ -+ local _ = [[ -+ -+ -+ -+ -+ Apples -+ Bananas -+ -+ -+ -+ -+ African Coffee Table -+ 80 -+ 120 -+ -+ -+ -+ ]] -+ -+ pending("xmlns is weird...", function() -+ -- the xmlns stuff doesn't make sense -+ end) -+ -+ end) -+ -+ -+ -+ describe("childtags()", function() -+ -+ it("returns the first child", function() -+ local doc = xml.new("main") -+ doc:add_child(xml.elem "one") -+ doc:text("hello") -+ doc:add_child(xml.elem "two") -+ doc:text("goodbye") -+ doc:add_child(xml.elem "three") -+ -+ local lst = {} -+ for node in doc:childtags() do -+ lst[#lst+1] = tostring(node) -+ end -+ assert.same({"", "", ""},lst) -+ end) -+ -+ end) -+ -+ -+ -+ describe("maptags()", function() -+ -+ it("updates nodes", function() -+ local doc = xml.new("main") -+ doc:add_child(xml.elem "one") -+ doc:text("hello") -+ doc:add_child(xml.elem "two") -+ doc:text("goodbye") -+ doc:add_child(xml.elem "three") -+ -+ doc:maptags(function(node) -+ if node.tag then -+ -- return a new object so we know it got replaced -+ return xml.new(node.tag:upper()) -+ end -+ return node -+ end) -+ assert.same("
    hellogoodbye
    ", doc:tostring()) -+ end) -+ -+ -+ it("removes nodes", function() -+ local doc = xml.new("main") -+ doc:add_child(xml.elem "one") -+ doc:text("hello") -+ doc:add_child(xml.elem "two") -+ doc:text("goodbye") -+ doc:add_child(xml.elem "three") -+ -+ doc:maptags(function(node) -+ if node.tag then -+ return nil -- remove it -+ end -+ return node -+ end) -+ assert.same("
    hellogoodbye
    ", doc:tostring()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("xml_escape()", function() -+ -+ it("escapes reserved characters", function() -+ local esc = xml.xml_escape([["'<>&]]) -+ assert.same(""'<>&", esc) -+ end) -+ -+ end) -+ -+ -+ -+ describe("xml_unescape()", function() -+ -+ it("escapes reserved characters", function() -+ local unesc = xml.xml_unescape(""'<>&") -+ assert.same([["'<>&]], unesc) -+ end) -+ -+ end) -+ -+ -+ -+ describe("get_text()", function() -+ -+ it("returns all text concatenated", function() -+ local doc = xml.new("main") -+ doc:text("one") -+ doc:add_child(xml.elem "two") -+ doc:text("three") -+ -+ assert.same("onethree", doc:get_text()) -+ end) -+ -+ it("returns empty string if no text", function() -+ local doc = xml.new("main") -+ doc:add_child(xml.elem "two") -+ -+ assert.same("", doc:get_text()) -+ end) -+ -+ end) -+ -+ -+ -+ describe("clone()", function() -+ -+ it("clones a document", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local doc2 = xml.clone(doc1) -+ assert.are.same(doc1:tostring(), doc2:tostring()) -+ assert.not_equal(doc1, doc2) -+ for i, elem1 in ipairs(doc1) do -+ assert.are.same(tostring(elem1), tostring(doc2[i])) -+ if type(elem1) == "table" then -+ assert.not_equal(elem1, doc2[i]) -+ end -+ end -+ end) -+ -+ -+ it("calls substitution callback and updates", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local repl = { -+ ["*TAG"] = { -+ main = "top", -+ this = "that", -+ is = "was", -+ a = "a", -+ b = "b", -+ nice = "bad", -+ }, -+ ["*TEXT"] = { -+ ["this content"] = "that content", -+ }, -+ hello = { -+ world = "universe", -+ }, -+ } -+ local subst = function(object, kind, parent) -+ if repl[kind] then -+ if repl[kind][object] then -+ return repl[kind][object] -+ else -+ error(("object '%s' of kind '%s' not found"):format(object,kind)) -+ end -+ else -+ error(("kind '%s' not found"):format(kind)) -+ end -+ end -+ -+ local doc2 = xml.clone(doc1, subst) -+ assert.equal("that content", doc2:tostring()) -+ end) -+ -+ -+ it("clones text nodes", function() -+ assert.equal("hello", xml.clone("hello")) -+ end) -+ -+ -+ it("errors on recursion", function() -+ local doc = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ "some", -+ xml.elem "is", -+ "text", -+ xml.elem "nice", -+ }) -+ -+ doc[#doc+1] = doc -- add recursion -+ -+ assert.has.error(function() -+ xml.clone(doc) -+ end, "recursion detected") -+ end) -+ -+ end) -+ -+ -+ -+ describe("compare()", function() -+ -+ it("returns true on equal docs", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local doc2 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local ok, err = xml.compare(doc1, doc2) -+ assert.is_nil(err) -+ assert.is_true(ok) -+ end) -+ -+ -+ it("compares types", function() -+ local ok, err = xml.compare(nil, true) -+ assert.equal("type mismatch", err) -+ assert.is_false(ok) -+ -+ local ok, err = xml.compare("true", true) -+ assert.equal("type mismatch", err) -+ assert.is_false(ok) -+ -+ local ok, err = xml.compare(true, true) -+ assert.equal("not a document", err) -+ assert.is_false(ok) -+ -+ local ok, err = xml.compare("text", "text") -+ assert.is_nil(err) -+ assert.is_true(ok) -+ -+ local ok, err = xml.compare("text1", "text2") -+ assert.equal("text text1 ~= text text2", err) -+ assert.is_false(ok) -+ end) -+ -+ -+ it("compares element size (array part)", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local doc2 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ "plain text", -+ }) -+ -+ local ok, err = xml.compare(doc1, doc2) -+ assert.equal("size 3 ~= size 4 for tag main", err) -+ assert.is_false(ok) -+ end) -+ -+ -+ it("compares children", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local doc2 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "c" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local ok, err = xml.compare(doc1, doc2) -+ assert.equal("tag b ~= tag c", err) -+ assert.is_false(ok) -+ end) -+ -+ -+ it("compares attributes", function() -+ local doc1 = xml.new("main", { -+ hello = "world", -+ goodbye = "universe" -+ }) -+ -+ local ok, err = xml.compare(doc1, xml.new("main", { -+ hello = "world", -+ goodbye = "universe" -+ })) -+ assert.equal(nil, err) -+ assert.is_true(ok) -+ -+ local ok, err = xml.compare(doc1, xml.new("main", { -+ -- hello = "world", -- one less attribute -+ goodbye = "universe" -+ })) -+ assert.equal("mismatch attrib", err) -+ assert.is_false(ok) -+ -+ local ok, err = xml.compare(doc1, xml.new("main", { -+ hello = "world", -+ goodbye = "universe", -+ one = "more", -- one more attribute -+ })) -+ assert.equal("mismatch attrib", err) -+ assert.is_false(ok) -+ end) -+ -+ -+ it("compares attributes order", function() -+ local doc1 = xml.new("main", { -+ [1] = "hello", -+ [2] = "goodbye", -+ hello = "world", -+ goodbye = "universe" -+ }) -+ -+ local ok, err = xml.compare(doc1, xml.new("main", { -+ -- no order, this compares ok -+ hello = "world", -+ goodbye = "universe" -+ })) -+ assert.equal(nil, err) -+ assert.is_true(ok) -+ -+ local ok, err = xml.compare(doc1, xml.new("main", { -+ [2] = "hello", -- order reversed, this should fail -+ [1] = "goodbye", -+ hello = "world", -+ goodbye = "universe" -+ })) -+ assert.equal("mismatch attrib order", err) -+ assert.is_false(ok) -+ end) -+ -+ -+ it("handles recursion", function() -+ local doc1 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ local doc2 = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ xml.elem("is", { -+ xml.elem "a", -+ xml.elem "b" -+ }), -+ xml.elem "nice", -+ }) -+ -+ doc1[#doc1 + 1] = doc1 -- add recursion -+ doc2[#doc2 + 1] = xml.elem "main" -- add tag by same name -+ -+ local ok, err = xml.compare(doc1, doc2) -+ assert.equal("recursive document", err) -+ assert.is_false(ok) -+ end) -+ -+ end) -+ -+ -+ -+ describe("walk()", function() -+ -+ it("calls on all tags", function() -+ local doc = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ "some", -+ xml.elem "is", -+ "text", -+ xml.elem "nice", -+ }) -+ -+ assert.equal("
    this contentsometext
    ", doc:tostring()) -+ -+ local list = {} -+ xml.walk(doc, nil, function(tag_name, node) -+ list[#list+1] = assert(tag_name) -+ end) -+ assert.same({"main", "this", "is", "nice"}, list) -+ -+ -- now depth_first -+ local list = {} -+ xml.walk(doc, true, function(tag_name, node) -+ list[#list+1] = assert(tag_name) -+ end) -+ assert.same({"this", "is", "nice", "main"}, list) -+ end) -+ -+ -+ it("errors on recursion", function() -+ local doc = xml.elem("main", { -+ hello = "world", -+ xml.elem("this", "this content"), -+ "some", -+ xml.elem "is", -+ "text", -+ xml.elem "nice", -+ }) -+ -+ doc[#doc+1] = doc -- add recursion -+ -+ assert.has.error(function() -+ xml.walk(doc, nil, function() end) -+ end, "recursion detected") -+ end) -+ -+ end) -+ -+ -+ -+ describe("parsehtml()", function() -+ -+ pending("to be deprecated...", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+ -+ -+ describe("basic_parse()", function() -+ -+ pending("to be deprecated...", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+ -+ -+ describe("match()", function() -+ -+ pending("figure out what it does...", function() -+ -- TODO: implement -+ end) -+ -+ end) -+ -+end) -+ -diff --git a/extra/penlight/tests/lua/mod52.lua b/extra/penlight/tests/lua/mod52.lua -index 7cc8c7e..4b3ec7c 100644 ---- a/extra/penlight/tests/lua/mod52.lua -+++ b/extra/penlight/tests/lua/mod52.lua -@@ -10,9 +10,17 @@ local _ENV,M = require 'pl.import_into' (rawget(_G,'STRICT')) - function answer () - -- of course, you don't have the usual global environment available - -- so define it as a local up above, or use utils.import(_G). -+ -+ local versioned_errors = { -+ ["1"] = "attempt to call global 'print'", -+ ["2"] = "attempt to call global 'print'", -+ ["3"] = "attempt to call a nil value", -+ ["4"] = "a nil value", -+ } -+ local expected = versioned_errors[LUA_VERSION:match("Lua 5.(%d)")] - test.assertraise(function() - print 'hello' -- end,(LUA_VERSION~="Lua 5.3") and "attempt to call global 'print'" or "attempt to call a nil value") -+ end, expected) - - -- but all the Penlight modules are available - return pretty.write(utils.split '10 20 30', '') -diff --git a/extra/penlight/tests/test-vector.lua b/extra/penlight/tests/test-__vector.lua -similarity index 100% -rename from tests/test-vector.lua -rename to tests/test-__vector.lua -diff --git a/extra/penlight/tests/test-app.lua b/extra/penlight/tests/test-app.lua -new file mode 100644 -index 0000000..fe60537 ---- /dev/null -+++ b/extra/penlight/tests/test-app.lua -@@ -0,0 +1,307 @@ -+local app = require "pl.app" -+local utils = require "pl.utils" -+local path = require "pl.path" -+local asserteq = require 'pl.test'.asserteq -+local lfs = require("lfs") -+ -+local quote = utils.quote_arg -+ -+local _, cmd = app.lua() -+cmd = cmd .. " " .. quote({"-e", "package.path=[[./lua/?.lua;./lua/?/init.lua;]]..package.path"}) -+ -+local function run_script(s, fname) -+ local tmpname = path.tmpname() -+ if fname then -+ tmpname = path.join(path.dirname(tmpname), fname) -+ end -+ assert(utils.writefile(tmpname, s)) -+ local success, code, stdout, stderr = utils.executeex(cmd.." "..tmpname) -+ os.remove(tmpname) -+ return success, code, stdout, stderr -+end -+ -+do -- app.script_name -+ -+ local success, code, stdout, stderr = run_script([[ -+ print(require("pl.app").script_name()) -+ ]], -+ "justsomescriptname.lua") -+ asserteq(stderr, "") -+ asserteq(stdout:match("(justsome.+)$"), "justsomescriptname.lua\n") -+ -+ -+ -- commandline, no scriptname -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = nil -- simulate no scriptname -+ local name, err = require("pl.app").script_name() -+ io.stdout:write(tostring(name)) -+ io.stderr:write(err) -+ ]]) -+ assert(stderr:find("No script name found")) -+ asserteq(stdout, "nil") -+ -+ -+ -- commandline, no args table -+ local success, code, stdout, stderr = run_script([[ -+ arg = nil -- simulate no arg table -+ local name, err = require("pl.app").script_name() -+ io.stdout:write(tostring(name)) -+ io.stderr:write(err) -+ ]]) -+ assert(stderr:find("No script name found")) -+ asserteq(stdout, "nil") -+end -+ -+do -- app.require_here -+ local cd = path.currentdir() --path.dirname(path.tmpname()) -+ -+ -- plain script name -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "justsomescriptname.lua" -+ local p = package.path -+ require("pl.app").require_here() -+ print(package.path:sub(1, -#p-1)) -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ assert(stdout:find(path.normcase(cd.."/?.lua;"), 1, true)) -+ assert(stdout:find(path.normcase(cd.."/?/init.lua;"), 1, true)) -+ -+ -+ -- plain script name, with a relative base name -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "justsomescriptname.lua" -+ local p = package.path -+ require("pl.app").require_here("basepath/to/somewhere") -+ print(package.path:sub(1, -#p-1)) -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?.lua;"), 1, true)) -+ assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?/init.lua;"), 1, true)) -+ -+ -+ -- plain script name, with an absolute base name -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "justsomescriptname.lua" -+ local p = package.path -+ require("pl.app").require_here("/basepath/to/somewhere") -+ print(package.path:sub(1, -#p-1)) -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ asserteq(stdout, path.normcase("/basepath/to/somewhere/?.lua;/basepath/to/somewhere/?/init.lua;\n")) -+ -+ -+ -- scriptname with a relative path -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "relative/prefix/justsomescriptname.lua" -+ local p = package.path -+ require("pl.app").require_here() -+ print(package.path:sub(1, -#p-1)) -+ os.exit() -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ assert(stdout:find(path.normcase(cd.."/relative/prefix/?.lua;"), 1, true)) -+ assert(stdout:find(path.normcase(cd.."/relative/prefix/?/init.lua;"), 1, true)) -+ -+ -+ -- script with an absolute path -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "/fixed/justsomescriptname.lua" -+ local p = package.path -+ require("pl.app").require_here() -+ print(package.path:sub(1, -#p-1)) -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ asserteq(stdout, path.normcase("/fixed/?.lua;/fixed/?/init.lua;\n")) -+ -+ -- symlinked script, check that we look beside the target of the link -+ -- -- step 1: find ourselves -+ local self = app.script_name() -+ if not path.isabs(self) then self = path.join(cd,self) end -+ local tadir = path.normcase(path.join(path.dirname(self),"test-app")) -+ -- -- step 2: create a link to our helper script -+ local scrl = path.tmpname() -+ local linkdir = path.normcase(path.dirname(scrl)) -+ os.remove(scrl) -+ assert(lfs.link(path.join(tadir,"require_here-link-target.lua"), scrl, true)) -+ -- -- step 3: check that we look next to ourselves -+ local success, code, stdout, stderr = utils.executeex(cmd.." "..scrl) -+ stdout = path.normcase(stdout) -+ assert(stdout:find(path.normcase(path.join(tadir, "?.lua;")), 1, true)) -+ assert(stdout:find(path.normcase(path.join(tadir, "?/init.lua;")), 1, true)) -+ assert(not stdout:find(path.normcase(path.join(linkdir, "?.lua;")), 1, true)) -+ assert(not stdout:find(path.normcase(path.join(linkdir, "?/init.lua;")), 1, true)) -+ -- -- step 4: ... but not if we turn on nofollow -+ local success, code, stdout, stderr = utils.executeex(cmd.." "..scrl.." x") -+ stdout = path.normcase(stdout) -+ assert(not stdout:find(path.normcase(path.join(tadir, "?.lua;")), 1, true)) -+ assert(not stdout:find(path.normcase(path.join(tadir, "?/init.lua;")), 1, true)) -+ assert(stdout:find(path.normcase(path.join(linkdir, "?.lua;")), 1, true)) -+ assert(stdout:find(path.normcase(path.join(linkdir, "?/init.lua;")), 1, true)) -+ os.remove(scrl) -+ -+end -+ -+ -+do -- app.appfile -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "some/path/justsomescriptname_for_penlight_testing.lua" -+ print(require("pl.app").appfile("filename.data")) -+ ]]) -+ asserteq(stderr, "") -+ stdout = path.normcase(stdout) -+ local fname = path.normcase(path.expanduser("~/.justsomescriptname_for_penlight_testing/filename.data")) -+ asserteq(stdout, fname .."\n") -+ assert(path.isdir(path.dirname(fname))) -+ path.rmdir(path.dirname(fname)) -+ -+end -+ -+ -+do -- app.lua -+ local success, code, stdout, stderr = run_script([[ -+ arg[0] = "justsomescriptname.lua" -+ local a,b = require("pl.app").lua() -+ print(a) -+ ]]) -+ asserteq(stderr, "") -+ asserteq(stdout, cmd .."\n") -+ -+end -+ -+ -+do -- app.parse_args -+ -+ -- no value specified -+ local args = utils.split("-a -b") -+ local t,s = app.parse_args(args, { a = true}) -+ asserteq(t, nil) -+ asserteq(s, "no value for 'a'") -+ -+ -+ -- flag that take a value, space separated -+ local args = utils.split("-a -b value -c") -+ local t,s = app.parse_args(args, { b = true}) -+ asserteq(t, { -+ a = true, -+ b = "value", -+ c = true, -+ }) -+ asserteq(s, {}) -+ -+ -+ -- flag_with_values specified as a list -+ local args = utils.split("-a -b value -c") -+ local t,s = app.parse_args(args, { "b" }) -+ asserteq(t, { -+ a = true, -+ b = "value", -+ c = true, -+ }) -+ asserteq(s, {}) -+ -+ -+ -- flag_with_values missing value at end -+ local args = utils.split("-a -b") -+ local t,s = app.parse_args(args, { "b" }) -+ asserteq(t, nil) -+ asserteq(s, "no value for 'b'") -+ -+ -+ -- error on an unknown flag -+ local args = utils.split("-a -b value -c") -+ local t,s = app.parse_args(args, { b = true }, { "b", "c" }) -+ asserteq(t, nil) -+ asserteq(s, "unknown flag 'a'") -+ -+ -+ -- flag that doesn't take a value -+ local args = utils.split("-a -b:value") -+ local t,s = app.parse_args(args, {}) -+ asserteq(t, { -+ ["a"] = true, -+ ["b"] = "value" -+ }) -+ asserteq(s, {}) -+ -+ -+ -- correctly parsed values, spaces, :, =, and multiple : or = -+ local args = utils.split("-a value -b value:one=two -c=value2:2") -+ local t,s = app.parse_args(args, { "a", "b", "c" }) -+ asserteq(t, { -+ ["a"] = "value", -+ ["b"] = "value:one=two", -+ ["c"] = "value2:2", -+ }) -+ asserteq(s, {}) -+ -+ -+ -- many values, duplicates, and parameters mixed -+ local args = utils.split( -+ "-a -b -cde --long1 --ff:ffvalue --gg=ggvalue -h:hvalue -i=ivalue " .. -+ "-i=2ndvalue param -i:3rdvalue -j1 -k2 -1:hello remaining values") -+ local t,s = app.parse_args(args) -+ asserteq({ -+ i = "3rdvalue", -+ ["1"] = "hello", -+ ff = "ffvalue", -+ long1 = true, -+ c = true, -+ b = true, -+ gg = "ggvalue", -+ j = "1", -+ k = "2", -+ d = true, -+ h = "hvalue", -+ a = true, -+ e = true -+ }, t) -+ asserteq({ -+ "param", -+ "remaining", -+ "values" -+ }, s) -+ -+ -+ -- specify valid flags and aliases -+ local args = utils.split("-a -b value -e -f3") -+ local t,s = app.parse_args(args, -+ { -+ "b", -+ f = true, -+ }, { -+ bully = "b", -- b with value will be reported as 'bully', alias as string -+ a = true, -- hash-type value -+ c = { "d", "e" }, -- e will be reported as c, aliases as list/table -+ }) -+ asserteq(t, { -+ a = true, -+ bully = "value", -+ c = true, -+ f = "3", -+ }) -+ asserteq(s, {}) -+ -+ -+ -- error on an unknown flag, in a chain of short ones -+ local args = utils.split("-b value -cd") -+ local t,s = app.parse_args(args, { b = true }, { "b", "c" }) -+ asserteq(t, nil) -+ asserteq(s, "unknown flag 'd'") -+ -+ -+ -- flag, in a chain of short ones, gets converted to alias -+ local args = utils.split("-dbc") -+ local t,s = app.parse_args(args, nil, { "d", full_name = "b", "c" }) -+ asserteq(t, { -+ full_name = true, -- specified as b in a chain of short ones -+ c = true, -+ d = true, -+ }) -+ asserteq(s, {}) -+ -+end -diff --git a/extra/penlight/tests/test-app/require_here-link-target.lua b/extra/penlight/tests/test-app/require_here-link-target.lua -new file mode 100644 -index 0000000..db0f1eb ---- /dev/null -+++ b/extra/penlight/tests/test-app/require_here-link-target.lua -@@ -0,0 +1,6 @@ -+-- This file is used as a symbolic link target in test-app.lua, to verify the -+-- behaviors of pl.app.require_here() -+ -+local p = package.path -+require("pl.app").require_here(nil, #arg > 0) -+print(package.path:sub(1, -#p-1)) -diff --git a/extra/penlight/tests/test-args.lua b/extra/penlight/tests/test-args.lua -deleted file mode 100644 -index 9d91874..0000000 ---- a/extra/penlight/tests/test-args.lua -+++ /dev/null -@@ -1,48 +0,0 @@ ---- testing app.parse_args --local asserteq = require 'pl.test'.asserteq --local app = require 'pl.app' --local path = require 'pl.path' --local parse_args = app.parse_args -- ---- shows the use of plain flags, long and short: --local flags,args = parse_args({'-abc','--flag','-v','one'}) -- --asserteq(flags,{a=true,b=true,c=true,flag=true,v=true}) --asserteq(args,{'one'}) -- ---- flags may be given values if the value follows or is separated by equals --flags,args = parse_args({'-n10','--out=20'}) -- --asserteq(flags,{n='10',out='20'}) --asserteq(args,{}) -- ---- a flag can be explicitly specified as taking a value: --flags,args = parse_args({'-k','-b=23','-o','hello','--out'},{o=true}) -- --asserteq(flags,{out=true,o="hello",k=true,b="23"}) --asserteq(args,{}) -- --local ok,err = parse_args({'-n'},{n=true}) --asserteq(ok,nil) --asserteq(err, "no value for 'n'") -- --ok,err = parse_args({'-n','-n'},{n=true}) --asserteq(ok,nil) --asserteq(err, "no value for 'n'") -- ---- modify this script's module path so it looks in the 'lua' subdirectory ---- for its modules --app.require_here 'lua' -- --asserteq(require 'foo.args'.answer(),42) --asserteq(require 'bar'.name(),'bar') -- -- --asserteq( -- app.appfile 'config', -- path.expanduser('~/.test-args/config'):gsub('/',path.sep) --) -- -- -- -- -diff --git a/extra/penlight/tests/test-array.lua b/extra/penlight/tests/test-array.lua -deleted file mode 100644 -index 9ed5bde..0000000 ---- a/extra/penlight/tests/test-array.lua -+++ /dev/null -@@ -1,77 +0,0 @@ --local array = require 'pl.array2d' --local asserteq = require('pl.test').asserteq --local L = require 'pl.utils'. string_lambda -- --local A = { -- {1,2,3,4}, -- {10,20,30,40}, -- {100,200,300,400}, -- {1000,2000,3000,4000}, --} -- --asserteq(array.column(A,2),{2,20,200,2000}) --asserteq(array.reduce_rows('+',A),{10,100,1000,10000}) --asserteq(array.reduce_cols('+',A),{1111,2222,3333,4444}) -- ----array.write(A) -- --local dump = require 'pl.pretty'.dump -- --asserteq(array.range(A,'A1:B1'),{1,2}) -- --asserteq(array.range(A,'A1:B2'),{{1,2},{10,20}}) -- --asserteq( -- array.product('..',{1,2,3},{'a','b','c'}), -- {{'1a','2a','3a'},{'1b','2b','3b'},{'1c','2c','3c'}} --) -- --asserteq( -- array.product('{}',{1,2},{'a','b','c'}), -- {{{1,'a'},{2,'a'}},{{1,'b'},{2,'b'}},{{1,'c'},{2,'c'}}} --) -- --asserteq( -- array.flatten {{1,2},{3,4},{5,6}}, -- {1,2,3,4,5,6} --) -- -- --A = {{1,2,3},{4,5,6}} -- ---- flatten in column order! --asserteq( -- array.reshape(A,1,true), -- {{1,4,2,5,3,6}} --) -- ---- regular row-order reshape --asserteq( -- array.reshape(A,3), -- {{1,2},{3,4},{5,6}} --) -- --asserteq( -- array.new(3,3,0), -- {{0,0,0},{0,0,0},{0,0,0}} --) -- --asserteq( -- array.new(3,3,L'|i,j| i==j and 1 or 0'), -- {{1,0,0},{0,1,0},{0,0,1}} --) -- --asserteq( -- array.reduce2('+','*',{{1,10},{2,10},{3,10}}), -- 60 -- i.e. 1*10 + 2*10 + 3*10 --) -- --A = array.new(4,4,0) --B = array.new(3,3,1) --array.move(A,2,2,B) --asserteq(A,{{0,0,0,0},{0,1,1,1},{0,1,1,1},{0,1,1,1}}) -- -- -- -- -- -diff --git a/extra/penlight/tests/test-class.lua b/extra/penlight/tests/test-class.lua -index 43dc4dc..8621005 100644 ---- a/extra/penlight/tests/test-class.lua -+++ b/extra/penlight/tests/test-class.lua -@@ -27,6 +27,10 @@ function B:foo () - self.eee = 1 - end - -+function B:foo2 () -+ self.g = 8 -+end -+ - asserteq(B(),{a=1,b=2}) - - -- can continue this chain -@@ -47,6 +51,77 @@ c = C() - c:foo() - - asserteq(c,{a=1,b=2,c=3,eee=1}) -+ -+-- test indirect inherit -+ -+D = class(C) -+ -+E = class(D) -+ -+function E:_init () -+ self:super() -+ self.e = 4 -+end -+ -+function E:foo () -+ -- recommended way to call inherited version of method... -+ self.eeee = 5 -+ C.foo(self) -+end -+ -+F = class(E) -+ -+function F:_init () -+ self:super() -+ self.f = 6 -+end -+ -+f = F() -+f:foo() -+f:foo2() -- Test : invocation inherits this function from all the way up in B -+ -+asserteq(f,{a=1,b=2,c=3,eee=1,e=4,eeee=5,f=6,g=8}) -+ -+-- Test that inappropriate calls to super() fail gracefully -+ -+G = class() -- Class with no init -+ -+H = class(G) -- Class with an init that wrongly calls super() -+ -+function H:_init() -+ self:super() -- Notice: G has no _init -+end -+ -+I = class(H) -- Inherits the init with a bad super -+J = class(I) -- Grandparent-inits the init with a bad super -+ -+K = class(J) -- Has an init, which calls the init with a bad super -+ -+function K:_init() -+ self:super() -+end -+ -+local function createG() -+ return G() -+end -+ -+local function createH() -- Wrapper function for pcall -+ return H() -+end -+ -+local function createJ() -+ return J() -+end -+ -+local function createK() -+ return K() -+end -+ -+assert(pcall(createG)) -- Should succeed -+assert(not pcall(createH)) -- These three should fail -+assert(not pcall(createJ)) -+assert(not pcall(createK)) -+ - --- class methods! - assert(c:is_a(C)) - assert(c:is_a(B)) -diff --git a/extra/penlight/tests/test-animal.lua b/extra/penlight/tests/test-class2.lua -similarity index 100% -rename from tests/test-animal.lua -rename to tests/test-class2.lua -diff --git a/extra/penlight/tests/test-klass.lua b/extra/penlight/tests/test-class3.lua -similarity index 100% -rename from tests/test-klass.lua -rename to tests/test-class3.lua -diff --git a/extra/penlight/tests/test-super.lua b/extra/penlight/tests/test-class4.lua -similarity index 100% -rename from tests/test-super.lua -rename to tests/test-class4.lua -diff --git a/extra/penlight/tests/test-compare-no-order.lua b/extra/penlight/tests/test-compare-no-order.lua -deleted file mode 100644 -index fc0e5b7..0000000 ---- a/extra/penlight/tests/test-compare-no-order.lua -+++ /dev/null -@@ -1,13 +0,0 @@ ---- test-compare-no-order.lua -- --local T = require 'pl.tablex' --local P = require 'pl.permute' -- --local t = {10,20,5,5,10,'one',555} -- --local permutations = P.table(t) --print('permutations',#permutations) --for _,p in ipairs(permutations) do -- if not T.compare_no_order(t,p) then return print 'different!' end --end --print 'DONE' -diff --git a/extra/penlight/tests/test-compat.lua b/extra/penlight/tests/test-compat.lua -index a52c6d4..c4a2724 100644 ---- a/extra/penlight/tests/test-compat.lua -+++ b/extra/penlight/tests/test-compat.lua -@@ -5,13 +5,25 @@ local compat = require "pl.compat" - local coroutine = require "coroutine" - - local code_generator = coroutine.wrap(function() -- local result = {"ret", "urn \"Hello World!\""} -- for _,v in ipairs(result) do -- coroutine.yield(v) -- end -- coroutine.yield(nil) -+ local result = {"ret", "urn \"Hello World!\""} -+ for _,v in ipairs(result) do -+ coroutine.yield(v) -+ end -+ coroutine.yield(nil) - end) - - local f, err = compat.load(code_generator) - asserteq(err, nil) - asserteq(f(), "Hello World!") -+ -+ -+-- package.searchpath -+if compat.lua51 and not compat.jit then -+ assert(package.searchpath("pl.compat", package.path):match("lua[/\\]pl[/\\]compat")) -+ -+ local path = "some/?/nice.path;another/?.path" -+ local ok, err = package.searchpath("my.file.name", path, ".", "/") -+ asserteq(err, "\tno file 'some/my/file/name/nice.path'\n\tno file 'another/my/file/name.path'") -+ local ok, err = package.searchpath("my/file/name", path, "/", ".") -+ asserteq(err, "\tno file 'some/my.file.name/nice.path'\n\tno file 'another/my.file.name.path'") -+end -\ No newline at end of file -diff --git a/extra/penlight/tests/test-config.lua b/extra/penlight/tests/test-config.lua -index 13fc1ac..a7da380 100644 ---- a/extra/penlight/tests/test-config.lua -+++ b/extra/penlight/tests/test-config.lua -@@ -195,7 +195,7 @@ MemFree: 220292 kB - } - ) - ---- altho this works, rather use pl.data.read for this kind of purpose. -+-- although this works, rather use pl.data.read for this kind of purpose. - testconfig ([[ - # this is just a set of comma-separated values - 1000,444,222 -diff --git a/extra/penlight/tests/test-job-query.lua b/extra/penlight/tests/test-data2.lua -similarity index 100% -rename from tests/test-job-query.lua -rename to tests/test-data2.lua -diff --git a/extra/penlight/tests/test-date.lua b/extra/penlight/tests/test-date.lua -index f8487bc..6a91a09 100644 ---- a/extra/penlight/tests/test-date.lua -+++ b/extra/penlight/tests/test-date.lua -@@ -1,44 +1,10 @@ - local test = require 'pl.test' --local app = require 'pl.app' --local utils = require 'pl.utils' - local asserteq, assertmatch = test.asserteq, test.assertmatch - local dump = require 'pl.pretty'.dump - local T = require 'pl.test'.tuple - - local Date = require 'pl.Date' - ----[[ --d = Date() --print(d) --print(d:year()) --d:day(20) --print(d) --d:add {day = 2} --print(d:day()) --d = Date() -- 'now' --print(d:last_day():day()) --print(d:month(7):last_day()) ----]] -- --function check_df(fmt,str,no_check) -- local df = Date.Format(fmt) -- local d = df:parse(str) -- --print(str,d) -- if not no_check then -- asserteq(df:tostring(d),str) -- end --end -- --check_df('dd/mm/yy','02/04/10') --check_df('mm/dd/yyyy','04/02/2010') --check_df('yyyy-mm-dd','2011-02-20') --check_df('yyyymmdd','20070320') -- ---- use single fields for 'slack' parsing --check_df('m/d/yyyy','1/5/2001',true) -- --check_df('HH:MM','23:10') -- - iso = Date.Format 'yyyy-mm-dd' -- ISO date - d = iso:parse '2010-04-10' - asserteq(T(d:day(),d:month(),d:year()),T(10,4,2010)) -@@ -70,7 +36,7 @@ end - -- specified as UTC plus/minus offset - - function parse_utc (s) -- local d = parse_date(s) -+ local d = parse_date(s) - return d:toUTC() - end - -@@ -119,10 +85,3 @@ asserteq(tostring(nxt - d), '1 month ') - --- Can explicitly get UTC date; these of course refer to same time - local now,utc = Date(), Date 'utc' - asserteq(tostring(now - utc),'zero') -- --if app.platform() ~= 'Windows' then -- print(app.lua()) -- if not utils.execute ("TZ='Europe/London' "..app.lua().." tests/test-tzone.lua") then -- error "buggered!" -- end --end -diff --git a/extra/penlight/tests/test-dir.lua b/extra/penlight/tests/test-dir.lua -index 6c208e8..b293abc 100644 ---- a/extra/penlight/tests/test-dir.lua -+++ b/extra/penlight/tests/test-dir.lua -@@ -4,6 +4,7 @@ local dir = require( "pl.dir" ) - local file = require( "pl.file" ) - local path = require( "pl.path" ) - local asserteq = require( "pl.test" ).asserteq -+local lfs = require("lfs") - - asserteq(dir.fnmatch("foobar", "foo*bar"), true) - asserteq(dir.fnmatch("afoobar", "foo*bar"), false) -@@ -20,11 +21,11 @@ asserteq(filtered, {"foobar", "foonbar"}) - - local normpath = path.normpath - --local doc_files = dir.getfiles(normpath "doc/", "*.ld") --asserteq(doc_files, {normpath "doc/config.ld"}) -+local doc_files = dir.getfiles(normpath "docs/", "*.css") -+asserteq(doc_files, {normpath "docs/ldoc_fixed.css"}) - --local all_doc_files = dir.getallfiles(normpath "doc/", "*.ld") --asserteq(all_doc_files, {normpath "doc/config.ld"}) -+local all_doc_files = dir.getallfiles(normpath "docs/", "*.css") -+asserteq(all_doc_files, {normpath "docs/ldoc_fixed.css"}) - - local test_samples = dir.getallfiles(normpath "tests/lua") - table.sort(test_samples) -@@ -39,10 +40,10 @@ asserteq(test_samples, { - -- Test move files ----------------------------------------- - - -- Create a dummy file --local fileName = path.tmpname() -+local fileName = path.tmpname() .. "Xx" - file.write( fileName, string.rep( "poot ", 1000 ) ) - --local newFileName = path.tmpname() -+local newFileName = path.tmpname() .. "Xx" - local err, msg = dir.movefile( fileName, newFileName ) - - -- Make sure the move is successful -@@ -54,6 +55,18 @@ asserteq( path.exists( fileName ), false ) - -- Check to make sure the new file is there - asserteq( path.exists( newFileName ) , newFileName ) - -+-- Test existence again, but explicitly check for correct casing -+local files = dir.getfiles(path.dirname(newFileName)) -+local found = false -+for i, filename in ipairs(files) do -+ if filename == newFileName then -+ found = true -+ break -+ end -+end -+assert(found, "file was not found in directory, check casing: " .. newFileName) -+ -+ - -- Try to move the original file again (which should fail) - local newFileName2 = path.tmpname() - local err, msg = dir.movefile( fileName, newFileName2 ) -@@ -69,7 +82,7 @@ file.delete( newFileName ) - local fileName = path.tmpname() - file.write( fileName, string.rep( "poot ", 1000 ) ) - --local newFileName = path.tmpname() -+local newFileName = path.tmpname() .. "xX" - local err, msg = dir.copyfile( fileName, newFileName ) - - -- Make sure the move is successful -@@ -78,6 +91,59 @@ assert( err, msg ) - -- Check to make sure the new file is there - asserteq( path.exists( newFileName ) , newFileName ) - -+-- Test existence again, but explicitly check for correct casing -+local files = dir.getfiles(path.dirname(newFileName)) -+local found = false -+for i, filename in ipairs(files) do -+ if filename == newFileName then -+ found = true -+ break -+ end -+end -+assert(found, "file was not found in directory, check casing: " .. newFileName) -+ -+ -+-- Try to move a non-existant file (which should fail) -+local fileName2 = 'blub' -+local newFileName2 = 'snortsh' -+local err, msg = dir.copyfile( fileName2, newFileName2 ) -+asserteq( err, false ) -+ -+-- Clean up the files -+file.delete( fileName ) -+file.delete( newFileName ) -+ -+ -+ -+-- Test make directory ----------------------------------------- -+ -+-- Create a dummy file -+local dirName = path.tmpname() .. "xX" -+local fullPath = dirName .. "/and/one/more" -+if path.is_windows then -+ fullPath = fullPath:gsub("/", "\\") -+end -+local err, msg = dir.makepath(fullPath) -+ -+-- Make sure the move is successful -+assert( err, msg ) -+ -+-- Check to make sure the new file is there -+assert(path.isdir(dirName)) -+assert(path.isdir(fullPath)) -+ -+-- Test existence again, but explicitly check for correct casing -+local files = dir.getdirectories(path.dirname(path.tmpname())) -+local found = false -+for i, filename in ipairs(files) do -+ if filename == dirName then -+ found = true -+ break -+ end -+end -+assert(found, "dir was not found in directory, check casing: " .. newFileName) -+ -+ - -- Try to move a non-existant file (which should fail) - local fileName2 = 'blub' - local newFileName2 = 'snortsh' -@@ -89,6 +155,47 @@ file.delete( fileName ) - file.delete( newFileName ) - - -+ -+ -+-- Test rmtree ----------------------------------------- -+do -+ local dirName = path.tmpname() -+ os.remove(dirName) -+ assert(dir.makepath(dirName)) -+ assert(file.write(path.normpath(dirName .. "/file_base.txt"), "hello world")) -+ assert(dir.makepath(path.normpath(dirName .. "/sub1"))) -+ assert(file.write(path.normpath(dirName .. "/sub1/file_sub1.txt"), "hello world")) -+ assert(dir.makepath(path.normpath(dirName .. "/sub2"))) -+ assert(file.write(path.normpath(dirName .. "/sub2/file_sub2.txt"), "hello world")) -+ -+ -+ local linkTarget = path.tmpname() -+ os.remove(linkTarget) -+ assert(dir.makepath(linkTarget)) -+ local linkFile = path.normpath(linkTarget .. "/file.txt") -+ assert(file.write(linkFile, "hello world")) -+ -+ local linkSource = path.normpath(dirName .. "/link1") -+ assert(lfs.link(linkTarget, linkSource, true)) -+ -+ -- test: rmtree will not follow symlinks -+ local ok, err = dir.rmtree(linkSource) -+ asserteq(ok, false) -+ asserteq(err, "will not follow symlink") -+ -+ -- test: rmtree removes a tree without following symlinks in that tree -+ local ok, err = dir.rmtree(dirName) -+ asserteq(err, nil) -+ asserteq(ok, true) -+ -+ asserteq(path.exists(dirName), false) -- tree is gone, including symlink -+ assert(path.exists(linkFile), "expected linked-to file to still exist") -- symlink target file is still there -+ -+ -- cleanup -+ assert(dir.rmtree(linkTarget)) -+end -+ -+ - -- have NO idea why forcing the return code is necessary here (Windows 7 64-bit) - os.exit(0) - -diff --git a/extra/penlight/tests/test-executeex.lua b/extra/penlight/tests/test-executeex.lua -deleted file mode 100644 -index fcf9783..0000000 ---- a/extra/penlight/tests/test-executeex.lua -+++ /dev/null -@@ -1,31 +0,0 @@ --local path = require 'pl.path' --local utils = require 'pl.utils' --local asserteq = require 'pl.test'.asserteq -- --local echo_lineending = "\n" --if path.is_windows then -- echo_lineending = " \n" --end -- --local function test_executeex(cmd, expected_successful, expected_retcode, expected_stdout, expected_stderr) -- local successful, retcode, stdout, stderr = utils.executeex(cmd) -- asserteq(successful, expected_successful) -- asserteq(retcode, expected_retcode) -- asserteq(stdout, expected_stdout) -- asserteq(stderr, expected_stderr) --end -- ---- Check the return codes --test_executeex("exit", true, 0, "", "") --test_executeex("exit 0", true, 0, "", "") --test_executeex("exit 1", false, 1, "", "") --test_executeex("exit 13", false, 13, "", "") --test_executeex("exit 255", false, 255, "", "") --test_executeex("exit 256", true, 0, "", "") --test_executeex("exit 257", false, 1, "", "") --test_executeex("exit 3809", false, 225, "", "") -- ---- Check output strings --test_executeex("echo stdout", true, 0, "stdout" .. echo_lineending, "") --test_executeex("(echo stderr 1>&2)", true, 0, "", "stderr" .. echo_lineending) --test_executeex("(echo stdout && (echo stderr 1>&2))", true, 0, "stdout" .. echo_lineending, "stderr" .. echo_lineending) -diff --git a/extra/penlight/tests/test-func.lua b/extra/penlight/tests/test-func.lua -index c8606d0..8c3f171 100644 ---- a/extra/penlight/tests/test-func.lua -+++ b/extra/penlight/tests/test-func.lua -@@ -11,19 +11,19 @@ function pprint (t) - end - - function test (e) -- local v = {} -- print('test',collect_values(e,v)) -- if #v > 0 then pprint(v) end -- local rep = repr(e) -+ local v = {} -+ print('test',collect_values(e,v)) -+ if #v > 0 then pprint(v) end -+ local rep = repr(e) - print(rep) - end - - function teste (e,rs,ve) -- local v = {} -- collect_values(e,v) -- if #v > 0 then asserteq(v,ve,nil,2) end -- local rep = repr(e) -- asserteq(rep,rs) -+ local v = {} -+ collect_values(e,v) -+ if #v > 0 then asserteq(v,ve,nil,1) end -+ local rep = repr(e) -+ asserteq(rep,rs, nil, 1) - end - - teste(_1+_2('hello'),'_1 + _2(_C1)',{"hello"}) -@@ -37,6 +37,28 @@ asserteq(instantiate(Or(Not(_1),_2))(true,true),true) - teste(_1() + _2() + _3(),'_1() + _2() + _3()',30) - asserteq(I(_1+_2)(10,20),30) - -+teste(_1() - -_2() % _3(), '_1() - - _2() % _3()') -+teste((_1() - -_2()) % _3(), '(_1() - - _2()) % _3()') -+ -+teste(_1() - _2() + _3(), '_1() - _2() + _3()') -+teste(_1() - (_2() + _3()), '_1() - (_2() + _3())') -+teste((_1() - _2()) + _3(), '_1() - _2() + _3()') -+ -+teste(_1() .. _2() .. _3(), '_1() .. _2() .. _3()') -+teste(_1() .. (_2() .. _3()), '_1() .. _2() .. _3()') -+teste((_1() .. _2()) .. _3(), '(_1() .. _2()) .. _3()') -+ -+teste(_1() ^ _2() ^ _3(), '_1() ^ _2() ^ _3()') -+teste(_1() ^ (_2() ^ _3()), '_1() ^ _2() ^ _3()') -+teste((_1() ^ _2()) ^ _3(), '(_1() ^ _2()) ^ _3()') -+ -+teste(-_1() * _2(), '- _1() * _2()') -+teste(-(_1() * _2()), '- (_1() * _2())') -+teste((-_1()) * _2(), '- _1() * _2()') -+teste(-_1() ^ _2(), '- _1() ^ _2()') -+teste(-(_1() ^ _2()), '- _1() ^ _2()') -+teste((-_1()) ^ _2(), '(- _1()) ^ _2()') -+ - asserteq(instantiate(_1+_2)(10,20),30) - - ls = List {1,2,3,4} -@@ -58,7 +80,7 @@ asserteq (map(_1:sub(1,2),{'one','four'}),{'on','fo'}) - --~ -- or you can do this using List:map - asserteq( List({'one','four'}):map(_1:sub(1,2)), List{'on','fo'}) - ----~ -- note that Len can't be represented generally by #, since this can only be overriden by userdata -+--~ -- note that Len can't be represented generally by #, since this can only be overridden by userdata - asserteq( map(Len(_1),{'one','four'}), {3,4} ) - - --~ -- simularly, 'and' and 'or' are not really operators in Lua, so we need a function notation for them -diff --git a/extra/penlight/tests/test-lapp.lua b/extra/penlight/tests/test-lapp.lua -index 6c0c7f9..40ef7ce 100644 ---- a/extra/penlight/tests/test-lapp.lua -+++ b/extra/penlight/tests/test-lapp.lua -@@ -47,16 +47,16 @@ Various flags and option types - - check(simple, - {'-o','in'}, -- {quiet=false,p=false,o='in',input=''}) -+ {quiet=false,p=false,o='in',input='', input_name="stdin"}) - - ---- value of flag may be separated by '=' or ':' - check(simple, - {'-o=in'}, -- {quiet=false,p=false,o='in',input=''}) -+ {quiet=false,p=false,o='in',input='', input_name="stdin"}) - - check(simple, - {'-o:in'}, -- {quiet=false,p=false,o='in',input=''}) -+ {quiet=false,p=false,o='in',input='', input_name="stdin"}) - - -- Check lapp.callback. - local calls = {} -@@ -118,6 +118,15 @@ check_error(extended,{'-n','x'},"unable to convert to number: x") - - check_error(extended,{'-n','12'},"n out of range") - -+local with_advanced_enum = [[ -+ -s (test1|test2()|%a) -+ -c (1-2|2-3|cool[]) -+]] -+ -+check(with_advanced_enum,{"-s", "test2()", "-c", "1-2"},{s='test2()',c='1-2'}) -+check(with_advanced_enum,{"-s", "test2()", "-c", "2-3"},{s='test2()',c='2-3'}) -+check(with_advanced_enum,{"-s", "%a", "-c", "2-3"},{s='%a',c='2-3'}) -+ - local with_dashes = [[ - --first-dash dash - --second-dash dash also -@@ -147,6 +156,23 @@ check (false_flag,{'-g','-f'},{f=false,g=true}) - check (false_flag,{'-g','--'},{f=true,g=true}) - check (false_flag,{'-g','--','-a','frodo'},{f=true,g=true; '-a','frodo'}) - -+ -+ -+local default_file_flag = [[ -+ -f (file-out default stdout) -+]] -+check (default_file_flag,{},{f="", f_name = "stdout"}) -+ -+ -+ -+local numbered_pos_args = [[ -+ (string) -+ (string) -+ <3arg3> (string) -+]] -+check (numbered_pos_args,{"1", "2", "3"},{arg1="1", arg2 = "2", _arg3 = "3"}) -+ -+ - local addtype = [[ - -l (intlist) List of items - ]] -diff --git a/extra/penlight/tests/test-lexer.lua b/extra/penlight/tests/test-lexer.lua -index 20a86a1..1c09b85 100644 ---- a/extra/penlight/tests/test-lexer.lua -+++ b/extra/penlight/tests/test-lexer.lua -@@ -137,3 +137,10 @@ asserteq(lexer.lineno(iter), 3) - iter() - iter() - asserteq(lexer.lineno(iter), 3) -+ -+do -- numbers without leading zero; ".123" -+ local s = 'hello = +.234' -+ test_scan(s, {space=true}, {number=true}, { -+ {'iden', 'hello'}, {'=', '='}, {'number', .234} -+ }) -+end -diff --git a/extra/penlight/tests/test-pylib.lua b/extra/penlight/tests/test-list2.lua -similarity index 66% -rename from tests/test-pylib.lua -rename to tests/test-list2.lua -index 0d7bb19..174b1c7 100644 ---- a/extra/penlight/tests/test-pylib.lua -+++ b/extra/penlight/tests/test-list2.lua -@@ -1,11 +1,7 @@ ---- test-pylib.lua - local List = require 'pl.List' --local text = require 'pl.text' --local Template = text.Template - local asserteq = require 'pl.test' . asserteq - --l = List{10,20,30,40,50} --s = List{1,2,3,4,5} -+local s = List{1,2,3,4,5} - - -- test using: lua pylist.lua - local lst = List() -@@ -19,7 +15,7 @@ lst:remove_value(40) - asserteq (lst,List{10,20,11,30,50}) - asserteq (lst:contains(11),true) - asserteq (lst:contains(40),false) --local q=lst:pop() -+local _ = lst:pop() - asserteq( lst:index(30),4 ) - asserteq( lst:count(10),1 ) - lst:sort() -@@ -37,7 +33,7 @@ asserteq (lst:slice(2,4),{20,30,40}) - asserteq (lst:slice(-4,-2),{20,30,40}) - - lst = List.range(0,9) --seq = List{0,1,2,3,4,5,6,7,8,9} -+local seq = List{0,1,2,3,4,5,6,7,8,9} - asserteq(List.range(4),{1,2,3,4}) - asserteq(List.range(0,8,2),{0,2,4,6,8}) - asserteq(List.range(0,1,0.2),{0,0.2,0.4,0.6,0.8,1},1e-9) -@@ -52,32 +48,13 @@ asserteq (List('abcd'),List{'a','b','c','d'}) - local caps = List() - List('abcd'):foreach(function(v) caps:append(v:upper()) end) - asserteq (caps,List{'A','B','C','D'}) --ls = List{10,20,30,40} -+local ls = List{10,20,30,40} - ls:slice_assign(2,3,{21,31}) - asserteq (ls , List{10,21,31,40}) - asserteq (ls:remove(2), List{10,31,40}) - asserteq (ls:clear(), List{}) - asserteq (ls:len(), 0) - ---- strings --- --require 'pl.stringx'.import() ---> convenient! --s = '123' --assert (s:isdigit()) --assert (not s:isspace()) - s = 'here the dog is just a dog' --assert (s:startswith('here')) --assert (s:endswith('dog')) --assert (s:count('dog') == 2) - assert (List.split(s) == List{'here', 'the', 'dog', 'is', 'just', 'a', 'dog'}) - assert (List.split('foo;bar;baz', ';') == List{'foo', 'bar', 'baz'}) --s = ' here we go ' --asserteq (s:lstrip() , 'here we go ') --asserteq (s:rstrip() , ' here we go') --asserteq (s:strip() , 'here we go') --asserteq (('hello'):center(20,'+') , '+++++++hello++++++++') -- --t = Template('${here} is the $answer') --asserteq(t:substitute {here = 'one', answer = 'two'} , 'one is the two') -- --asserteq (('hello dolly'):title() , 'Hello Dolly') --asserteq (('h bk bonzo TOK fred m'):title() , 'H Bk Bonzo Tok Fred M') -diff --git a/extra/penlight/tests/test-map.lua b/extra/penlight/tests/test-map.lua -index 793c9c1..1c0bee6 100644 ---- a/extra/penlight/tests/test-map.lua -+++ b/extra/penlight/tests/test-map.lua -@@ -3,17 +3,132 @@ - local test = require 'pl.test' - local Map = require 'pl.Map' - local tablex = require 'pl.tablex' -+local Set = require 'pl.Set' -+local utils = require 'pl.utils' - - local asserteq = test.asserteq -- - local cmp = tablex.compare_no_order - -+ -+ -+-- construction, plain - local m = Map{alpha=1,beta=2,gamma=3} - --assert (cmp(m:values(),{1,2,3})) -+assert(cmp( -+ m:values(), -+ {1, 2, 3} -+)) - --assert (cmp(m:keys(),{'alpha','beta','gamma'})) -+assert(cmp( -+ m:keys(), -+ {'alpha', 'beta', 'gamma'} -+)) - --asserteq (m:items(),{{'alpha',1},{'beta',2},{'gamma',3}}) -+asserteq( -+ m:items(), -+ { -+ {'alpha', 1}, -+ {'beta', 2}, -+ {'gamma', 3}, -+ } -+) - - asserteq (m:getvalues {'alpha','gamma'}, {1,3}) -+ -+ -+ -+-- construction, from a set -+local s = Set{'red','orange','green','blue'} -+m = Map(s) -+ -+asserteq( -+ m:items(), -+ { -+ {'blue', true}, -+ {'green', true}, -+ {'orange', true}, -+ {'red', true}, -+ } -+) -+ -+ -+-- iter() -+m = Map{alpha=1,beta=2,gamma=3} -+local t = {alpha=1,beta=2,gamma=3} -+for k,v in m:iter() do -+ asserteq(v, t[k]) -+ t[k] = nil -+end -+assert(next(t) == nil, "expected the table to be empty by now") -+ -+ -+ -+-- setdefault() -+m = Map{alpha=1,beta=2,gamma=3} -+local v = m:setdefault("charlie", 4) -+asserteq(v, 4) -+v = m:setdefault("alpha", 10) -+asserteq(v, 1) -+asserteq( -+ m:items(), -+ { -+ {'alpha', 1}, -+ {'beta', 2}, -+ {'charlie', 4}, -+ {'gamma', 3}, -+ } -+) -+v = m:set("alpha", false) -+v = m:setdefault("alpha", true) -- falsy value should not be altered -+asserteq(false, m:get("alpha")) -+ -+ -+ -+-- len() -+m = Map{alpha=1,beta=2,gamma=3} -+asserteq(3, m:len()) -+m = Map{} -+asserteq(0, m:len()) -+m:set("charlie", 4) -+asserteq(1, m:len()) -+ -+ -+ -+-- set() & get() -+m = Map{} -+m:set("charlie", 4) -+asserteq(4, m:get("charlie")) -+m:set("charlie", 5) -+asserteq(5, m:get("charlie")) -+m:set("charlie", nil) -+asserteq(nil, m:get("charlie")) -+ -+ -+ -+-- getvalues() -+m = Map{alpha=1,beta=2,gamma=3} -+local x = m:getvalues{"gamma", "beta"} -+asserteq({3, 2}, x) -+ -+ -+ -+-- __eq() -- equality -+local m1 = Map{alpha=1,beta=2,gamma=3} -+local m2 = Map{alpha=1,beta=2,gamma=3} -+assert(m1 == m2) -+m1 = Map() -+m2 = Map() -+assert(m1 == m2) -+ -+ -+ -+-- __tostring() -+m = Map() -+asserteq("{}", tostring(m)) -+m = Map{alpha=1} -+asserteq("{alpha=1}", tostring(m)) -+m = Map{alpha=1,beta=2} -+assert(({ -- test 2 versions, since we cannot rely on order -+ ["{alpha=1,beta=2}"] = true, -+ ["{beta=2,alpha=1}"] = true, -+ })[tostring(m)]) -diff --git a/extra/penlight/tests/test-orderedmap.lua b/extra/penlight/tests/test-orderedmap.lua -new file mode 100644 -index 0000000..1d0fc67 ---- /dev/null -+++ b/extra/penlight/tests/test-orderedmap.lua -@@ -0,0 +1,98 @@ -+local List = require 'pl.List' -+ -+local asserteq = require 'pl.test' . asserteq -+local asserteq2 = require 'pl.test' . asserteq2 -+local OrderedMap = require 'pl.OrderedMap' -+ -+ -+m = OrderedMap() -+m:set('one',1) -+m:set('two',2) -+m:set('three',3) -+ -+asserteq(m:values(),List{1,2,3}) -+ -+-- usually exercized like this: -+--for k,v in m:iter() do print(k,v) end -+ -+local fn = m:iter() -+asserteq2 ('one',1,fn()) -+asserteq2 ('two',2,fn()) -+asserteq2 ('three',3,fn()) -+ -+-- Keys overriding methods can be used. -+m:set('set', 4) -+asserteq(m:values(),List{1,2,3,4}) -+ -+local o1 = OrderedMap {{z=2},{beta=1},{name='fred'}} -+asserteq(tostring(o1),'{z=2,beta=1,name="fred"}') -+ -+-- order of keys is not preserved here! -+local o2 = OrderedMap {z=4,beta=1.1,name='alice',extra='dolly'} -+ -+o1:update(o2) -+asserteq(tostring(o1),'{z=4,beta=1.1,name="alice",extra="dolly"}') -+ -+o1:set('beta',nil) -+asserteq(o1,OrderedMap{{z=4},{name='alice'},{extra='dolly'}}) -+ -+local o3 = OrderedMap() -+o3:set('dog',10) -+o3:set('cat',20) -+o3:set('mouse',30) -+ -+asserteq(o3:keys(),{'dog','cat','mouse'}) -+ -+o3:set('dog',nil) -+ -+asserteq(o3:keys(),{'cat','mouse'}) -+ -+-- Vadim found a problem when clearing a key which did not exist already. -+-- The keys list would then contain the key, although the map would not -+o3:set('lizard',nil) -+ -+asserteq(o3:keys(),{'cat','mouse'}) -+asserteq(o3:values(), {20,30}) -+asserteq(tostring(o3),'{cat=20,mouse=30}') -+ -+-- copy constructor -+local o4 = OrderedMap(o3) -+ -+asserteq(o4,o3) -+ -+-- constructor throws an error if the argument is bad -+-- (errors same as OrderedMap:update) -+asserteq(false,pcall(function() -+ m = OrderedMap('string') -+end)) -+ -+---- changing order of key/value pairs ---- -+ -+o3 = OrderedMap{{cat=20},{mouse=30}} -+ -+o3:insert(1,'bird',5) -- adds key/value before specified position -+o3:insert(1,'mouse') -- moves key keeping old value -+asserteq(o3:keys(),{'mouse','bird','cat'}) -+asserteq(tostring(o3),'{mouse=30,bird=5,cat=20}') -+o3:insert(2,'cat',21) -- moves key and sets new value -+asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') -+-- if you don't specify a value for an unknown key, nothing happens to the map -+o3:insert(3,'alligator') -+asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') -+ -+---- short-cut notation -+ -+local o5 = OrderedMap() -+o5.alpha = 1 -+o5.beta = 2 -+o5.gamma = 3 -+ -+asserteq(o5,OrderedMap{{alpha=1},{beta=2},{gamma=3}}) -+ -+o5.alpha = 10 -+o5.beta = 20 -+o5.gamma = 30 -+o5.delta = 40 -+o5.checked = false -+ -+asserteq(o5,OrderedMap{{alpha=10},{beta=20},{gamma=30},{delta=40},{checked=false}}) -diff --git a/extra/penlight/tests/test-path.lua b/extra/penlight/tests/test-path.lua -index 43d9228..8023270 100644 ---- a/extra/penlight/tests/test-path.lua -+++ b/extra/penlight/tests/test-path.lua -@@ -2,83 +2,199 @@ local path = require 'pl.path' - asserteq = require 'pl.test'.asserteq - - function quote(s) -- return '"'..s..'"' -+ return '"'..s..'"' - end - - function print2(s1,s2) -- print(quote(s1),quote(s2)) -+ print(quote(s1),quote(s2)) - end - --function testpath(pth,p1,p2,p3) -- local dir,rest = path.splitpath(pth) -- local name,ext = path.splitext(rest) -- asserteq(dir,p1) -- asserteq(name,p2) -- asserteq(ext,p3) -+function slash (p) -+ return (p:gsub('\\','/')) - end - --testpath ([[/bonzo/dog_stuff/cat.txt]],[[/bonzo/dog_stuff]],'cat','.txt') --testpath ([[/bonzo/dog/cat/fred.stuff]],'/bonzo/dog/cat','fred','.stuff') --testpath ([[../../alice/jones]],'../../alice','jones','') --testpath ([[alice]],'','alice','') --testpath ([[/path-to/dog/]],[[/path-to/dog]],'','') -+-- path.currentdir -+do -+ local cp = path.currentdir() -+ path.chdir("docs") -+ asserteq(path.currentdir(), cp .. path.sep .. "docs") -+ path.chdir("..") -+ asserteq(path.currentdir(), cp) -+end -+ -+-- path.isdir -+asserteq( path.isdir( "docs" ), true ) -+asserteq( path.isdir( "docs/" ), true ) -+asserteq( path.isdir( "docs/index.html" ), false ) -+asserteq( path.isdir( path.currentdir() ), true) -+asserteq( path.isdir( "c:\\" ), path.is_windows ) -+asserteq( path.isdir( "c:/" ), path.is_windows ) -+ -+-- path.isfile -+asserteq( path.isfile( "docs" ), false ) -+asserteq( path.isfile( "docs/index.html" ), true ) -+ -+-- path.exists -+asserteq( path.exists( "docs"), "docs") -+asserteq( path.exists( "docs/index.html"), "docs/index.html") -+ -+ -+do -- path.splitpath & path.splitext -+ function testpath(pth,p1,p2,p3) -+ local dir,rest = path.splitpath(pth) -+ local name,ext = path.splitext(rest) -+ asserteq(dir,p1) -+ asserteq(name,p2) -+ asserteq(ext,p3) -+ end -+ -+ testpath ([[/bonzo/dog_stuff/cat.txt]],[[/bonzo/dog_stuff]],'cat','.txt') -+ testpath ([[/bonzo/dog/cat/fred.stuff]],'/bonzo/dog/cat','fred','.stuff') -+ testpath ([[../../alice/jones]],'../../alice','jones','') -+ testpath ([[alice]],'','alice','') -+ testpath ([[/path-to/dog/]],[[/path-to/dog]],'','') -+ -+ asserteq({path.splitpath("some/dir/myfile.txt")}, {"some/dir", "myfile.txt"}) -+ asserteq({path.splitpath("some/dir/")}, {"some/dir", ""}) -+ asserteq({path.splitpath("some_dir")}, {"", "some_dir"}) -+ -+ asserteq({path.splitext("/bonzo/dog_stuff/cat.txt")}, {"/bonzo/dog_stuff/cat", ".txt"}) -+ asserteq({path.splitext("cat.txt")}, {"cat", ".txt"}) -+ asserteq({path.splitext("cat")}, {"cat", ""}) -+ asserteq({path.splitext(".txt")}, {"", ".txt"}) -+ asserteq({path.splitext("")}, {"", ""}) -+end -+ -+ -+-- TODO: path.abspath -+ -+-- TODO: path.dirname -+ -+-- TODO: path.basename -+ -+-- TODO: path.extension -+ -+ -+do -- path.isabs -+ asserteq(path.isabs("/hello/path"), true) -+ asserteq(path.isabs("hello/path"), false) -+ asserteq(path.isabs("./hello/path"), false) -+ asserteq(path.isabs("../hello/path"), false) -+ if path.is_windows then -+ asserteq(path.isabs("c:/"), true) -+ asserteq(path.isabs("c:/hello/path"), true) -+ asserteq(path.isabs("c:"), false) -+ asserteq(path.isabs("c:hello/path"), false) -+ asserteq(path.isabs("c:./hello/path"), false) -+ asserteq(path.isabs("c:../hello/path"), false) -+ end -+end -+ -+ -+do -- path.join -+ assert(path.join("somepath",".") == "somepath"..path.sep..".") -+ assert(path.join(".","readme.txt") == "."..path.sep.."readme.txt") -+ assert(path.join("/a_dir", "abs_path/") == "/a_dir"..path.sep.."abs_path/") -+ assert(path.join("a_dir", "/abs_path/") == "/abs_path/") -+ assert(path.join("a_dir", "/abs_path/", "/abs_path2/") == "/abs_path2/") -+ assert(path.join("a_dir", "/abs_path/", "not_abs_path2/") == "/abs_path/not_abs_path2/") -+ assert(path.join("a_dir", "/abs_path/", "not_abs_path2/", "/abs_path3/", "not_abs_path4/") == "/abs_path3/not_abs_path4/") -+ assert(path.join("first","second","third") == "first"..path.sep.."second"..path.sep.."third") -+ assert(path.join("first","second","") == "first"..path.sep.."second"..path.sep) -+ assert(path.join("first","","third") == "first"..path.sep.."third") -+ assert(path.join("","second","third") == "second"..path.sep.."third") -+ assert(path.join("","") == "") -+end -+ -+ -+do -- path.normcase -+ if path.iswindows then -+ asserteq('c:\\hello\\world', 'c:\\hello\\world') -+ asserteq('C:\\Hello\\wORLD', 'c:\\hello\\world') -+ asserteq('c:/hello/world', 'c:\\hello\\world') -+ else -+ asserteq('/Hello/wORLD', '/Hello/wORLD') -+ end -+end -+ -+ -+do -- path.normpath -+ local norm = path.normpath -+ local p = norm '/a/b' - --asserteq( path.isdir( "doc" ), true ) --asserteq( path.isdir( "doc/config.ld" ), false ) -+ asserteq(norm '/a/fred/../b',p) -+ asserteq(norm '/a//b',p) - --asserteq( path.isfile( "doc" ), false ) --asserteq( path.isfile( "doc/config.ld" ), true ) -+ function testnorm(p1,p2) -+ asserteq(norm(p1):gsub('\\','/'), p2) -+ end - --local norm = path.normpath --local p = norm '/a/b' -+ testnorm('a/b/..','a') -+ testnorm('a/b/../..','.') -+ testnorm('a/b/../c/../../d','d') -+ testnorm('a/.','a') -+ testnorm('a/./','a') -+ testnorm('a/b/.././..','.') -+ testnorm('../../a/b','../../a/b') -+ testnorm('../../a/b/../../','../..') -+ testnorm('../../a/b/../c','../../a/c') -+ testnorm('./../../a/b/../c','../../a/c') -+ testnorm('a/..b', 'a/..b') -+ testnorm('./a', 'a') -+ testnorm('a/.', 'a') -+ testnorm('a/', 'a') -+ testnorm('/a', '/a') -+ testnorm('', ".") - --asserteq(norm '/a/fred/../b',p) --asserteq(norm '/a//b',p) -+ if path.is_windows then -+ testnorm('C://a', 'C:/a') -+ testnorm('C:/../a', 'C:/../a') -+ asserteq(norm [[\a\.\b]], p) -+ -- UNC paths -+ asserteq(norm [[\\bonzo\..\dog]], [[\\dog]]) -+ asserteq(norm [[\\?\c:\bonzo\dog\.\]], [[\\?\c:\bonzo\dog]]) -+ else -+ testnorm('//a', '//a') -+ testnorm('///a', '/a') -+ end - --function testnorm(p1,p2) -- asserteq(norm(p1):gsub('\\','/'), p2) -+ asserteq(norm '1/2/../3/4/../5',norm '1/3/5') -+ asserteq(norm '1/hello/../3/hello/../HELLO',norm '1/3/HELLO') - end - --testnorm('a/b/..','a') --testnorm('a/b/../..','.') --testnorm('a/b/../c/../../d','d') --testnorm('a/.','a') --testnorm('a/./','a') --testnorm('a/b/.././..','.') --testnorm('../../a/b','../../a/b') --testnorm('../../a/b/../../','../..') --testnorm('../../a/b/../c','../../a/c') --testnorm('./../../a/b/../c','../../a/c') --testnorm('a/..b', 'a/..b') --testnorm('./a', 'a') --testnorm('a/.', 'a') --testnorm('a/', 'a') --testnorm('/a', '/a') -- --if path.is_windows then -- testnorm('C://a', 'C:/a') -- testnorm('C:/../a', 'C:/../a') -- asserteq(norm [[\a\.\b]], p) -- -- UNC paths -- asserteq(norm [[\\bonzo\..\dog]], [[\\dog]]) -- asserteq(norm [[\\?\c:\bonzo\dog\.\]], [[\\?\c:\bonzo\dog]]) --else -- testnorm('//a', '//a') -- testnorm('///a', '/a') -+ -+do -- path.relpath -+ local testpath = '/a/B/c' -+ -+ function try (p,r) -+ asserteq(slash(path.relpath(p,testpath)),r) -+ end -+ -+ try('/a/B/c/one.lua','one.lua') -+ try('/a/B/c/bonZO/two.lua','bonZO/two.lua') -+ try('/a/B/three.lua','../three.lua') -+ try('/a/four.lua','../../four.lua') -+ try('one.lua','one.lua') -+ try('../two.lua','../two.lua') -+end -+ -+ -+-- TODO: path.tmpname -+ -+ -+do -- path.common_prefix -+ asserteq(slash(path.common_prefix("../anything","../anything/goes")),"../anything") -+ asserteq(slash(path.common_prefix("../anything/goes","../anything")),"../anything") -+ asserteq(slash(path.common_prefix("../anything/goes","../anything/goes")),"../anything") -+ asserteq(slash(path.common_prefix("../anything/","../anything/")),"../anything") -+ asserteq(slash(path.common_prefix("../anything","../anything")),"..") -+ asserteq(slash(path.common_prefix("/hello/world","/hello/world/filename.doc")),"/hello/world") -+ asserteq(slash(path.common_prefix("/hello/filename.doc","/hello/filename.doc")),"/hello") -+ if path.is_windows then -+ asserteq(path.common_prefix("c:\\hey\\there","c:\\hey"),"c:\\hey") -+ asserteq(path.common_prefix("c:/HEy/there","c:/hEy"),"c:\\hEy") -- normalized separators, original casing -+ end - end - --asserteq(norm '1/2/../3/4/../5',norm '1/3/5') -- --assert(path.join("somepath",".") == "somepath"..path.sep..".") --assert(path.join(".","readme.txt") == "."..path.sep.."readme.txt") --assert(path.join("/a_dir", "abs_path/") == "/a_dir"..path.sep.."abs_path/") --assert(path.join("a_dir", "/abs_path/") == "/abs_path/") --assert(path.join("a_dir", "/abs_path/", "/abs_path2/") == "/abs_path2/") --assert(path.join("a_dir", "/abs_path/", "not_abs_path2/") == "/abs_path/not_abs_path2/") --assert(path.join("a_dir", "/abs_path/", "not_abs_path2/", "/abs_path3/", "not_abs_path4/") == "/abs_path3/not_abs_path4/") --assert(path.join("first","second","third") == "first"..path.sep.."second"..path.sep.."third") --assert(path.join("first","second","") == "first"..path.sep.."second"..path.sep) --assert(path.join("first","","third") == "first"..path.sep.."third") --assert(path.join("","second","third") == "second"..path.sep.."third") --assert(path.join("","") == "") - -+-- TODO: path.package_path -diff --git a/extra/penlight/tests/test-pretty-number.lua b/extra/penlight/tests/test-pretty-number.lua -deleted file mode 100644 -index 347a5cf..0000000 ---- a/extra/penlight/tests/test-pretty-number.lua -+++ /dev/null -@@ -1,33 +0,0 @@ --local test = require 'pl.test' --local pretty = require 'pl.pretty' -- --function testm(x,s) -- test.asserteq(pretty.number(x,'M'),s) --end -- --testm(123,'123B') --testm(1234,'1.2KiB') --testm(10*1024,'10.0KiB') --testm(1024*1024,'1.0MiB') -- --function testn(x,s) -- test.asserteq(pretty.number(x,'N',2),s) --end -- --testn(123,'123') --testn(1234,'1.23K') --testn(10*1024,'10.24K') --testn(1024*1024,'1.05M') --testn(1024*1024*1024,'1.07B') -- --function testc(x,s) -- test.asserteq(pretty.number(x,'T'),s) --end -- --testc(123,'123') --testc(1234,'1,234') --testc(12345,'12,345') --testc(123456,'123,456') --testc(1234567,'1,234,567') --testc(12345678,'12,345,678') -- -diff --git a/extra/penlight/tests/test-pretty.lua b/extra/penlight/tests/test-pretty.lua -index 9d9277d..9da2584 100644 ---- a/extra/penlight/tests/test-pretty.lua -+++ b/extra/penlight/tests/test-pretty.lua -@@ -84,11 +84,49 @@ t2 = {} - t1[1],t1[2] = t2,t2 - asserteq( pretty.write(t1,""), [[{{},{}}]] ) - -+-- Check that write correctly print table with non number or string as keys -+ -+t1 = { [true] = "boolean", [false] = "untrue", a = "a", b = "b", [1] = 1, [0] = 0 } -+asserteq( pretty.write(t1,""), [[{1,["false"]="untrue",["true"]="boolean",a="a",b="b",[0]=0}]] ) -+ -+ - -- Check number formatting - asserteq(pretty.write({1/0, -1/0, 0/0, 1, 1/2}, ""), "{Inf,-Inf,NaN,1,0.5}") - --if _VERSION == "Lua 5.3" then -+if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then - asserteq(pretty.write({1.0}, ""), "{1.0}") - else - asserteq(pretty.write({1.0}, ""), "{1}") - end -+ -+do -- issue #203, item 3 -+ local t = {}; t[t] = 1 -+ pretty.write(t) -- should not crash -+end -+ -+ -+do -+ local float = 1e100 -+ local max_int = 9007199254740991 -- 1 << 53 - 1 -+ local min_int = -9007199254740991 -+ -+ asserteq(pretty.write(float), "1.0e+100") -+ if _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then -+ --There is no way to portably format with %d before 5.3 -+ asserteq(pretty.write(min_int - 3), "-9007199254740994") -+ asserteq(pretty.write(max_int + 3), "9007199254740994") -+ asserteq(pretty.write(min_int), "-9007199254740991") -+ asserteq(pretty.write(max_int), "9007199254740991") -+ end -+end -+ -+-- pretty.write fails if an __index metatable raises an error #257 -+-- only applies to 5.3+ where iterators respect metamethods -+do -+ local t = setmetatable({},{ -+ __index = function(self, key) -+ error("oops... couldn't find " .. tostring(key)) -+ end -+ }) -+ asserteq(pretty.write(t), "{\n}") -+end -diff --git a/extra/penlight/tests/test-relpath.lua b/extra/penlight/tests/test-relpath.lua -deleted file mode 100644 -index 78d1ad3..0000000 ---- a/extra/penlight/tests/test-relpath.lua -+++ /dev/null -@@ -1,23 +0,0 @@ --local path = require 'pl.path' --local test = require 'pl.test' -- --relpath = path.relpath -- --path = '/a/b/c' -- --function slash (p) -- return (p:gsub('\\','/')) --end -- --function try (p,r) -- test.asserteq(slash(relpath(p,path)),r) --end -- --try('/a/b/c/one.lua','one.lua') --try('/a/b/c/bonzo/two.lua','bonzo/two.lua') --try('/a/b/three.lua','../three.lua') --try('/a/four.lua','../../four.lua') --try('one.lua','one.lua') --try('../two.lua','../two.lua') -- -- -diff --git a/extra/penlight/tests/test-seq.lua b/extra/penlight/tests/test-seq.lua -index 190d829..e59a4be 100644 ---- a/extra/penlight/tests/test-seq.lua -+++ b/extra/penlight/tests/test-seq.lua -@@ -68,7 +68,11 @@ asserteq(C(seq.map(L'#_',{'one','tw'})),{3,2}) - - --for l1,l2 in seq.last{10,20,30} do print(l1,l2) end - --asserteq(C2(seq.last{10,20,30}),{{20,10},{30,20}} ) -+asserteq( C2(seq.last{10,20,30}),{{20,10},{30,20}} ) -+ -+asserteq( C2(seq.last{40}),{} ) -+ -+asserteq( C2(seq.last{}),{} ) - - asserteq( - seq{10,20,30}:map(L'_+1'):copy(), -diff --git a/extra/penlight/tests/test-set.lua b/extra/penlight/tests/test-set.lua -deleted file mode 100644 -index 7402971..0000000 ---- a/extra/penlight/tests/test-set.lua -+++ /dev/null -@@ -1,201 +0,0 @@ --class = require 'pl.class' --M = require 'pl.Map' --S = require 'pl.Set' --List = require 'pl.List' -- --asserteq = require 'pl.test' . asserteq --asserteq2 = require 'pl.test' . asserteq2 --MultiMap = require 'pl.MultiMap' --OrderedMap = require 'pl.OrderedMap' -- --s1 = S{1,2} --s2 = S{1,2} ---- equality --asserteq(s1,s2) ---- union --asserteq(S{1,2} + S{2,3}, S{1,2,3}) --asserteq(S{1,2} + 3, S{1,2,3}) ---- intersection --asserteq(S{1,2} * S{2,3}, S{2}) ---- difference --fruit = S{'apple','banana','orange','apricots'} --tropical = S{'banana','orange'} -- --asserteq(fruit - tropical, S{'apple','apricots'}) --asserteq(tropical - 'orange', S{'banana'}) -- ---- symmetric_difference --asserteq(S{1,2} ^ S{2,3}, S{1,3}) ---- tostring - illustrative, because these assertions may or may not work, ---- due to no ordering in set elements ----asserteq(tostring(S{1,2}),'[1,2]') ----asserteq(tostring(S{1,S{2,3}}),'[1,[2,3]]') -- --s3 = S() --asserteq(S.isempty(s3),true) -- --s4 = S{1,2,3} -- ---- subsets/supersets --asserteq(s4 > s1,true) -- --S.set(s3,'one',true) --s3.two = true --asserteq(s3,S{'one','two'}) -- --m = M{one=1,two=2} --asserteq(m,M{one=1,two=2}) --m:update {three=3,four=4} --asserteq(m,M{one=1,two=2,three=3,four=4}) -- --class.Animal() -- --function Animal:_init(name) -- self.name = name --end -- --function Animal:__tostring() -- return self.name..': '..self:speak() --end -- --class.Dog(Animal) -- --function Dog:speak() -- return 'bark' --end -- --class.Cat(Animal) -- --function Cat:_init(name,breed) -- self:super(name) -- must init base! -- self.breed = breed --end -- --function Cat:speak() -- return 'meow' --end -- --Lion = class(Cat) -- --function Lion:speak() -- return 'roar' --end -- --fido = Dog('Fido') --felix = Cat('Felix','Tabby') --leo = Lion('Leo','African') -- --asserteq(tostring(fido),'Fido: bark') --asserteq(tostring(felix),'Felix: meow') --asserteq(tostring(leo),'Leo: roar') -- --assert(Dog:class_of(fido)) --assert(fido:is_a(Dog)) -- --assert(leo:is_a(Animal)) -- --m = MultiMap() --m:set('john',1) --m:set('jane',3) --m:set('john',2) -- --ms = MultiMap{john={1,2},jane={3}} -- --asserteq(m,ms) -- --m = OrderedMap() --m:set('one',1) --m:set('two',2) --m:set('three',3) -- --asserteq(m:values(),List{1,2,3}) -- ---- usually exercized like this: ----for k,v in m:iter() do print(k,v) end -- --fn = m:iter() --asserteq2 ('one',1,fn()) --asserteq2 ('two',2,fn()) --asserteq2 ('three',3,fn()) -- ---- Keys overriding methods can be used. --m:set('set', 4) --asserteq(m:values(),List{1,2,3,4}) -- --o1 = OrderedMap {{z=2},{beta=1},{name='fred'}} --asserteq(tostring(o1),'{z=2,beta=1,name="fred"}') -- ---- order of keys is not preserved here! --o2 = OrderedMap {z=4,beta=1.1,name='alice',extra='dolly'} -- --o1:update(o2) --asserteq(tostring(o1),'{z=4,beta=1.1,name="alice",extra="dolly"}') -- --o1:set('beta',nil) --asserteq(o1,OrderedMap{{z=4},{name='alice'},{extra='dolly'}}) -- --o3 = OrderedMap() --o3:set('dog',10) --o3:set('cat',20) --o3:set('mouse',30) -- --asserteq(o3:keys(),{'dog','cat','mouse'}) -- --o3:set('dog',nil) -- --asserteq(o3:keys(),{'cat','mouse'}) -- ---- Vadim found a problem when clearing a key which did not exist already. ---- The keys list would then contain the key, although the map would not --o3:set('lizard',nil) -- --asserteq(o3:keys(),{'cat','mouse'}) --asserteq(o3:values(), {20,30}) --asserteq(tostring(o3),'{cat=20,mouse=30}') -- ---- copy constructor --o4 = OrderedMap(o3) -- --asserteq(o4,o3) -- ---- constructor throws an error if the argument is bad ---- (errors same as OrderedMap:update) --asserteq(false,pcall(function() -- m = OrderedMap('string') --end)) -- ------ changing order of key/value pairs ---- -- --o3 = OrderedMap{{cat=20},{mouse=30}} -- --o3:insert(1,'bird',5) -- adds key/value before specified position --o3:insert(1,'mouse') -- moves key keeping old value --asserteq(o3:keys(),{'mouse','bird','cat'}) --asserteq(tostring(o3),'{mouse=30,bird=5,cat=20}') --o3:insert(2,'cat',21) -- moves key and sets new value --asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') ---- if you don't specify a value for an unknown key, nothing happens to the map --o3:insert(3,'alligator') --asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') -- ------ short-cut notation -- --o5 = OrderedMap() --o5.alpha = 1 --o5.beta = 2 --o5.gamma = 3 -- --asserteq(o5,OrderedMap{{alpha=1},{beta=2},{gamma=3}}) -- --o5.alpha = 10 --o5.beta = 20 --o5.gamma = 30 --o5.delta = 40 --o5.checked = false -- --asserteq(o5,OrderedMap{{alpha=10},{beta=20},{gamma=30},{delta=40},{checked=false}}) -- -- -- -- -- -diff --git a/extra/penlight/tests/test-sip.lua b/extra/penlight/tests/test-sip.lua -index ea4c19a..cf4b344 100644 ---- a/extra/penlight/tests/test-sip.lua -+++ b/extra/penlight/tests/test-sip.lua -@@ -52,13 +52,13 @@ check('just a string', 'not that string') - local months={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"} - - local function adjust_year(res) -- if res.year < 100 then -- if res.year < 70 then -- res.year = res.year + 2000 -- else -- res.year = res.year + 1900 -- end -- end -+ if res.year < 100 then -+ if res.year < 70 then -+ res.year = res.year + 2000 -+ else -+ res.year = res.year + 1900 -+ end -+ end - end - - local shortdate = sip.compile('$d{day}/$d{month}/$d{year}') -@@ -71,17 +71,17 @@ local function dcheck (d1,d2) - end - - local function dates(str,tbl) -- local res = {} -- if shortdate(str,res) then -- dcheck(res,tbl) -+ local res = {} -+ if shortdate(str,res) then -+ dcheck(res,tbl) - elseif isodate(str,res) then - dcheck(res,tbl) -- elseif longdate(str,res) then -- res.month = tablex.find(months,res.month) -- dcheck(res,tbl) -- else -- assert(tbl == nil) -- end -+ elseif longdate(str,res) then -+ res.month = tablex.find(months,res.month) -+ dcheck(res,tbl) -+ else -+ assert(tbl == nil) -+ end - end - - dates ('10/12/2007',{year=2007,month=12,day=10}) -diff --git a/extra/penlight/tests/test-strict.lua b/extra/penlight/tests/test-strict.lua -index bb29082..12b0fad 100644 ---- a/extra/penlight/tests/test-strict.lua -+++ b/extra/penlight/tests/test-strict.lua -@@ -1,3 +1,4 @@ -+require 'pl.compat' -- require this one before loading strict - local strict = require 'pl.strict' - local test = require 'pl.test' - local app = require 'pl.app' -@@ -42,9 +43,103 @@ end,"variable 'sine' is not declared in 'math'") - - - -- -- -- -- -- -- -+-- module -+do -+ local testmodule = { -+ hello = function() return "supremacy" end -+ } -+ -- make strict and allow extra field "world" -+ strict.module("my_test", testmodule, { world = true }) -+ -+ test.asserteq(testmodule.hello(), "supremacy") -+ test.assertraise(function() -+ print(testmodule.not_allowed_key) -+ end, "variable 'not_allowed_key' is not declared in 'my_test'") -+ -+ test.asserteq(testmodule.world, nil) -+ testmodule.world = "supremacy" -+ test.asserteq(testmodule.world, "supremacy") -+ -+ -+ -- table with a __newindex method -+ local mod1 = strict.module("mod1", setmetatable( -+ { -+ hello = "world", -+ }, { -+ __newindex = function(self, key, value) -+ if key == "Lua" then -+ rawset(self, key, value) -+ end -+ end, -+ } -+ )) -+ test.asserteq(mod1.hello, "world") -+ mod1.Lua = "hello world" -+ test.asserteq(mod1.Lua, "hello world") -+ test.assertraise(function() -+ print(mod1.not_allowed_key) -+ end, "variable 'not_allowed_key' is not declared in 'mod1'") -+ -+ -+ -- table with a __index method -+ local mod1 = strict.module("mod1", setmetatable( -+ { -+ hello = "world", -+ }, { -+ __index = function(self, key) -+ if key == "Lua" then -+ return "rocks" -+ end -+ end, -+ } -+ )) -+ test.asserteq(mod1.hello, "world") -+ test.asserteq(mod1.Lua, "rocks") -+ test.assertraise(function() -+ print(mod1.not_allowed_key) -+ end, "variable 'not_allowed_key' is not declared in 'mod1'") -+ -+ -+ -- table with a __index table -+ local mod1 = strict.module("mod1", setmetatable( -+ { -+ hello = "world", -+ }, { -+ __index = { -+ Lua = "rocks!" -+ } -+ } -+ )) -+ test.asserteq(mod1.hello, "world") -+ test.asserteq(mod1.Lua, "rocks!") -+ test.assertraise(function() -+ print(mod1.not_allowed_key) -+ end, "variable 'not_allowed_key' is not declared in 'mod1'") -+ -+end -+ -+ -+do -+ -- closed_module -+ -- what does this do? this does not seem a usefull function??? -+ -+ local testmodule = { -+ hello = function() return "supremacy" end -+ } -+ local M = strict.closed_module(testmodule, "my_test") -+ -+ -- read acces to original is granted, but not to the new one -+ test.asserteq(testmodule.hello(), "supremacy") -+ test.assertraise(function() -+ print(M.hello()) -+ end, "variable 'hello' is not declared in 'my_test'") -+ -+ -- write access to both is granted -+ testmodule.world = "domination" -+ M.world = "domination" -+ -+ -- read acces to set field in original is granted, but not set -+ test.asserteq(testmodule.world, nil) -+ test.asserteq(M.world, "domination") -+ -+end -diff --git a/extra/penlight/tests/test-stringx.lua b/extra/penlight/tests/test-stringx.lua -deleted file mode 100644 -index 7802eb4..0000000 ---- a/extra/penlight/tests/test-stringx.lua -+++ /dev/null -@@ -1,354 +0,0 @@ --local stringx = require 'pl.stringx' --local utils = require 'pl.utils' --local asserteq = require 'pl.test' . asserteq --local T = require 'pl.test'.tuple -- --local function FIX(s) -- io.stderr:write('FIX:' .. s .. '\n') --end -- -- ---- isalpha --asserteq(T(stringx.isalpha''), T(false)) --asserteq(T(stringx.isalpha' '), T(false)) --asserteq(T(stringx.isalpha'0'), T(false)) --asserteq(T(stringx.isalpha'\0'), T(false)) --asserteq(T(stringx.isalpha'azAZ'), T(true)) --asserteq(T(stringx.isalpha'az9AZ'), T(false)) -- ---- isdigit --asserteq(T(stringx.isdigit''), T(false)) --asserteq(T(stringx.isdigit' '), T(false)) --asserteq(T(stringx.isdigit'a'), T(false)) --asserteq(T(stringx.isdigit'0123456789'), T(true)) -- ---- isalnum --asserteq(T(stringx.isalnum''), T(false)) --asserteq(T(stringx.isalnum' '), T(false)) --asserteq(T(stringx.isalnum('azAZ01234567890')), T(true)) -- ---- isspace --asserteq(T(stringx.isspace''), T(false)) --asserteq(T(stringx.isspace' '), T(true)) --asserteq(T(stringx.isspace' \r\n\f\t'), T(true)) --asserteq(T(stringx.isspace' \r\n-\f\t'), T(false)) -- ---- islower --asserteq(T(stringx.islower''), T(false)) --asserteq(T(stringx.islower'az'), T(true)) --asserteq(T(stringx.islower'aMz'), T(false)) --asserteq(T(stringx.islower'a z'), T(true)) -- ---- startswith --local startswith = stringx.startswith --asserteq(T(startswith('', '')), T(true)) --asserteq(T(startswith('', 'a')), T(false)) --asserteq(T(startswith('a', '')), T(true)) --asserteq(T(startswith('a', 'a')), T(true)) --asserteq(T(startswith('a', 'b')), T(false)) --asserteq(T(startswith('a', 'ab')), T(false)) --asserteq(T(startswith('abc', 'ab')), T(true)) --asserteq(T(startswith('abc', 'bc')), T(false)) -- off by one --asserteq(T(startswith('abc', '.')), T(false)) -- Lua pattern char --asserteq(T(startswith('a\0bc', 'a\0b')), T(true)) -- '\0' -- --asserteq(startswith('abcfoo',{'abc','def'}),true) --asserteq(startswith('deffoo',{'abc','def'}),true) --asserteq(startswith('cdefoo',{'abc','def'}),false) -- -- ---- endswith ---- http://snippets.luacode.org/sputnik.lua?p=snippets/Check_string_ends_with_other_string_74 --local endswith = stringx.endswith --asserteq(T(endswith("", "")), T(true)) --asserteq(T(endswith("", "a")), T(false)) --asserteq(T(endswith("a", "")), T(true)) --asserteq(T(endswith("a", "a")), T(true)) --asserteq(T(endswith("a", "A")), T(false)) -- case sensitive --asserteq(T(endswith("a", "aa")), T(false)) --asserteq(T(endswith("abc", "")), T(true)) --asserteq(T(endswith("abc", "ab")), T(false)) -- off by one --asserteq(T(endswith("abc", "c")), T(true)) --asserteq(T(endswith("abc", "bc")), T(true)) --asserteq(T(endswith("abc", "abc")), T(true)) --asserteq(T(endswith("abc", " abc")), T(false)) --asserteq(T(endswith("abc", "a")), T(false)) --asserteq(T(endswith("abc", ".")), T(false)) -- Lua pattern char --asserteq(T(endswith("ab\0c", "b\0c")), T(true)) -- \0 --asserteq(T(endswith("ab\0c", "b\0d")), T(false)) -- \0 -- --asserteq(endswith('dollar.dot',{'.dot','.txt'}),true) --asserteq(endswith('dollar.txt',{'.dot','.txt'}),true) --asserteq(endswith('dollar.rtxt',{'.dot','.txt'}),false) -- ---- splitlines --asserteq(stringx.splitlines(''), {}) --asserteq(stringx.splitlines('a'), {'a'}) --asserteq(stringx.splitlines('\n'), {''}) --asserteq(stringx.splitlines('\n\n'), {'', ''}) --asserteq(stringx.splitlines('\r\r'), {'', ''}) --asserteq(stringx.splitlines('\r\n'), {''}) --asserteq(stringx.splitlines('ab\ncd\n'), {'ab', 'cd'}) --asserteq(stringx.splitlines('ab\ncd\n', true), {'ab\n', 'cd\n'}) --asserteq(stringx.splitlines('\nab\r\r\ncd\n', true), {'\n', 'ab\r', '\r\n', 'cd\n'}) -- ---- expandtabs -----FIX[[raises error --asserteq(T(stringx.expandtabs('',0)), T('')) --asserteq(T(stringx.expandtabs('',1)), T('')) --asserteq(T(stringx.expandtabs(' ',1)), T(' ')) ---- expandtabs now works like Python's str.expandtabs (up to next tab stop) --asserteq(T(stringx.expandtabs(' \t ')), T((' '):rep(1+8))) --asserteq(T(stringx.expandtabs(' \t ',2)), T(' ')) ----]] -- ---- lfind --asserteq(T(stringx.lfind('', '')), T(1)) --asserteq(T(stringx.lfind('a', '')), T(1)) --asserteq(T(stringx.lfind('ab', 'b')), T(2)) --asserteq(T(stringx.lfind('abc', 'cd')), T(nil)) --asserteq(T(stringx.lfind('abcbc', 'bc')), T(2)) --asserteq(T(stringx.lfind('ab..cd', '.')), T(3)) -- pattern char --asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3)), T(4)) --asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 4)), T(nil)) --asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 5)), T(4)) --asserteq(T(stringx.lfind('abcbcbbc', 'bc', nil, 5)), T(2)) -- ---- rfind --asserteq(T(stringx.rfind('', '')), T(1)) --asserteq(T(stringx.rfind('ab', '')), T(3)) --asserteq(T(stringx.rfind('abc', 'cd')), T(nil)) --asserteq(T(stringx.rfind('abcbc', 'bc')), T(4)) --asserteq(T(stringx.rfind('abcbcb', 'bc')), T(4)) --asserteq(T(stringx.rfind('ab..cd', '.')), T(4)) -- pattern char --asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3)), T(7)) --asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 4)), T(nil)) --asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 5)), T(4)) --asserteq(T(stringx.rfind('abcbcbbc', 'bc', nil, 5)), T(4)) -- ---- replace --asserteq(T(stringx.replace('', '', '')), T('')) --asserteq(T(stringx.replace(' ', '', '')), T(' ')) --asserteq(T(stringx.replace(' ', '', ' ')), T(' ')) --asserteq(T(stringx.replace(' ', ' ', '')), T('')) --asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC')), T('aBCaBCaBC')) --asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 1)), T('aBCabcabc')) --asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 0)), T('abcabcabc')) --asserteq(T(stringx.replace('abc', 'd', 'e')), T('abc')) --asserteq(T(stringx.replace('a.b', '.', '%d')), T('a%db')) -- ---- split --local split = stringx.split --asserteq(split('', ''), {''}) --asserteq(split('', 'z'), {}) --FIX:intended and specified behavior? --asserteq(split('a', ''), {'a'}) --FIX:intended and specified behavior? --asserteq(split('a', 'a'), {''}) ---- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. ---- If you need to split on a pattern, use utils.split() ---- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars ---- note that leading space is ignored by the default --asserteq(split(' 1 2 3 '),{'1','2','3'}) --asserteq(split('a*bb*c*ddd','*'),{'a','bb','c','ddd'}) --asserteq(split('dog:fred:bonzo:alice',':',3), {'dog','fred','bonzo:alice'}) --asserteq(split('///','/'),{'','','',''}) ---- capitalize --asserteq(T(stringx.capitalize('')), T('')) --asserteq(T(stringx.capitalize('abC deF1')), T('Abc Def1')) -- Python behaviour -- ---- count --asserteq(T(stringx.count('', '')), T(0)) --infinite loop]] --asserteq(T(stringx.count(' ', '')), T(2)) --infinite loop]] --asserteq(T(stringx.count('a..c', '.')), T(2)) -- pattern chars --asserteq(T(stringx.count('a1c', '%d')), T(0)) -- pattern chars -- ---- ljust --asserteq(T(stringx.ljust('', 0)), T('')) --asserteq(T(stringx.ljust('', 2)), T(' ')) --asserteq(T(stringx.ljust('ab', 3)), T('ab ')) --asserteq(T(stringx.ljust('ab', 3, '%')), T('ab%')) --asserteq(T(stringx.ljust('abcd', 3)), T('abcd')) -- agrees with Python -- ---- rjust --asserteq(T(stringx.rjust('', 0)), T('')) --asserteq(T(stringx.rjust('', 2)), T(' ')) --asserteq(T(stringx.rjust('ab', 3)), T(' ab')) --asserteq(T(stringx.rjust('ab', 3, '%')), T('%ab')) --asserteq(T(stringx.rjust('abcd', 3)), T('abcd')) -- agrees with Python -- ---- center --asserteq(T(stringx.center('', 0)), T('')) --asserteq(T(stringx.center('', 1)), T(' ')) --asserteq(T(stringx.center('', 2)), T(' ')) --asserteq(T(stringx.center('a', 1)), T('a')) --asserteq(T(stringx.center('a', 2)), T('a ')) --asserteq(T(stringx.center('a', 3)), T(' a ')) -- -- ---- ltrim ---- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 --local trim = stringx.lstrip --asserteq(T(trim''), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim'a'), T'a') --asserteq(T(trim' a'), T'a') --asserteq(T(trim'a '), T'a ') --asserteq(T(trim' a '), T'a ') --asserteq(T(trim' a '), T'a ') --asserteq(T(trim' ab cd '), T'ab cd ') --asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b \r\t\n\f\v') ---- more -- -- ---- rtrim ---- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 --local trim = stringx.rstrip --asserteq(T(trim''), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim'a'), T'a') --asserteq(T(trim' a'), T' a') --asserteq(T(trim'a '), T'a') --asserteq(T(trim' a '), T' a') --asserteq(T(trim' a '), T' a') --asserteq(T(trim' ab cd '), T' ab cd') --asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T' \t\r\n\f\va\000b') ---- more -- -- ---- trim ---- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 --local trim = stringx.strip --asserteq(T(trim''), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim' '), T'') --asserteq(T(trim'a'), T'a') --asserteq(T(trim' a'), T'a') --asserteq(T(trim'a '), T'a') --asserteq(T(trim' a '), T'a') --asserteq(T(trim' a '), T'a') --asserteq(T(trim' ab cd '), T'ab cd') --asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b') ---- more -- -- ---- partition ---- as per str.partition in Python, delimiter must be non-empty; ---- interpreted as a plain string ----asserteq(T(stringx.partition('', '')), T('', '', '')) -- error]] ----asserteq(T(stringx.partition('a', '')), T('', '', 'a')) --error]] --asserteq(T(stringx.partition('a', 'a')), T('', 'a', '')) --asserteq(T(stringx.partition('abc', 'b')), T('a', 'b', 'c')) --asserteq(T(stringx.partition('abc', '.+')), T('abc','','')) --asserteq(T(stringx.partition('a,b,c', ',')), T('a',',','b,c')) ---- rpartition --asserteq(T(stringx.rpartition('a/b/c', '/')), T('a/b', '/', 'c')) --asserteq(T(stringx.rpartition('abc', 'b')), T('a', 'b', 'c')) -- -- ---- at (works like s:sub(idx,idx), so negative indices allowed --asserteq(T(stringx.at('a', 1)), T('a')) --asserteq(T(stringx.at('ab', 2)), T('b')) --asserteq(T(stringx.at('abcd', -1)), T('d')) -- ---- lines --local function merge(it, ...) -- assert(select('#', ...) == 0) -- local ts = {} -- for val in it do ts[#ts+1] = val end -- return ts --end --asserteq(merge(stringx.lines('')), {''}) --asserteq(merge(stringx.lines('ab')), {'ab'}) --asserteq(merge(stringx.lines('ab\ncd')), {'ab', 'cd'}) -- ---- shorten ---- The returned string is always equal or less to the given size. --asserteq(T(stringx.shorten('', 0)), T'') --asserteq(T(stringx.shorten('a', 1)), T'a') --asserteq(T(stringx.shorten('ab', 1)), T'.') --FIX:ok? --asserteq(T(stringx.shorten('abc', 3)), T'abc') --asserteq(T(stringx.shorten('abcd', 3)), T'...') --asserteq(T(stringx.shorten('abcde', 5)), T'abcde') --asserteq(T(stringx.shorten('abcde', 4)), T'a...') --asserteq(T(stringx.shorten('abcde', 3)), T'...') --asserteq(T(stringx.shorten('abcde', 2)), T'..') --asserteq(T(stringx.shorten('abcde', 0)), T'') --asserteq(T(stringx.shorten('', 0, true)), T'') --asserteq(T(stringx.shorten('a', 1, true)), T'a') --asserteq(T(stringx.shorten('ab', 1, true)), T'.') --asserteq(T(stringx.shorten('abcde', 5, true)), T'abcde') --asserteq(T(stringx.shorten('abcde', 4, true)), T'...e') --asserteq(T(stringx.shorten('abcde', 3, true)), T'...') --asserteq(T(stringx.shorten('abcde', 2, true)), T'..') --asserteq(T(stringx.shorten('abcde', 0, true)), T'') -- ---- strip --asserteq(stringx.strip(' hello '),'hello') --asserteq(stringx.strip('--[hello] -- - ','-[] '),'hello') --asserteq(stringx.rstrip('--[hello] -- - ','-[] '),'--[hello') -- ---- -- --local assert_str_round_trip = function(s) -- -- local qs = stringx.quote_string(s) -- local compiled, err = utils.load("return "..qs) -- -- if not compiled then -- print( -- ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): -- format(s, qs, err) -- ) -- error() -- else -- compiled = compiled() -- end -- -- if compiled ~= s then -- print("strinx.quote_string assert Failed: String compiled but did not round trip.") -- print("input string:\t\t",s, #s) -- print("compiled string:\t", compiled, #compiled) -- print("output string:\t\t",qs, #qs) -- error() -- else -- -- print("input string:\t\t",s) -- -- print("compiled string:\t", compiled) -- -- print("output string:\t\t",qs) -- end --end -- --assert_str_round_trip( "normal string with nothing weird.") --assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") -- --assert_str_round_trip( "Unescapped quote \" in the middle") --assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") --assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) --assert_str_round_trip( "[[Completely normal\n long quote. ]]") --assert_str_round_trip( "String with a newline\nending with a closing bracket]") --assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") --assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") --assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') --assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") --assert_str_round_trip( "This\tincludes\ttabs.") --assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") --assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") -- --assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") --assert_str_round_trip('"A quoted string looks like what?"') --assert_str_round_trip( "'I think that it should be quoted, anyway.'") --assert_str_round_trip( "[[Even if they're long quoted.]]") --assert_str_round_trip( "]=]==]") -- --assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") --assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") --assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") --assert_str_round_trip( "") --assert_str_round_trip( " ") --assert_str_round_trip( "\n") --tricky. --assert_str_round_trip( "\r") --assert_str_round_trip( "\r\n") --assert_str_round_trip( "\r1\n") --assert_str_round_trip( "[[") --assert_str_round_trip( "''") --assert_str_round_trip( '""') -diff --git a/extra/penlight/tests/test-tablex.lua b/extra/penlight/tests/test-tablex.lua -index 59213d5..c8bbf01 100644 ---- a/extra/penlight/tests/test-tablex.lua -+++ b/extra/penlight/tests/test-tablex.lua -@@ -16,49 +16,70 @@ end - - - asserteq( -- copy {10,20,30}, -- {10,20,30} -+ copy {10,20,30}, -+ {10,20,30} - ) - - asserteq( -- deepcopy {10,20,{30,40}}, -- {10,20,{30,40}} -+ deepcopy {10,20,{30,40}}, -+ {10,20,{30,40}} -+) -+ -+local t = { -+ a = "hello", -+ b = { -+ c = "world" -+ } -+} -+t.b.d = t.b -+ -+local tcopy = { -+ a = "hello", -+ b = { -+ c = "world" -+ } -+} -+tcopy.b.d = tcopy.b -+ -+asserteq( -+ deepcopy(t), -+ tcopy - ) - - asserteq( -- pairmap(function(i,v) return v end,{10,20,30}), -- {10,20,30} -+ pairmap(function(i,v) return v end,{10,20,30}), -+ {10,20,30} - ) - - asserteq_no_order( -- pairmap(L'_',{fred=10,bonzo=20}), -- {'fred','bonzo'} -+ pairmap(L'_',{fred=10,bonzo=20}), -+ {'fred','bonzo'} - ) - - asserteq_no_order( -- pairmap(function(k,v) return v end,{fred=10,bonzo=20}), -- {10,20} -+ pairmap(function(k,v) return v end,{fred=10,bonzo=20}), -+ {10,20} - ) - - asserteq_no_order( -- pairmap(function(i,v) return v,i end,{10,20,30}), -- {10,20,30} -+ pairmap(function(i,v) return v,i end,{10,20,30}), -+ {10,20,30} - ) - - asserteq( -- pairmap(function(k,v) return {k,v},k end,{one=1,two=2}), -- {one={'one',1},two={'two',2}} -+ pairmap(function(k,v) return {k,v},k end,{one=1,two=2}), -+ {one={'one',1},two={'two',2}} - ) - -- same as above, using string lambdas - asserteq( -- pairmap(L'|k,v|{k,v},k',{one=1,two=2}), -- {one={'one',1},two={'two',2}} -+ pairmap(L'|k,v|{k,v},k',{one=1,two=2}), -+ {one={'one',1},two={'two',2}} - ) - - - asserteq( -- map(function(v) return v*v end,{10,20,30}), -- {100,400,900} -+ map(function(v) return v*v end,{10,20,30}), -+ {100,400,900} - ) - - -- extra arguments to map() are passed to the function; can use -@@ -81,13 +102,13 @@ asserteq( - - -- mapn operates over an arbitrary number of input tables (but use map2 for n=2) - asserteq( -- mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}), -- {111,222,333} -+ mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}), -+ {111,222,333} - ) - - asserteq( -- mapn(math.max, {1,20,300},{10,2,3},{100,200,100}), -- {100,200,300} -+ mapn(math.max, {1,20,300},{10,2,3},{100,200,100}), -+ {100,200,300} - ) - - asserteq( -@@ -186,3 +207,142 @@ asserteq(tablex.reduce('-', {}, 2), 2) - asserteq(tablex.reduce('-', {}), nil) - asserteq(tablex.reduce('-', {1,2,3,4,5}), -13) - asserteq(tablex.reduce('-', {1,2,3,4,5}, 1), -14) -+ -+ -+-- tablex.compare -+do -+ asserteq(tablex.compare({},{}, "=="), true) -+ asserteq(tablex.compare({1,2,3}, {1,2,3}, "=="), true) -+ asserteq(tablex.compare({1,"hello",3}, {1,2,3}, "=="), false) -+ asserteq(tablex.compare( -+ {1,2,3, hello = "world"}, -+ {1,2,3}, -+ function(v1, v2) return v1 == v2 end), -+ true) -- only compares the list part -+end -+ -+ -+-- tablex.rfind -+do -+ local rfind = tablex.rfind -+ local lst = { "Rudolph", "the", "red-nose", "raindeer" } -+ asserteq(rfind(lst, "Santa"), nil) -+ asserteq(rfind(lst, "raindeer", -2), nil) -+ asserteq(rfind(lst, "raindeer"), 4) -+ asserteq(rfind(lst, "Rudolph"), 1) -+ asserteq(rfind(lst, "the", -3), 2) -+ asserteq(rfind(lst, "the", -30), nil) -+ asserteq(rfind({10,10,10},10), 3) -+end -+ -+ -+-- tablex.find_if -+do -+ local fi = tablex.find_if -+ local lst = { "Rudolph", true, false, 15 } -+ asserteq({fi(lst, "==", "Rudolph")}, {1, true}) -+ asserteq({fi(lst, "==", true)}, {2, true}) -+ asserteq({fi(lst, "==", false)}, {3, true}) -+ asserteq({fi(lst, "==", 15)}, {4, true}) -+ -+ local cmp = function(v1, v2) return v1 == v2 and v2 end -+ asserteq({fi(lst, cmp, "Rudolph")}, {1, "Rudolph"}) -+ asserteq({fi(lst, cmp, true)}, {2, true}) -+ asserteq({fi(lst, cmp, false)}, {}) -- 'false' cannot be returned! -+ asserteq({fi(lst, cmp, 15)}, {4, 15}) -+end -+ -+ -+-- tablex.map_named_method -+do -+ local Car = {} -+ Car.__index = Car -+ function Car.new(car) -+ return setmetatable(car or {}, Car) -+ end -+ Car.speed = 0 -+ function Car:faster(increase) -+ self.speed = self.speed + (increase or 1) -+ return self.speed -+ end -+ function Car:slower(self, decrease) -+ self.speed = self.speed - (decrease or 1) -+ return self.speed -+ end -+ -+ local ferrari = Car.new{ name = "Ferrari" } -+ local lamborghini = Car.new{ name = "Lamborghini", speed = 50 } -+ local cars = { ferrari, lamborghini } -+ -+ asserteq(ferrari.speed, 0) -+ asserteq(lamborghini.speed, 50) -+ asserteq(tablex.map_named_method("faster", cars, 10), {10, 60}) -+ asserteq(ferrari.speed, 10) -+ asserteq(lamborghini.speed, 60) -+ -+end -+ -+ -+-- tablex.foreach -+do -+ local lst = { "one", "two", "three", hello = "world" } -+ tablex.foreach(lst, function(v, k, sep) -+ lst[k] = tostring(k) .. sep .. v -+ end, " = ") -+ asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "hello = world"}) -+end -+ -+ -+-- tablex.foreachi -+do -+ local lst = { "one", "two", "three", hello = "world" } -+ tablex.foreachi(lst, function(v, k, sep) -+ lst[k] = tostring(k) .. sep .. v -+ end, " = ") -+ asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "world"}) -+end -+ -+ -+-- tablex.new -+asserteq(tablex.new(3, "hi"), { "hi", "hi", "hi" }) -+ -+ -+-- tablex.search -+do -+ local t = { -+ penlight = { -+ battery = { -+ type = "AA", -+ capacity = "1500mah", -+ }, -+ }, -+ hello = { -+ world = { -+ also = "AA" -+ } -+ } -+ } -+ asserteq(tablex.search(t, "1500mah"), "penlight.battery.capacity") -+ asserteq(tablex.search(t, "AA", {t.penlight} ), "hello.world.also") -+ asserteq(tablex.search(t, "xxx"), nil) -+end -+ -+ -+-- tablex.readonly -+do -+ local ro = tablex.readonly { 1,2,3, hello = "world" } -+ asserteq(pcall(function() ro.hello = "hi there" end), false) -+ asserteq(getmetatable(ro), false) -+ -+ if not utils.lua51 then -+ asserteq(#ro, 3) -+ -+ local r = {} -+ for k,v in pairs(ro) do r[k] = v end -+ asserteq(r, { 1,2,3, hello = "world" }) -+ -+ r = {} -+ for k,v in ipairs(ro) do r[k] = v end -+ asserteq(r, { 1,2,3 }) -+ end -+end -diff --git a/extra/penlight/tests/test-move.lua b/extra/penlight/tests/test-tablex3.lua -similarity index 100% -rename from tests/test-move.lua -rename to tests/test-tablex3.lua -diff --git a/extra/penlight/tests/test-template.lua b/extra/penlight/tests/test-template.lua -index 3cda83d..9c9825a 100644 ---- a/extra/penlight/tests/test-template.lua -+++ b/extra/penlight/tests/test-template.lua -@@ -46,6 +46,29 @@ asserteq(subst([[ - ]]) - - -+-------------------------------------------------------------------------------- -+-- Regression tests for issue #451 (can't use % for escapes) -+asserteq(subst([[ -+% for i = 1,3 do -+ $(text[i]) -+% end -+]],{_parent=_G,_escape='%',text={'foo','bar','baz'}}),[[ -+ foo -+ bar -+ baz -+]]) -+ -+asserteq(subst([[ -+? for i = 1,3 do -+ %(text[i]) -+? end -+]],{_parent=_G,_escape='?',_inline_escape='%',text={'foo','bar','baz'}}),[[ -+ foo -+ bar -+ baz -+]]) -+-------------------------------------------------------------------------------- -+ - - -- handle templates with a lot of substitutions - asserteq(subst(("$(x)\n"):rep(300), {x = "y"}), ("y\n"):rep(300)) -@@ -161,7 +184,7 @@ local my_env = { - ipairs = ipairs, - T = {'one','two','three'} - } --local t, err = template.compile(tmpl, { debug = true, newline = "" }) -+local t, err = template.compile(tmpl, { debug = true, newline = true }) - local res, err, code = t:render(my_env) - --print(res, err, code) - asserteq(res, [[some list: ONE,TWO,THREE]]) -@@ -174,7 +197,7 @@ local tmpl = [[ - header: $("hello" * 10) - ]] - --local t, err = template.compile(tmpl, { debug = true, newline = "" }) -+local t, err = template.compile(tmpl, { debug = true, newline = true }) - local res, err, code = t:render() - --print(res, err, code) - assert(res == nil, "expected nil here because of the runtime error") -@@ -183,6 +206,22 @@ asserteq(type(utils.load(code)), "function") - - - -+-------------------------------------------------- -+-- Test template run-time, doesn't fail on table value -+-- table.concat fails if we insert a non-string (table) value -+local tmpl = [[ -+header: $(myParam) -+]] -+ -+local t, err = template.compile(tmpl, { debug = true, newline = true }) -+local myParam = {} -+local res, err, code = t:render( {myParam = myParam } ) -- insert a table -+--print(res, err, code) -+asserteq(res, "header: "..tostring(myParam)) -+asserteq(type(err), "nil") -+ -+ -+ - -------------------------------------------------- - -- Test template compile-time error - local tmpl = [[ -@@ -193,7 +232,7 @@ local my_env = { - ipairs = ipairs, - T = {'one','two','three'} - } --local t, err, code = template.compile(tmpl, { debug = true, newline = "" }) -+local t, err, code = template.compile(tmpl, { debug = true, newline = true }) - --print(t, err, code) - assert(t==nil, "expected t to be nil here because of the syntax error") - asserteq(type(err), "string") -@@ -223,7 +262,63 @@ asserteq(code, [[return "
      \ -

      a paragraph

      \ -

      a paragraph

      \ -
    \ --"]]) -+";]]) -+ -+ -+ -+-------------------------------------------------- -+-- Test template preserves line numbers, both when -+-- stripping and not stripping newlines -+local tmpl = [[ -+# local function foo(x) -+ a$(x)b$(x + 1)c -+# return x + 2 -+# end -+Hello -+there -+ -+ -+# foo(1) -+foo$(foo)bar -+# -- -+]] -+local expected_num = select(2, string.gsub(tmpl, "\n", "\n")) -+ -+-- Trailing newline, no newline stripping -+local t, err = template.compile(tmpl, { debug = true }) -+local res, err, code = t:render(my_env) -+--print(res, err, code) -+ -+assert(res, "rendering should not fail here") -+asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) -+ -+-- Trailing newline, with newline stripping -+local t, err = template.compile(tmpl, { debug = true, newline = true }) -+local res, err, code = t:render(my_env) -+--print(res, err, code) -+ -+assert(res, "rendering should not fail here") -+asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) -+ -+ -+tmpl = string.sub(code, 1, -2) -- Remove the trailing newline -+-- num_expected remains unchanged because the template will append a trailing newline -+ -+-- No trailing newline, no newline stripping -+local t, err = template.compile(tmpl, { debug = true }) -+local res, err, code = t:render(my_env) -+--print(res, err, code) -+ -+assert(res, "rendering should not fail here") -+asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) -+ -+-- No trailing newline, with newline stripping -+local t, err = template.compile(tmpl, { debug = true, newline = true }) -+local res, err, code = t:render(my_env) -+--print(res, err, code) -+ -+assert(res, "rendering should not fail here") -+asserteq(select(2, string.gsub(code, "\n", "\n")), expected_num) - - - print("template: success") -diff --git a/extra/penlight/tests/test-template2.lua b/extra/penlight/tests/test-template2.lua -new file mode 100644 -index 0000000..14e55a6 ---- /dev/null -+++ b/extra/penlight/tests/test-template2.lua -@@ -0,0 +1,76 @@ -+local T = require 'pl.text' -+local utils = require 'pl.utils' -+local Template = T.Template -+local asserteq = require 'pl.test'.asserteq -+local OrderedMap = require 'pl.OrderedMap' -+local template = require 'pl.template' -+ -+local t = [[ -+# for i = 1,3 do -+ print($(i+1)) -+# end -+]] -+ -+asserteq(template.substitute(t),[[ -+ print(2) -+ print(3) -+ print(4) -+]]) -+ -+t = [[ -+> for i = 1,3 do -+ print(${i+1}) -+> end -+]] -+ -+asserteq(template.substitute(t,{_brackets='{}',_escape='>'}),[[ -+ print(2) -+ print(3) -+ print(4) -+]]) -+ -+t = [[ -+#@ for i = 1,3 do -+ print(@{i+1}) -+#@ end -+]] -+ -+asserteq(template.substitute(t,{_brackets='{}',_escape='#@',_inline_escape='@'}),[[ -+ print(2) -+ print(3) -+ print(4) -+]]) -+ -+--- iteration using pairs is usually unordered. But using OrderedMap -+--- we can get the exact original ordering. -+ -+t = [[ -+# for k,v in pairs(T) do -+ "$(k)", -- $(v) -+# end -+]] -+ -+if utils.lua51 then -+ -- easy enough to define a general pairs in Lua 5.1 -+ local rawpairs = pairs -+ function pairs(t) -+ local mt = getmetatable(t) -+ local f = mt and mt.__pairs -+ if f then -+ return f(t) -+ else -+ return rawpairs(t) -+ end -+ end -+end -+ -+ -+local Tee = OrderedMap{{Dog = 'Bonzo'}, {Cat = 'Felix'}, {Lion = 'Leo'}} -+ -+-- note that the template will also look up global functions using _parent -+asserteq(template.substitute(t,{T=Tee,_parent=_G}),[[ -+ "Dog", -- Bonzo -+ "Cat", -- Felix -+ "Lion", -- Leo -+]]) -+ -diff --git a/extra/penlight/tests/test-text.lua b/extra/penlight/tests/test-text.lua -deleted file mode 100644 -index 9419b22..0000000 ---- a/extra/penlight/tests/test-text.lua -+++ /dev/null -@@ -1,177 +0,0 @@ --local T = require 'pl.text' --local utils = require 'pl.utils' --local Template = T.Template --local asserteq = require 'pl.test'.asserteq --local OrderedMap = require 'pl.OrderedMap' -- --local t1 = Template [[ --while true do -- $contents --end --]] -- --assert(t1:substitute {contents = 'print "hello"'},[[ --while true do -- print "hello" --end --]]) -- --assert(t1:indent_substitute {contents = [[ --for i = 1,10 do -- gotcha(i) --end --]]},[[ --while true do -- for i = 1,10 do -- gotcha(i) -- end --end --]]) -- --asserteq(T.dedent [[ -- one -- two -- three --]],[[ --one --two --three --]]) --asserteq(T.fill ([[ --It is often said of Lua that it does not include batteries. That is because the goal of Lua is to produce a lean expressive language that will be used on all sorts of machines, (some of which don't even have hierarchical filesystems). The Lua language is the equivalent of an operating system kernel; the creators of Lua do not see it as their responsibility to create a full software ecosystem around the language. That is the role of the community. --]],20),[[ --It is often said of Lua --that it does not include --batteries. That is because --the goal of Lua is to --produce a lean expressive --language that will be --used on all sorts of machines, --(some of which don't --even have hierarchical --filesystems). The Lua --language is the equivalent --of an operating system --kernel; the creators of --Lua do not see it as their --responsibility to create --a full software ecosystem --around the language. That --is the role of the community. --]]) -- --local template = require 'pl.template' -- --local t = [[ --# for i = 1,3 do -- print($(i+1)) --# end --]] -- --asserteq(template.substitute(t),[[ -- print(2) -- print(3) -- print(4) --]]) -- --t = [[ --> for i = 1,3 do -- print(${i+1}) --> end --]] -- --asserteq(template.substitute(t,{_brackets='{}',_escape='>'}),[[ -- print(2) -- print(3) -- print(4) --]]) -- --t = [[ --#@ for i = 1,3 do -- print(@{i+1}) --#@ end --]] -- --asserteq(template.substitute(t,{_brackets='{}',_escape='#@',_inline_escape='@'}),[[ -- print(2) -- print(3) -- print(4) --]]) -- ----- iteration using pairs is usually unordered. But using OrderedMap ----- we can get the exact original ordering. -- --t = [[ --# for k,v in pairs(T) do -- "$(k)", -- $(v) --# end --]] -- --if utils.lua51 then -- -- easy enough to define a general pairs in Lua 5.1 -- local rawpairs = pairs -- function pairs(t) -- local mt = getmetatable(t) -- local f = mt and mt.__pairs -- if f then -- return f(t) -- else -- return rawpairs(t) -- end -- end --end -- -- --local Tee = OrderedMap{{Dog = 'Bonzo'}, {Cat = 'Felix'}, {Lion = 'Leo'}} -- ---- note that the template will also look up global functions using _parent --asserteq(template.substitute(t,{T=Tee,_parent=_G}),[[ -- "Dog", -- Bonzo -- "Cat", -- Felix -- "Lion", -- Leo --]]) -- ---- for those with a fondness for Python-style % formatting... --T.format_operator() --asserteq('[%s]' % 'home', '[home]') --asserteq('%s = %d' % {'fred',42},'fred = 42') -- ---- mostly works like string.format, except that %s forces use of tostring() ---- rather than throwing an error --local List = require 'pl.List' --asserteq('TBL:%s' % List{1,2,3},'TBL:{1,2,3}') -- ---- table with keys and format with $ --asserteq('<$one>' % {one=1}, '<1>') ---- (second arg may also be a function, like os.getenv) --function subst(k) -- if k == 'A' then return 'ay' -- elseif k == 'B' then return 'bee' -- else return '?' -- end --end --asserteq( -- '$A & $B' % subst,'ay & bee' --) -- --t = [[ --a whole lot --of love --]] -- --asserteq(T.indent(t,4),[[ -- a whole lot -- of love --]]) -- --asserteq(T.indent([[ --easy -- --enough! --]],2,'*'),[[ --**easy --** --**enough! --]]) -- -- -diff --git a/extra/penlight/tests/test-types.lua b/extra/penlight/tests/test-types.lua -index 075b970..7cf313d 100644 ---- a/extra/penlight/tests/test-types.lua -+++ b/extra/penlight/tests/test-types.lua -@@ -7,33 +7,133 @@ local list = List() - local array = {10,20,30} - local map = {one=1,two=2} - ---- extened type() function -+-- extended type() function - asserteq(types.type(array),'table') - asserteq(types.type('hello'),'string') - -- knows about Lua file objects - asserteq(types.type(io.stdin),'file') -+local f = io.open("tests/test-types.lua") -+asserteq(types.type(f),'file') -+f:close() - -- and class names - asserteq(types.type(list),'List') -+-- tables with unknown metatable -+asserteq(types.type(setmetatable({},{})), "unknown table") -+-- userdata with unknown metatable -+if newproxy then -+ asserteq(types.type(newproxy(true)), "unknown userdata") -+end - - asserteq(types.is_integer(10),true) - asserteq(types.is_integer(10.1),false) -+asserteq(types.is_integer(-10),true) -+asserteq(types.is_integer(-10.1),false) - -- do note that for Lua < 5.3, 10.0 is the same as 10; an integer. - - asserteq(types.is_callable(asserteq),true) - asserteq(types.is_callable(List),true) -+do -+ local mt = setmetatable({}, { -+ __index = { -+ __call = function() return "ok" end -+ } -+ }) -+ asserteq(type(mt.__call), "function") -- __call is looked-up through another metatable -+ local nc = setmetatable({}, mt) -+ -- proof-of-pudding, let's call it. To verify Lua behaves the same on all engines -+ local success, result = pcall(function() return nc() end) -+ assert(result ~= "ok", "expected result to not be 'ok'") -+ asserteq(success, false) -+ -- real test now -+ asserteq(types.is_callable(nc), false) -- NOT callable, since __call is fetched using RAWget by Lua -+end - - asserteq(types.is_indexable(array),true) --asserteq(types.is_iterable(array),true) - asserteq(types.is_indexable('hello'),nil) - asserteq(types.is_indexable(10),nil) -+if newproxy then -+ local v = newproxy(true) -+ local mt = getmetatable(v) -+ mt.__len = true -+ mt.__index = true -+ asserteq(types.is_indexable(v), true) -+end -+if newproxy then -+ local v = newproxy(true) -+ asserteq(types.is_indexable(v), nil) -+end -+ -+asserteq(types.is_iterable(array),true) -+asserteq(types.is_iterable(true),nil) -+asserteq(types.is_iterable(42),nil) -+asserteq(types.is_iterable("array"),nil) -+if newproxy then -+ local v = newproxy(true) -+ local mt = getmetatable(v) -+ mt.__pairs = true -+ asserteq(types.is_iterable(v), true) -+end -+if newproxy then -+ local v = newproxy(true) -+ asserteq(types.is_iterable(v), nil) -+end -+ -+asserteq(types.is_writeable(array),true) -+asserteq(types.is_writeable(true),nil) -+asserteq(types.is_writeable(42),nil) -+asserteq(types.is_writeable("array"),nil) -+if newproxy then -+ local v = newproxy(true) -+ local mt = getmetatable(v) -+ mt.__newindex = true -+ asserteq(types.is_writeable(v), true) -+end -+if newproxy then -+ local v = newproxy(true) -+ asserteq(types.is_writeable(v), nil) -+end - - asserteq(types.is_empty(nil),true) - asserteq(types.is_empty({}),true) -+asserteq(types.is_empty({[false] = false}),false) - asserteq(types.is_empty(""),true) - asserteq(types.is_empty(" ",true),true) -+asserteq(types.is_empty(" "),false) -+asserteq(types.is_empty(true),true) -+-- Numbers -+asserteq(types.is_empty(0), true) -+asserteq(types.is_empty(20), true) -+-- Booleans -+asserteq(types.is_empty(false), true) -+asserteq(types.is_empty(true), true) -+-- Functions -+asserteq(types.is_empty(print), true) -+-- Userdata -+--asserteq(types.is_empty(newproxy()), true) --newproxy was removed in Lua 5.2 - - -- a more relaxed kind of truthiness.... - asserteq(types.to_bool('yes'),true) - asserteq(types.to_bool('true'),true) -+asserteq(types.to_bool('y'),true) -+asserteq(types.to_bool('t'),true) -+asserteq(types.to_bool('YES'),true) -+asserteq(types.to_bool('1'),true) -+asserteq(types.to_bool('no'),false) -+asserteq(types.to_bool('false'),false) -+asserteq(types.to_bool('n'),false) -+asserteq(types.to_bool('f'),false) -+asserteq(types.to_bool('NO'),false) -+asserteq(types.to_bool('0'),false) - asserteq(types.to_bool(1),true) --asserteq(types.to_bool(0),false) -\ No newline at end of file -+asserteq(types.to_bool(0),false) -+local de_fr = { 'ja', 'oui' } -+asserteq(types.to_bool('ja', de_fr),true) -+asserteq(types.to_bool('OUI', de_fr),true) -+local t_e = {} -+local t_ne = { "not empty" } -+asserteq(types.to_bool(t_e,nil,false),false) -+asserteq(types.to_bool(t_e,nil,true),false) -+asserteq(types.to_bool(t_ne,nil,false),false) -+asserteq(types.to_bool(t_ne,nil,true),true) -+asserteq(types.to_bool(coroutine.create(function() end),nil,true),true) -+asserteq(types.to_bool(coroutine.create(function() end),nil,false),false) -diff --git a/extra/penlight/tests/test-tzone.lua b/extra/penlight/tests/test-tzone.lua -deleted file mode 100644 -index 230fb18..0000000 ---- a/extra/penlight/tests/test-tzone.lua -+++ /dev/null -@@ -1,10 +0,0 @@ --local Date = require 'pl.Date' --local test = require 'pl.test' --local df = Date.Format() --local dl = df:parse '2008-07-05' --local du = dl:toUTC() -- --test.asserteq(dl,du) -- -- -- -diff --git a/extra/penlight/tests/test-url.lua b/extra/penlight/tests/test-url.lua -index 656415c..9d9af6a 100644 ---- a/extra/penlight/tests/test-url.lua -+++ b/extra/penlight/tests/test-url.lua -@@ -30,3 +30,8 @@ asserteq(url.unquote('%60%7E%21%40%23%24%25%5E%26%2A%28%29'), '`~!@#$%^&*()') - asserteq(url.unquote('%252'), '%2') - asserteq(url.unquote('2%52%1%%'), '2R%1%%') - asserteq(url.unquote('2R%251%25%25'), '2R%1%%') -+ -+asserteq(url.quote(true), true) -+asserteq(url.quote(42), 42) -+asserteq(url.unquote(true), true) -+asserteq(url.unquote(42), 42) -diff --git a/extra/penlight/tests/test-utils.lua b/extra/penlight/tests/test-utils.lua -index b115bea..cff400a 100644 ---- a/extra/penlight/tests/test-utils.lua -+++ b/extra/penlight/tests/test-utils.lua -@@ -3,10 +3,71 @@ local path = require 'pl.path' - local test = require 'pl.test' - local asserteq, T = test.asserteq, test.tuple - ----- escaping magic chars --local escape = utils.escape --asserteq(escape '[a]','%[a%]') --asserteq(escape '$(bonzo)','%$%(bonzo%)') -+ -+local function quote(s) -+ if utils.is_windows then -+ return '"'..s..'"' -+ else -+ return "'"..s.."'" -+ end -+end -+ -+-- construct command to run external lua, we need to to be able to run some -+-- tests on the same lua engine, but also need to pass on the LuaCov flag -+-- if it was used, to make sure we report the proper coverage. -+local cmd = "-e " -+do -+ local i = 0 -+ while arg[i-1] do -+ local a = arg[i-1] -+ if a:find("package%.path") and a:sub(1,1) ~= "'" then -+ a = quote(a) -+ end -+ cmd = a .. " " .. cmd -+ i = i - 1 -+ end -+end -+ -+ -+--- quitting -+do -+ local luacode = quote("require([[pl.utils]]).quit([[hello world]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ if utils.is_windows then -+ asserteq(code, -1) -+ else -+ asserteq(code, 255) -+ end -+ asserteq(stdout, "") -+ asserteq(stderr, "hello world\n") -+ -+ local luacode = quote("require([[pl.utils]]).quit(2, [[hello world]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ asserteq(code, 2) -+ asserteq(stdout, "") -+ asserteq(stderr, "hello world\n") -+ -+ local luacode = quote("require([[pl.utils]]).quit(2, [[hello %s]], 42)") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ asserteq(code, 2) -+ asserteq(stdout, "") -+ asserteq(stderr, "hello 42\n") -+ -+ local luacode = quote("require([[pl.utils]]).quit(2)") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ asserteq(code, 2) -+ asserteq(stdout, "") -+ asserteq(stderr, "") -+end -+ -+----- importing module tables wholesale --- -+utils.import(math) -+asserteq(type(sin),"function") -+asserteq(type(abs),"function") - - --- useful patterns - local P = utils.patterns -@@ -14,6 +75,15 @@ asserteq(("+0.1e10"):match(P.FLOAT) ~= nil, true) - asserteq(("-23430"):match(P.INTEGER) ~= nil, true) - asserteq(("my_little_pony99"):match(P.IDEN) ~= nil, true) - -+--- escaping magic chars -+local escape = utils.escape -+asserteq(escape '[a]','%[a%]') -+asserteq(escape '$(bonzo)','%$%(bonzo%)') -+ -+--- choose -+asserteq(utils.choose(true, 1, 2), 1) -+asserteq(utils.choose(false, 1, 2), 2) -+ - --- splitting strings --- - local split = utils.split - asserteq(split("hello dolly"),{"hello","dolly"}) -@@ -22,6 +92,12 @@ asserteq(split("hello,dolly,",","),{"hello","dolly"}) - - local first,second = utils.splitv("hello:dolly",":") - asserteq(T(first,second),T("hello","dolly")) -+local first,second = utils.splitv("hello:dolly:parton",":", false, 2) -+asserteq(T(first,second),T("hello","dolly:parton")) -+local first,second,third = utils.splitv("hello=dolly:parton","[:=]") -+asserteq(T(first,second,third),T("hello","dolly","parton")) -+local first,second = utils.splitv("hello=dolly:parton","[:=]", false, 2) -+asserteq(T(first,second),T("hello","dolly:parton")) - - ----- table of values to table of strings - asserteq(utils.array_tostring{1,2,3},{"1","2","3"}) -@@ -117,12 +193,128 @@ else - asserteq(utils.quote_arg([['a\'b]]), [[''\''a\'\''b']]) - end - ------- importing module tables wholesale --- --utils.import(math) --asserteq(type(sin),"function") --asserteq(type(abs),"function") -+-- packing and unpacking arguments in a nil-safe way -+local t = utils.pack(nil, nil, "hello", nil) -+asserteq(t.n, 4) -- the last nil does count as an argument -+ -+local arg1, arg2, arg3, arg4 = utils.unpack(t) -+assert(arg1 == nil) -+assert(arg2 == nil) -+asserteq("hello", arg3) -+assert(arg4 == nil) - - -+-- Assert arguments assert_arg -+local ok, err = pcall(function() -+ utils.assert_arg(4,'!@#$%^&*','string',require("pl.path").isdir,'not a directory') -+end) -+asserteq(ok, false) -+asserteq(err:match("(argument .+)$"), "argument 4: '!@#$%^&*' not a directory") -+ -+local ok, err = pcall(function() -+ utils.assert_arg(1, "hello", "table") -+end) -+asserteq(ok, false) -+asserteq(err:match("(argument .+)$"), "argument 1 expected a 'table', got a 'string'") -+ -+local ok, err = pcall(function() -+ return utils.assert_arg(1, "hello", "string") -+end) -+asserteq(ok, true) -+asserteq(err, "hello") -+ -+-- assert_string -+local success, err = pcall(utils.assert_string, 2, 5) -+asserteq(success, false) -+asserteq(err:match("(argument .+)$"), "argument 2 expected a 'string', got a 'number'") - -+local x = utils.assert_string(2, "5") -+asserteq(x, "5") - - -+do -+ -- printf -- without template -+ local luacode = quote("require([[pl.utils]]).printf([[hello world]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, true) -+ asserteq(code, 0) -+ asserteq(stdout, "hello world") -+ asserteq(stderr, "") -+ -+ -- printf -- with template -+ local luacode = quote("require([[pl.utils]]).printf([[hello %s]], [[world]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, true) -+ asserteq(code, 0) -+ asserteq(stdout, "hello world") -+ asserteq(stderr, "") -+ -+ -- printf -- with bad template -+ local luacode = quote("require([[pl.utils]]).printf(42)") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ asserteq(code, 1) -+ asserteq(stdout, "") -+ assert(stderr:find("argument 1 expected a 'string', got a 'number'")) -+end -+ -+do -+ -- on_error, raise -- default -+ utils.on_error("default") -+ local ok, err = utils.raise("some error") -+ asserteq(ok, nil) -+ asserteq(err, "some error") -+ local ok, err = pcall(utils.on_error, "bad one") -+ asserteq(ok, false) -+ asserteq(err, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'") -+ -+ -- on_error, raise -- error -+ utils.on_error("error") -+ local ok, err = pcall(utils.raise, "some error") -+ asserteq(ok, false) -+ asserteq(err, "some error") -+ local ok, err = pcall(utils.on_error, "bad one") -+ asserteq(ok, false) -+ assert(err:find("Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'")) -+ -+ -- on_error, raise -- quit -+ utils.on_error("quit") -+ local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.raise([[some error]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ if utils.is_windows then -+ asserteq(code, -1) -+ else -+ asserteq(code, 255) -+ end -+ asserteq(stdout, "") -+ asserteq(stderr, "some error\n") -+ -+ local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.on_error([[bad one]])") -+ local success, code, stdout, stderr = utils.executeex(cmd..luacode) -+ asserteq(success, false) -+ if utils.is_windows then -+ asserteq(code, -1) -+ else -+ asserteq(code, 255) -+ end -+ asserteq(stdout, "") -+ asserteq(stderr, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'\n") -+ -+ utils.on_error("default") -- cleanup by restoring behaviour after on_error + raise tests -+end -+ -+do -+ -- readlines -+ local f = utils.readlines("tests/test-utils.lua") -+ asserteq(type(f), "table") -+ local v = "some extraordinary string this is only in this file for test purposes so we can go and find it" -+ local found = false -+ for i, line in ipairs(f) do -+ if line:find(v) then -+ found = true -+ break -+ end -+ end -+ asserteq(found, true) -+end -diff --git a/extra/penlight/tests/test-utils2.lua b/extra/penlight/tests/test-utils2.lua -new file mode 100644 -index 0000000..897da3e ---- /dev/null -+++ b/extra/penlight/tests/test-utils2.lua -@@ -0,0 +1,53 @@ -+local path = require 'pl.path' -+local utils = require 'pl.utils' -+local asserteq = require 'pl.test'.asserteq -+ -+local echo_lineending = "\n" -+if path.is_windows then -+ echo_lineending = " \n" -+end -+ -+local function test_executeex(cmd, expected_successful, expected_retcode, expected_stdout, expected_stderr) -+--print("\n"..cmd) -+--print(os.execute(cmd)) -+--print(utils.executeex(cmd)) -+ local successful, retcode, stdout, stderr = utils.executeex(cmd) -+ asserteq(successful, expected_successful) -+ asserteq(retcode, expected_retcode) -+ asserteq(stdout, expected_stdout) -+ asserteq(stderr, expected_stderr) -+end -+ -+-- Check the return codes -+if utils.is_windows then -+ test_executeex("exit", true, 0, "", "") -+ test_executeex("exit 0", true, 0, "", "") -+ test_executeex("exit 1", false, 1, "", "") -+ test_executeex("exit 13", false, 13, "", "") -+ test_executeex("exit 255", false, 255, "", "") -+ test_executeex("exit 256", false, 256, "", "") -+ test_executeex("exit 257", false, 257, "", "") -+ test_executeex("exit 3809", false, 3809, "", "") -+ test_executeex("exit -1", false, -1, "", "") -+ test_executeex("exit -13", false, -13, "", "") -+ test_executeex("exit -255", false, -255, "", "") -+ test_executeex("exit -256", false, -256, "", "") -+ test_executeex("exit -257", false, -257, "", "") -+ test_executeex("exit -3809", false, -3809, "", "") -+else -+ test_executeex("exit", true, 0, "", "") -+ test_executeex("exit 0", true, 0, "", "") -+ test_executeex("exit 1", false, 1, "", "") -+ test_executeex("exit 13", false, 13, "", "") -+ test_executeex("exit 255", false, 255, "", "") -+ -- on posix anything other than 0-255 is undefined -+ test_executeex("exit 256", true, 0, "", "") -+ test_executeex("exit 257", false, 1, "", "") -+ test_executeex("exit 3809", false, 225, "", "") -+end -+ -+-- Check output strings -+test_executeex("echo stdout", true, 0, "stdout" .. echo_lineending, "") -+test_executeex("(echo stderr 1>&2)", true, 0, "", "stderr" .. echo_lineending) -+test_executeex("(echo stdout && (echo stderr 1>&2))", true, 0, "stdout" .. echo_lineending, "stderr" .. echo_lineending) -+ -diff --git a/extra/penlight/tests/test-fenv.lua b/extra/penlight/tests/test-utils3.lua -similarity index 94% -rename from tests/test-fenv.lua -rename to tests/test-utils3.lua -index f16b327..a635de5 100644 ---- a/extra/penlight/tests/test-fenv.lua -+++ b/extra/penlight/tests/test-utils3.lua -@@ -41,8 +41,8 @@ if not package.path:find '.[/\\]%?' then - end - - asserteq( -- package.searchpath('tests.test-fenv',package.path):gsub('\\','/'), -- './tests/test-fenv.lua' -+ package.searchpath('tests.test-utils3',package.path):gsub('\\','/'), -+ './tests/test-utils3.lua' - ) - - -- testing getfenv and setfenv for both interpreters -diff --git a/extra/penlight/tests/test-xml.lua b/extra/penlight/tests/test-xml.lua -index 5e32eab..e1b541e 100644 ---- a/extra/penlight/tests/test-xml.lua -+++ b/extra/penlight/tests/test-xml.lua -@@ -1,21 +1,34 @@ - local xml = require 'pl.xml' - local asserteq = require 'pl.test'.asserteq - local dump = require 'pl.pretty'.dump -+local path = require 'pl.path' -+local utils = require 'pl.utils' - - -- Prosody stanza.lua style XML building - - d = xml.new 'top' : addtag 'child' : text 'alice' : up() : addtag 'child' : text 'bob' - - d = xml.new 'children' : -- addtag 'child' : -- addtag 'name' : text 'alice' : up() : addtag 'age' : text '5' : up() : addtag('toy',{type='fluffy'}) : up() : -- up() : -- addtag 'child': -- addtag 'name' : text 'bob' : up() : addtag 'age' : text '6' : up() : addtag('toy',{type='squeaky'}) -- --asserteq( --xml.tostring(d,'',' '), --[[ -+ addtag 'child' : -+ addtag 'name' : -+ text 'alice' : -+ up() : -+ addtag 'age' : -+ text '5' : -+ up() : -+ addtag('toy',{type='fluffy'}) : -+ up() : -+ up() : -+ addtag 'child': -+ addtag 'name' : -+ text 'bob' : -+ up() : -+ addtag 'age' : -+ text '6' : -+ up() : -+ addtag('toy',{type='squeaky'}) -+ -+asserteq(xml.tostring(d,'',' '), [[ - - - -@@ -38,7 +51,6 @@ d1 = children { - child {name 'alice', age '5', toy {type='fluffy'}}, - child {name 'bob', age '6', toy {type='squeaky'}} - } -- - assert(xml.compare(d,d1)) - - -- or we can use a template document to convert Lua data to LOM -@@ -133,8 +145,8 @@ t1 = [[ - - - match(t1,{ -- condition = "Clear", -- temp = "24", -+ condition = "Clear", -+ temp = "24", - } ) - - t2 = [[ -@@ -149,30 +161,30 @@ t2 = [[ - ]] - - local conditions = { -- { -- low = "60", -- high = "89", -- day = "Sat", -- condition = "Clear", -- }, -- { -- low = "53", -- high = "86", -- day = "Sun", -- condition = "Clear", -- }, -- { -- low = "57", -- high = "87", -- day = "Mon", -- condition = "Clear", -- }, -- { -- low = "60", -- high = "84", -- day = "Tue", -- condition = "Clear", -- } -+ { -+ low = "60", -+ high = "89", -+ day = "Sat", -+ condition = "Clear", -+ }, -+ { -+ low = "53", -+ high = "86", -+ day = "Sun", -+ condition = "Clear", -+ }, -+ { -+ low = "57", -+ high = "87", -+ day = "Mon", -+ condition = "Clear", -+ }, -+ { -+ low = "60", -+ high = "84", -+ day = "Tue", -+ condition = "Clear", -+ } - } - - match(t2,conditions) -@@ -195,9 +207,9 @@ match([[ - {{$value}} - - ]],{ -- {key="alpha", value = "1.3"}, -- {key="beta", value = "10"}, -- {key="name",value = "bozo"}, -+ {key="alpha", value = "1.3"}, -+ {key="beta", value = "10"}, -+ {key="name",value = "bozo"}, - }) - -- can be numerical indices - match([[ -@@ -205,9 +217,9 @@ match([[ - {{<1->$2}} - - ]],{ -- {"alpha","1.3"}, -- {"beta","10"}, -- {"name","bozo"}, -+ {"alpha","1.3"}, -+ {"beta","10"}, -+ {"name","bozo"}, - }) - -- _ is special; means 'this value is key of captured table' - match([[ -@@ -215,9 +227,9 @@ match([[ - {{<_->$1}} - - ]],{ -- alpha = {"1.3"}, -- beta = {"10"}, -- name = {"bozo"}, -+ alpha = {"1.3"}, -+ beta = {"10"}, -+ name = {"bozo"}, - }) - - -- the numerical index 0 is special: a capture of {[0]=val} becomes simply the value val -@@ -226,9 +238,9 @@ match([[ - {{<_->$0}} - - ]],{ -- alpha = "1.3", -- name = "bozo", -- beta = "10" -+ alpha = "1.3", -+ name = "bozo", -+ beta = "10" - }) - - -- this can of course also work with attributes, but then we don't want to collapse! -@@ -248,9 +260,9 @@ match([[ - {{<_- type='$1'>$2}} - - ]],{ -- alpha = {"number","1.3"}, -- beta = {"number","10"}, -- name = {"string","bozo"}, -+ alpha = {"number","1.3"}, -+ beta = {"number","10"}, -+ name = {"string","bozo"}, - }) - - d,err = parse [[ -@@ -279,16 +291,16 @@ res,err = d:match [[ - ]] - - asserteq(res,{ -- HOST = "windows-unknown-linux-gnu", -- COPYRIGHT = "Copyright (C) 1999-2009 ImageMagick Studio LLC", -- NAME = "ImageMagick", -- LIB_VERSION = "0x651", -- VERSION = "6.5.1", -- RELEASE_DATE = "2009-05-01", -- WEBSITE = "http://www.imagemagick.org", -- LIB_VERSION_NUMBER = "6,5,1,3", -- CC = "vs7", -- DELEGATES = "bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib" -+ HOST = "windows-unknown-linux-gnu", -+ COPYRIGHT = "Copyright (C) 1999-2009 ImageMagick Studio LLC", -+ NAME = "ImageMagick", -+ LIB_VERSION = "0x651", -+ VERSION = "6.5.1", -+ RELEASE_DATE = "2009-05-01", -+ WEBSITE = "http://www.imagemagick.org", -+ LIB_VERSION_NUMBER = "6,5,1,3", -+ CC = "vs7", -+ DELEGATES = "bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib" - }) - - -- short excerpt from -@@ -524,3 +536,17 @@ asserteq(xml.tostring(doc),[[ - dolly]]) - - -+-- parsing by file name -+ -+local filename = path.tmpname() -+utils.writefile(filename, '') -+doc = xml.parse(filename, true, true) -+os.remove(filename) -+asserteq(type(doc), 'table') -+asserteq(xml.tostring(doc, '', ' ', nil, true), [[ -+ -+ -+ -+]]) -+ -+ -Submodule pkg/optim 656c42a..a5ceed7: -diff --git a/pkg/optim/adam.lua b/pkg/optim/adam.lua -index bc80b5e..2e127e9 100644 ---- a/pkg/optim/adam.lua -+++ b/pkg/optim/adam.lua -@@ -1,4 +1,4 @@ ----[[ An implementation of Adam http://arxiv.org/pdf/1412.6980.pdf -+--[[ An implementation of Adam https://arxiv.org/abs/1412.6980 - - ARGS: - diff --git a/install-x86.sh b/install-x86.sh new file mode 100755 index 000000000..eae7ba186 --- /dev/null +++ b/install-x86.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +SKIP_RC=0 +BATCH_INSTALL=0 + +THIS_DIR=$(cd $(dirname $0); pwd) +if [[ "$THIS_DIR" == *" "* ]]; then + echo "$THIS_DIR: Torch cannot install to a path containing whitespace. +Please try a different path, one without any spaces." + exit 1 +fi +PREFIX=${PREFIX:-"${THIS_DIR}/install"} +TORCH_LUA_VERSION=${TORCH_LUA_VERSION:-"LUA51"} # by default install LUA51 + +while getopts 'bsh' x; do + case "$x" in + h) + echo "usage: $0 +This script will install Torch and related, useful packages into $PREFIX. + + -b Run without requesting any user input (will automatically add PATH to shell profile) + -s Skip adding the PATH to shell profile +" + exit 2 + ;; + b) + BATCH_INSTALL=1 + ;; + s) + SKIP_RC=1 + ;; + esac +done + +# Scrub an anaconda/conda install, if exists, from the PATH. +# It has a malformed MKL library (as of 1/17/2015) +OLDPATH=$PATH +if [[ $(echo $PATH | grep conda) ]]; then + export PATH=$(echo $PATH | tr ':' '\n' | grep -v "conda[2-9]\?" | uniq | tr '\n' ':') +fi + +echo "Prefix set to $PREFIX" + +if [[ `uname` == 'Linux' ]]; then + export CMAKE_LIBRARY_PATH=$PREFIX/include:/opt/OpenBLAS/include:$PREFIX/lib:/opt/OpenBLAS/lib:$CMAKE_LIBRARY_PATH +fi +export CMAKE_PREFIX_PATH=$PREFIX + +git submodule update --init --recursive + +# If we're on OS X, use clang +if [[ `uname` == "Darwin" ]]; then + # make sure that we build with Clang. CUDA's compiler nvcc + # does not play nice with any recent GCC version. + export CC=clang + export CXX=clang++ +fi +# If we're on Arch linux, use gcc v5 +if [[ `uname -a` == *"ARCH"* ]]; then + path_to_gcc5=$(which gcc-5) + if [ -x "$path_to_gcc5" ]; then + export CC="$path_to_gcc5" + else + echo "Warning: GCC v5 not found. CUDA v8 is incompatible with GCC v6, if installation fails, consider running \$ pacman -S gcc5" + fi +fi + +echo "Installing Lua version: ${TORCH_LUA_VERSION}" +mkdir -p install +mkdir -p build +cd build + +cmake .. -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON 2>&1 >>$PREFIX/install.log || exit 1 +(make 2>&1 >>$PREFIX/install.log || exit 1) && (make install 2>&1 >>$PREFIX/install.log || exit 1) +cd .. \ No newline at end of file diff --git a/riscv64_cross_compilation_setup.sh b/riscv64_cross_compilation_setup.sh new file mode 100755 index 000000000..6f13ed253 --- /dev/null +++ b/riscv64_cross_compilation_setup.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash + +set -e # Exit on any error + +# ============================================================================== +# Configuration and Setup +# ============================================================================== + +THIS_DIR=$(cd "$(dirname "$0")"; pwd) +PREFIX=${PREFIX:-"${THIS_DIR}/install"} +RISCV_PREFIX="${PREFIX}/riscv64-cmake" + +# Cross-compilation toolchain configuration +export CC="riscv64-linux-gnu-gcc" +export CXX="riscv64-linux-gnu-g++" +export AR="riscv64-linux-gnu-ar" +export RANLIB="riscv64-linux-gnu-ranlib" + +# Compiler flags for RISC-V 64-bit with disabled SIMD +export CFLAGS="-march=rv64gc -mabi=lp64d -fPIC" +export CXXFLAGS="-march=rv64gc -mabi=lp64d -fPIC" +export LDFLAGS="-latomic -lm -lpthread" + +# CMake configuration arguments (reused across all builds) +CMAKE_ARGS=( + "CC=$CC" + "CXX=$CXX" + "AR=$AR" + "RANLIB=$RANLIB" + "STRIP=$STRIP" + "CFLAGS=$CFLAGS" + "CXXFLAGS=$CXXFLAGS" + "LDFLAGS=$LDFLAGS" + "CMAKE_C_COMPILER=$CC" + "CMAKE_CXX_COMPILER=$CXX" + "CMAKE_C_FLAGS=$CFLAGS" + "CMAKE_CXX_FLAGS=$CXXFLAGS" + "CMAKE_SHARED_LINKER_FLAGS=$LDFLAGS" + "CMAKE_POSITION_INDEPENDENT_CODE=ON" + "BUILD_SHARED_LIBS=OFF" +) + +# ============================================================================== +# Utility Functions +# ============================================================================== + +# Function to install a luarocks package with cross-compilation settings +install_luarock() { + local package_dir="$1" + local rockspec="$2" + local exit_code="${3:-1}" # Default exit code is 1 + + echo "Installing $(basename "$package_dir")..." + + if [[ ! -d "$package_dir" ]]; then + echo "Error: Directory $package_dir not found" + exit "$exit_code" + fi + + cd "$package_dir" + "$PREFIX/bin/luarocks" make "$rockspec" \ + --tree="$RISCV_PREFIX" \ + "${CMAKE_ARGS[@]}" || exit "$exit_code" + + echo "✓ Successfully installed $(basename "$package_dir")" +} + +# Function to validate cross-compiler +validate_cross_compiler() { + echo "Testing cross-compiler..." + if echo 'int main(){return 0;}' | $CC $CFLAGS -x c - -o test_riscv 2>/dev/null; then + rm -f test_riscv + echo "✓ Cross-compiler working" + else + echo "ERROR: Cross-compiler not working. Install with: sudo apt install gcc-riscv64-linux-gnu" + exit 1 + fi +} + +# ============================================================================== +# Cleanup and Preparation +# ============================================================================== + +echo "Cleaning previous build artifacts..." +find . -name "build" -type d -path "*/pkg/*" -exec rm -rf {} + 2>/dev/null || true +find . -name "*.so" -path "*/pkg/*" -delete 2>/dev/null || true +find . -name "*.so" -path "*/extra/*" -delete 2>/dev/null || true + +# Verify native luarocks is available +if [[ ! -f "$PREFIX/bin/luarocks" ]]; then + echo "Error: Native luarocks not found. Please run './install-x86.sh -b' first" + exit 1 +fi + +# Setup Lua environment +echo "Setting up Lua environment..." +setup_lua_env_cmd=$("$PREFIX/bin/luarocks" path) +eval "$setup_lua_env_cmd" + +# Create necessary directories +mkdir -p "$RISCV_PREFIX/lib/lua/5.1" +mkdir -p "$RISCV_PREFIX/share/lua/5.1" + +# Validate cross-compiler +validate_cross_compiler + +echo "✓ Setup complete. Starting package installation..." + +# ============================================================================== +# Common Lua Packages Installation +# ============================================================================== + +echo "" +echo "=== Installing Common Lua Packages ===" + +install_luarock "${THIS_DIR}/extra/luafilesystem" "rockspecs/luafilesystem-1.6.3-1.rockspec" +install_luarock "${THIS_DIR}/extra/penlight" "penlight-scm-1.rockspec" +install_luarock "${THIS_DIR}/extra/lua-cjson" "lua-cjson-2.1devel-1.rockspec" + +# ============================================================================== +# Core Torch Packages Installation +# ============================================================================== + +echo "" +echo "=== Installing Core Torch Packages ===" + +install_luarock "${THIS_DIR}/extra/luaffifb" "luaffi-scm-1.rockspec" +#install_luarock "${THIS_DIR}/pkg/sundown" "rocks/sundown-scm-1.rockspec" +install_luarock "${THIS_DIR}/pkg/cwrap" "rocks/cwrap-scm-1.rockspec" +install_luarock "${THIS_DIR}/pkg/paths" "rocks/paths-scm-1.rockspec" + +install_luarock "${THIS_DIR}/pkg/torch" "rocks/torch-scm-1.rockspec" + +#install_luarock "${THIS_DIR}/pkg/dok" "rocks/dok-scm-1.rockspec" +#install_luarock "${THIS_DIR}/exe/trepl" "trepl-scm-1.rockspec" +#install_luarock "${THIS_DIR}/pkg/sys" "sys-1.1-0.rockspec" +#install_luarock "${THIS_DIR}/pkg/xlua" "xlua-1.0-0.rockspec" + +# ============================================================================== +# Extended Torch Packages Installation +# ============================================================================== + +echo "" +echo "=== Installing Extended Torch Packages ===" + +install_luarock "${THIS_DIR}/extra/moses" "rockspec/moses-1.6.1-1.rockspec" +install_luarock "${THIS_DIR}/extra/nn" "rocks/nn-scm-1.rockspec" +#install_luarock "${THIS_DIR}/extra/graph" "rocks/graph-scm-1.rockspec" +#install_luarock "${THIS_DIR}/extra/nngraph" "nngraph-scm-1.rockspec" +#install_luarock "${THIS_DIR}/pkg/image" "image-1.1.alpha-0.rockspec" +#install_luarock "${THIS_DIR}/pkg/optim" "optim-1.0.5-0.rockspec" + +# ============================================================================== +# Optional Packages (Commented Out) +# ============================================================================== + +# Uncomment and modify as needed: +# echo "" +# echo "=== Installing Optional Torch Packages ===" +# install_luarock "${THIS_DIR}/pkg/gnuplot" "rocks/gnuplot-scm-1.rockspec" +# install_luarock "${THIS_DIR}/exe/env" "env-scm-1.rockspec" +# install_luarock "${THIS_DIR}/extra/nnx" "nnx-0.1-1.rockspec" +# install_luarock "${THIS_DIR}/exe/qtlua" "rocks/qtlua-scm-1.rockspec" +# install_luarock "${THIS_DIR}/pkg/qttorch" "rocks/qttorch-scm-1.rockspec" +# install_luarock "${THIS_DIR}/extra/threads" "rocks/threads-scm-1.rockspec" +# install_luarock "${THIS_DIR}/extra/argcheck" "rocks/argcheck-scm-1.rockspec" + + + +# ============================================================================== +# Uncomment if you need to build TH library directly with CMake: +# ============================================================================== + +# echo "" +# echo "=== Building TH Library using bare cmake ===" +# cd "${THIS_DIR}/pkg/torch/lib/TH" +# mkdir -p build-riscv +# cd build-riscv +# +# cmake .. \ +# -DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \ +# -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ +# -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ +# -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ +# -DCMAKE_INSTALL_PREFIX="$RISCV_PREFIX" \ +# -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \ +# -DWITH_OPENMP=OFF -DWITH_SSE=OFF -DWITH_AVX=OFF \ +# -DTH_HAVE_X86_SIMD=OFF 2>&1 | tee /tmp/th-build.log +# +# make -j2 VERBOSE=1 2>&1 | tee -a /tmp/th-build.log && \ +# make install 2>&1 | tee -a /tmp/th-build.log +# +# cd "$THIS_DIR" +# +# # Reset Lua environment +# setup_lua_env_cmd=$("$PREFIX/bin/luarocks" path) +# eval "$setup_lua_env_cmd" + +echo "" +echo "✅ All packages installed successfully!" +echo "Installation directory: $RISCV_PREFIX" +echo "To verify installation, check for RISC-V shared objects:" +echo "find '$RISCV_PREFIX' -name '*.so' | head -5 | xargs file" diff --git a/test.lua b/test.lua new file mode 100644 index 000000000..ce9430531 --- /dev/null +++ b/test.lua @@ -0,0 +1,22 @@ +print("=== Testing Fixed Torch ===") +local ok, torch = pcall(require, 'torch') +if ok then + print("✓ torch loaded") + local t = torch.Tensor(2,2):fill(1) + print("✓ basic tensor:", t:sum()) + + local ok_nn, nn = pcall(require, 'nn') + if ok_nn then + print("✓ nn loaded") + local linear = nn.Linear(2, 1) + local out = linear:forward(torch.randn(2)) + print("✓ neural network works") + end + + local ok_img, image = pcall(require, 'image') + if ok_img then + print("✓ image loaded") + end +else + print("❌ torch failed:", torch) +end diff --git a/torch-static.patch b/torch-static.patch new file mode 100644 index 000000000..57af69dea --- /dev/null +++ b/torch-static.patch @@ -0,0 +1,733 @@ +Subject: [PATCH] torch-static +--- +Index: pkg/torch/lib/TH/THAllocator.c +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/THAllocator.c b/pkg/torch/lib/TH/THAllocator.c +--- a/pkg/torch/lib/TH/THAllocator.c (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/THAllocator.c (date 1753084737000) +@@ -105,6 +105,10 @@ + + static void *_map_alloc(void* ctx_, ptrdiff_t size) + { ++ if (size == 0) { ++ return NULL; ++ } ++ + THMapAllocatorContext *ctx = ctx_; + void *data = NULL; + +@@ -332,6 +336,9 @@ + } + + static void THMapAllocator_free(void* ctx_, void* data) { ++ if (data == NULL) ++ return; ++ + THMapAllocatorContext *ctx = ctx_; + + #ifdef _WIN32 +Index: extra/nn/rocks/nn-scm-1.rockspec +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/extra/nn/rocks/nn-scm-1.rockspec b/extra/nn/rocks/nn-scm-1.rockspec +--- a/extra/nn/rocks/nn-scm-1.rockspec (revision 1e668eabf80c8214a58f7a5b5c318b338c2eefee) ++++ b/extra/nn/rocks/nn-scm-1.rockspec (date 1756378843580) +@@ -2,7 +2,7 @@ + version = "scm-1" + + source = { +- url = "git://github.com/torch/nn.git", ++ url = "https://github.com/torch/nn.git", + } + + description = { +Index: pkg/torch/lib/TH/generic/THTensorMath.c +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/generic/THTensorMath.c b/pkg/torch/lib/TH/generic/THTensorMath.c +--- a/pkg/torch/lib/TH/generic/THTensorMath.c (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/generic/THTensorMath.c (date 1753084737000) +@@ -1595,10 +1595,6 @@ + THLongTensor_zero(indices_); + + if(t->size[dimension] == 1) { +- if (!keepdim) { +- THTensor_(squeeze1d)(values_, values_, dimension); +- THLongTensor_squeeze1d(indices_, indices_, dimension); +- } + return; + } + +@@ -1675,10 +1671,6 @@ + THLongTensor_zero(indices_); + + if(t->size[dimension] == 1) { +- if (!keepdim) { +- THTensor_(squeeze1d)(values_, values_, dimension); +- THLongTensor_squeeze1d(indices_, indices_, dimension); +- } + return; + } + +@@ -1927,18 +1919,6 @@ + THTensor_(zero)(r_); + } + +-void THTensor_(zerosLike)(THTensor *r_, THTensor *input) +-{ +- THTensor_(resizeAs)(r_, input); +- THTensor_(zero)(r_); +-} +- +-void THTensor_(onesLike)(THTensor *r_, THTensor *input) +-{ +- THTensor_(resizeAs)(r_, input); +- THTensor_(fill)(r_, 1); +-} +- + void THTensor_(ones)(THTensor *r_, THLongStorage *size) + { + THTensor_(resize)(r_, size, NULL); +@@ -2856,14 +2836,19 @@ + TH_TENSOR_APPLY2(real, t, real, r_, *r__data = CFUNC(*t_data);); \ + } \ + ++#define LAB_IMPLEMENT_BASIC_FUNCTION_VALUE(NAME, CFUNC) \ ++ void THTensor_(NAME)(THTensor *r_, THTensor *t, real value) \ ++ { \ ++ THTensor_(resizeAs)(r_, t); \ ++ TH_TENSOR_APPLY2(real, t, real, r_, *r__data = CFUNC(*t_data, value);); \ ++ } \ ++ + #if defined(TH_REAL_IS_LONG) + LAB_IMPLEMENT_BASIC_FUNCTION(abs,labs) +-LAB_IMPLEMENT_BASIC_FUNCTION(neg,-) + #endif /* long only part */ + + #if defined(TH_REAL_IS_SHORT) || defined(TH_REAL_IS_INT) + LAB_IMPLEMENT_BASIC_FUNCTION(abs,abs) +-LAB_IMPLEMENT_BASIC_FUNCTION(neg,-) + #endif /* int only part */ + + #if defined(TH_REAL_IS_BYTE) +@@ -2905,6 +2890,7 @@ + LAB_IMPLEMENT_BASIC_FUNCTION(tan,TH_MATH_NAME(tan)) + LAB_IMPLEMENT_BASIC_FUNCTION(atan,TH_MATH_NAME(atan)) + LAB_IMPLEMENT_BASIC_FUNCTION(tanh,TH_MATH_NAME(tanh)) ++LAB_IMPLEMENT_BASIC_FUNCTION_VALUE(pow,TH_MATH_NAME(pow)) + LAB_IMPLEMENT_BASIC_FUNCTION(sqrt,TH_MATH_NAME(sqrt)) + LAB_IMPLEMENT_BASIC_FUNCTION(rsqrt,TH_MATH_NAME(TH_rsqrt)) + LAB_IMPLEMENT_BASIC_FUNCTION(ceil,TH_MATH_NAME(ceil)) +@@ -2917,35 +2903,6 @@ + LAB_IMPLEMENT_BASIC_FUNCTION(cinv, TH_MATH_NAME(1.0) / ) + + +-void THTensor_(pow)(THTensor *r_, THTensor *t, real value) +-{ +- THTensor_(resizeAs)(r_, t); +- if(value == 1){ +- THTensor_(copy)(r_, t); +- } +- else if(value == 2){ +- THTensor_(cmul)(r_, t, t); +- } +- else if(value == 3){ +- TH_TENSOR_APPLY2(real, t, real, r_, *r__data = *t_data * *t_data * *t_data;); +- } +- else if(value == 0.5){ +- THTensor_(sqrt)(r_, t); +- } +- else if(value == -0.5){ +- THTensor_(rsqrt)(r_, t); +- } +- else if(value == -1){ +- THTensor_(cinv)(r_, t); +- } +- else if(value == -2){ +- TH_TENSOR_APPLY2(real, t, real, r_, *r__data = TH_MATH_NAME(1.0) / (*t_data * *t_data);); +- } +- else{ +- TH_TENSOR_APPLY2(real, t, real, r_, *r__data = TH_MATH_NAME(pow)(*t_data, value);); +- } +-} +- + void THTensor_(atan2)(THTensor *r_, THTensor *tx, THTensor *ty) + { + THTensor_(resizeAs)(r_, tx); +@@ -3201,7 +3158,10 @@ + } + + if(n == 1) { +- THTensor_(set1d)(r_, 0, a); ++ TH_TENSOR_APPLY(real, r_, ++ *r__data = a; ++ i++; ++ ); + } else { + TH_TENSOR_APPLY(real, r_, + *r__data = a + i*(b-a)/((real)(n-1)); +@@ -3221,7 +3181,10 @@ + } + + if(n == 1) { +- THTensor_(set1d)(r_, 0, TH_MATH_NAME(pow)(10.0, a)); ++ TH_TENSOR_APPLY(real, r_, ++ *r__data = TH_MATH_NAME(pow)(10.0, a); ++ i++; ++ ); + } else { + TH_TENSOR_APPLY(real, r_, + *r__data = TH_MATH_NAME(pow)(10.0, a + i*(b-a)/((real)(n-1))); +Index: exe/qtlua/rocks/qtlua-scm-1.rockspec +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/exe/qtlua/rocks/qtlua-scm-1.rockspec b/exe/qtlua/rocks/qtlua-scm-1.rockspec +--- a/exe/qtlua/rocks/qtlua-scm-1.rockspec (revision 8b80419ca2458e172b0d245999114f2b9d4ac118) ++++ b/exe/qtlua/rocks/qtlua-scm-1.rockspec (date 1756115680926) +@@ -2,7 +2,7 @@ + version = "scm-1" + + source = { +- url = "git://github.com/torch/qtlua.git", ++ url = "https://github.com/torch/qtlua.git", + } + + description = { +Index: pkg/torch/lib/TH/generic/THTensorMath.h +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/generic/THTensorMath.h b/pkg/torch/lib/TH/generic/THTensorMath.h +--- a/pkg/torch/lib/TH/generic/THTensorMath.h (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/generic/THTensorMath.h (date 1753084737000) +@@ -90,9 +90,7 @@ + TH_API void THTensor_(cminValue)(THTensor *r, THTensor *t, real value); + + TH_API void THTensor_(zeros)(THTensor *r_, THLongStorage *size); +-TH_API void THTensor_(zerosLike)(THTensor *r_, THTensor *input); + TH_API void THTensor_(ones)(THTensor *r_, THLongStorage *size); +-TH_API void THTensor_(onesLike)(THTensor *r_, THTensor *input); + TH_API void THTensor_(diag)(THTensor *r_, THTensor *t, int k); + TH_API void THTensor_(eye)(THTensor *r_, long n, long m); + TH_API void THTensor_(arange)(THTensor *r_, accreal xmin, accreal xmax, accreal step); +Index: pkg/torch/rocks/torch-scm-1.rockspec +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/rocks/torch-scm-1.rockspec b/pkg/torch/rocks/torch-scm-1.rockspec +--- a/pkg/torch/rocks/torch-scm-1.rockspec (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/rocks/torch-scm-1.rockspec (date 1756115513782) +@@ -2,7 +2,7 @@ + version = "scm-1" + + source = { +- url = "git://github.com/torch/torch7.git", ++ url = "https://github.com/torch/torch7.git", + } + + description = { +Index: pkg/torch/lib/TH/THStorage.c +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/THStorage.c b/pkg/torch/lib/TH/THStorage.c +--- a/pkg/torch/lib/TH/THStorage.c (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/THStorage.c (date 1753084737000) +@@ -34,11 +34,11 @@ + if (dim_infer != -1) { + THDescBuff buf = THLongStorage_sizeDesc(size); + THArgCheck(total_size > 0 && nElement % total_size == 0, 2, +- "size '%s' is invalid for input with %td elements", buf.str, nElement); ++ "size '%s' is invalid for input of with %td elements", buf.str, nElement); + } else { + THDescBuff buf = THLongStorage_sizeDesc(size); + THArgCheck(nElement == total_size, 2, +- "size '%s' is invalid for input with %td elements", buf.str, nElement); ++ "size '%s' is invalid for input of with %td elements", buf.str, nElement); + } + THLongStorage* copy = THLongStorage_newWithSize(size->size); + THLongStorage_copy(copy, size); +@@ -132,16 +132,6 @@ + long stride = (dim >= 0) ? + tensorStrides[dim] : expandedSizesCalc[i + 1] * expandedStridesCalc[i+1]; + long targetSize = THLongStorage_data(sizes)[i]; +- if (targetSize == -1) { +- if (dim < 0) { +- THFree(expandedSizesCalc); +- THFree(expandedStridesCalc); +- snprintf(error_buffer, buffer_len, "The expanded size of the tensor (%ld) isn't allowed in a leading, non-existing dimension %ld.", targetSize, i); +- return -1; +- } else { +- targetSize = size; +- } +- } + if (size != targetSize) { + if (size == 1) { + size = targetSize; +Index: pkg/paths/rocks/paths-scm-1.rockspec +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/paths/rocks/paths-scm-1.rockspec b/pkg/paths/rocks/paths-scm-1.rockspec +--- a/pkg/paths/rocks/paths-scm-1.rockspec (revision 4ebe222ba12589fb9d86c1d3895d7f509df77b6a) ++++ b/pkg/paths/rocks/paths-scm-1.rockspec (date 1756116527364) +@@ -2,7 +2,7 @@ + version = "scm-1" + + source = { +- url = "git://github.com/torch/paths.git", ++ url = "https://github.com/torch/paths.git", + } + + description = { +Index: pkg/torch/lib/TH/generic/THTensorRandom.h +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/generic/THTensorRandom.h b/pkg/torch/lib/TH/generic/THTensorRandom.h +--- a/pkg/torch/lib/TH/generic/THTensorRandom.h (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/generic/THTensorRandom.h (date 1753084737000) +@@ -3,8 +3,6 @@ + #else + + TH_API void THTensor_(random)(THTensor *self, THGenerator *_generator); +-TH_API void THTensor_(clampedRandom)(THTensor *self, THGenerator *_generator, long min, long max); +-TH_API void THTensor_(cappedRandom)(THTensor *self, THGenerator *_generator, long max); + TH_API void THTensor_(geometric)(THTensor *self, THGenerator *_generator, double p); + TH_API void THTensor_(bernoulli)(THTensor *self, THGenerator *_generator, double p); + TH_API void THTensor_(bernoulli_FloatTensor)(THTensor *self, THGenerator *_generator, THFloatTensor *p); +@@ -13,9 +11,6 @@ + #if defined(TH_REAL_IS_FLOAT) || defined(TH_REAL_IS_DOUBLE) + TH_API void THTensor_(uniform)(THTensor *self, THGenerator *_generator, double a, double b); + TH_API void THTensor_(normal)(THTensor *self, THGenerator *_generator, double mean, double stdv); +-TH_API void THTensor_(normal_means)(THTensor *self, THGenerator *gen, THTensor *means, double stddev); +-TH_API void THTensor_(normal_stddevs)(THTensor *self, THGenerator *gen, double mean, THTensor *stddevs); +-TH_API void THTensor_(normal_means_stddevs)(THTensor *self, THGenerator *gen, THTensor *means, THTensor *stddevs); + TH_API void THTensor_(exponential)(THTensor *self, THGenerator *_generator, double lambda); + TH_API void THTensor_(cauchy)(THTensor *self, THGenerator *_generator, double median, double sigma); + TH_API void THTensor_(logNormal)(THTensor *self, THGenerator *_generator, double mean, double stdv); +Index: exe/luajit-rocks/lua-5.1/CMakeLists.txt +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/exe/luajit-rocks/lua-5.1/CMakeLists.txt b/exe/luajit-rocks/lua-5.1/CMakeLists.txt +--- a/exe/luajit-rocks/lua-5.1/CMakeLists.txt (revision 20184a238783b5f6ed909f4dd40c443d42eaa606) ++++ b/exe/luajit-rocks/lua-5.1/CMakeLists.txt (date 1753266004552) +@@ -91,13 +91,16 @@ + PREFIX "lib" IMPORT_PREFIX "lib" OUTPUT_NAME "lua") + ADD_EXECUTABLE(lua src/lua.c ${SRC_LIB}) + ADD_EXECUTABLE(luac src/luac.c src/print.c ${SRC_LIB}) +-TARGET_LINK_LIBRARIES(liblua ${LIBS}) +-TARGET_LINK_LIBRARIES(lua ${LIBS}) ++ + + if(CMAKE_CROSSCOMPILING) + target_link_libraries(luac ${LIBS} dl) ++ TARGET_LINK_LIBRARIES(liblua ${LIBS} dl) ++ TARGET_LINK_LIBRARIES(lua ${LIBS} dl) + else() + target_link_libraries(luac ${LIBS}) ++ TARGET_LINK_LIBRARIES(liblua ${LIBS}) ++ TARGET_LINK_LIBRARIES(lua ${LIBS}) + endif() + + +Index: pkg/torch/lib/TH/CMakeLists.txt +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/CMakeLists.txt b/pkg/torch/lib/TH/oldfCMakeLists.txt +rename from pkg/torch/lib/TH/CMakeLists.txt +rename to pkg/torch/lib/TH/oldfCMakeLists.txt +--- a/pkg/torch/lib/TH/CMakeLists.txt (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/oldfCMakeLists.txt (date 1756279809000) +@@ -8,6 +8,9 @@ + SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) + SET(CMAKE_LIBRARY_PATH /usr/lib/x86_64-linux-gnu/ ${CMAKE_LIBRARY_PATH}) + ++set(CMAKE_POSITION_INDEPENDENT_CODE ON) ++ ++ + # Can be compiled standalone + IF(NOT TH_INSTALL_BIN_SUBDIR + OR NOT TH_INSTALL_LIB_SUBDIR +@@ -82,6 +85,7 @@ + ENDIF () + ENDIF () + ++SET(WITH_OPENMP OFF CACHE BOOL "OpenMP support if available?" FORCE) + IF (WITH_OPENMP) + FIND_PACKAGE(OpenMP) + IF(OPENMP_FOUND) +@@ -154,7 +158,7 @@ + MESSAGE(STATUS "SSE3 Found") + SET(CMAKE_C_FLAGS "${C_SSE3_FLAGS} -DUSE_SSE3 ${CMAKE_C_FLAGS}") + ENDIF(C_SSE3_FOUND) +-# we don't set -mavx and -mavx2 flags globally, but only for specific files ++# we dont set -mavx and -mavx2 flags globally, but only for specific files + # however, we want to enable the AVX codepaths, so we still need to + # add USE_AVX and USE_AVX2 macro defines + IF(C_AVX_FOUND) +@@ -300,26 +304,20 @@ + ENDIF() + ENDIF() + +-FIND_PACKAGE(BLAS) +-IF(BLAS_FOUND) +- SET(USE_BLAS 1) +- IF ($ENV{TH_BINARY_BUILD}) +- MESSAGE(STATUS "TH_BINARY_BUILD detected. Enabling special linkage.") +- TARGET_LINK_LIBRARIES(TH "${BLAS_LIBRARIES};${BLAS_LIBRARIES};${BLAS_LIBRARIES}") +- ELSE ($ENV{TH_BINARY_BUILD}) +- TARGET_LINK_LIBRARIES(TH ${BLAS_LIBRARIES}) +- ENDIF ($ENV{TH_BINARY_BUILD}) +- +- IF(BLAS_INFO STREQUAL "mkl") +- ADD_DEFINITIONS(-DTH_BLAS_MKL) +- ENDIF() +-ENDIF(BLAS_FOUND) ++#FIND_PACKAGE(BLAS) ++#IF(BLAS_FOUND) ++# SET(USE_BLAS 1) ++# TARGET_LINK_LIBRARIES(TH ${BLAS_LIBRARIES}) ++# IF(BLAS_INFO STREQUAL "mkl") ++# ADD_DEFINITIONS(-DTH_BLAS_MKL) ++# ENDIF() ++#ENDIF(BLAS_FOUND) + +-FIND_PACKAGE(LAPACK) +-IF(LAPACK_FOUND) +- SET(USE_LAPACK 1) +- TARGET_LINK_LIBRARIES(TH ${LAPACK_LIBRARIES}) +-ENDIF(LAPACK_FOUND) ++#FIND_PACKAGE(LAPACK) ++#IF(LAPACK_FOUND) ++# SET(USE_LAPACK 1) ++# TARGET_LINK_LIBRARIES(TH ${LAPACK_LIBRARIES}) ++#ENDIF(LAPACK_FOUND) + + IF (UNIX AND NOT APPLE) + INCLUDE(CheckLibraryExists) +@@ -358,15 +356,15 @@ + ENDIF(NOT MSVC) + + # Is __thread supported? +-IF(NOT MSVC) +- CHECK_C_SOURCE_COMPILES("static __thread int x = 1; int main() { return x; }" C_HAS_THREAD) +-ELSE(NOT MSVC) +- CHECK_C_SOURCE_COMPILES("static __declspec( thread ) int x = 1; int main() { return x; }" C_HAS_THREAD) +-ENDIF(NOT MSVC) ++#IF(NOT MSVC) ++# CHECK_C_SOURCE_COMPILES("static __thread int x = 1; int main() { return x; }" C_HAS_THREAD) ++#ELSE(NOT MSVC) ++# CHECK_C_SOURCE_COMPILES("static __declspec( thread ) int x = 1; int main() { return x; }" C_HAS_THREAD) ++#ENDIF(NOT MSVC) + IF(NOT C_HAS_THREAD) + MESSAGE(STATUS "Warning: __thread is not supported, generating thread-unsafe code") +-ELSE(NOT C_HAS_THREAD) +- SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DTH_HAVE_THREAD") ++#ELSE(NOT C_HAS_THREAD) ++# SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DTH_HAVE_THREAD") + ENDIF(NOT C_HAS_THREAD) + + INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") +@@ -473,12 +471,14 @@ + Install_Required_Library(${BLAS_openblas_LIBRARY}) + Install_Required_Library("${libpath}/libquadmath") + Install_Required_Library("${libpath}/libgfortran") ++ Install_Required_Library("${libpath}/libquadmath") + Install_Required_Library("${libpath}/libgcc") + ENDIF() + ENDIF() + + # Create THConfig.cmake + set(TH_LOCATION "$") ++ + SET(TH_LIBRARIES "${CMAKE_INSTALL_PREFIX}/${TH_INSTALL_LIB_SUBDIR}/${TH_OUTPUT_NAME}") + SET(TH_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${TH_INSTALL_INCLUDE_SUBDIR}/TH") + CONFIGURE_FILE(THConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/cmake-exports/THConfig.cmake") +Index: pkg/torch/lib/TH/cmake/FindMKL.cmake +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/cmake/FindMKL.cmake b/pkg/torch/lib/TH/cmake/FindMKL.cmake +--- a/pkg/torch/lib/TH/cmake/FindMKL.cmake (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/cmake/FindMKL.cmake (date 1753084737000) +@@ -50,7 +50,7 @@ + IF(CMAKE_COMPILER_IS_GNUCC) + SET(mklthreads "mkl_gnu_thread" "mkl_intel_thread") + SET(mklifaces "gf" "intel") +- SET(mklrtls "gomp" "iomp5") ++ SET(mklrtls "iomp5") + ELSE(CMAKE_COMPILER_IS_GNUCC) + SET(mklthreads "mkl_intel_thread") + SET(mklifaces "intel") +@@ -92,7 +92,7 @@ + # Try linking multiple libs + MACRO(CHECK_ALL_LIBRARIES LIBRARIES _name _list _flags) + # This macro checks for the existence of the combination of libraries given by _list. +- # If the combination is found, this macro checks whether we can link against that library ++ # If the combination is found, this macro whether we can link against that library + # combination using the name of a routine given by _name using the linker + # flags given by _flags. If the combination of libraries is found and passes + # the link test, LIBRARIES is set to the list of complete library paths that +@@ -116,15 +116,8 @@ + message(STATUS "Checking for [${__list}]") + FOREACH(_library ${_list}) + SET(_combined_name ${_combined_name}_${_library}) +- IF(_libraries_work) +- IF(${_library} STREQUAL "gomp") +- FIND_PACKAGE(OpenMP) +- IF(OPENMP_FOUND) +- SET(${_prefix}_${_library}_LIBRARY ${OpenMP_C_FLAGS}) +- ENDIF(OPENMP_FOUND) +- ELSE(${_library} STREQUAL "gomp") +- FIND_LIBRARY(${_prefix}_${_library}_LIBRARY NAMES ${_library}) +- ENDIF(${_library} STREQUAL "gomp") ++ IF(_libraries_work) ++ FIND_LIBRARY(${_prefix}_${_library}_LIBRARY NAMES ${_library}) + MARK_AS_ADVANCED(${_prefix}_${_library}_LIBRARY) + SET(${LIBRARIES} ${${LIBRARIES}} ${${_prefix}_${_library}_LIBRARY}) + SET(_libraries_work ${${_prefix}_${_library}_LIBRARY}) +@@ -138,7 +131,6 @@ + # Test this combination of libraries. + IF(_libraries_work) + SET(CMAKE_REQUIRED_LIBRARIES ${_flags} ${${LIBRARIES}}) +- SET(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};${CMAKE_REQUIRED_LIBRARIES}") + CHECK_FUNCTION_EXISTS(${_name} ${_prefix}${_combined_name}_WORKS) + SET(CMAKE_REQUIRED_LIBRARIES) + MARK_AS_ADVANCED(${_prefix}${_combined_name}_WORKS) +@@ -158,11 +150,6 @@ + set(mkl_m "m") + endif(WIN32) + +-if(UNIX AND NOT APPLE) +- set(mkl_dl "${CMAKE_DL_LIBS}") +-else(UNIX AND NOT APPLE) +- set(mkl_dl "") +-endif(UNIX AND NOT APPLE) + + # Check for version 10/11 + IF (NOT MKL_LIBRARIES) +@@ -174,7 +161,7 @@ + FOREACH(mklthread ${mklthreads}) + IF (NOT MKL_LIBRARIES AND NOT INTEL_MKL_SEQUENTIAL) + CHECK_ALL_LIBRARIES(MKL_LIBRARIES cblas_sgemm +- "mkl_${mkliface}${mkl64};${mklthread};mkl_core;${mklrtl};pthread;${mkl_m};${mkl_dl}" "") ++ "mkl_${mkliface}${mkl64};${mklthread};mkl_core;${mklrtl};pthread;${mkl_m}" "") + ENDIF (NOT MKL_LIBRARIES AND NOT INTEL_MKL_SEQUENTIAL) + ENDFOREACH(mklthread) + ENDFOREACH(mkl64) +@@ -185,7 +172,7 @@ + FOREACH(mkl64 ${mkl64s} "") + IF (NOT MKL_LIBRARIES) + CHECK_ALL_LIBRARIES(MKL_LIBRARIES cblas_sgemm +- "mkl_${mkliface}${mkl64};mkl_sequential;mkl_core;${mkl_m};${mkl_dl}" "") ++ "mkl_${mkliface}${mkl64};mkl_sequential;mkl_core;${mkl_m}" "") + IF (MKL_LIBRARIES) + SET(mklseq "_sequential") + ENDIF (MKL_LIBRARIES) +@@ -199,7 +186,7 @@ + FOREACH(mklthread ${mklthreads}) + IF (NOT MKL_LIBRARIES) + CHECK_ALL_LIBRARIES(MKL_LIBRARIES cblas_sgemm +- "mkl_${mkliface}${mkl64};${mklthread};mkl_core;${mklrtl};pthread;${mkl_m};${mkl_dl}" "") ++ "mkl_${mkliface}${mkl64};${mklthread};mkl_core;${mklrtl};pthread;${mkl_m}" "") + ENDIF (NOT MKL_LIBRARIES) + ENDFOREACH(mklthread) + ENDFOREACH(mkl64) +Index: pkg/torch/lib/TH/cmake/FindARM.cmake +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/cmake/FindARM.cmake b/pkg/torch/lib/TH/cmake/FindARM.cmake +--- a/pkg/torch/lib/TH/cmake/FindARM.cmake (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/cmake/FindARM.cmake (date 1753084737000) +@@ -5,7 +5,7 @@ + EXEC_PROGRAM(cat ARGS "/proc/cpuinfo" OUTPUT_VARIABLE CPUINFO) + + #neon instruction can be found on the majority part of modern ARM processor +- STRING(REGEX REPLACE "^.*(neon).*$" "\\1" NEON_THERE "${CPUINFO}") ++ STRING(REGEX REPLACE "^.*(neon).*$" "\\1" NEON_THERE ${CPUINFO}) + STRING(COMPARE EQUAL "neon" "${NEON_THERE}" NEON_TRUE) + IF (NEON_TRUE) + set(NEON_FOUND true CACHE BOOL "NEON available on host") +@@ -14,7 +14,7 @@ + ENDIF (NEON_TRUE) + + # on ARMv8, neon is inherit and instead listed as 'asimd' in /proc/cpuinfo +- STRING(REGEX REPLACE "^.*(asimd).*$" "\\1" ASIMD_THERE "${CPUINFO}") ++ STRING(REGEX REPLACE "^.*(asimd).*$" "\\1" ASIMD_THERE ${CPUINFO}) + STRING(COMPARE EQUAL "asimd" "${ASIMD_THERE}" ASIMD_TRUE) + IF (ASIMD_TRUE) + set(ASIMD_FOUND true CACHE BOOL "ASIMD/NEON available on host") +@@ -23,7 +23,7 @@ + ENDIF (ASIMD_TRUE) + + #Find the processor type (for now OMAP3 or OMAP4) +- STRING(REGEX REPLACE "^.*(OMAP3).*$" "\\1" OMAP3_THERE "${CPUINFO}") ++ STRING(REGEX REPLACE "^.*(OMAP3).*$" "\\1" OMAP3_THERE ${CPUINFO}) + STRING(COMPARE EQUAL "OMAP3" "${OMAP3_THERE}" OMAP3_TRUE) + IF (OMAP3_TRUE) + set(CORTEXA8_FOUND true CACHE BOOL "OMAP3 available on host") +@@ -32,7 +32,7 @@ + ENDIF (OMAP3_TRUE) + + #Find the processor type (for now OMAP3 or OMAP4) +- STRING(REGEX REPLACE "^.*(OMAP4).*$" "\\1" OMAP4_THERE "${CPUINFO}") ++ STRING(REGEX REPLACE "^.*(OMAP4).*$" "\\1" OMAP4_THERE ${CPUINFO}) + STRING(COMPARE EQUAL "OMAP4" "${OMAP4_THERE}" OMAP4_TRUE) + IF (OMAP4_TRUE) + set(CORTEXA9_FOUND true CACHE BOOL "OMAP4 available on host") +@@ -45,7 +45,7 @@ + CPUINFO) + + #neon instruction can be found on the majority part of modern ARM processor +- STRING(REGEX REPLACE "^.*(neon).*$" "\\1" NEON_THERE "${CPUINFO}") ++ STRING(REGEX REPLACE "^.*(neon).*$" "\\1" NEON_THERE ${CPUINFO}) + STRING(COMPARE EQUAL "neon" "${NEON_THERE}" NEON_TRUE) + IF (NEON_TRUE) + set(NEON_FOUND true CACHE BOOL "NEON available on host") +Index: pkg/torch/lib/TH/generic/THTensorRandom.c +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/generic/THTensorRandom.c b/pkg/torch/lib/TH/generic/THTensorRandom.c +--- a/pkg/torch/lib/TH/generic/THTensorRandom.c (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/generic/THTensorRandom.c (date 1753084737000) +@@ -23,16 +23,6 @@ + #endif + } + +-void THTensor_(clampedRandom)(THTensor *self, THGenerator *_generator, long min, long max) { +- THArgCheck(max > min, 2, "max must be greater than min"); +- TH_TENSOR_APPLY(real, self, *self_data = (real)((THRandom_random(_generator) % (max - min)) + min);) +-} +- +-void THTensor_(cappedRandom)(THTensor *self, THGenerator *_generator, long max) { +- THArgCheck(max > 0, 1, "max must be positive"); +- THTensor_(clampedRandom)(self, _generator, 0, max); +-} +- + void THTensor_(geometric)(THTensor *self, THGenerator *_generator, double p) + { + TH_TENSOR_APPLY(real, self, *self_data = (real)THRandom_geometric(_generator, p);); +@@ -65,29 +55,6 @@ + TH_TENSOR_APPLY(real, self, *self_data = (real)THRandom_normal(_generator, mean, stdv);); + } + +-void THTensor_(normal_means)(THTensor *self, THGenerator *gen, THTensor *means, double stddev) +-{ +- THTensor_(resizeAs)(self, means); +- THTensor_(normal)(self, gen, 0, stddev); +- THTensor_(cadd)(self, self, 1, means); +-} +- +-void THTensor_(normal_stddevs)(THTensor *self, THGenerator *gen, double mean, THTensor *stddevs) +-{ +- THTensor_(resizeAs)(self, stddevs); +- THTensor_(normal)(self, gen, 0, 1); +- THTensor_(cmul)(self, self, stddevs); +- THTensor_(add)(self, self, mean); +-} +- +-void THTensor_(normal_means_stddevs)(THTensor *self, THGenerator *gen, THTensor *means, THTensor *stddevs) +-{ +- THTensor_(resizeAs)(self, means); +- THTensor_(normal)(self, gen, 0, 1); +- THTensor_(cmul)(self, self, stddevs); +- THTensor_(cadd)(self, self, 1, means); +-} +- + void THTensor_(exponential)(THTensor *self, THGenerator *_generator, double lambda) + { + TH_TENSOR_APPLY(real, self, *self_data = (real)THRandom_exponential(_generator, lambda);); +@@ -147,7 +114,7 @@ + THTensor_fastSet1d(J, small, large); + q_data[large * q->stride[0]] -= 1.0 - THTensor_fastGet1d(q, small); + +- if(q_data[large * q->stride[0]] < 1.0) ++ if(q_data[large] < 1.0) + { + THTensor_fastSet1d(smaller, small_c-1, large); + large_c -= 1; +Index: pkg/torch/lib/TH/vector/VSX.c +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/vector/VSX.c b/pkg/torch/lib/TH/vector/VSX.c +--- a/pkg/torch/lib/TH/vector/VSX.c (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/vector/VSX.c (date 1753084737000) +@@ -1345,7 +1345,7 @@ + // TODO + // + // +-// Finished running all tests. All tests PASSED. ++// Finished runnning all tests. All tests PASSED. + // + //------------------------------------------------ + #ifdef RUN_VSX_TESTS +@@ -2509,7 +2509,7 @@ + + + +- printf("Finished running all tests. All tests PASSED.\n"); ++ printf("Finished runnning all tests. All tests PASSED.\n"); + return 0; + } + +Index: pkg/torch/lib/TH/generic/simd/simd.h +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/pkg/torch/lib/TH/generic/simd/simd.h b/pkg/torch/lib/TH/generic/simd/simd.h +--- a/pkg/torch/lib/TH/generic/simd/simd.h (revision 1600de6d4b9f9df15c02bd2969f09ed9e8973dfe) ++++ b/pkg/torch/lib/TH/generic/simd/simd.h (date 1756376075000) +@@ -54,7 +54,7 @@ + }; + + +-#if defined(__arm__) || defined(__aarch64__) // incl. armel, armhf, arm64 ++#if defined(__arm__) || defined(__aarch64__) || (!defined(__x86_64__) && !defined(__i386__)) // incl. armel, armhf, arm64 // x86 + + #if defined(__NEON__) + +@@ -96,7 +96,7 @@ + + #endif + +-#else // x86 ++#else + static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) + { + #if defined(_MSC_VER) +diff --git a/extra/luaffifb/ffi.c b/extra/luaffifb/ffi.c.backup +rename from extra/luaffifb/ffi.c +rename to extra/luaffifb/ffi.c.backup +diff --git a/extra/nn/lib/THNN/CMakeLists.txt b/extra/nn/lib/THNN/oldCMakeLists.txt +rename from extra/nn/lib/THNN/CMakeLists.txt +rename to extra/nn/lib/THNN/oldCMakeLists.txt +diff --git a/extra/luaffifb/ffi.h b/extra/luaffifb/ffi.h.backup +rename from extra/luaffifb/ffi.h +rename to extra/luaffifb/ffi.h.backup From 4b63af96436babe744e626a45a3c25689967cef5 Mon Sep 17 00:00:00 2001 From: Cristina Raluca Vijulie Date: Tue, 9 Sep 2025 21:52:27 +0200 Subject: [PATCH 6/6] adding instructions and manual gcc crosscompilation hack --- README.md | 28 +++++++++++++++++++++++++++- cross-gcc | 2 ++ install-x86.sh | 3 +++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100755 cross-gcc diff --git a/README.md b/README.md index 99f6cebf2..5dbb17778 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,32 @@ [![Build Status](https://travis-ci.org/torch/distro.svg?branch=master)](https://travis-ci.org/torch/distro) -> For the cross-compilation to work with submodules, clone this repo using the `--recursive` option and then apply the patches in the file `toolchain-changes.patch` + +# Torch for RISCV64 + +Cross-compiled Torch framework for RISCV64 architecture. + +## Quick Setup + +```shell script +git clone --recursive torch-riscv +cd torch-riscv +patch -p1 < torch-static.patch +bash install-deps +./install-x86.sh +#The crosscompilation step might still use a standard gcc +#on some steps so we need to manually re-wire it to the riscv one +mkdir -p /tmp/cross-wrappers +cp cross-gcc /tmp/cross-wrappers/gcc +export PATH="/tmp/cross-wrappers:$PATH" +./riscv64_cross_compilation_setup.sh +``` + +## Requirements + +- Ubuntu/Debian Linux +- RISCV64 cross-compiler: `sudo apt install gcc-riscv64-linux-gnu` +- Standard build tools: `sudo apt install cmake build-essential` + #### NOTE: Torch is not actively developed anymore and is in maintenance mode. diff --git a/cross-gcc b/cross-gcc new file mode 100755 index 000000000..345c4b7fd --- /dev/null +++ b/cross-gcc @@ -0,0 +1,2 @@ +#!/bin/bash +exec riscv64-linux-gnu-gcc "$@" diff --git a/install-x86.sh b/install-x86.sh index eae7ba186..e465ef7cc 100755 --- a/install-x86.sh +++ b/install-x86.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +#This is a truncated version of the original install.sh script +# which defaults to LUA51 and compiles luarocks to run natievly on the build machine + SKIP_RC=0 BATCH_INSTALL=0