Lua的函數調用和協程中,棧的變化情況


Lua的函數調用和協程中,棧的變化情況

 

1. lua_call / lua_pcall
 
對於這兩個函數,對棧底是沒有影響的——調用的時候,參數會被從棧中移除,當函數返
回的時候,其返回值會從函數處開始壓入,可以通過新的棧頂減去舊的棧頂得到返回值數
量。
 
2. lua_yield
 
對 Lua 函數來說, yield相當於只是在調用一個函數,對C函數也是這樣。yield的參數
是返回值的數量,這些返回值會被返回給resume,見下。
 
2. lua_resume
 
resume是最復雜的一個函數:首先,第一次調用的時候,它相當於是個lua_call,也就是
說,被resume的那個函數只能看到自己的參數在棧頂,更低的就看不見了。其次,當函數
被yield之后,resume返回,這時resume的棧頂只有yield返回的參數,其他的棧元素是看
不到的。
在這個基礎上,coroutine的狀態是LUA_YIELD,如果需要繼續執行,就需要再次 resume
,這里就有一些微妙的區別了。在這種情況下,resume的nargs參數是無用的——也就是
說,無論你傳遞的是多少,所有棧頂的元素都會被返回給yield,也就是說,如果需要在
返回之前清除掉棧,那么就需要你自己手動去清除,然后再resume。
 
下面是測試的示例代碼:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
 
int luv_dumpstack(lua_State *L) {
     int i, top;
     printf ( "-------------------\n" );
     printf ( "dumpstack: L=%p\n" , L);
     top = lua_gettop(L);
     printf ( "top: %d\n" , top);
     for (i = 1; i <= top; ++i) {
         printf ( "[%d][%s]: %s\n" ,
                 i,
                 luaL_typename(L, i),
                 luaL_tolstring(L, i, NULL));
         lua_pop(L, 1);
     }
     printf ( "-------------------\n" );
     return top;
}
 
static int cont(lua_State *L) {
     printf ( "stack after yield\n" );
     luv_dumpstack(L);
     lua_pushinteger(L, 21);
     lua_pushinteger(L, 22);
     lua_pushinteger(L, 23);
     return lua_gettop(L);
}
 
static int test_yield(lua_State *L) {
     printf ( "stack before yield\n" );
     luv_dumpstack(L);
     lua_pushinteger(L, 1);
     lua_pushinteger(L, 2);
     lua_pushinteger(L, 3);
     lua_pushinteger(L, 4);
     lua_pushinteger(L, 5);
     return lua_yieldk(L, 2, 0, cont);
}
 
static int test_resume(lua_State *L) {
     lua_State *L1 = lua_newthread(L);
     lua_pushinteger(L1, 11);
     lua_pushinteger(L1, 12);
     lua_pushcfunction(L1, test_yield);
     lua_pushinteger(L1, 13);
     lua_pushinteger(L1, 14);
     lua_pushinteger(L1, 15);
     printf ( "stack before resume\n" );
     luv_dumpstack(L1);
     printf ( "resume: %d\n" , lua_resume(L1, L, 3));
     printf ( "stack after resume\n" );
     luv_dumpstack(L1);
     lua_pushinteger(L1, 24);
     lua_pushinteger(L1, 25);
     printf ( "stack before second resume\n" );
     luv_dumpstack(L1);
     /* XXX notice even we pass 2, all values in stack (4,5,24,25)
      * will passed to coroutine */
     printf ( "resume: %d\n" , lua_resume(L1, L, 2));
     printf ( "stack after second resume\n" );
     luv_dumpstack(L1);
     return 0;
}
 
 
int main( void ) {
     lua_State *L = luaL_newstate();
     lua_pushcfunction(L, test_resume);
     lua_call(L, 0, 0);
     lua_close(L);
     return 0;
}
/* cc: libs+='-llua52' */

 

 

 

在Lua里面聲明小數組的最好方法是什么?

 

在很早以前,就看到“閉包比表要快”的這么一個言論。一直沒有驗證過,只是心里就這么覺得了。所以自己第一次寫的Lua網游,大量利用了閉包,最后估計還是有很嚴重的內存問題……在我的對象模型里面,對象構造函數通常是這樣的:

