Lua源码剖析(lmathlib.c)

0、该模块是Lua数学库math模块的实现,读该模块源码是为了学习Lua与C的API以及注册方法。

1、当在Lua程序中,调用require("math")时,就会调用下面的luaopen_math函数,来注册函数库:
     static const luaL_Reg mathlib[] = {
       {"abs",   math_abs},
       {"acos",  math_acos},
       {"asin",  math_asin},
       {"atan2", math_atan2},
       {"atan",  math_atan},
       {"ceil",  math_ceil},
       {"cosh",   math_cosh},
       {"cos",   math_cos},
       {"deg",   math_deg},
       {"exp",   math_exp},
       {"floor", math_floor},
       {"fmod",   math_fmod},
       {"frexp", math_frexp},
       {"ldexp", math_ldexp},
#if defined(LUA_COMPAT_LOG10)
       {"log10", math_log10},
#endif
       {"log",   math_log},
       {"max",   math_max},
       {"min",   math_min},
       {"modf",   math_modf},
       {"pow",   math_pow},
       {"rad",   math_rad},
       {"random",     math_random},
       {"randomseed", math_randomseed},
       {"sinh",   math_sinh},
       {"sin",   math_sin},
       {"sqrt",  math_sqrt},
       {"tanh",   math_tanh},
       {"tan",   math_tan},
       {NULL, NULL}
     };

     LUAMOD_API int luaopen_math (lua_State *L) {
       luaL_newlib(L, mathlib); 
       lua_pushnumber(L, PI);
       lua_setfield(L, -2, "pi");
       lua_pushnumber(L, HUGE_VAL);
       lua_setfield(L, -2, "huge");
       return 1;
     }

     结构体实例mathlib中保存了lua函数库所有的接口,结构体luaL_Reg在lauxlib.h中的定义的:
     typedef struct luaL_Reg {
       const char *name;
       lua_CFunction func;
     } luaL_Reg;
这里的函数类型lua_CFunction在lua.h中定义的:typedef int (*lua_CFunction) (lua_State *L);所有用C实现
的Lua函数,都是必须是这种类型,即一个lua_State的参数,返回值为int。

     函数LUAMOD_API int luaopen_math (lua_State *L)调用auxiliary library中的接口luaL_newlib(L,mathlib)注册数学库,在lauxlib.h中,我们可以看到该接口实质就是一个宏:
     #define luaL_newlib(L,l)     (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)),
而luaL_newlibtable在lauxlib.h实质上也是一个宏:
     #define luaL_newlibtable(L,l)     \
       lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
它根据要注册的结构体中的元素,创建一个空的table,并压入栈顶,在提前知道table中有多少元素的情况时,用接口lua_createtable来创建table比lua_newtable()更高效,注意上面减去1,是因为结构体mathlib最后一个元素为{NULL,NULL}。创建空的table,再利用接口luaL_setfuncs把结构体mathlib中所有函数注册到刚才创建的table中。

     接下来代码:
     lua_pushnumber(L, PI);
     lua_setfield(L, -2, "pi");
用来向函数库中注册常量pi,PI是一个宏,定义在lmathlib.c中,这两行实质上就是通过lua的C API设置前面创建的table,其中key为"pi",value为PI。类似的lua_pushnumber(L, HUGE_VAL); lua_setfield(L, -2, "huge");注册函数库常量huge。HUGE_VAL是C语言在<error.h>定义的一个非常大的double正数。

     最后注意函数luaopen_math()前面有一个宏LUAMOD_API,它是在luaconf.h中定义的,也就是extern,它是用来标识那些标准打开的库函数,类似的LUALIB_API也是extern,它用来标识所有的auxiliary库函数,LUA_API也是extern,用来标识所有的core API函数。


2、由于math模块的API非常多,下面分析math模块中比较常用的API实现,首先看math.random()的实现,它对应的C实现如下:
     static int math_random (lua_State *L) {
       /* the `%' avoids the (rare) case of r==1, and is needed also because on
          some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */
       lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
       switch (lua_gettop(L)) {  /* check number of arguments */
          case 0: {  /* no arguments */
            lua_pushnumber(L, r);  /* Number between 0 and 1 */
            break;
          }
          case 1: {  /* only upper limit */
            lua_Number u = luaL_checknumber(L, 1);
            luaL_argcheck(L, 1.0 <= u, 1, "interval is empty");
            lua_pushnumber(L, l_tg(floor)(r*u) + 1.0);  /* int in [1, u] */
            break;
          }
          case 2: {  /* lower and upper limits */
            lua_Number l = luaL_checknumber(L, 1);
            lua_Number u = luaL_checknumber(L, 2);
            luaL_argcheck(L, l <= u, 2, "interval is empty");
            lua_pushnumber(L, l_tg(floor)(r*(u-l+1)) + l);  /* int in [l, u] */
            break;
          }
          default: return luaL_error(L, "wrong number of arguments");
       }
       return 1;
     }

     函数static标识该函数只在模块内可见,这是一个非常好的习惯。
     lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
上面语句利用C语言的标准库函数,生成一个[0,1)之间的随机数,这里的lua_Number是lua.h定义的LUA_NUMBER类型,而LUA_NUMBER是在luaconf.h中定义的一个宏,它标识Lua中的数字类型,标准实现了double型。

     lua_gettop(L)用来获取栈顶元素的索引,也就是栈中元素的个数。
     若栈中元素个数为0,即在Lua中调用math.random(),这时候返回的[0,1)之间的值,即通过接口lua_pushnumber(L,r)把r压入栈顶,返回这个值。
     若栈中元素个数为1,即在Lua中调用math.random(m),这时候返回的是[1,m]之间的值。接口luaL_checknumber()用来检测是否是一个number,并返回这个number,luaL_argcheck(),用来检测条件是否为true,若不是,则会引起一个错误。注意l_tg是在lmathlib.c中定义的一个宏:
     #define l_tg(x)     (x) 
这样做的目的,允许x在任何数学算术操作中可以在x后面加后缀l或f。比如当你想把Lua的Number类型该为long duoble时,便可以通过修改这个宏的定义,改变操作Number的C函数。比如使用sinl(或者使用sinf操作float类型)而不是sin。
     若栈中元素个数为2,即在lua中调用math.random(n,m),这时候返回的是[n,m]之间的值,代码玩家类似于只有一个元素的情况。
     若是其他情况,则调用接口int luaL_error (lua_State *L, const char *fmt, ...);引发一个错误,错误信息是格式化的fmt。
     用参数个数来区分功能上的微小差异是典型的Lua风格,这是Lua接口设计上的一个惯例。
    
     我们还可以注意到math_abs,math_acos等函数,都是直接用C中的库函数来实现的。     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值