Lua内置库的实现(一)_math模块(二)_math模块API实现

 math模块API实现

       math模块内的各个数学函数的实现中规中矩,就是使用的Lua手册里给出的API来实现的。

        Lua的扩展方式是编写一个原型为int lua_CFunction (lua_State *L)的函数。L对于使用者来说,不必关心其内部结构。实际上,公开API定义所在的lua. h中也没有lua_State的结构定义。对于一个用C编写的系统,模块化设计的重点在于接口的简洁和稳定。数据结构的细节和内存布局最好能藏在实现层面,Lua的API设计在这方面做了一个很好的示范。这个函数通常不会也不建议被C程序的其它部分直接调用,所以一般藏在源文件内部,以static修饰之。

       Lua的C函数以堆栈的形式和Lua虚拟机交换数据,由一系列API从L中取出值,经过一番处理,压回L中的堆栈。具体的使用方式见Lua手册。

/* 源代码2.5 lmathlib.c:l_tg */
#if !defined(l_tg)
#define l_tg(x)
#endif

static int math_abs (lua_State *L){
	lua_pushnumber(L, l_tg(fabs)(luaL_checknumber(L, l)));
	return 1;
}

static int math_sin (lua_State *L){
	lua_pushnumber(L, l_tg(sin)(luaL_checknumber(L, l)));
	return 1;
}

static int math_sinh (lua_State *L){
	lua_pushnumber(L, l_tg(sinh)(luaL_checknumber(L, l)));
	return 1;
}

         稍微值得留意的是,在这里还定义了一个宏l_tg。可以看出Lua在可定制性上的考虑。当你想把Lua的Number类型修改为long double时,便可以通过修改这个宏定义,改变操作Number的C函数。比如使用sinl(或是使用sinf操作float类型)而不是sin
    我们再看另一小段代码:math.log的实现:
/* 源代码2.6 lmathlib.c:log */
static int math_log(lua_State *L){
	lua_Number x = luaL_checknumber(L, l);
	lua_Number res;
	if (lua_isnoneornil(L, 2)) then
		res = l_log(log)(x);
	else{
		lua_Number base = luaL_checknumber(L, 2);
		if (base == 10.0) res = l_tg(log10)(x);
	else res = l_tg(log)(x)/l_tg(log)(base);
	}
	lua_pushnumber(L, res);
	
	return l;
}

#if defined(LUA_COMPAT_LOG10)
static int math_log10 (lua_State *L){
	lua_pushnumber(L, l_tg(log10)(luaL_checknumber(L, l)));
	return l;
}

#endif

        这里可以看出Lua对API的锤炼,以及对宿主语言C语言的逐步脱离。早期的版本中,是有math.log和math.logl0两个API的。目前1og10这个版本仅仅考虑兼容因素时才存在。这缘于C语言中也有1og10的API。但从语义上来看,只需要一个lob函数就够了。(因为人类更习惯用十进制计数,而计算机则内部运行采用的是二进制。由于浮点数表示误差的缘故,log(x)/log(10)往往并不严格等于log10(x),而是有少许误差,在常用的X86浮点指令集中,CPU硬件支持以10为底的对数计算,在C语言也有独立的函数通过不同的机器指令来实现。)早期的Lua函数看起来更像是对C函数的直接映射、而这些年Lua正向独立语言而演变,在更高的层面设计API就不必再表达实现层面的差别了。
        这一小节最后一段值得一读的是math.random的实现:
/* 源代码2.7 lmathlib.c: random */
static int math_random (lua_State *L){
/* 	the '%' avoids the (rare) case of r == 1, and is needed alse because on some 
	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, l);
			luaL_argcheck(L, 1.0 <= u, 1, "interval_is_empty");
			lua_pushnumber(L, l_tg(floor)(r*u) + 1.0);/* int in[1, u]*/
		}
		case 2:{/* lower and upper limits */
			lua_Number l = luaL_checknumber(L, 1);
			lua_Number u = luaL_checknumber(L, 2);
			lua_argcheck(L, 1 <= u, 2, "interval_is_empty");
			lua_pushnumber(L, l_tg(floor)(r*(u-l+l)) + l);/*int in [l, u]*/
			break;
		}
		default: return luaL_error (L, "wrong_number_of_arguments");
	}
	return 1;
}
       用参数个数来区分功能上的微小差异是典型的Lua风格,这是Lua接口设计上的一个惯例。另外、编码中考虑平台差异很考量程序员对各平台细节的了解,例如这里注释中提到的SunOS的rand()的小问题。

阅读更多
换一批

没有更多推荐了,返回首页