?
1
2
3
4
5
6
7
8
9
10
11
12
function Object()
    local t = {}
    local object_state1
    local object_state2
    -- init ...
 
    -- methods
    function t:foo(...) return ... end
    function t:bar(...) return ... end
    -- return object
    return t
end

這個設計可以保證,在訪問對象的函數的時候,速度可以達到最快——因為沒有元表查詢。但是,這樣做恰好就違背了這樣的原則:“在設計的初期,不要過早地考慮優化”。是的,因為任何優化都是有代價的,這里的代價理所當然就是內存了。

另外,在Lua-5.1中,我后來自己測試的結果是,表貌似還是比閉包要快一點,關鍵點是,這樣貌似占用內存還小很多,自從這么擺了一道以后,反正至少對於“看見大括號就有點恐懼內存分配”的心理上是好過多了。不過至少對於閉包的大小和速度什么的心里頭反而就沒底了。

今天突然想到了一個叫做lua-vararg的lua庫。這個庫可以對vararg進行包裝,提供對用戶來說“比較自然”的vararg的體驗——說白了,這就是用vararg去實現了元組(tuple)嘛。這個庫很早以前就知道了,我還自己親自改過,但是性能到底怎么樣呢?心里沒底,所以決定今天寫個偽測試來看看效果。

首先,我們看一下Lua源代碼里面表和閉包的描述:

?
lobject.h
560
561
562
563
564
565
566
567
568
569
570
typedef struct Table {
   CommonHeader;
   lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
   lu_byte lsizenode;  /* log2 of size of `node' array */
   struct Table *metatable;
   TValue *array;  /* array part */
   Node *node;
   Node *lastfree;  /* any free position is before this position */
   GCObject *gclist;
   int sizearray;  /* size of `array' array */
} Table;
?
lobject.h
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
#define ClosureHeader \
     CommonHeader; lu_byte nupvalues; GCObject *gclist
 
typedef struct CClosure {
   ClosureHeader;
   lua_CFunction f;
   TValue upvalue[1];  /* list of upvalues */
} CClosure;
 
 
typedef struct LClosure {
   ClosureHeader;
   struct Proto *p;
   UpVal *upvals[1];  /* list of upvalues */
} LClosure;
 
 
typedef union Closure {
   CClosure c;
   LClosure l;
} Closure;

OK,代碼有了,那么第一個問題是:表和閉包分別占多大的大小呢?

額= =這個問題不好搞啊,看起來好復雜的樣子,還得考慮對齊……額,不好搞的話,直接寫個程序不就可以了嗎?

?
1
2
3
4
5
6
7
8
9
#include "src/lobject.h"
 
#include <stdio.h>
 
int main( void ) {
     printf ( "size of Table: %d\n" , sizeof (Table));
     printf ( "size of Closure: %d\n" , sizeof (Closure));
     return 0;
}

恩,輸出結果是32和24……(別打偶……)

好吧,閉包居然比表要小!恩,這是很正常的。而且,這里的表可是“裸表”哦,一點數據都沒有的,而這個被聲明的閉包是默認帶上了一個upvalue的!

那么,對於有十個元素的表和閉包,大小又是怎么樣的呢?注意到表保存數組元素用的是TValue指針,而閉包里面的upvalue(主要指C閉包)也同樣是TValue,我們只需要分別給表和閉包的大小增加10個TValue的大小即可——額,閉包只需要加9,因為前面已經有個元素了,最后得到的大小是:112字節和96字節……恩~

好了,那么在大小方面,的確閉包(特指C閉包)是保存小數組的最好途徑了,那么,除開大小,在速度上面,它們之間有什么變化嗎?我們繼續測試一下!首先聲明一下,這里的測試對於真實的環境是沒有意義的,通常來說,我們並不需要考慮像分配小數組之間的效率問題,這里的測試,只是在於檢驗“閉包比表快”這個說法罷了,這樣的檢驗本身,可以當作是使用vararg模塊的依據,對做出某些設計決策是有好處的。

