diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 5fd1e752..6d69327e 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -8,8 +8,10 @@ // #define LUA_USE_BUILTIN_IO // for io.xxx(), partially work // #define LUA_USE_BUILTIN_OS // for os.xxx(), not work -// #define LUA_USE_BUILTIN_DEBUG // for debug.xxx(), not work +// #define LUA_USE_BUILTIN_DEBUG +#define LUA_USE_BUILTIN_DEBUG_MINIMAL // for debug.getregistry() and debug.traceback() +#ifndef LUA_CROSS_COMPILER #define LUA_USE_MODULES #ifdef LUA_USE_MODULES @@ -46,5 +48,6 @@ //#define LUA_USE_MODULES_HX711 #endif /* LUA_USE_MODULES */ +#endif #endif /* __USER_MODULES_H__ */ diff --git a/app/libc/c_stdlib.c b/app/libc/c_stdlib.c index a180cad8..50c685c2 100644 --- a/app/libc/c_stdlib.c +++ b/app/libc/c_stdlib.c @@ -1,9 +1,20 @@ +//#include "user_interface.h" +#include "user_config.h" + +#ifdef LUA_CROSS_COMPILER + +#include +#include +#include +#define ICACHE_RODATA_ATTR +#define TRUE 1 +#define FALSE 0 + +#else + #include "c_stdlib.h" -#include "c_stdio.h" #include "c_types.h" #include "c_string.h" -#include "user_interface.h" -#include "user_config.h" // const char *lua_init_value = "print(\"Hello world\")"; const char *lua_init_value = "@init.lua"; @@ -21,7 +32,6 @@ const char *c_getenv(const char *__string) } return NULL; } - // make sure there is enough memory before real malloc, otherwise malloc will panic and reset // void *c_malloc(size_t __size){ // if(__size>system_get_free_heap_size()){ @@ -43,7 +53,8 @@ const char *c_getenv(const char *__string) // // NODE_ERR("free1: %d\n", system_get_free_heap_size()); // os_free(p); // // NODE_ERR("-free1: %d\n", system_get_free_heap_size()); -// } +// }c_stdlib.s + // int c_rand(void){ @@ -55,9 +66,8 @@ const char *c_getenv(const char *__string) // } #include <_ansi.h> //#include -#include //#include "mprec.h" - +#endif double powersOf10[] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = /* Table giving binary powers of 10. Entry */ { 10., /* is 10^2^i. Used to convert decimal */ diff --git a/app/lua/lapi.c b/app/lua/lapi.c index 712e0666..e3d04e5b 100644 --- a/app/lua/lapi.c +++ b/app/lua/lapi.c @@ -4,17 +4,15 @@ ** See Copyright Notice in lua.h */ - -//#include "c_assert.h" -#include "c_math.h" -#include "c_stdarg.h" -#include "c_string.h" - #define lapi_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +//#include C_HEADER_ASSERT +#include C_HEADER_MATH +#include C_HEADER_STRING #include "lapi.h" #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/lauxlib.c b/app/lua/lauxlib.c index e5c89ad5..35ff8266 100644 --- a/app/lua/lauxlib.c +++ b/app/lua/lauxlib.c @@ -4,14 +4,18 @@ ** See Copyright Notice in lua.h */ +#define LUAC_CROSS_FILE -#include "c_ctype.h" -// #include "c_errno.h" -#include "c_stdarg.h" -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" +#include "lua.h" +#include C_HEADER_CTYPE +#include C_HEADER_ERRNO +#include C_HEADER_STDIO +#include C_HEADER_STDLIB +#include C_HEADER_STRING +#ifndef LUA_CROSS_COMPILER #include "flash_fs.h" +#else +#endif /* This file uses only the official API of Lua. ** Any function declared here could be written as an application function. @@ -20,8 +24,6 @@ #define lauxlib_c #define LUA_LIB -#include "lua.h" - #include "lrotable.h" #include "lauxlib.h" @@ -573,7 +575,7 @@ LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { ** ======================================================= */ -#if 0 +#ifdef LUA_CROSS_COMPILER typedef struct LoadF { int extraline; @@ -647,7 +649,7 @@ LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { #else -#include "c_fcntl.h" +#include C_HEADER_FCNTL typedef struct LoadFSF { int extraline; diff --git a/app/lua/lauxlib.h b/app/lua/lauxlib.h index fbaaf1a6..809a7de5 100644 --- a/app/lua/lauxlib.h +++ b/app/lua/lauxlib.h @@ -9,11 +9,14 @@ #define lauxlib_h -//#include "c_stddef.h" -#include "c_stdio.h" - #include "lua.h" +#ifdef LUA_CROSS_COMPILER +#include +#else +#include "c_stdio.h" +#endif + #if defined(LUA_COMPAT_GETN) LUALIB_API int (luaL_getn) (lua_State *L, int t); @@ -79,7 +82,7 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, LUALIB_API int (luaL_ref) (lua_State *L, int t); LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); -#if 0 +#ifdef LUA_CROSS_COMPILER LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); #else LUALIB_API int (luaL_loadfsfile) (lua_State *L, const char *filename); diff --git a/app/lua/lbaselib.c b/app/lua/lbaselib.c index b7e328f0..7c05a6ea 100644 --- a/app/lua/lbaselib.c +++ b/app/lua/lbaselib.c @@ -6,16 +6,14 @@ -#include "c_ctype.h" -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" - #define lbaselib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" - +#include C_HEADER_STDIO +#include C_HEADER_STRING +#include C_HEADER_STDLIB #include "lauxlib.h" #include "lualib.h" #include "lrotable.h" @@ -295,7 +293,7 @@ static int luaB_loadstring (lua_State *L) { static int luaB_loadfile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); -#if 0 +#ifdef LUA_CROSS_COMPILER return load_aux(L, luaL_loadfile(L, fname)); #else return load_aux(L, luaL_loadfsfile(L, fname)); @@ -342,7 +340,7 @@ static int luaB_load (lua_State *L) { static int luaB_dofile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); int n = lua_gettop(L); -#if 0 +#ifdef LUA_CROSS_COMPILER if (luaL_loadfile(L, fname) != 0) lua_error(L); #else if (luaL_loadfsfile(L, fname) != 0) lua_error(L); diff --git a/app/lua/lcode.c b/app/lua/lcode.c index f0282704..bf5c3fc3 100644 --- a/app/lua/lcode.c +++ b/app/lua/lcode.c @@ -5,12 +5,12 @@ */ -#include "c_stdlib.h" - #define lcode_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDLIB #include "lcode.h" #include "ldebug.h" diff --git a/app/lua/not_used/ldblib.c b/app/lua/ldblib.c similarity index 97% rename from app/lua/not_used/ldblib.c rename to app/lua/ldblib.c index cd61775f..c9eaa644 100644 --- a/app/lua/not_used/ldblib.c +++ b/app/lua/ldblib.c @@ -5,18 +5,19 @@ */ -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" - #define ldblib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDIO +#include C_HEADER_STDLIB +#include C_HEADER_STRING #include "lauxlib.h" #include "lualib.h" #include "lrotable.h" +#include "user_modules.h" @@ -25,6 +26,7 @@ static int db_getregistry (lua_State *L) { return 1; } +#ifndef LUA_USE_BUILTIN_DEBUG_MINIMAL static int db_getmetatable (lua_State *L) { luaL_checkany(L, 1); @@ -73,7 +75,7 @@ static void settabsi (lua_State *L, const char *i, int v) { lua_setfield(L, -2, i); } - +#endif static lua_State *getthread (lua_State *L, int *arg) { if (lua_isthread(L, 1)) { *arg = 1; @@ -84,7 +86,7 @@ static lua_State *getthread (lua_State *L, int *arg) { return L; } } - +#ifndef LUA_USE_BUILTIN_DEBUG_MINIMAL static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { if (L == L1) { @@ -324,7 +326,7 @@ static int db_debug (lua_State *L) { lua_settop(L, 0); /* remove eventual returns */ } } - +#endif #define LEVELS1 12 /* size of the first part of the stack */ #define LEVELS2 10 /* size of the second part of the stack */ @@ -384,12 +386,15 @@ static int db_errorfb (lua_State *L) { #define MIN_OPT_LEVEL 1 #include "lrodefs.h" const LUA_REG_TYPE dblib[] = { +#ifndef LUA_USE_BUILTIN_DEBUG_MINIMAL {LSTRKEY("debug"), LFUNCVAL(db_debug)}, {LSTRKEY("getfenv"), LFUNCVAL(db_getfenv)}, {LSTRKEY("gethook"), LFUNCVAL(db_gethook)}, {LSTRKEY("getinfo"), LFUNCVAL(db_getinfo)}, {LSTRKEY("getlocal"), LFUNCVAL(db_getlocal)}, +#endif {LSTRKEY("getregistry"), LFUNCVAL(db_getregistry)}, +#ifndef LUA_USE_BUILTIN_DEBUG_MINIMAL {LSTRKEY("getmetatable"), LFUNCVAL(db_getmetatable)}, {LSTRKEY("getupvalue"), LFUNCVAL(db_getupvalue)}, {LSTRKEY("setfenv"), LFUNCVAL(db_setfenv)}, @@ -397,6 +402,7 @@ const LUA_REG_TYPE dblib[] = { {LSTRKEY("setlocal"), LFUNCVAL(db_setlocal)}, {LSTRKEY("setmetatable"), LFUNCVAL(db_setmetatable)}, {LSTRKEY("setupvalue"), LFUNCVAL(db_setupvalue)}, +#endif {LSTRKEY("traceback"), LFUNCVAL(db_errorfb)}, {LNILKEY, LNILVAL} }; diff --git a/app/lua/ldebug.c b/app/lua/ldebug.c index d22c2af9..9f1dff2d 100644 --- a/app/lua/ldebug.c +++ b/app/lua/ldebug.c @@ -5,15 +5,12 @@ */ -#include "c_stdarg.h" -#include "c_stddef.h" -#include "c_string.h" - - #define ldebug_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "lapi.h" #include "lcode.h" diff --git a/app/lua/ldo.c b/app/lua/ldo.c index 8189f108..9a2ddd0e 100644 --- a/app/lua/ldo.c +++ b/app/lua/ldo.c @@ -4,15 +4,14 @@ ** See Copyright Notice in lua.h */ - #include -#include "c_stdlib.h" -#include "c_string.h" #define ldo_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/ldump.c b/app/lua/ldump.c index c48878dc..9f3543b3 100644 --- a/app/lua/ldump.c +++ b/app/lua/ldump.c @@ -4,13 +4,12 @@ ** See Copyright Notice in lua.h */ -#include "c_stddef.h" -#include "c_string.h" - #define ldump_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "lobject.h" #include "lstate.h" diff --git a/app/lua/lfunc.c b/app/lua/lfunc.c index 7562d00d..b82ea17b 100644 --- a/app/lua/lfunc.c +++ b/app/lua/lfunc.c @@ -4,11 +4,9 @@ ** See Copyright Notice in lua.h */ - -#include "c_stddef.h" - #define lfunc_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" diff --git a/app/lua/lgc.c b/app/lua/lgc.c index d9c89716..4288933d 100644 --- a/app/lua/lgc.c +++ b/app/lua/lgc.c @@ -4,12 +4,12 @@ ** See Copyright Notice in lua.h */ -#include "c_string.h" - #define lgc_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/llex.c b/app/lua/llex.c index 11db1a64..d38c993b 100644 --- a/app/lua/llex.c +++ b/app/lua/llex.c @@ -5,14 +5,14 @@ */ -#include "c_ctype.h" -#include "c_locale.h" -#include "c_string.h" - #define llex_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_CTYPE +#include C_HEADER_LOCALE +#include C_HEADER_STRING #include "ldo.h" #include "llex.h" diff --git a/app/lua/llimits.h b/app/lua/llimits.h index 246f13ab..62131805 100644 --- a/app/lua/llimits.h +++ b/app/lua/llimits.h @@ -9,12 +9,9 @@ //#include "c_limits.h" -//#include "c_stddef.h" - #include "lua.h" - typedef LUAI_UINT32 lu_int32; typedef LUAI_UMEM lu_mem; diff --git a/app/lua/lmathlib.c b/app/lua/lmathlib.c index c7f22502..42ab2b8a 100644 --- a/app/lua/lmathlib.c +++ b/app/lua/lmathlib.c @@ -5,13 +5,13 @@ */ -#include "c_stdlib.h" -#include "c_math.h" - #define lmathlib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDLIB +#include C_HEADER_MATH #include "lauxlib.h" #include "lualib.h" @@ -35,7 +35,7 @@ static int math_abs (lua_State *L) { } #ifndef LUA_NUMBER_INTEGRAL - +#if 0 static int math_sin (lua_State *L) { lua_pushnumber(L, sin(luaL_checknumber(L, 1))); return 1; @@ -85,6 +85,7 @@ static int math_atan2 (lua_State *L) { lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } +#endif static int math_ceil (lua_State *L) { lua_pushnumber(L, ceil(luaL_checknumber(L, 1))); @@ -95,7 +96,7 @@ static int math_floor (lua_State *L) { lua_pushnumber(L, floor(luaL_checknumber(L, 1))); return 1; } - +#if 0 static int math_fmod (lua_State *L) { lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; @@ -108,6 +109,7 @@ static int math_modf (lua_State *L) { lua_pushnumber(L, fp); return 2; } +#endif #else // #ifndef LUA_NUMBER_INTEGRAL @@ -173,7 +175,7 @@ static int math_pow (lua_State *L) { #ifndef LUA_NUMBER_INTEGRAL - +#if 0 static int math_log (lua_State *L) { lua_pushnumber(L, log(luaL_checknumber(L, 1))); return 1; @@ -210,6 +212,7 @@ static int math_ldexp (lua_State *L) { lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))); return 1; } +#endif #endif // #ifdef LUA_NUMBER_INTEGRAL @@ -306,6 +309,8 @@ static int math_randomseed (lua_State *L) { return 0; } + + #define MIN_OPT_LEVEL 1 #include "lrodefs.h" const LUA_REG_TYPE math_map[] = { diff --git a/app/lua/lmem.c b/app/lua/lmem.c index f8939e0a..9886b771 100644 --- a/app/lua/lmem.c +++ b/app/lua/lmem.c @@ -5,10 +5,9 @@ */ -#include "c_stddef.h" - #define lmem_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" diff --git a/app/lua/lmem.h b/app/lua/lmem.h index f6938452..4766454f 100644 --- a/app/lua/lmem.h +++ b/app/lua/lmem.h @@ -8,7 +8,11 @@ #define lmem_h +//#ifdef LUA_CROSS_COMPILER +//#include +//#else //#include "c_stddef.h" +//#endif #include "llimits.h" #include "lua.h" diff --git a/app/lua/loadlib.c b/app/lua/loadlib.c index 42722c4a..00e8f8dd 100644 --- a/app/lua/loadlib.c +++ b/app/lua/loadlib.c @@ -9,15 +9,18 @@ */ -#include "c_stdlib.h" -#include "c_string.h" -#include "c_fcntl.h" -#include "flash_fs.h" - #define loadlib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDLIB +#include C_HEADER_STRING +#include C_HEADER_FCNTL + +#ifndef LUA_CROSS_COMPILER +#include "flash_fs.h" +#endif #include "lauxlib.h" #include "lualib.h" @@ -328,7 +331,7 @@ static int ll_loadlib (lua_State *L) { ** 'require' function ** ======================================================= */ -#if 0 +#ifdef LUA_CROSS_COMPILER static int readable (const char *filename) { FILE *f = c_fopen(filename, "r"); /* try to open file */ if (f == NULL) return 0; /* open failed */ @@ -389,7 +392,7 @@ static int loader_Lua (lua_State *L) { const char *name = luaL_checkstring(L, 1); filename = findfile(L, name, "path"); if (filename == NULL) return 1; /* library not found in this path */ -#if 0 +#ifdef LUA_CROSS_COMPILER if (luaL_loadfile(L, filename) != 0) #else if (luaL_loadfsfile(L, filename) != 0) diff --git a/app/lua/lobject.c b/app/lua/lobject.c index 73399e58..8a142b4e 100644 --- a/app/lua/lobject.c +++ b/app/lua/lobject.c @@ -4,16 +4,15 @@ ** See Copyright Notice in lua.h */ -#include "c_ctype.h" -#include "c_stdarg.h" -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" #define lobject_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDIO +#include C_HEADER_STRING +#include C_HEADER_STDLIB #include "ldo.h" #include "lmem.h" @@ -21,9 +20,11 @@ #include "lstate.h" #include "lstring.h" #include "lvm.h" - +#ifndef LUA_CROSS_COMPILER #include "flash_api.h" - +#else +#include +#endif const TValue luaO_nilobject_ = {LUA_TVALUE_NIL}; @@ -64,9 +65,11 @@ int luaO_log2 (unsigned int x) { }; int l = -1; while (x >= 256) { l += 8; x >>= 8; } - // return l + log_2[x]; +#ifdef LUA_CROSS_COMPILER + return l + log_2[x]; +#else return l + byte_of_aligned_array(log_2,x); - +#endif } @@ -95,7 +98,21 @@ int luaO_str2d (const char *s, lua_Number *result) { *result = lua_str2number(s, &endptr); if (endptr == s) return 0; /* conversion failed */ if (*endptr == 'x' || *endptr == 'X') /* maybe an hexadecimal constant? */ +#if defined(LUA_CROSS_COMPILER) + { + long lres = strtoul(s, &endptr, 16); +#if LONG_MAX != 2147483647L + if (lres & ~0xffffffffL) + *result = cast_num(-1); + else if (lres & 0x80000000L) + *result = cast_num(lres | ~0x7fffffffL); + else +#endif + *result = cast_num(lres); + } +#else *result = cast_num(c_strtoul(s, &endptr, 16)); +#endif if (*endptr == '\0') return 1; /* most common case */ while (isspace(cast(unsigned char, *endptr))) endptr++; if (*endptr != '\0') return 0; /* invalid trailing characters? */ diff --git a/app/lua/lobject.h b/app/lua/lobject.h index 0ebc5abf..e41b65bc 100644 --- a/app/lua/lobject.h +++ b/app/lua/lobject.h @@ -8,9 +8,11 @@ #ifndef lobject_h #define lobject_h - +#ifdef LUA_CROSS_COMPILER +#include +#else #include "c_stdarg.h" - +#endif #include "llimits.h" #include "lua.h" diff --git a/app/lua/lopcodes.c b/app/lua/lopcodes.c index 4cc74523..f6ecd06a 100644 --- a/app/lua/lopcodes.c +++ b/app/lua/lopcodes.c @@ -6,11 +6,11 @@ #define lopcodes_c #define LUA_CORE +#define LUAC_CROSS_FILE - +#include "luac_cross.h" #include "lopcodes.h" - /* ORDER OP */ const char *const luaP_opnames[NUM_OPCODES+1] = { diff --git a/app/lua/lparser.c b/app/lua/lparser.c index a3653c7d..72fbcaf7 100644 --- a/app/lua/lparser.c +++ b/app/lua/lparser.c @@ -5,12 +5,12 @@ */ -#include "c_string.h" - #define lparser_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "lcode.h" #include "ldebug.h" diff --git a/app/lua/lrotable.c b/app/lua/lrotable.c index 8d7e54e4..33e0b45d 100644 --- a/app/lua/lrotable.c +++ b/app/lua/lrotable.c @@ -1,8 +1,9 @@ /* Read-only tables for Lua */ +#define LUAC_CROSS_FILE -#include "c_string.h" -#include "lrotable.h" #include "lua.h" +#include C_HEADER_STRING +#include "lrotable.h" #include "lauxlib.h" #include "lstring.h" #include "lobject.h" diff --git a/app/lua/lstate.c b/app/lua/lstate.c index 8b71cb47..b4289742 100644 --- a/app/lua/lstate.c +++ b/app/lua/lstate.c @@ -5,10 +5,9 @@ */ -#include "c_stddef.h" - #define lstate_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" diff --git a/app/lua/lstring.c b/app/lua/lstring.c index d2144f22..8f03e52a 100644 --- a/app/lua/lstring.c +++ b/app/lua/lstring.c @@ -5,12 +5,13 @@ */ -#include "c_string.h" #define lstring_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "lmem.h" #include "lobject.h" diff --git a/app/lua/lstrlib.c b/app/lua/lstrlib.c index 9611e961..58f702ac 100644 --- a/app/lua/lstrlib.c +++ b/app/lua/lstrlib.c @@ -5,16 +5,13 @@ */ -#include "c_ctype.h" -#include "c_stddef.h" -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" - #define lstrlib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDIO +#include C_HEADER_STRING #include "lauxlib.h" #include "lualib.h" diff --git a/app/lua/ltable.c b/app/lua/ltable.c index 7cc5d8d5..914c03ad 100644 --- a/app/lua/ltable.c +++ b/app/lua/ltable.c @@ -18,13 +18,13 @@ ** Hence even when the load factor reaches 100%, performance remains good. */ -#include "c_math.h" -#include "c_string.h" - #define ltable_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_MATH +#include C_HEADER_STRING #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/ltablib.c b/app/lua/ltablib.c index 540c6572..a7182b00 100644 --- a/app/lua/ltablib.c +++ b/app/lua/ltablib.c @@ -5,10 +5,9 @@ */ -#include "c_stddef.h" - #define ltablib_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" diff --git a/app/lua/ltm.c b/app/lua/ltm.c index 1cb14ff7..fccbbe67 100644 --- a/app/lua/ltm.c +++ b/app/lua/ltm.c @@ -5,12 +5,12 @@ */ -#include "c_string.h" - #define ltm_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "lobject.h" #include "lstate.h" diff --git a/app/lua/lua.h b/app/lua/lua.h index 1fd7de51..2986e2dc 100644 --- a/app/lua/lua.h +++ b/app/lua/lua.h @@ -8,10 +8,18 @@ #ifndef lua_h #define lua_h - +#ifdef LUAC_CROSS_FILE +#include "luac_cross.h" +#endif +#ifdef LUA_CROSS_COMPILER +#include +#include +#include +#else #include "c_stdarg.h" #include "c_stddef.h" #include "c_types.h" +#endif #include "luaconf.h" @@ -382,7 +390,9 @@ typedef struct __lua_load{ int lua_main( int argc, char **argv ); +#ifndef LUA_CROSS_COMPILER void lua_handle_input (bool force); +#endif /****************************************************************************** * Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. diff --git a/app/lua/luac_cross.h b/app/lua/luac_cross.h new file mode 100644 index 00000000..2eea25ce --- /dev/null +++ b/app/lua/luac_cross.h @@ -0,0 +1,76 @@ +/* +** Header to allow luac.cross compile within NodeMCU +** See Copyright Notice in lua.h +*/ +#ifndef luac_cross_h +#define luac_cross_h + +#ifdef LUA_CROSS_COMPILER + +#define C_HEADER_ASSERT +#define C_HEADER_CTYPE +#define C_HEADER_ERRNO +#define C_HEADER_FCNTL +#define C_HEADER_LOCALE +#define C_HEADER_MATH +#define C_HEADER_STDIO +#define C_HEADER_STDLIB +#define C_HEADER_STRING +#define C_HEADER_TIME + +#define ICACHE_RODATA_ATTR + +#define c_abs abs +#define c_exit exit +#define c_fclose fclose +#define c_feof feof +#define c_ferror ferror +#define c_fopen fopen +#define c_fread fread +#define c_free free +#define c_freopen freopen +#define c_getc getc +#define c_getenv getenv +#define c_memcmp memcmp +#define c_memcpy memcpy +#define c_printf printf +#define c_puts puts +#define c_reader reader +#define c_realloc realloc +#define c_sprintf sprintf +#define c_stderr stderr +#define c_stdin stdin +#define c_stdout stdout +#define c_strcat strcat +#define c_strchr strchr +#define c_strcmp strcmp +#define c_strcoll strcoll +#define c_strcpy strcpy +#define c_strcspn strcspn +#define c_strerror strerror +#define c_strlen strlen +#define c_strncat strncat +#define c_strncmp strncmp +#define c_strncpy strncpy +#define c_strpbrk strpbrk +#define c_strrchr strrchr +#define c_strstr strstr +double c_strtod(const char *__n, char **__end_PTR); +#define c_strtoul strtoul +#define c_ungetc ungetc + +#else + +#define C_HEADER_ASSERT "c_assert.h" +#define C_HEADER_CTYPE "c_ctype.h" +#define C_HEADER_ERRNO "c_errno.h" +#define C_HEADER_FCNTL "c_fcntl.h" +#define C_HEADER_LOCALE "c_locale.h" +#define C_HEADER_MATH "c_math.h" +#define C_HEADER_STDIO "c_stdio.h" +#define C_HEADER_STDLIB "c_stdlib.h" +#define C_HEADER_STRING "c_string.h" +#define C_HEADER_TIME "c_time.h" + +#endif /* LUA_CROSS_COMPILER */ +#endif /* luac_cross_h */ diff --git a/app/lua/not_ported/loslib.c b/app/lua/luac_cross/loslib.c similarity index 97% rename from app/lua/not_ported/loslib.c rename to app/lua/luac_cross/loslib.c index 3484180a..5bff693b 100644 --- a/app/lua/not_ported/loslib.c +++ b/app/lua/luac_cross/loslib.c @@ -4,12 +4,14 @@ ** See Copyright Notice in lua.h */ +#define LUAC_CROSS_FILE -#include -#include -#include -#include -#include +#include "luac_cross.h" +#include C_HEADER_ERRNO +#include C_HEADER_LOCALE +#include C_HEADER_STDLIB +#include C_HEADER_STRING +#include C_HEADER_TIME #define loslib_c #define LUA_LIB diff --git a/app/lua/not_ported/luac.c b/app/lua/luac_cross/luac.c similarity index 97% rename from app/lua/not_ported/luac.c rename to app/lua/luac_cross/luac.c index 7063c425..bdd3e01a 100644 --- a/app/lua/not_ported/luac.c +++ b/app/lua/luac_cross/luac.c @@ -4,10 +4,13 @@ ** See Copyright Notice in lua.h */ -#include "c_errno.h" -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" +#define LUAC_CROSS_FILE + +#include "luac_cross.h" +#include C_HEADER_ERRNO +#include C_HEADER_STDIO +#include C_HEADER_STDLIB +#include C_HEADER_STRING #define luac_c #define LUA_CORE diff --git a/app/lua/not_ported/print.c b/app/lua/luac_cross/print.c similarity index 98% rename from app/lua/not_ported/print.c rename to app/lua/luac_cross/print.c index dccce975..1273d47e 100644 --- a/app/lua/not_ported/print.c +++ b/app/lua/luac_cross/print.c @@ -4,8 +4,11 @@ ** See Copyright Notice in lua.h */ -#include "c_ctype.h" -#include "c_stdio.h" +#define LUAC_CROSS_FILE + +#include "luac_cross.h" +#include C_HEADER_CTYPE +#include C_HEADER_STDIO #define luac_c #define LUA_CORE diff --git a/app/lua/luaconf.h b/app/lua/luaconf.h index 7e0b182c..69a18d3f 100644 --- a/app/lua/luaconf.h +++ b/app/lua/luaconf.h @@ -8,8 +8,13 @@ #ifndef lconfig_h #define lconfig_h +#ifdef LUA_CROSS_COMPILER +#include +#include +#else #include "c_limits.h" #include "c_stddef.h" +#endif #include "user_config.h" /* @@ -252,7 +257,12 @@ #define lua_stdin_is_tty() isatty(0) #elif defined(LUA_WIN) #include +#ifdef LUA_CROSS_COMPILER +#include +else #include "c_stdio.h" +#endif + #define lua_stdin_is_tty() _isatty(_fileno(stdin)) #else #define lua_stdin_is_tty() 1 /* assume stdin is a tty */ @@ -294,8 +304,8 @@ ** GNU readline and history facilities). */ #if defined(LUA_USE_STDIO) -#if defined(LUA_USE_READLINE) -#include "c_stdio.h" +#if defined(LUA_CROSS_COMPILER) && defined(LUA_USE_READLINE) +#include #include #include #define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) @@ -303,7 +313,7 @@ if (lua_strlen(L,idx) > 0) /* non-empty line? */ \ add_history(lua_tostring(L, idx)); /* add it to history */ #define lua_freeline(L,b) ((void)L, c_free(b)) -#else // #if defined(LUA_USE_READLINE) +#else // #if defined(LUA_CROSS_COMPILER) && defined(LUA_USE_READLINE) #define lua_readline(L,b,p) \ ((void)L, c_fputs(p, c_stdout), c_fflush(c_stdout), /* show prompt */ \ c_fgets(b, LUA_MAXINPUT, c_stdin) != NULL) /* get line */ @@ -623,7 +633,11 @@ extern int readline4lua(const char *prompt, char *buffer, int length); @@ The luai_num* macros define the primitive operations over numbers. */ #if defined(LUA_CORE) +#ifdef LUA_CROSS_COMPILER +#include +#else #include "c_math.h" +#endif #define luai_numadd(a,b) ((a)+(b)) #define luai_numsub(a,b) ((a)-(b)) #define luai_nummul(a,b) ((a)*(b)) @@ -866,7 +880,10 @@ union luai_Cast { double l_d; long l_l; }; #define LUA_INTFRMLEN "l" #define LUA_INTFRM_T long - +#ifndef LUA_CROSS_COMPILER +typedef short int16_t; +typedef long int32_t; +#endif #endif diff --git a/app/lua/lundump.c b/app/lua/lundump.c index b036e7a5..7644e4de 100644 --- a/app/lua/lundump.c +++ b/app/lua/lundump.c @@ -4,13 +4,12 @@ ** See Copyright Notice in lua.h */ -#include "c_string.h" -#include "c_types.h" - #define lundump_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/lundump.h b/app/lua/lundump.h index 852bdc62..219b980d 100644 --- a/app/lua/lundump.h +++ b/app/lua/lundump.h @@ -7,7 +7,11 @@ #ifndef lundump_h #define lundump_h +#ifdef LUA_CROSS_COMPILER +#include +#else #include "c_stdint.h" +#endif #include "lobject.h" #include "lzio.h" diff --git a/app/lua/lvm.c b/app/lua/lvm.c index 06147856..f475e3e9 100644 --- a/app/lua/lvm.c +++ b/app/lua/lvm.c @@ -5,15 +5,14 @@ */ -#include "c_stdio.h" -#include "c_stdlib.h" -#include "c_string.h" -#include "c_math.h" - #define lvm_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STDIO +#include C_HEADER_STRING +#include C_HEADER_MATH #include "ldebug.h" #include "ldo.h" diff --git a/app/lua/lzio.c b/app/lua/lzio.c index e01dedeb..84d811c3 100644 --- a/app/lua/lzio.c +++ b/app/lua/lzio.c @@ -5,12 +5,12 @@ */ -#include "c_string.h" - #define lzio_c #define LUA_CORE +#define LUAC_CROSS_FILE #include "lua.h" +#include C_HEADER_STRING #include "llimits.h" #include "lmem.h" diff --git a/app/modules/linit.c b/app/modules/linit.c index 8bdbedfe..a0fbf96f 100644 --- a/app/modules/linit.c +++ b/app/modules/linit.c @@ -7,6 +7,7 @@ #define linit_c #define LUA_LIB +#define LUAC_CROSS_FILE #include "lua.h" @@ -47,7 +48,7 @@ static const luaL_Reg lualibs[] = { {LUA_TABLIBNAME, luaopen_table}, #endif - #if defined(LUA_USE_BUILTIN_DEBUG) + #if defined(LUA_USE_BUILTIN_DEBUG) || defined(LUA_USE_BUILTIN_DEBUG_MINIMAL) {LUA_DBLIBNAME, luaopen_debug}, #endif #endif @@ -71,7 +72,7 @@ extern const luaR_entry syslib[]; extern const luaR_entry tab_funcs[]; #endif -#if defined(LUA_USE_BUILTIN_DEBUG) +#if defined(LUA_USE_BUILTIN_DEBUG) || defined(LUA_USE_BUILTIN_DEBUG_MINIMAL) extern const luaR_entry dblib[]; #endif @@ -99,7 +100,7 @@ const luaR_table lua_rotable[] = {LUA_TABLIBNAME, tab_funcs}, #endif - #if defined(LUA_USE_BUILTIN_DEBUG) + #if defined(LUA_USE_BUILTIN_DEBUG) || defined(LUA_USE_BUILTIN_DEBUG_MINIMAL) {LUA_DBLIBNAME, dblib}, #endif diff --git a/tools/build.lua b/tools/build.lua new file mode 100644 index 00000000..0d93dea2 --- /dev/null +++ b/tools/build.lua @@ -0,0 +1,848 @@ +-- eLua build system + +module( ..., package.seeall ) + +local lfs = require "lfs" +local sf = string.format +utils = require "tools.utils" + +------------------------------------------------------------------------------- +-- Various helpers + +-- Return the time of the last modification of the file +local function get_ftime( path ) + local t = lfs.attributes( path, 'modification' ) + return t or -1 +end + +-- Check if a given target name is phony +local function is_phony( target ) + return target:find( "#phony" ) == 1 +end + +-- Return a string with $(key) replaced with 'value' +local function expand_key( s, key, value ) + if not value then return s end + local fmt = sf( "%%$%%(%s%%)", key ) + return ( s:gsub( fmt, value ) ) +end + +-- Return a target name considering phony targets +local function get_target_name( s ) + if not is_phony( s ) then return s end +end + +-- 'Liniarize' a file name by replacing its path separators indicators with '_' +local function linearize_fname( s ) + return ( s:gsub( "[\\/]", "__" ) ) +end + +-- Helper: transform a table into a string if needed +local function table_to_string( t ) + if not t then return nil end + if type( t ) == "table" then t = table.concat( t, " " ) end + return t +end + +-- Helper: return the extended type of an object (takes into account __type) +local function exttype( o ) + local t = type( o ) + if t == "table" and o.__type then t = o:__type() end + return t +end + +--------------------------------------- +-- Table utils +-- (from http://lua-users.org/wiki/TableUtils) + +function table.val_to_str( v ) + if "string" == type( v ) then + v = string.gsub( v, "\n", "\\n" ) + if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then + return "'" .. v .. "'" + end + return '"' .. string.gsub(v,'"', '\\"' ) .. '"' + else + return "table" == type( v ) and table.tostring( v ) or tostring( v ) + end +end + +function table.key_to_str ( k ) + if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then + return k + else + return "[" .. table.val_to_str( k ) .. "]" + end +end + +function table.tostring( tbl ) + local result, done = {}, {} + for k, v in ipairs( tbl ) do + table.insert( result, table.val_to_str( v ) ) + done[ k ] = true + end + for k, v in pairs( tbl ) do + if not done[ k ] then + table.insert( result, + table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) + end + end + return "{" .. table.concat( result, "," ) .. "}" +end + +------------------------------------------------------------------------------- +-- Dummy 'builder': simply checks the date of a file + +local _fbuilder = {} + +_fbuilder.new = function( target, dep ) + local self = {} + setmetatable( self, { __index = _fbuilder } ) + self.target = target + self.dep = dep + return self +end + +_fbuilder.build = function( self ) + -- Doesn't build anything but returns 'true' if the dependency is newer than + -- the target + if is_phony( self.target ) then + return true + else + return get_ftime( self.dep ) > get_ftime( self.target ) + end +end + +_fbuilder.target_name = function( self ) + return get_target_name( self.dep ) +end + +-- Object type +_fbuilder.__type = function() + return "_fbuilder" +end + +------------------------------------------------------------------------------- +-- Target object + +local _target = {} + +_target.new = function( target, dep, command, builder, ttype ) + local self = {} + setmetatable( self, { __index = _target } ) + self.target = target + self.command = command + self.builder = builder + builder:register_target( target, self ) + self:set_dependencies( dep ) + self.dep = self:_build_dependencies( self.origdep ) + self.dont_clean = false + self.can_substitute_cmdline = false + self._force_rebuild = #self.dep == 0 + builder.runlist[ target ] = false + self:set_type( ttype ) + return self +end + +-- Set dependencies as a string; actual dependencies are computed by _build_dependencies +-- (below) when 'build' is called +_target.set_dependencies = function( self, dep ) + self.origdep = dep +end + +-- Set the target type +-- This is only for displaying actions +_target.set_type = function( self, ttype ) + local atable = { comp = { "[COMPILE]", 'blue' } , dep = { "[DEPENDS]", 'magenta' }, link = { "[LINK]", 'yellow' }, asm = { "[ASM]", 'white' } } + local tdata = atable[ ttype ] + if not tdata then + self.dispstr = is_phony( self.target ) and "[PHONY]" or "[TARGET]" + self.dispcol = 'green' + else + self.dispstr = tdata[ 1 ] + self.dispcol = tdata[ 2 ] + end +end + +-- Set dependencies +-- This uses a proxy table and returns string deps dynamically according +-- to the targets currently registered in the builder +_target._build_dependencies = function( self, dep ) + -- Step 1: start with an array + if type( dep ) == "string" then dep = utils.string_to_table( dep ) end + -- Step 2: linearize "dep" array keeping targets + local filter = function( e ) + local t = exttype( e ) + return t ~= "_ftarget" and t ~= "_target" + end + dep = utils.linearize_array( dep, filter ) + -- Step 3: strings are turned into _fbuilder objects if not found as targets; + -- otherwise the corresponding target object is used + for i = 1, #dep do + if type( dep[ i ] ) == 'string' then + local t = self.builder:get_registered_target( dep[ i ] ) + dep[ i ] = t or _fbuilder.new( self.target, dep[ i ] ) + end + end + return dep +end + +-- Set pre-build function +_target.set_pre_build_function = function( self, f ) + self._pre_build_function = f +end + +-- Set post-build function +_target.set_post_build_function = function( self, f ) + self._post_build_function = f +end + +-- Force rebuild +_target.force_rebuild = function( self, flag ) + self._force_rebuild = flag +end + +-- Set additional arguments to send to the builder function if it is a callable +_target.set_target_args = function( self, args ) + self._target_args = args +end + +-- Function to execute in clean mode +_target._cleaner = function( target, deps, tobj, disp_mode ) + -- Clean the main target if it is not a phony target + local dprint = function( ... ) + if disp_mode ~= "minimal" then print( ... ) end + end + if not is_phony( target ) then + if tobj.dont_clean then + dprint( sf( "[builder] Target '%s' will not be deleted", target ) ) + return 0 + end + if disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", target ) ) end + if os.remove( target ) then dprint "done." else dprint "failed!" end + end + return 0 +end + +-- Build the given target +_target.build = function( self ) + if self.builder.runlist[ self.target ] then return end + local docmd = self:target_name() and lfs.attributes( self:target_name(), "mode" ) ~= "file" + docmd = docmd or self.builder.global_force_rebuild + local initdocmd = docmd + self.dep = self:_build_dependencies( self.origdep ) + local depends, dep, previnit = '', self.dep, self.origdep + -- Iterate through all dependencies, execute each one in turn + local deprunner = function() + for i = 1, #dep do + local res = dep[ i ]:build() + docmd = docmd or res + local t = dep[ i ]:target_name() + if exttype( dep[ i ] ) == "_target" and t and not is_phony( self.target ) then + docmd = docmd or get_ftime( t ) > get_ftime( self.target ) + end + if t then depends = depends .. t .. " " end + end + end + deprunner() + -- Execute the preb-build function if needed + if self._pre_build_function then self._pre_build_function( self, docmd ) end + -- If the dependencies changed as a result of running the pre-build function + -- run through them again + if previnit ~= self.origdep then + self.dep = self:_build_dependencies( self.origdep ) + depends, dep, docmd = '', self.dep, initdocmd + deprunner() + end + -- If at least one dependency is new rebuild the target + docmd = docmd or self._force_rebuild or self.builder.clean_mode + local keep_flag = true + if docmd and self.command then + if self.builder.disp_mode ~= 'all' and self.builder.disp_mode ~= "minimal" and not self.builder.clean_mode then + io.write( utils.col_funcs[ self.dispcol ]( self.dispstr ) .. " " ) + end + local cmd, code = self.command + if self.builder.clean_mode then cmd = _target._cleaner end + if type( cmd ) == 'string' then + cmd = expand_key( cmd, "TARGET", self.target ) + cmd = expand_key( cmd, "DEPENDS", depends ) + cmd = expand_key( cmd, "FIRST", dep[ 1 ]:target_name() ) + if self.builder.disp_mode == 'all' then + print( cmd ) + elseif self.builder.disp_mode ~= "minimal" then + print( self.target ) + end + code = self:execute( cmd ) + else + if not self.builder.clean_mode and self.builder.disp_mode ~= "all" and self.builder.disp_mode ~= "minimal" then + print( self.target ) + end + code = cmd( self.target, self.dep, self.builder.clean_mode and self or self._target_args, self.builder.disp_mode ) + if code == 1 then -- this means "mark target as 'not executed'" + keep_flag = false + code = 0 + end + end + if code ~= 0 then + print( utils.col_red( "[builder] Error building target" ) ) + if self.builder.disp_mode ~= 'all' and type( cmd ) == "string" then + print( utils.col_red( "[builder] Last executed command was: " ) ) + print( cmd ) + end + os.exit( 1 ) + end + end + -- Execute the post-build function if needed + if self._post_build_function then self._post_build_function( self, docmd ) end + -- Marked target as "already ran" so it won't run again + self.builder.runlist[ self.target ] = true + return docmd and keep_flag +end + +-- Return the actual target name (taking into account phony targets) +_target.target_name = function( self ) + return get_target_name( self.target ) +end + +-- Restrict cleaning this target +_target.prevent_clean = function( self, flag ) + self.dont_clean = flag +end + +-- Object type +_target.__type = function() + return "_target" +end + +_target.execute = function( self, cmd ) + local code + if utils.is_windows() and #cmd > 8190 and self.can_substitute_cmdline then + -- Avoid cmd's maximum command line length limitation + local t = cmd:find( " " ) + f = io.open( "tmpcmdline", "w" ) + local rest = cmd:sub( t + 1 ) + f:write( ( rest:gsub( "\\", "/" ) ) ) + f:close() + cmd = cmd:sub( 1, t - 1 ) .. " @tmpcmdline" + end + local code = os.execute( cmd ) + os.remove( "tmpcmdline" ) + return code +end + +_target.set_substitute_cmdline = function( self, flag ) + self.can_substitute_cmdline = flag +end + +------------------------------------------------------------------------------- +-- Builder public interface + +builder = { KEEP_DIR = 0, BUILD_DIR_LINEARIZED = 1 } + +--------------------------------------- +-- Initialization and option handling + +-- Create a new builder object with the output in 'build_dir' and with the +-- specified compile, dependencies and link command +builder.new = function( build_dir ) + self = {} + setmetatable( self, { __index = builder } ) + self.build_dir = build_dir or ".build" + self.exe_extension = utils.is_windows() and "exe" or "" + self.clean_mode = false + self.opts = utils.options_handler() + self.args = {} + self.user_args = {} + self.build_mode = self.KEEP_DIR + self.targets = {} + self.targetargs = {} + self._tlist = {} + self.runlist = {} + self.disp_mode = 'all' + self.cmdline_macros = {} + self.c_targets = {} + self.preprocess_mode = false + self.asm_mode = false + return self +end + +-- Helper: create the build output directory +builder._create_build_dir = function( self ) + if self.build_dir_created then return end + if self.build_mode ~= self.KEEP_DIR then + -- Create builds directory if needed + local mode = lfs.attributes( self.build_dir, "mode" ) + if not mode or mode ~= "directory" then + if not utils.full_mkdir( self.build_dir ) then + print( "[builder] Unable to create directory " .. self.build_dir ) + os.exit( 1 ) + end + end + end + self.build_dir_created = true +end + +-- Add an options to the builder +builder.add_option = function( self, name, help, default, data ) + self.opts:add_option( name, help, default, data ) +end + +-- Initialize builder from the given command line +builder.init = function( self, args ) + -- Add the default options + local opts = self.opts + opts:add_option( "build_mode", 'choose location of the object files', self.KEEP_DIR, + { keep_dir = self.KEEP_DIR, build_dir_linearized = self.BUILD_DIR_LINEARIZED } ) + opts:add_option( "build_dir", 'choose build directory', self.build_dir ) + opts:add_option( "disp_mode", 'set builder display mode', 'summary', { 'all', 'summary', 'minimal' } ) + -- Apply default values to all options + for i = 1, opts:get_num_opts() do + local o = opts:get_option( i ) + self.args[ o.name:upper() ] = o.default + end + -- Read and interpret command line + for i = 1, #args do + local a = args[ i ] + if a:upper() == "-C" then -- clean option (-c) + self.clean_mode = true + elseif a:upper() == '-H' then -- help option (-h) + self:_show_help() + os.exit( 1 ) + elseif a:upper() == "-E" then -- preprocess + self.preprocess_mode = true + elseif a:upper() == "-S" then -- generate assembler + self.asm_mode = true + elseif a:find( '-D' ) == 1 and #a > 2 then -- this is a macro definition that will be auomatically added to the compiler flags + table.insert( self.cmdline_macros, a:sub( 3 ) ) + elseif a:find( '=' ) then -- builder argument (key=value) + local k, v = opts:handle_arg( a ) + if not k then + self:_show_help() + os.exit( 1 ) + end + self.args[ k:upper() ] = v + self.user_args[ k:upper() ] = true + else -- this must be the target name / target arguments + if self.targetname == nil then + self.targetname = a + else + table.insert( self.targetargs, a ) + end + end + end + -- Read back the default options + self.build_mode = self.args.BUILD_MODE + self.build_dir = self.args.BUILD_DIR + self.disp_mode = self.args.DISP_MODE +end + +-- Return the value of the option with the given name +builder.get_option = function( self, optname ) + return self.args[ optname:upper() ] +end + +-- Returns true if the given option was specified by the user on the command line, false otherwise +builder.is_user_option = function( self, optname ) + return self.user_args[ optname:upper() ] +end + +-- Show builder help +builder._show_help = function( self ) + print( "[builder] Valid options:" ) + print( " -h: help (this text)" ) + print( " -c: clean target" ) + print( " -E: generate preprocessed output for single file targets" ) + print( " -S: generate assembler output for single file targets" ) + self.opts:show_help() +end + +--------------------------------------- +-- Builder configuration + +-- Set the compile command +builder.set_compile_cmd = function( self, cmd ) + self.comp_cmd = cmd +end + +-- Set the link command +builder.set_link_cmd = function( self, cmd ) + self.link_cmd = cmd +end + +-- Set the assembler command +builder.set_asm_cmd = function( self, cmd ) + self._asm_cmd = cmd +end + +-- Set (actually force) the object file extension +builder.set_object_extension = function( self, ext ) + self.obj_extension = ext +end + +-- Set (actually force) the executable file extension +builder.set_exe_extension = function( self, ext ) + self.exe_extension = ext +end + +-- Set the clean mode +builder.set_clean_mode = function( self, isclean ) + self.clean_mode = isclean +end + +-- Sets the build mode +builder.set_build_mode = function( self, mode ) + self.build_mode = mode +end + +-- Set the build directory +builder.set_build_dir = function( self, dir ) + if self.build_dir_created then + print "[builder] Error: build directory already created" + os.exit( 1 ) + end + self.build_dir = dir + self:_create_build_dir() +end + +-- Return the current build directory +builder.get_build_dir = function( self ) + return self.build_dir +end + +-- Return the target arguments +builder.get_target_args = function( self ) + return self.targetargs +end + +-- Set a specific dependency generation command for the assembler +-- Pass 'false' to skip dependency generation for assembler files +builder.set_asm_dep_cmd = function( self, asm_dep_cmd ) + self.asm_dep_cmd = asm_dep_cmd +end + +-- Set a specific dependency generation command for the compiler +-- Pass 'false' to skip dependency generation for C files +builder.set_c_dep_cmd = function( self, c_dep_cmd ) + self.c_dep_cmd = c_dep_cmd +end + +-- Save the builder configuration for a given component to a string +builder._config_to_string = function( self, what ) + local ctable = {} + local state_fields + if what == 'comp' then + state_fields = { 'comp_cmd', '_asm_cmd', 'c_dep_cmd', 'asm_dep_cmd', 'obj_extension' } + elseif what == 'link' then + state_fields = { 'link_cmd' } + else + print( sf( "Invalid argument '%s' to _config_to_string", what ) ) + os.exit( 1 ) + end + utils.foreach( state_fields, function( k, v ) ctable[ v ] = self[ v ] end ) + return table.tostring( ctable ) +end + +-- Check the configuration of the given component against the previous one +-- Return true if the configuration has changed +builder._compare_config = function( self, what ) + local res = false + local crtstate = self:_config_to_string( what ) + if not self.clean_mode then + local fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "rb" ) + if fconf then + local oldstate = fconf:read( "*a" ) + fconf:close() + if oldstate:lower() ~= crtstate:lower() then res = true end + end + end + -- Write state to build dir + fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "wb" ) + if fconf then + fconf:write( self:_config_to_string( what ) ) + fconf:close() + end + return res +end + +-- Sets the way commands are displayed +builder.set_disp_mode = function( self, mode ) + mode = mode:lower() + if mode ~= 'all' and mode ~= 'summary' and mode ~= "minimal" then + print( sf( "[builder] Invalid display mode '%s'", mode ) ) + os.exit( 1 ) + end + self.disp_mode = mode +end + +--------------------------------------- +-- Command line builders + +-- Internal helper +builder._generic_cmd = function( self, args ) + local compcmd = args.compiler or "gcc" + compcmd = compcmd .. " " + local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags + local defines = type( args.defines ) == 'table' and table_to_string( utils.linearize_array( args.defines ) ) or args.defines + local includes = type( args.includes ) == 'table' and table_to_string( utils.linearize_array( args.includes ) ) or args.includes + local comptype = table_to_string( args.comptype ) or "-c" + compcmd = compcmd .. utils.prepend_string( defines, "-D" ) + compcmd = compcmd .. utils.prepend_string( includes, "-I" ) + return compcmd .. flags .. " " .. comptype .. " -o $(TARGET) $(FIRST)" +end + +-- Return a compile command based on the specified args +builder.compile_cmd = function( self, args ) + args.defines = { args.defines, self.cmdline_macros } + if self.preprocess_mode then + args.comptype = "-E" + elseif self.asm_mode then + args.comptype = "-S" + else + args.comptype = "-c" + end + return self:_generic_cmd( args ) +end + +-- Return an assembler command based on the specified args +builder.asm_cmd = function( self, args ) + args.defines = { args.defines, self.cmdline_macros } + args.compiler = args.assembler + args.comptype = self.preprocess_mode and "-E" or "-c" + return self:_generic_cmd( args ) +end + +-- Return a link command based on the specified args +builder.link_cmd = function( self, args ) + local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags + local libraries = type( args.libraries ) == 'table' and table_to_string( utils.linearize_array( args.libraries ) ) or args.libraries + local linkcmd = args.linker or "gcc" + linkcmd = linkcmd .. " " .. flags .. " -o $(TARGET) $(DEPENDS)" + linkcmd = linkcmd .. " " .. utils.prepend_string( libraries, "-l" ) + return linkcmd +end + +--------------------------------------- +-- Target handling + +-- Create a return a new C to object target +builder.c_target = function( self, target, deps, comp_cmd ) + return _target.new( target, deps, comp_cmd or self.comp_cmd, self, 'comp' ) +end + +-- Create a return a new ASM to object target +builder.asm_target = function( self, target, deps, asm_cmd ) + return _target.new( target, deps, asm_cmd or self._asm_cmd, self, 'asm' ) +end + +-- Return the name of a dependency file name corresponding to a C source +builder.get_dep_filename = function( self, srcname ) + return utils.replace_extension( self.build_dir .. utils.dir_sep .. linearize_fname( srcname ), "d" ) +end + +-- Create a return a new C dependency target +builder.dep_target = function( self, dep, depdeps, dep_cmd ) + local depname = self:get_dep_filename( dep ) + return _target.new( depname, depdeps, dep_cmd, self, 'dep' ) +end + +-- Create and return a new link target +builder.link_target = function( self, out, dep, link_cmd ) + local path, ext = utils.split_ext( out ) + if not ext and self.exe_extension and #self.exe_extension > 0 then + out = out .. self.exe_extension + end + local t = _target.new( out, dep, link_cmd or self.link_cmd, self, 'link' ) + if self:_compare_config( 'link' ) then t:force_rebuild( true ) end + t:set_substitute_cmdline( true ) + return t +end + +-- Create and return a new generic target +builder.target = function( self, dest_target, deps, cmd ) + return _target.new( dest_target, deps, cmd, self ) +end + +-- Register a target (called from _target.new) +builder.register_target = function( self, name, obj ) + self._tlist[ name:gsub( "\\", "/" ) ] = obj +end + +-- Returns a registered target (nil if not found) +builder.get_registered_target = function( self, name ) + return self._tlist[ name:gsub( "\\", "/" ) ] +end + +--------------------------------------- +-- Actual building functions + +-- Return the object name corresponding to a source file name +builder.obj_name = function( self, name, ext ) + local r = ext or self.obj_extension + if not r then + r = utils.is_windows() and "obj" or "o" + end + local objname = utils.replace_extension( name, r ) + -- KEEP_DIR: object file in the same directory as source file + -- BUILD_DIR_LINEARIZED: object file in the build directory, linearized filename + if self.build_mode == self.KEEP_DIR then + return objname + elseif self.build_mode == self.BUILD_DIR_LINEARIZED then + return self.build_dir .. utils.dir_sep .. linearize_fname( objname ) + end +end + +-- Read and interpret dependencies for each file specified in "ftable" +-- "ftable" is either a space-separated string with all the source files or an array +builder.read_depends = function( self, ftable ) + if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end + -- Read dependency data + local dtable = {} + for i = 1, #ftable do + local f = io.open( self:get_dep_filename( ftable[ i ] ), "rb" ) + local lines = ftable[ i ] + if f then + lines = f:read( "*a" ) + f:close() + lines = lines:gsub( "\n", " " ):gsub( "\\%s+", " " ):gsub( "%s+", " " ):gsub( "^.-: (.*)", "%1" ) + end + dtable[ ftable[ i ] ] = lines + end + return dtable +end + +-- Create and return compile targets for the given sources +builder.create_compile_targets = function( self, ftable, res ) + if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end + res = res or {} + ccmd, oname = "-c", "o" + if self.preprocess_mode then + ccmd, oname = '-E', "pre" + elseif self.asm_mode then + ccmd, oname = '-S', 's' + end + -- Build dependencies for all targets + for i = 1, #ftable do + local isasm = ftable[ i ]:find( "%.c$" ) == nil + -- Skip assembler targets if 'asm_dep_cmd' is set to 'false' + -- Skip C targets if 'c_dep_cmd' is set to 'false' + local skip = isasm and self.asm_dep_cmd == false + skip = skip or ( not isasm and self.c_dep_cmd == false ) + local deps = self:get_dep_filename( ftable[ i ] ) + local target + if not isasm then + local depcmd = skip and self.comp_cmd or ( self.c_dep_cmd or self.comp_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) ) + target = self:c_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd ) + else + local depcmd = skip and self._asm_cmd or ( self.asm_dep_cmd or self._asm_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) ) + target = self:asm_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd ) + end + -- Pre build step: replace dependencies with the ones from the compiler generated dependency file + local dprint = function( ... ) if self.disp_mode ~= "minimal" then print( ... ) end end + if not skip then + target:set_pre_build_function( function( t, _ ) + if not self.clean_mode then + local fres = self:read_depends( ftable[ i ] ) + local fdeps = fres[ ftable[ i ] ] + if #fdeps:gsub( "%s+", "" ) == 0 then fdeps = ftable[ i ] end + t:set_dependencies( fdeps ) + else + if self.disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", deps ) ) end + if os.remove( deps ) then dprint "done." else dprint "failed!" end + end + end ) + end + target.srcname = ftable[ i ] + -- TODO: check clean mode? + if not isasm then self.c_targets[ #self.c_targets + 1 ] = target end + table.insert( res, target ) + end + return res +end + +-- Add a target to the list of builder targets +builder.add_target = function( self, target, help, alias ) + self.targets[ target.target ] = { target = target, help = help } + alias = alias or {} + for _, v in ipairs( alias ) do + self.targets[ v ] = { target = target, help = help } + end + return target +end + +-- Make a target the default one +builder.default = function( self, target ) + self.deftarget = target.target + self.targets.default = { target = target, help = "default target" } +end + +-- Build everything +builder.build = function( self, target ) + local t = self.targetname or self.deftarget + if not t then + print( utils.col_red( "[builder] Error: build target not specified" ) ) + os.exit( 1 ) + end + local trg + -- Look for single targets (C source files) + for _, ct in pairs( self.c_targets ) do + if ct.srcname == t then + trg = ct + break + end + end + if not trg then + if not self.targets[ t ] then + print( sf( "[builder] Error: target '%s' not found", t ) ) + print( "Available targets: " ) + print( " all source files" ) + for k, v in pairs( self.targets ) do + if not is_phony( k ) then + print( sf( " %s - %s", k, v.help or "(no help available)" ) ) + end + end + if self.deftarget and not is_phony( self.deftarget ) then + print( sf( "Default target is '%s'", self.deftarget ) ) + end + os.exit( 1 ) + else + if self.preprocess_mode or self.asm_mode then + print( "[builder] Error: preprocess (-E) or asm (-S) works only with single file targets." ) + os.exit( 1 ) + end + trg = self.targets[ t ].target + end + end + self:_create_build_dir() + -- At this point check if we have a change in the state that would require a rebuild + if self:_compare_config( 'comp' ) then + print( utils.col_yellow( "[builder] Forcing rebuild due to configuration change." ) ) + self.global_force_rebuild = true + else + self.global_force_rebuild = false + end + -- Do the actual build + local res = trg:build() + if not res then print( utils.col_yellow( sf( '[builder] %s: up to date', t ) ) ) end + if self.clean_mode then + os.remove( self.build_dir .. utils.dir_sep .. ".builddata.comp" ) + os.remove( self.build_dir .. utils.dir_sep .. ".builddata.link" ) + end + print( utils.col_yellow( "[builder] Done building target." ) ) + return res +end + +-- Create dependencies, create object files, link final object +builder.make_exe_target = function( self, target, file_list ) + local odeps = self:create_compile_targets( file_list ) + local exetarget = self:link_target( target, odeps ) + self:default( self:add_target( exetarget ) ) + return exetarget +end + +------------------------------------------------------------------------------- +-- Other exported functions + +function new_builder( build_dir ) + return builder.new( build_dir ) +end + diff --git a/tools/cross-lua.lua b/tools/cross-lua.lua new file mode 100644 index 00000000..69eaebe6 --- /dev/null +++ b/tools/cross-lua.lua @@ -0,0 +1,42 @@ +local args = { ... } +local b = require "tools.build" +local builder = b.new_builder( ".build/cross-lua" ) +local utils = b.utils +local sf = string.format + +if not (_VERSION == "Lua 5.1" and pcall(require,"lfs")) then + print [[ + +cross_lua.lua must be run within Lua 5.1 and it requires the Lua Filesystem to be installed. +On most *nix distrubitions youwill find a packages lua-5.1 and lua-filesystem, or +alternalively you can install lua-rocks and use the Rocks package manager to install lfs. +]] + os.exit(1) +end +builder:init( args ) +builder:set_build_mode( builder.BUILD_DIR_LINEARIZED ) +local output = 'luac.cross' +local cdefs = '-DLUA_CROSS_COMPILER' + +-- Lua source files and include path +local lua_files = [[ + lapi.c lauxlib.c lbaselib.c lcode.c ldblib.c ldebug.c ldo.c ldump.c + lfunc.c lgc.c llex.c lmathlib.c lmem.c loadlib.c lobject.c lopcodes.c + lparser.c lrotable.c lstate.c lstring.c lstrlib.c ltable.c ltablib.c + ltm.c lundump.c lvm.c lzio.c + luac_cross/luac.c luac_cross/loslib.c luac_cross/print.c + ../modules/linit.c + ../libc/c_stdlib.c + ]] +lua_files = lua_files:gsub( "\n" , "" ) +local lua_full_files = utils.prepend_path( lua_files, "app/lua" ) +local local_include = "-Iapp/include -Iinclude -Iapp/lua" + +-- Compiler/linker options +builder:set_compile_cmd( sf( "gcc -O2 %s -Wall %s -c $(FIRST) -o $(TARGET)", local_include, cdefs ) ) +builder:set_link_cmd( "gcc -o $(TARGET) $(DEPENDS) -lm" ) + +-- Build everything +builder:make_exe_target( output, lua_full_files ) +builder:build() + diff --git a/tools/utils.lua b/tools/utils.lua new file mode 100644 index 00000000..a9527511 --- /dev/null +++ b/tools/utils.lua @@ -0,0 +1,425 @@ +-- Generic utility functions + +module( ..., package.seeall ) + +local lfs = require "lfs" +local sf = string.format +-- Taken from Lake +dir_sep = package.config:sub( 1, 1 ) +is_os_windows = dir_sep == '\\' + +-- Converts a string with items separated by 'sep' into a table +string_to_table = function( s, sep ) + if type( s ) ~= "string" then return end + sep = sep or ' ' + if s:sub( -1, -1 ) ~= sep then s = s .. sep end + s = s:gsub( sf( "^%s*", sep ), "" ) + local t = {} + local fmt = sf( "(.-)%s+", sep ) + for w in s:gmatch( fmt ) do table.insert( t, w ) end + return t +end + +-- Split a file name into 'path part' and 'extension part' +split_ext = function( s ) + local pos + for i = #s, 1, -1 do + if s:sub( i, i ) == "." then + pos = i + break + end + end + if not pos or s:find( dir_sep, pos + 1 ) then return s end + return s:sub( 1, pos - 1 ), s:sub( pos ) +end + +-- Replace the extension of a given file name +replace_extension = function( s, newext ) + local p, e = split_ext( s ) + if e then + if newext and #newext > 0 then + s = p .. "." .. newext + else + s = p + end + end + return s +end + +-- Return 'true' if building from Windows, false otherwise +is_windows = function() + return is_os_windows +end + +-- Prepend each component of a 'pat'-separated string with 'prefix' +prepend_string = function( s, prefix, pat ) + if not s or #s == 0 then return "" end + pat = pat or ' ' + local res = '' + local st = string_to_table( s, pat ) + foreach( st, function( k, v ) res = res .. prefix .. v .. " " end ) + return res +end + +-- Like above, but consider 'prefix' a path +prepend_path = function( s, prefix, pat ) + return prepend_string( s, prefix .. dir_sep, pat ) +end + +-- full mkdir: create all the paths needed for a multipath +full_mkdir = function( path ) + local ptables = string_to_table( path, dir_sep ) + local p, res = '' + for i = 1, #ptables do + p = ( i ~= 1 and p .. dir_sep or p ) .. ptables[ i ] + res = lfs.mkdir( p ) + end + return res +end + +-- Concatenate the given paths to form a complete path +concat_path = function( paths ) + return table.concat( paths, dir_sep ) +end + +-- Return true if the given array contains the given element, false otherwise +array_element_index = function( arr, element ) + for i = 1, #arr do + if arr[ i ] == element then return i end + end +end + +-- Linearize an array with (possibly) embedded arrays into a simple array +_linearize_array = function( arr, res, filter ) + if type( arr ) ~= "table" then return end + for i = 1, #arr do + local e = arr[ i ] + if type( e ) == 'table' and filter( e ) then + _linearize_array( e, res, filter ) + else + table.insert( res, e ) + end + end +end + +linearize_array = function( arr, filter ) + local res = {} + filter = filter or function( v ) return true end + _linearize_array( arr, res, filter ) + return res +end + +-- Return an array with the keys of a table +table_keys = function( t ) + local keys = {} + foreach( t, function( k, v ) table.insert( keys, k ) end ) + return keys +end + +-- Return an array with the values of a table +table_values = function( t ) + local vals = {} + foreach( t, function( k, v ) table.insert( vals, v ) end ) + return vals +end + +-- Returns true if 'path' is a regular file, false otherwise +is_file = function( path ) + return lfs.attributes( path, "mode" ) == "file" +end + +-- Returns true if 'path' is a directory, false otherwise +is_dir = function( path ) + return lfs.attributes( path, "mode" ) == "directory" +end + +-- Return a list of files in the given directory matching a given mask +get_files = function( path, mask, norec, level ) + local t = '' + level = level or 0 + for f in lfs.dir( path ) do + local fname = path .. dir_sep .. f + if lfs.attributes( fname, "mode" ) == "file" then + local include + if type( mask ) == "string" then + include = fname:find( mask ) + else + include = mask( fname ) + end + if include then t = t .. ' ' .. fname end + elseif lfs.attributes( fname, "mode" ) == "directory" and not fname:find( "%.+$" ) and not norec then + t = t .. " " .. get_files( fname, mask, norec, level + 1 ) + end + end + return level > 0 and t or t:gsub( "^%s+", "" ) +end + +-- Check if the given command can be executed properly +check_command = function( cmd ) + local res = os.execute( cmd .. " > .build.temp 2>&1" ) + os.remove( ".build.temp" ) + return res +end + +-- Execute a command and capture output +-- From: http://stackoverflow.com/a/326715/105950 +exec_capture = function( cmd, raw ) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + if raw then return s end + s = string.gsub(s, '^%s+', '') + s = string.gsub(s, '%s+$', '') + s = string.gsub(s, '[\n\r]+', ' ') + return s +end + +-- Execute the given command for each value in a table +foreach = function ( t, cmd ) + if type( t ) ~= "table" then return end + for k, v in pairs( t ) do cmd( k, v ) end +end + +-- Generate header with the given #defines, return result as string +gen_header_string = function( name, defines ) + local s = "// eLua " .. name:lower() .. " definition\n\n" + s = s .. "#ifndef __" .. name:upper() .. "_H__\n" + s = s .. "#define __" .. name:upper() .. "_H__\n\n" + + for key,value in pairs(defines) do + s = s .. string.format("#define %-25s%-19s\n",key:upper(),value) + end + + s = s .. "\n#endif\n" + return s +end + +-- Generate header with the given #defines, save result to file +gen_header_file = function( name, defines ) + local hname = concat_path{ "inc", name:lower() .. ".h" } + local h = assert( io.open( hname, "w" ) ) + h:write( gen_header_string( name, defines ) ) + h:close() +end + +-- Remove the given elements from an array +remove_array_elements = function( arr, del ) + del = istable( del ) and del or { del } + foreach( del, function( k, v ) + local pos = array_element_index( arr, v ) + if pos then table.remove( arr, pos ) end + end ) +end + +-- Remove a directory recusively +-- USE WITH CARE!! Doesn't do much checks :) +rmdir_rec = function ( dirname ) + if lfs.attributes( dirname, "mode" ) ~= "directory" then return end + for f in lfs.dir( dirname ) do + local ename = string.format( "%s/%s", dirname, f ) + local attrs = lfs.attributes( ename ) + if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then + rmdir_rec( ename ) + elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then + os.remove( ename ) + end + end + lfs.rmdir( dirname ) +end + +-- Concatenates the second table into the first one +concat_tables = function( dst, src ) + foreach( src, function( k, v ) dst[ k ] = v end ) +end + +------------------------------------------------------------------------------- +-- Color-related funtions +-- Currently disabled when running in Windows +-- (they can be enabled by setting WIN_ANSI_TERM) + +local dcoltable = { 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' } +local coltable = {} +foreach( dcoltable, function( k, v ) coltable[ v ] = k - 1 end ) + +local _col_builder = function( col ) + local _col_maker = function( s ) + if is_os_windows and not os.getenv( "WIN_ANSI_TERM" ) then + return s + else + return( sf( "\027[%d;1m%s\027[m", coltable[ col ] + 30, s ) ) + end + end + return _col_maker +end + +col_funcs = {} +foreach( coltable, function( k, v ) + local fname = "col_" .. k + _G[ fname ] = _col_builder( k ) + col_funcs[ k ] = _G[ fname ] +end ) + +------------------------------------------------------------------------------- +-- Option handling + +local options = {} + +options.new = function() + local self = {} + self.options = {} + setmetatable( self, { __index = options } ) + return self +end + +-- Argument validator: boolean value +options._bool_validator = function( v ) + if v == '0' or v:upper() == 'FALSE' then + return false + elseif v == '1' or v:upper() == 'TRUE' then + return true + end +end + +-- Argument validator: choice value +options._choice_validator = function( v, allowed ) + for i = 1, #allowed do + if v:upper() == allowed[ i ]:upper() then return allowed[ i ] end + end +end + +-- Argument validator: choice map (argument value maps to something) +options._choice_map_validator = function( v, allowed ) + for k, value in pairs( allowed ) do + if v:upper() == k:upper() then return value end + end +end + +-- Argument validator: string value (no validation) +options._string_validator = function( v ) + return v +end + +-- Argument printer: boolean value +options._bool_printer = function( o ) + return "true|false", o.default and "true" or "false" +end + +-- Argument printer: choice value +options._choice_printer = function( o ) + local clist, opts = '', o.data + for i = 1, #opts do + clist = clist .. ( i ~= 1 and "|" or "" ) .. opts[ i ] + end + return clist, o.default +end + +-- Argument printer: choice map printer +options._choice_map_printer = function( o ) + local clist, opts, def = '', o.data + local i = 1 + for k, v in pairs( opts ) do + clist = clist .. ( i ~= 1 and "|" or "" ) .. k + if o.default == v then def = k end + i = i + 1 + end + return clist, def +end + +-- Argument printer: string printer +options._string_printer = function( o ) + return nil, o.default +end + +-- Add an option of the specified type +options._add_option = function( self, optname, opttype, help, default, data ) + local validators = + { + string = options._string_validator, choice = options._choice_validator, + boolean = options._bool_validator, choice_map = options._choice_map_validator + } + local printers = + { + string = options._string_printer, choice = options._choice_printer, + boolean = options._bool_printer, choice_map = options._choice_map_printer + } + if not validators[ opttype ] then + print( sf( "[builder] Invalid option type '%s'", opttype ) ) + os.exit( 1 ) + end + table.insert( self.options, { name = optname, help = help, validator = validators[ opttype ], printer = printers[ opttype ], data = data, default = default } ) +end + +-- Find an option with the given name +options._find_option = function( self, optname ) + for i = 1, #self.options do + local o = self.options[ i ] + if o.name:upper() == optname:upper() then return self.options[ i ] end + end +end + +-- 'add option' helper (automatically detects option type) +options.add_option = function( self, name, help, default, data ) + local otype + if type( default ) == 'boolean' then + otype = 'boolean' + elseif data and type( data ) == 'table' and #data == 0 then + otype = 'choice_map' + elseif data and type( data ) == 'table' then + otype = 'choice' + data = linearize_array( data ) + elseif type( default ) == 'string' then + otype = 'string' + else + print( sf( "Error: cannot detect option type for '%s'", name ) ) + os.exit( 1 ) + end + self:_add_option( name, otype, help, default, data ) +end + +options.get_num_opts = function( self ) + return #self.options +end + +options.get_option = function( self, i ) + return self.options[ i ] +end + +-- Handle an option of type 'key=value' +-- Returns both the key and the value or nil for error +options.handle_arg = function( self, a ) + local si, ei, k, v = a:find( "([^=]+)=(.*)$" ) + if not k or not v then + print( sf( "Error: invalid syntax in '%s'", a ) ) + return + end + local opt = self:_find_option( k ) + if not opt then + print( sf( "Error: invalid option '%s'", k ) ) + return + end + local optv = opt.validator( v, opt.data ) + if optv == nil then + print( sf( "Error: invalid value '%s' for option '%s'", v, k ) ) + return + end + return k, optv +end + +-- Show help for all the registered options +options.show_help = function( self ) + for i = 1, #self.options do + local o = self.options[ i ] + print( sf( "\n %s: %s", o.name, o.help ) ) + local values, default = o.printer( o ) + if values then + print( sf( " Possible values: %s", values ) ) + end + print( sf( " Default value: %s", default or "none (changes at runtime)" ) ) + end +end + +-- Create a new option handler +function options_handler() + return options.new() +end +