我們准備測試四種情況下的速度情況

  1. 用C API創建一個表,向其中壓入10個數字
  2. 用C API創建一個閉包,其中有10個upvalue
  3. 用Lua代碼創建一個表,其中有10個數字
  4. 用Lua代碼創建一個閉包,其中有10個upvalue

下面是測試結果:

?
1
2
3
4
5
6
7
8
9
10
test
c table time: 2.35s
c table memory: 5724
c closure time: 0.20s
c closure memory: 6020
lua table time: 0.40s
lua table memory: 5836
lua closure time: 1.51s
lua closure memory: 4556
Hit any key to close this window...

我們來分析一下這個數據,最簡單的分析方法是排序,對於時間的順序是c closure < lua table < lua closure < c table,對於內存剩余的順序是lua closure < c table == lua table < c closure。

好奇怪的結果!!我們試着來分析一下吧!看起來內存里面有一項是相同的:即用c和用lua創建表,占用內存的大小是近似相同的。而用C創建閉包,占用內存最大,但是也大不了多少。這里面是什么情況呢?我們關閉一下垃圾回收試試?對了!原來是這樣……關掉垃圾回收以后,內存的占用非常非常誇張,原來,這是垃圾回收以后的結果(順便說一下,關閉垃圾回收,所有的測試都變慢了,不過相對結果不變)。

那么,內存的意義上就不大了。只能說是垃圾回收的策略不同罷了。我們來分析一下時間問題好了。

首先,這個數據實在是太奇怪了了:如果說C的API比Lua代碼快,那么為什么c table是最慢的呢?如果說閉包比表快,那么為什么lua閉包不如lua的表呢?這是怎么回事呢?

很容易發現問題的是lua閉包,這是倒數第二慢的……因為Lua5.2出了一個新功能:在創建lua閉包的時候,會和之前緩存的進行比較,看看是新的閉包還是以前的,因為我們創建的閉包都是新的函數調用,顯然和以前閉包的upvalue不可能一樣,因此這個比較始終是會失敗的……看來也只能是這個地方會占用時間了。當然了,關聯upvalue、關閉upvalue等等都需要占據時間。lua閉包比較慢幾乎是可以預見到的了。這一點我們不奇怪。

奇怪的是,為什么用C API寫的新建表比lua代碼還要慢呢?注意到,Lua代碼是不需要再創建新的“整數”的,它們是直接從常量表(K表)載入的,所以pushnumber會拖慢一點速度,其次,lua_rawseti會比luaH_setint多做一些檢查,除了這兩點以外,還有一個很容易忽視的地方是API調用開銷:因為Lua是在DLL里面的,因此API調用相對較慢,如果靜態鏈接的話,對其他測試來說,結果影響不大,但是c table測試瞬間就快了20%——也許是因為每次推入新的包,c table測試比別的方法多了10個C API的原因吧……Lua里面沒有一次給表設置多個值的方法,必須一個一個地設置,每次設置都必須壓入新值,所以即使是相對較快的lua_rawseti,也架不住每次設置的兩次API啊……當然,從這一點上也可以看出來,我們的測試是很不合理的,因為即使是API效率,都可以極大地影響測試結果。當然這里需要說明的是,即使是將API效率的原因去除,c table仍然是最慢的。

然后,c cloure和lua table就比較符合我們的期望了。看來的確是閉包會快一點啊……

這里只比較了創建的效率,對於取值的效率,大家可以自己去比較。但是從這里可以看出來,如果希望交換大量小數據塊,那么closure的確是一個合理的選擇:它體積較小,沒有多余的功能(比如表支持的哈希查找或者增加大小或者獨立的元表等等)。作為需要快速保存少量數據,lua-vararg的確是一個很好的選擇——另外,盡量選擇C實現的版本,如果無法使用C庫,那就還是用表來實現吧……這樣比較快……

 

最后,附上我自己寫的vararg的C實現:https://gist.github.com/starwing/5893607

 

vararg.c

#define LUA_LIB
#include <lua.h>
#include <lauxlib.h>

static lua_Integer posrelat(lua_Integer pos, size_t len) {
    if (pos >= 0) return pos;
    else if (0u - (size_t)pos > len) return 0;
    else return (lua_Integer)len + pos + 1;
}

static int tuple(lua_State *L) {
    int top, n = (int)lua_tointeger(L, lua_upvalueindex(1));
    lua_Integer i, j;
    switch (lua_type(L, 1)) {
    case LUA_TNIL: /* as iterator */
        i = lua_tointeger(L, 2) + 1;
        if (i <= 0 || i > n) return 0;
        lua_pushinteger(L, i);
        lua_pushvalue(L, lua_upvalueindex(i + 1));
        return 2;
    case LUA_TSTRING: /* as length operator */
        if (*lua_tostring(L, 1) == '#') {
            lua_pushinteger(L, n);
            return 1;
        }
        break;
    case LUA_TNONE: /* get all varargs */
        luaL_checkstack(L, n, "too many values");
        for (i = 1; i <= n; ++i)
            lua_pushvalue(L, lua_upvalueindex(i+1));
        return n;
    case LUA_TNUMBER: /* get/set a range */
        i = posrelat(luaL_checkinteger(L, 1), n);
        j = posrelat(luaL_optinteger(L, 2, i), n);
        if (i > j) return 0;
        n = (int)(j-i+1);
        luaL_checkstack(L, n, "too many values");
        if ((top = lua_gettop(L)) <= 2) { /* get */
            for (; i <= j; ++i)
                lua_pushvalue(L, lua_upvalueindex(i+1));
        }
        else {
            int idx;
            lua_settop(L, top = n + 2);
            for (idx = 3; idx <= top; ++idx) {
                lua_pushvalue(L, idx);
                lua_replace(L, lua_upvalueindex(i+idx-2));
            }
        }
        return n;
    }
    return luaL_argerror(L, 1, "invalid argument");
}

static int Lpack(lua_State *L) {
    int n = lua_gettop(L);
    if (n >= 255) luaL_error(L, "too many values to pack");
    lua_pushinteger(L, n);
    lua_insert(L, 1);
    lua_pushcclosure(L, tuple, n+1);
    return 1;
}

static int Lrange(lua_State *L) {
    int n = lua_gettop(L) - 2;
    lua_Integer i, j;
    if (n < 0) return 0;
    i = posrelat(luaL_checkinteger(L, 1), n);
    j = posrelat(luaL_checkinteger(L, 2), n);
    if (i > j || j == 0) return 0;
    if (j > n) luaL_checkstack(L, j-n, "range is too big");
    lua_settop(L, j + 2);
    return j-i+1;
}

static int Linsert(lua_State *L) {
    int n = lua_gettop(L) - 2;
    lua_Integer i = posrelat(luaL_checkinteger(L, 2), n);
    if (i > n) {
        luaL_checkstack(L, i-n, "index is too big");
        lua_settop(L, i + 1);
        lua_pushvalue(L, 1);
        return i;
    }
    lua_pushvalue(L, 1);
    lua_insert(L, i + 2);
    return n + 1;
}

static int Lremove(lua_State *L) {
    int n = lua_gettop(L) - 1;
    lua_Integer i = posrelat(luaL_checkinteger(L, 1), n);
    if (i <= n) {
        lua_remove(L, i + 1);
        --n;
    }
    return n;
}

static int Lreplace(lua_State *L) {
    int n = lua_gettop(L) - 2;
    lua_Integer i = posrelat(luaL_checkinteger(L, 2), n);
    if (i > n) {
        luaL_checkstack(L, i-n, "index is too big");
        lua_settop(L, i + 1);
        lua_pushvalue(L, 1);
        return i;
    }
    lua_pushvalue(L, 1);
    lua_replace(L, i + 2);
    return n;
}

static int Lpush(lua_State *L) {
    lua_pushvalue(L, 1);
    return lua_gettop(L) - 1;
}

static int Lpop(lua_State *L) {
    lua_pop(L, 1);
    return lua_gettop(L);
}

static int Ltake(lua_State *L) {
    int n = lua_gettop(L) - 1;
    lua_Integer i = posrelat(luaL_checkinteger(L, 1), n);
    if (i > n) return 0;
    lua_pop(L, n-i);
    return i;
}

static int Ltail(lua_State *L) {
    int n = lua_gettop(L) - 1;
    lua_Integer i = posrelat(luaL_checkinteger(L, 1), n);
    if (i > n) return 0;
    return n-i+1;
}

static int Lshift(lua_State *L) {
    return lua_gettop(L) - 1;
}

static int Lmap(lua_State *L) {
    int i, n = lua_gettop(L);
    luaL_checkany(L, 1);
    for (i = 2; i <= n; ++i) {
        lua_pushvalue(L, 1);
        lua_pushvalue(L, i);
        lua_call(L, 1, 1);
        lua_replace(L, i);
    }
    return n-1;
}

static int Lfilter(lua_State *L) {
    int i, n = lua_gettop(L);
    luaL_checkany(L, 1);
    for (i = 2; i <= n; ++i) {
        lua_pushvalue(L, 1);
        lua_pushvalue(L, i);
        lua_call(L, 1, 1);
        if (!lua_toboolean(L, -1)) {
            lua_remove(L, i);
            --i, --n;
        }
        lua_pop(L, 1);
    }
    return n-1;
}

static int Lreduce(lua_State *L) {
    int i, n = lua_gettop(L);
    luaL_checkany(L, 1);
    if (n <= 3) {
        lua_call(L, n-1, 1);
        return 1;
    }
    lua_pushvalue(L, 1);
    lua_pushvalue(L, 2);
    lua_pushvalue(L, 3);
    lua_call(L, 2, 1);
    for (i = 4; i <= n; ++i) {
        lua_pushvalue(L, 1);
        lua_insert(L, -2);
        lua_pushvalue(L, i);
        lua_call(L, 2, 1);
    }
    return 1;
}

static int Lunpack(lua_State *L) {
    int i, n = lua_gettop(L);
    for (i = 1; i <= n; ++i) {
        lua_pushvalue(L, i);
        lua_call(L, 0, LUA_MULTRET);
    }
    return lua_gettop(L)-n;
}

static int Lrotate(lua_State *L) {
    int n = lua_gettop(L) - 1;
    lua_Integer i, c = luaL_checkinteger(L, 1) % n;
#if LUA_VERSION_NUM >= 503
    (void)i; /* unused */
    lua_rotate(L, 2, (int)c);
#else
    c = -c + ((c > 0) ? n+1 : 1);
    if (c > 1) luaL_checkstack(L, c-1, "too many values");
    for (i = 2; i <= c; ++i)
        lua_pushvalue(L, i);
#endif
    return n;
}

static int Lreverse(lua_State *L) {
    int i, j, n = lua_gettop(L);
    for (i = 1, j = n; i < j; ++i, --j) {
        lua_pushvalue(L, i);
        lua_pushvalue(L, j);
        lua_replace(L, i);
        lua_replace(L, j);
    }
    return n;
}

static int Lrep(lua_State *L) {
    int top = lua_gettop(L), n = top - 1;
    lua_Integer i, j, count = luaL_checkinteger(L, 1);
    if (count <= 0) return 0;
    if (n == 0) {
        luaL_checkstack(L, count, "too many values");
        lua_settop(L, count+1);
        return count;
    }
    luaL_checkstack(L, n*(count-1), "too many values");
    for (i = 1; i < count; ++i)
        for (j = 2; j <= top; ++j)
            lua_pushvalue(L, j);
    return n*count;
}

LUALIB_API int luaopen_vararg(lua_State *L) {
    luaL_Reg libs[] = {
        { "concat",  Lunpack  },
        { "unpack",  Lunpack  },
        { "filter",  Lfilter  },
        { "insert",  Linsert  },
        { "map",     Lmap     },
        { "pack",    Lpack    },
        { "pop",     Lpop     },
        { "push",    Lpush    },
        { "range",   Lrange   },
        { "reduce",  Lreduce  },
        { "remove",  Lremove  },
        { "rep",     Lrep     },
        { "replace", Lreplace },
        { "reverse", Lreverse },
        { "rotate",  Lrotate  },
        { "shift",   Lshift   },
        { "tail",    Ltail    },
        { "take",    Ltake    },
        { NULL, NULL }
    };
#if LUA_VERSION_NUM >= 502
    luaL_newlib(L, libs);
#else
    luaL_register(L, "vararg", libs);
#endif
    return 1;
}
/* cc: flags+='-s -O3 -mdll -DLUA_BUILD_AS_DLL' libs+='-llua53'
 * cc: output='vararg.dll' */

 

vararg_test.lua

local _G = require "_G"
local assert = _G.assert
local pcall = _G.pcall
local print = _G.print
local select = _G.select
local type = _G.type

local math = require "math"
local ceil = math.ceil
local huge = math.huge
local min = math.min

local table = require "table"
local unpack = table.unpack or _G.unpack

local vararg = require "vararg"
local pack = vararg.pack
local range = vararg.range
local insert = vararg.insert
local remove = vararg.remove
local replace = vararg.replace
local push = vararg.push
local concat = vararg.concat
local map = vararg.map
local rotate = vararg.rotate

-- auxiliary functions----------------------------------------------------------

local values = {}
local maxstack
for i = 1, huge do
    if not pcall(unpack, values, 1, 2^i) then
        local min, max = 2^(i-1), 2^i
        while min < max do
            local mid = ceil((min+max)/2)
            if pcall(unpack, values, 1, mid) then
                min = mid
            else
                max = mid-1
            end
        end
        maxstack = max
        break
    end
end
for i = 1, maxstack, 2 do
    values[i] = i
end

local function tpack(...)
    return {..., n=select("#", ...)}
end

local function assertsame(v, i, j, ...)
    local count = select("#", ...)
    assert(count == j-i+1, count..","..i..","..j)
    for pos = 1, count do
        assert(v[i+pos-1] == select(pos, ...))
    end
end

local function asserterror(expected, f, ...)
    local ok, actual = pcall(f, ...)
    assert(ok == false, "error was expected")
    assert(actual:find(expected, 1), "wrong error, got "..actual)
end

-- test 'pack' function --------------------------------------------------------

local function testpack(...)
    local v = {...}
    local n = select("#", ...)
    local p = pack(...)
    assertsame(v, 1, n, p())
    assert(n == p("#"))
    for i,pv in p do assert(v[i] == pv) end
    for i = 1, n do
        assert(v[i] == p(i))
        if n > 0 then
            assert(v[i] == p(i-n-1))
        end
    end
    for i = 1, n, 10 do
        local j = i+9
        assertsame(v, i, j, p(i, j))
        if n > j then
            assertsame(v, i, j, p(i-n-1, j-n-1))
        end
    end
end

testpack()
testpack({},{},{})
testpack(nil)
testpack(nil, nil)
testpack(nil, 1, nil)
testpack(unpack(values, 1, 254))

local ok, err = pcall(pack, unpack(values, 1, 255))
if ok then -- Lua version
    assert(type(err) == "function")
else -- C version
    assert(ok == false and err == "too many values to pack")
end

-- test 'range' function -------------------------------------------------------

local function testrange(n, ...)
    local v = {...}
    for c = 1, 3 do
        for i = 1, n, c do
            local j = min(i+c-1, n)
            assertsame(v, i, j, range(i, j, ...))
            local n = select("#", ...)
            if n > 0 then
                assertsame(v, i, j, range(i-n-1, j-n-1, ...))
            end
        end
    end
end

local ok, err = pcall(range, 0, 0, ...)
if ok then -- Lua version
    assert(err == nil)
else -- C version
    assert(ok == false and err == "bad argument #1 to '?' (index out of bounds (0))")
end

testrange(10)
testrange(10, 1,2,3,4,5,6,7,8,9,0)
maxstack = 10000 -- use a smaller value
testrange(maxstack, unpack(values, 1, maxstack))

-- test other functions --------------------------------------------------------

assertsame({1,2,3,4,5}, 1, 5, insert(3, 3, 1,2,4,5))
assertsame({1,2,3,4,5}, 1, 5, insert(4,-1, 1,2,3,5))
assertsame({1,2,nil,4}, 1, 4, insert(4, 4, 1,2))
assertsame({nil,nil,3}, 1, 3, insert(3, 3))

assertsame({1,2,3,4,5}, 1, 5, replace(3, 3, 1,2,0,4,5))
assertsame({1,2,3,4,5}, 1, 5, replace(5,-1, 1,2,3,4,0))
assertsame({1,2,nil,4}, 1, 4, replace(4, 4, 1,2))
assertsame({nil,nil,3}, 1, 3, replace(3, 3))

assertsame({1,2,3,4,5}, 1, 5, remove( 3, 1,2,0,3,4,5))
assertsame({1,2,3,4,5}, 1, 5, remove(-1, 1,2,3,4,5,0))
assertsame({1,2,nil,4}, 1, 4, remove( 4, 1,2,nil,0,4))
assertsame({nil,nil,3}, 1, 3, remove( 3, nil,nil,0,3))
assertsame({1,2,3,4,5}, 1, 5, remove(10, 1,2,3,4,5))

assertsame({1,2,3,4,5}, 1, 5, push(5, 1,2,3,4))
assertsame({1,2,nil,4}, 1, 4, push(4, 1,2,nil))
assertsame({nil,nil,3}, 1, 3, push(3, nil,nil))

assertsame({5,1,2,3,4}, 1, 5, rotate(1, 1,2,3,4,5))
assertsame({2,3,4,5,1}, 1, 5, rotate(-1, 1,2,3,4,5))
assertsame({5,1,2,3,4}, 1, 5, rotate(11, 1,2,3,4,5))
assertsame({2,3,4,5,1}, 1, 5, rotate(-11, 1,2,3,4,5))

assertsame({1,2,3,4,5,6,7,8,9}, 1, 9, concat(pack(1,2,3),
                                             pack(4,5,6),
                                             pack(7,8,9)))

-- test function errors and expectional conditions ---------------------------

assertsame({"1","2","3","4","5"}, 1, 5, map(tostring, 1,2,3,4,5))
assertsame({"1","2","nil","4"  }, 1, 4, map(tostring, 1,2,nil,4))
assertsame({"nil","nil","3"    }, 1, 3, map(tostring, nil,nil,3))
assertsame({"1","nil","nil"    }, 1, 3, map(tostring, 1,nil,nil))

asserterror("bad argument #2 to '[^']*' %(number expected, got no value%)", insert)
asserterror("bad argument #2 to '[^']*' %(number expected, got no value%)", insert, nil)
asserterror("bad argument #2 to '[^']*' %(number expected, got nil%)", insert, nil, nil)
--asserterror("bad argument #2 to '[^']*' %(index out of bounds %(0%)%)", insert, nil, 0)
assert(select('#', insert(nil, 0)) == 1)

asserterror("bad argument #2 to '[^']*' %(number expected, got no value%)", replace)
asserterror("bad argument #2 to '[^']*' %(number expected, got no value%)", replace, nil)
asserterror("bad argument #2 to '[^']*' %(number expected, got nil%)", replace, nil, nil)
--asserterror("bad argument #2 to '[^']*' %(index out of bounds %(0%)%)", replace, nil, 0)
assert(select('#', replace(nil, 0)) == 0)

asserterror("bad argument #1 to '[^']*' %(number expected, got no value%)", remove)
asserterror("bad argument #1 to '[^']*' %(number expected, got nil%)", remove, nil)
--asserterror("bad argument #1 to '[^']*' %(index out of bounds %(0%)%)", remove, 0)
assert(select('#', remove(0)) == 0)

assertsame({}, 1, 0, push())
assertsame({nil}, 1, 1, push(nil))

assertsame({}, 1, 0, concat())
asserterror("attempt to call a nil value", concat, nil)

asserterror("bad argument #1 to '[^']*' %(value expected%)", map)
assertsame({}, 1, 0, map(nil))
asserterror("attempt to call a nil value", map, nil, nil)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM