From 50453c05f33f8af98868aa41f75d4c2b168114c8 Mon Sep 17 00:00:00 2001 From: cloudfreexiao <996442717qqcom@gmail.com> Date: Sun, 25 Jul 2021 17:22:35 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3=20chore(lapis):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/lualib-src/Makefile | 6 +- framework/lualib-src/lua-fmt/Makefile | 29 -- framework/lualib-src/lua-fmt/lfmt.c | 627 ---------------------- framework/lualib/3rd/misc/mm.lua | 724 ++++++++++++++++++++++++++ 4 files changed, 725 insertions(+), 661 deletions(-) delete mode 100644 framework/lualib-src/lua-fmt/Makefile delete mode 100644 framework/lualib-src/lua-fmt/lfmt.c create mode 100644 framework/lualib/3rd/misc/mm.lua diff --git a/framework/lualib-src/Makefile b/framework/lualib-src/Makefile index 319dac2..dac8545 100644 --- a/framework/lualib-src/Makefile +++ b/framework/lualib-src/Makefile @@ -26,10 +26,10 @@ LUASOCKET_SO = $(LUA_CLIB_PATH)/socket.so TERMFX_SO = $(LUA_CLIB_PATH)/termfx.so RC4_SO = $(LUA_CLIB_PATH)/rc4.so MATH_SO = $(LUA_CLIB_PATH)/math3d.so -FMT_SO = $(LUA_CLIB_PATH)/fmt.so CFFI_SO = $(LUA_CLIB_PATH)/cffi.so ##################################################### + all: $(LFS_SO) \ $(CJSON_SO) \ $(PROFILE_SO) \ @@ -43,7 +43,6 @@ all: $(LFS_SO) \ $(RC4_SO) \ $(CLUA_SO) \ $(MATH_SO) \ - $(FMT_SO) \ $(CFFI_SO) \ $(LUASOCKET_SO) @@ -79,9 +78,6 @@ $(CLUA_SO): $(ECS_SO): cd lua-ecs && $(MAKE) PLAT=$(PLAT) -$(FMT_SO): - cd lua-fmt && $(MAKE) PLAT=$(PLAT) - $(MATH_SO): cd math3d && $(MAKE) PLAT=$(PLAT) diff --git a/framework/lualib-src/lua-fmt/Makefile b/framework/lualib-src/lua-fmt/Makefile deleted file mode 100644 index d353c1d..0000000 --- a/framework/lualib-src/lua-fmt/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -SKYNET_ROOT ?= ../../skynet -include $(SKYNET_ROOT)/platform.mk - -PLAT ?= none - -TARGET = ../../luaclib/fmt.so - -ifeq ($(PLAT), macosx) - CFLAGS = -g -O2 -dynamiclib -Wl,-undefined,dynamic_lookup -else -ifeq ($(PLAT), linux) - CFLAGS = -g -O2 -shared -fPIC -endif -endif - -LUA_LIB ?= $(SKYNET_ROOT)/3rd/lua/ -LUA_INC ?= $(SKYNET_ROOT)/3rd/lua/ - -SRC = . - -.PHONY: all clean - -all: $(TARGET) - -$(TARGET): $(foreach dir, $(SRC), $(wildcard $(dir)/*.c)) - $(CC) $(CFLAGS) $(SHARED) -I$(LUA_INC) $^ -o $@ - -clean: - rm -f *.o $(TARGET) \ No newline at end of file diff --git a/framework/lualib-src/lua-fmt/lfmt.c b/framework/lualib-src/lua-fmt/lfmt.c deleted file mode 100644 index 8c8af4f..0000000 --- a/framework/lualib-src/lua-fmt/lfmt.c +++ /dev/null @@ -1,627 +0,0 @@ -#define LUA_LIB -#include -#include - -#include -#include - -// https://github.com/starwing/lua-fmt - -#if LUA_VERSION_NUM == 501 -static void luaL_tolstring(lua_State *L, int idx, size_t *len) -{ - int tt; - const char *kind; - (void)len; - if (luaL_callmeta(L, idx, "__tostring")) - { - if (!lua_isstring(L, -1)) - luaL_error(L, "'__tostring' must return a string"); - return; - } - switch (lua_type(L, idx)) - { - case LUA_TSTRING: - lua_pushvalue(L, idx); - break; - case LUA_TBOOLEAN: - lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); - break; - case LUA_TNIL: - lua_pushliteral(L, "nil"); - break; - default: - tt = luaL_getmetafield(L, idx, "__name"); - kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : luaL_typename(L, idx); - lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); - if (tt != LUA_TNIL) - lua_remove(L, -2); - } -} -#endif - -#if LUA_VERSION_NUM < 502 && !defined(LUA_OK) -static lua_Integer lua_tointegerx(lua_State *L, int idx, int *isint) -{ - lua_Integer i = lua_tointeger(L, idx); - *isint = i == 0 ? lua_type(L, idx) == LUA_TNUMBER : lua_tonumber(L, idx) == i; - return i; -} -#endif - -#if LUA_VERSION_NUM < 503 -static void lua_geti(lua_State *L, int idx, int i) -{ - lua_pushinteger(L, i); - lua_gettable(L, idx); -} - -static int lua_isinteger(lua_State *L, int idx) -{ - lua_Number v = lua_tonumber(L, idx); - if (v == 0.0 && lua_type(L, idx) != LUA_TNUMBER) - return 0; - return (lua_Number)(lua_Integer)v == v; -} -#endif - -typedef struct fmt_State -{ - lua_State *L; - luaL_Buffer B; - int idx, top, zeroing; - const char *p, *e; -} fmt_State; - -#define fmt_check(S, cond, ...) ((void)((cond) || luaL_error((S)->L, __VA_ARGS__))) - -/* read argid */ - -#define fmt_value(S, i) ((S)->top + (i)) -#define fmt_isdigit(ch) ((ch) >= '0' && (ch) <= '9') -#define fmt_isalpha(ch) ((ch) == '_' || ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z')) - -#define FMT_AUTO "automatic field numbering" -#define FMT_MANUAL "manual field specification" -#define FMT_A2M "cannot switch from " FMT_AUTO " to " FMT_MANUAL -#define FMT_M2A "cannot switch from " FMT_MANUAL " to " FMT_AUTO - -static void fmt_manualidx(fmt_State *S) -{ - fmt_check(S, S->idx <= 1, FMT_A2M); - S->idx = 0; -} - -static int fmt_autoidx(fmt_State *S) -{ - fmt_check(S, S->idx != 0, FMT_M2A); - fmt_check(S, ++S->idx <= S->top, "automatic index out of range"); - return S->idx; -} - -static int fmt_integer(fmt_State *S, int *pv) -{ - const char *p = S->p; - unsigned idx = 0; - while (p < S->e && fmt_isdigit(*p)) - { - int o = idx < INT_MAX / 10 || (idx == INT_MAX / 10 && *p++ <= INT_MAX % 10); - fmt_check(S, o, "Too many decimal digits in format string"); - idx = idx * 10 + (*p++ - '0'); - } - if (p == S->p) - return 0; - if (pv) - *pv = (int)idx; - S->p = p; - return 1; -} - -static int fmt_identity(fmt_State *S) -{ - const char *p = S->p; - if (fmt_isalpha(*p)) - while (++p < S->e && (fmt_isalpha(*p) || fmt_isdigit(*p))) - ; - if (p == S->p) - return 0; - lua_pushlstring(S->L, S->p, p - S->p); - S->p = p; - return 1; -} - -static int fmt_accessor(fmt_State *S, int to) -{ - /* "." (number | identity) | "[" "]" */ - while (*S->p == '.' || *S->p == '[') - { - int idx; - const char *p = ++S->p; - if (p[-1] == '.') - { - if (fmt_integer(S, &idx)) - lua_geti(S->L, to, idx); - else if (fmt_identity(S)) - lua_gettable(S->L, to); - else - luaL_error(S->L, "unexpected '%c' in field name", *S->p); - } - else if (fmt_integer(S, &idx) && *S->p == ']') - lua_geti(S->L, to, idx), ++S->p; - else - { - while (p < S->e && *p != ']') - ++p; - fmt_check(S, p < S->e, "expected '}' before end of string"); - lua_pushlstring(S->L, S->p, p - S->p); - S->p = p + 1; - lua_gettable(S->L, to); - } - lua_replace(S->L, to); - } - return 1; -} - -static int fmt_argid(fmt_State *S, int to) -{ - /* [(number | identity) [accessor]] */ - int idx; - fmt_check(S, S->p < S->e, "expected '}' before end of string"); - if (*S->p == ':' || *S->p == '}') - lua_pushvalue(S->L, fmt_autoidx(S)); - else if (fmt_integer(S, &idx)) - { - fmt_manualidx(S); - fmt_check(S, idx >= 1 && idx <= S->top, "argument index out of range"); - lua_pushvalue(S->L, idx + 1); - } - else - { - fmt_manualidx(S); - fmt_check(S, fmt_identity(S), "unexpected '%c' in field name", *S->p); - lua_gettable(S->L, 2); - } - lua_replace(S->L, to); - return fmt_accessor(S, to); -} - -/* read spec */ - -typedef struct fmt_Spec -{ - int fill; - int align; /* '<', '^', '>' */ - int sign; /* ' ', '+', '-' */ - int alter; /* '#' */ - int zero; /* '0' */ - int width; - int grouping; /* '_', ',' */ - int precision; - int type; -} fmt_Spec; - -static int fmt_readchar(fmt_State *S) -{ - int ch = *S->p++; - fmt_check(S, S->p < S->e, "unmatched '{' in format spec"); - return ch; -} - -static int fmt_readint(fmt_State *S, int required, const char *name) -{ - int isint, v = 0; - if (*S->p != '{') - { - fmt_check(S, fmt_integer(S, &v) || !required, - "Format specifier missing %s", name); - fmt_check(S, S->p < S->e, "unmatched '{' in format spec"); - } - else - { - ++S->p; - fmt_argid(S, fmt_value(S, 2)); - fmt_check(S, *S->p == '}', "unexpected '%c' in field name", *S->p); - ++S->p; - v = (int)lua_tointegerx(S->L, fmt_value(S, 2), &isint); - fmt_check(S, isint, "integer expected for %s, got %s", - name, luaL_typename(S->L, fmt_value(S, 2))); - } - return v; -} - -static int fmt_spec(fmt_State *S, fmt_Spec *d) -{ - /* [[fill]align][sign]["#"]["0"][width][grouping]["." precision][type] */ - if (S->p[1] == '<' || S->p[1] == '>' || S->p[1] == '^') - d->fill = fmt_readchar(S), d->align = fmt_readchar(S); - else if (*S->p == '<' || *S->p == '>' || *S->p == '^') - d->align = fmt_readchar(S); - if (*S->p == ' ' || *S->p == '+' || *S->p == '-') - d->sign = fmt_readchar(S); - if (*S->p == '#') - d->alter = fmt_readchar(S); - if (*S->p == '0') - d->zero = fmt_readchar(S); - d->width = fmt_readint(S, 0, "width"); - if (*S->p == '_' || *S->p == ',') - d->grouping = fmt_readchar(S); - if (*S->p == '.') - ++S->p, d->precision = fmt_readint(S, 1, "precision"); - if (*S->p != '}') - { - const char *p = S->p++; - d->type = *p; - if (*S->p != '}') - { - while (S->p < S->e && *S->p != '}') - ++S->p; - fmt_check(S, S->p < S->e, "unmatched '{' in format spec"); - return luaL_error(S->L, "Invalid format specifier: '%s'", p); - } - } - return 1; -} - -/* write spec */ - -#define FMT_DELIMITPOS 3 -#define FMT_UTF8BUFFSIZ 8 -#define FMT_FMTLEN 10 /* "%#.99f" */ -#define FMT_FLTMAXPREC 100 -#define FMT_INTBUFFSIZ 100 -#define FMT_FLTBUFFSIZ (10 + FMT_FLTMAXPREC + FLT_MAX_10_EXP) - -static void fmt_addpadding(fmt_State *S, int ch, size_t len) -{ - char *s; - if (ch == 0) - ch = ' '; - while (len > LUAL_BUFFERSIZE) - { - s = luaL_prepbuffer(&S->B); - memset(s, ch, LUAL_BUFFERSIZE); - luaL_addsize(&S->B, LUAL_BUFFERSIZE); - len -= LUAL_BUFFERSIZE; - } - s = luaL_prepbuffer(&S->B); - memset(s, ch, len); - luaL_addsize(&S->B, len); -} - -static void fmt_addzeroing(fmt_State *S, const fmt_Spec *d, size_t len) -{ - char *s = luaL_prepbuffer(&S->B); - if (len > (size_t)S->zeroing) - { - int pref = (len - S->zeroing) % 4; - if (pref > 2) - *s++ = '0', luaL_addsize(&S->B, 1); - if (pref > 0) - *s++ = '0', *s++ = d->grouping, luaL_addsize(&S->B, 2); - len -= pref; - while (len > 4) - { - size_t curr = len > LUAL_BUFFERSIZE ? LUAL_BUFFERSIZE : len; - s = luaL_prepbuffer(&S->B); - while (curr > 4) - { - s[0] = s[1] = s[2] = '0', s[3] = d->grouping; - s += 4, luaL_addsize(&S->B, 4), curr -= 4, len -= 4; - } - } - } - memset(s, '0', len), luaL_addsize(&S->B, len); -} - -static void fmt_addstring(fmt_State *S, int shrink, size_t width, const fmt_Spec *d) -{ - size_t len, plen; - const char *s = lua_tolstring(S->L, fmt_value(S, 1), &len); - if (shrink && d->precision) - len = len > (size_t)d->precision ? (size_t)d->precision : len; - if (len > width) - { - lua_pushvalue(S->L, fmt_value(S, 1)); - luaL_addvalue(&S->B); - return; - } - plen = width - (int)len; - switch (d->align) - { - case 0: - case '<': - !d->zero || d->grouping == 0 ? fmt_addpadding(S, d->fill ? d->fill : d->zero, plen) : fmt_addzeroing(S, d, plen); - luaL_addlstring(&S->B, s, len); - break; - case '>': - luaL_addlstring(&S->B, s, len); - fmt_addpadding(S, d->fill, plen); - break; - case '^': - fmt_addpadding(S, d->fill, plen / 2); - luaL_addlstring(&S->B, s, len); - fmt_addpadding(S, d->fill, plen - plen / 2); - break; - } -} - -static void fmt_dumpstr(fmt_State *S, const fmt_Spec *d) -{ - fmt_check(S, !d->type || d->type == 's' || d->type == 'p', - "Unknown format code '%c' for object of type 'string'", d->type); - fmt_check(S, !d->sign, - "Sign not allowed in string format specifier"); - fmt_check(S, !d->alter, - "Alternate form (#) not allowed in string format specifier"); - fmt_check(S, !d->zero, - "Zero form (0) not allowed in string format specifier"); - fmt_check(S, !d->grouping, - "Grouping form (%c) not allowed in string format specifier", - d->grouping); - fmt_addstring(S, 1, d->width, d); -} - -static void fmt_pushutf8(fmt_State *S, unsigned long x) -{ - char buff[FMT_UTF8BUFFSIZ], *p = buff + FMT_UTF8BUFFSIZ; - unsigned int mfb = 0x3f; - if (x < 0x80) - { - lua_pushfstring(S->L, "%c", x); - return; - } - do - { - *--p = (char)(0x80 | (x & 0x3f)); - x >>= 6, mfb >>= 1; - } while (x > mfb); - *--p = (char)((~mfb << 1) | x); - lua_pushlstring(S->L, p, FMT_UTF8BUFFSIZ - (p - buff)); -} - -static void fmt_dumpchar(fmt_State *S, lua_Integer cp, const fmt_Spec *d) -{ - fmt_check(S, !d->sign, - "Sign not allowed with integer format specifier 'c'"); - fmt_check(S, !d->alter, - "Alternate form (#) not allowed with integer format specifier 'c'"); - fmt_check(S, !d->zero, - "Zero form (0) not allowed with integer format specifier 'c'"); - fmt_check(S, !d->grouping, - "Cannot specify '%c' with 'c'", d->grouping); - fmt_check(S, cp >= 0 && cp <= INT_MAX, - "'c' arg not in range(%d)", INT_MAX); - fmt_pushutf8(S, (unsigned long)cp); - lua_replace(S->L, fmt_value(S, 1)); - fmt_addstring(S, 0, d->width, d); -} - -static int fmt_writesign(int sign, int dsign) -{ - switch (dsign) - { - case '+': - return sign ? '+' : '-'; - case ' ': - return sign ? ' ' : '-'; - default: - return sign ? 0 : '-'; - } -} - -static int fmt_writeint(char **pp, lua_Integer v, const fmt_Spec *d) -{ - const char *hexa = "0123456789abcdef"; - int radix = 10, zeroing; - char *p = *pp; - switch (d->type) - { - case 'X': - hexa = "0123456789ABCDEF"; /* FALLTHROUGH */ - case 'x': - radix = 16; - break; - case 'o': - case 'O': - radix = 8; - break; - case 'b': - case 'B': - radix = 2; - break; - } - zeroing = d->grouping ? FMT_DELIMITPOS : 0; - while (*--p = hexa[v % radix], v /= radix, --zeroing, v) - if (!zeroing) - zeroing = FMT_DELIMITPOS, *--p = d->grouping; - *pp = p; - return zeroing; -} - -static void fmt_dumpint(fmt_State *S, lua_Integer v, const fmt_Spec *d) -{ - char buff[FMT_INTBUFFSIZ], *p = buff + FMT_INTBUFFSIZ, *dp; - int sign = !(v < 0), width = d->width; - if (!sign) - v = -v; - S->zeroing = fmt_writeint(&p, v, d); - dp = p; - if (d->alter && d->type != 0 && d->type != 'd') - *--p = d->type, *--p = '0'; - if ((p[-1] = fmt_writesign(sign, d->sign)) != 0) - --p; - if (d->zero && d->width > FMT_INTBUFFSIZ - (p - buff)) - { - if (dp > p) - luaL_addlstring(&S->B, p, dp - p); - width -= (int)(dp - p), p = dp; - } - lua_pushlstring(S->L, p, FMT_INTBUFFSIZ - (p - buff)); - lua_replace(S->L, fmt_value(S, 1)); - fmt_addstring(S, 0, width, d); -} - -static int fmt_writeflt(char *s, size_t n, lua_Number v, const fmt_Spec *d) -{ - int type = d->type ? d->type : 'g'; - int (*ptr_snprintf)(char *s, size_t n, const char *fmt, ...) = snprintf; - char fmt[FMT_FMTLEN]; - const char *percent = ""; - if (d->type == '%') - type = 'f', v *= 100.0, percent = "%%"; - if (d->precision) - ptr_snprintf(fmt, FMT_FMTLEN, "%%%s.%d%c%s", - d->alter ? "#" : "", d->precision, type, percent); - else if ((lua_Number)(lua_Integer)v == v) - ptr_snprintf(fmt, FMT_FMTLEN, "%%.1f%s", percent); - else - ptr_snprintf(fmt, FMT_FMTLEN, "%%%s%c%s", - d->alter ? "#" : "", type, percent); - return ptr_snprintf(s, n, fmt, v); -} - -static void fmt_dumpflt(fmt_State *S, lua_Number v, const fmt_Spec *d) -{ - int sign = !(v < 0), len, width = d->width; - char buff[FMT_FLTBUFFSIZ], *p = buff, *dp = p; - fmt_check(S, d->precision < FMT_FLTMAXPREC, - "precision specifier too large"); - fmt_check(S, !d->grouping, - "Grouping form (%c) not allowed in float format specifier", - d->grouping); - if (!sign) - v = -v; - if ((*dp = fmt_writesign(sign, d->sign)) != 0) - ++dp; - len = fmt_writeflt(dp, FMT_FLTBUFFSIZ - (dp - buff), v, d); - if (d->zero && width > len) - { - if (dp > p) - luaL_addlstring(&S->B, buff, dp - p); - width -= (int)(dp - buff), p = dp; - } - lua_pushlstring(S->L, p, len); - lua_replace(S->L, fmt_value(S, 1)); - fmt_addstring(S, 0, width, d); -} - -static void fmt_dumpnumber(fmt_State *S, const fmt_Spec *d) -{ - int type = d->type; - if (type == 0) - type = lua_isinteger(S->L, fmt_value(S, 1)) ? 'd' : 'g'; - switch (type) - { - case 'c': - fmt_dumpchar(S, lua_tointeger(S->L, fmt_value(S, 1)), d); - break; - case 'd': - case 'b': - case 'B': - case 'o': - case 'O': - case 'x': - case 'X': - fmt_dumpint(S, lua_tointeger(S->L, fmt_value(S, 1)), d); - break; - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case '%': - fmt_dumpflt(S, lua_tonumber(S->L, fmt_value(S, 1)), d); - break; - default: - luaL_error(S->L, "Unknown format code '%c' for object of type 'number'", - d->type); - } -} - -static void fmt_dump(fmt_State *S, const fmt_Spec *d) -{ - int type = lua_type(S->L, fmt_value(S, 1)); - if (type == LUA_TNUMBER) - { - fmt_dumpnumber(S, d); - return; - } - if (d->type != 'p') - luaL_tolstring(S->L, fmt_value(S, 1), NULL); - else - { - fmt_check(S, type != LUA_TNIL && type != LUA_TBOOLEAN, - "Unknown format code '%c' for object of type '%s'", - d->type, lua_typename(S->L, type)); - lua_pushfstring(S->L, "%p", lua_topointer(S->L, fmt_value(S, 1))); - } - lua_replace(S->L, fmt_value(S, 1)); - fmt_dumpstr(S, d); -} - -/* format */ - -static void fmt_parse(fmt_State *S, fmt_Spec *d) -{ - /* "{" [arg_id] [":" format_spec] "}" */ - fmt_argid(S, fmt_value(S, 1)); - if (*S->p == ':' && ++S->p < S->e) - fmt_spec(S, d); - fmt_check(S, S->p < S->e && *S->p == '}', - "expected '}' before end of string"); - ++S->p; -} - -static int fmt_format(fmt_State *S) -{ - lua_settop(S->L, fmt_value(S, 2)); - luaL_buffinit(S->L, &S->B); - while (S->p < S->e) - { - const char *p = S->p; - while (p < S->e && *p != '{' && *p != '}') - ++p; - luaL_addlstring(&S->B, S->p, p - S->p), S->p = p; - if (S->p >= S->e) - break; - if (*S->p == S->p[1]) - luaL_addchar(&S->B, *S->p), S->p += 2; - else - { - fmt_Spec d; - if (*S->p++ == '}' || S->p >= S->e) - return luaL_error(S->L, - "Single '%c' encountered in format string", S->p[-1]); - memset(&d, 0, sizeof(d)); - fmt_parse(S, &d); - fmt_dump(S, &d); - } - } - luaL_pushresult(&S->B); - return 1; -} - -static int Lformat(lua_State *L) -{ - size_t len; - fmt_State S; - S.p = luaL_checklstring(L, 1, &len); - S.e = S.p + len; - S.L = L; - S.idx = 1; - S.top = lua_gettop(L); - return fmt_format(&S); -} - -LUALIB_API int luaopen_fmt(lua_State *L) -{ - lua_pushcfunction(L, Lformat); - return 1; -} - -/* cc: flags+='-O3 --coverage -pedantic' - * unixcc: flags+='-shared -fPIC' output='fmt.so' - * maccc: flags+='-undefined dynamic_lookup' - * win32cc: flags+='-s -mdll -DLUA_BUILD_AS_DLL ' - * win32cc: libs+='-llua54' output='fmt.dll' */ diff --git a/framework/lualib/3rd/misc/mm.lua b/framework/lualib/3rd/misc/mm.lua new file mode 100644 index 0000000..b51911f --- /dev/null +++ b/framework/lualib/3rd/misc/mm.lua @@ -0,0 +1,724 @@ +-- https://github.com/nenofite/mm + +-- Terminal color (and formatting) codes. +local C = { + e = '\27[0m', -- reset + + -- Text attributes. + br = '\27[1m', -- bright + di = '\27[2m', -- dim + it = '\27[3m', -- italics + un = '\27[4m', -- underscore + bl = '\27[5m', -- blink + re = '\27[7m', -- reverse + hi = '\27[8m', -- hidden + + -- Text colors. + k = '\27[30m', -- black + r = '\27[31m', -- red + g = '\27[32m', -- green + y = '\27[33m', -- yellow + b = '\27[34m', -- blue + m = '\27[35m', -- magenta + c = '\27[36m', -- cyan + w = '\27[37m', -- white + + -- Background colors. + _k = '\27[40m', -- black + _r = '\27[41m', -- red + _g = '\27[42m', -- green + _y = '\27[43m', -- yellow + _b = '\27[44m', -- blue + _m = '\27[45m', -- magenta + _c = '\27[46m', -- cyan + _w = '\27[47m', -- white +} + +-- If we're on Windows, set all colors to empty strings so we don't spam the +-- output with meaningless escape codes. +local ON_WINDOWS = string.find(package.path, '\\') ~= nil +if ON_WINDOWS then + for k, v in pairs(C) do + C[k] = '' + end +end + +local METATABLE = { + "", + colors = C.it .. C.y, +} +local INDENT = " " + +-- The default sequence separator. +local SEP = " " + +-- The open and close brackets can be any piece (notably, a sequence with +-- colors). The separator must be a plain string. +local BOPEN, BSEP, BCLOSE = 1, 2, 3 + +-- The default frame brackets and separator. +local BRACKETS = {{ + "{", + colors = C.br, +}, ",", { + "}", + colors = C.br, +}} + +local STR_HALF = 30 +local MAX_STR_LEN = STR_HALF * 2 + +-- Names to use for named references. The order is important; these are aligned +-- with the colors in `NAME_COLORS`. +local NAMES = {"Cherry", "Apple", "Lemon", "Blueberry", "Jam", "Cream", "Rhubarb", "Lime", "Butter", "Grape", + "Pomegranate", "Sugar", "Cinnamon", "Avocado", "Honey"} + +-- Colors to use for named references. Don't use black nor white. +local NAME_COLORS = {C.r, C.g, C.y, C.b, C.m, C.c} + +-- Reserved Lua keywords as a convenient look-up table. +local RESERVED = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +-- +-- Namers +-- + +local function new_namer() + local index = 1 + local suffix = 1 + local color_index = 1 + + return function() + -- Pick the name. + local result = NAMES[index] + if suffix > 1 then + result = result .. " " .. tostring(suffix) + end + + index = index + 1 + if index > #NAMES then + index = 1 + suffix = suffix + 1 + end + + -- Pick the color. + local color = NAME_COLORS[color_index] + + color_index = color_index + 1 + if color_index > #NAME_COLORS then + color_index = 1 + end + + return { + result, + colors = C.un .. color, + } + end +end + +-- +-- Context +-- + +local function new_context() + return { + occur = {}, + named = {}, + next_name = new_namer(), + + prev_indent = '', + next_indent = INDENT, + line_len = 0, + max_width = 78, + + result = '', + } +end + +-- +-- Translating into pieces +-- + +-- Translaters take any Lua value and create pieces to represent them. +-- +-- Some values should only be serialized once, both to prevent cycles and to +-- prevent redundancy. Or in other cases, these values cannot be serialized +-- (such as functions) but if they appear multiple times we want to express +-- that they are the same. +-- +-- When a translater encounters such a value for the first time, it is +-- registered in the context in `occur`. The value is wrapped in a plain table +-- with the `id` field pointing to the original value. If the value is +-- serializable, such as a table, then the the `def` field contains the piece +-- to display. If it is unserializable or it is not the first time this value +-- has occurred, the `def` field is nil. +-- +-- In the cleaning stage, these `id` fields are replaced with their names. If a +-- `def` field is present, then a sequence is generated to define the name with +-- the piece. + +local translaters = {} +local translate, ident_friendly + +function translate(val, ctx) + -- Try to find a type-specific translater. + local by_type = translaters[type(val)] + + if by_type then + -- If there is a type-specific translater, call it. + return by_type(val, ctx) + end + + -- Otherwise perform the default translation. + + -- Check whether we've already encountered this value. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + + -- Return the value as a reference. + return { + id = val, + } + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + + -- Return the value as a definition. + return { + id = val, + def = tostring(val), + } + end +end + +translaters['function'] = function(val, ctx) + -- Check whether we've already encountered this function. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + end + + -- Return the unserialized function. + return { + id = val, + } +end + +function translaters.table(val, ctx) + -- Check whether we've already encountered this table. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + + -- Return the unserialized table. + return { + id = val, + } + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + + -- Construct the frame for this table. + local result = { + bracket = BRACKETS, + } + + -- The equals-sign between key and value. + local eq = { + "=", + colors = C.di, + } + + -- Represent the metatable, if present. + local mt = getmetatable(val) + if mt then + -- Translate the metatable. + mt = translate(mt, ctx) + table.insert(result, {METATABLE, eq, mt}) + end + + -- Represent the contents. + for k, v in pairs(val) do + -- If it is a string key which can be represented without quotes, leave + -- it plain. + if ident_friendly(k) then + -- Leave the key as it is. + k = { + k, + colors = C.m, + } + else + -- Otherwise translate the key. + k = translate(k, ctx) + end + + -- Translate the value. + v = translate(v, ctx) + + table.insert(result, {k, eq, v}) + end + + -- Wrap the result with its id. + return { + id = val, + def = result, + } + end +end + +function translaters.string(val, ctx) + if #val <= MAX_STR_LEN then + -- The string is short enough; display it all. + local a = string.format('%q', val) + a = string.gsub(a, '\n', 'n') + + return { + a, + colors = C.g, + } + else + -- The string is too long. Only show the start and end. + local a = string.format('%q', string.sub(val, 1, STR_HALF)) + a = string.gsub(a, '\n', 'n') + local b = string.format('%q', string.sub(val, -STR_HALF)) + b = string.gsub(b, '\n', 'n') + + return { + a, + { + "...", + colors = C.di, + }, + b, + colors = C.g, + sep = '', + tight = true, + } + end +end + +function translaters.number(val, ctx) + return { + tostring(val), + colors = C.m .. C.br, + } +end + +-- Check whether a value can be represented as a Lua identifier, without the +-- need for quotes or translation. +-- +-- If the value is not a string, this immediately returns false. Otherwise, the +-- string must be a valid Lua name: a sequence of letters, digits, and +-- underscores that doesn't start with a digit and isn't a reserved keyword. +-- +-- See http://www.lua.org/manual/5.3/manual.html#3.1 +function ident_friendly(val) + -- The value must be a string. + if type(val) ~= 'string' then + return false + end + + if string.find(val, '^[_%a][_%a%d]*$') then + -- The value is a Lua name; check if it is reserved. + if RESERVED[val] then + -- The value is a resreved keyword. + return false + else + -- The value is a valid name. + return true + end + else + -- The value is not a Lua name. + return false + end +end + +-- +-- Cleaning pieces +-- + +local function clean(piece, ctx) + if type(piece) == 'table' then + -- Check if it's an id reference. + if piece.id then + local name = ctx.named[piece.id] + local def = piece.def + + -- Check whether it has been given a name. + if name then + local header = { + "<", + type(piece.id), + " ", + name, + ">", + colors = C.it, + sep = '', + tight = true, + } + -- Named. Check whether the reference has a definition. + if def then + -- Create a sequence defining the name to the definition. + return {header, { + "is", + colors = C.di, + }, clean(piece.def, ctx)} + else + -- Show just the name. + return header + end + else + -- No name. Check whether the reference has a definition. + if def then + -- Display the definition without any header. + return clean(piece.def, ctx) + else + -- Display just the type. + return { + "<", + type(piece.id), + ">", + colors = C.it, + sep = '', + tight = true, + } + end + end + + -- Check if it's a frame. + elseif piece.bracket then + -- Clean each child. + for i, child in ipairs(piece) do + piece[i] = clean(child, ctx) + end + return piece + + -- Otherwise it's a sequence. + else + -- Clean each child. + for i, child in ipairs(piece) do + piece[i] = clean(child, ctx) + end + return piece + end + else + -- It's a plain value, not a table; no cleaning is needed. + return piece + end +end + +-- +-- Displaying pieces +-- + +-- Pieces are either frames (with brackets), sequences (no brackets), or +-- strings. + +-- Frames are displayed either short-form as { a = 1 } or long-form as +-- { +-- a = 1 +-- }. + +-- Declare all the local functions first, so they can refer to each other. +local min_len, display, display_frame, display_sequence, display_string, display_frame_short, display_frame_long, + newline, newline_no_indent, write, write_nolength, space_here, space_newline + +-- Dispatch based on the piece's type. +function display(piece, ctx) + if type(piece) == 'string' then + -- String. + return display_string(piece, ctx) + elseif piece.bracket then + -- Frame. + return display_frame(piece, ctx) + else + -- Sequence. + return display_sequence(piece, ctx) + end +end + +-- Display a frame. +function display_frame(frame, ctx) + if #frame == 0 then + -- If the frame is empty, just display the brackets. + local str = { + frame.bracket[BOPEN], + frame.bracket[BCLOSE], + sep = '', + tight = true, + } + return display(str, ctx) + end + + local ml = min_len(frame) + + -- Try to fit the frame short-form on this line. + if ml <= space_here(ctx) then + return display_frame_short(frame, ctx) + + -- Otherwise try to fit it short-form on the next line. + elseif ml <= space_newline(ctx) then + newline(ctx) + return display_frame_short(frame, ctx) + + -- Otherwise display it long-form. + else + return display_frame_long(frame, ctx) + end +end + +function display_frame_short(frame, ctx) + -- Short-form frames never wrap onto new lines, so we don't need to do any + -- length checking (it's already been done for us). + + -- Write the open bracket. + display(frame.bracket[BOPEN], ctx) + write(" ", ctx) + + -- Display the first child. + display(frame[1], ctx) + + -- Display the remaining children. + for i = 2, #frame do + local child = frame[i] + + -- Write the separator. + write(frame.bracket[BSEP], ctx) + write(" ", ctx) + + -- Display the child. + display(child, ctx) + end + + -- Write the close bracket. + write(" ", ctx) + display(frame.bracket[BCLOSE], ctx) +end + +function display_frame_long(frame, ctx) + -- Remember the original value of next_indent. + local old_old_indent = ctx.prev_indent + local old_indent = ctx.next_indent + + -- Display the open bracket. + display(frame.bracket[BOPEN], ctx) + + -- Increase the indentation. + ctx.prev_indent = old_indent + ctx.next_indent = old_indent .. INDENT + + -- For all but the last child... + for i = 1, #frame - 1 do + local child = frame[i] + + -- Start a new line with old indentation. + newline_no_indent(ctx) + write(old_indent, ctx) + + -- Display the child. + display(child, ctx) + + -- Write the separator. + write(frame.bracket[BSEP], ctx) + end + + -- For the last child... + do + local child = frame[#frame] + + -- Start a new line with old indentation. + newline_no_indent(ctx) + write(old_indent, ctx) + + -- Display the child. + display(child, ctx) + -- No separator. + end + + -- Write the close bracket. + newline_no_indent(ctx) + write(old_old_indent, ctx) + display(frame.bracket[BCLOSE], ctx) + + -- Return to the old indentation. + ctx.prev_indent = old_old_indent + ctx.next_indent = old_indent +end + +function display_sequence(piece, ctx) + if #piece > 0 then + -- Check if this is a tight sequence. + if piece.tight then + -- Try to fit the entire sequence on one line. + local ml = min_len(piece, ctx) + + -- If it won't fit here, but it would fit on the next line, then write it + -- on the next line; otherwise, write it here. + if ml > space_here(ctx) and ml <= space_newline(ctx) then + newline(ctx) + end + end + + -- Apply the colors, if given. + if piece.colors then + write_nolength(piece.colors, ctx) + end + + -- Display the first child. + display(piece[1], ctx) + + -- For each following children: + for i = 2, #piece do + local child = piece[i] + + -- Apply the colors, if given. + if piece.colors then + write_nolength(piece.colors, ctx) + end + + -- Write a separator. + write(piece.sep or SEP, ctx) + + -- Then display the child. + display(child, ctx) + end + + -- Reset the colors. + if piece.colors then + write_nolength(C.e, ctx) + end + end +end + +function display_string(piece, ctx) + local ml = min_len(piece) + + -- If it won't fit here, but it would fit on the next line, then write it on + -- the next line; otherwise, write it here. + if ml > space_here(ctx) and ml <= space_newline(ctx) then + newline(ctx) + end + + write(piece, ctx) +end + +-- The minimum length to display this piece, if it is placed all on one line. +function min_len(piece, ctx) + -- For strings, simply return their length. + if type(piece) == 'string' then + return #piece + end + + -- Otherwise, we have some calculations to do. + local result = 0 + + if piece.bracket then + -- This is a frame. + + -- If it's an empty frame, just the open and close brackets. + if #piece == 0 then + return min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) + end + + -- Open and close brackets, plus a space for each. + result = result + min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) + 2 + + -- A separator between each item, plus a space for each. + result = result + (#piece - 1) * (#piece.bracket[BSEP] + 1) + else + -- This is a sequence. + + -- If it's an empty sequence, then nothing. + if #piece == 0 then + return 0 + end + + -- A single separator between each item. + result = result + (#piece - 1) * #(piece.sep or SEP) + end + + -- For both frames and sequences: + -- Find the minimum length of each child. + for _, child in ipairs(piece) do + result = result + min_len(child, ctx) + end + + return result +end + +function newline(ctx) + ctx.result = ctx.result .. "\n" + ctx.line_len = 0 + write(ctx.next_indent, ctx) +end + +function newline_no_indent(ctx) + ctx.result = ctx.result .. "\n" + ctx.line_len = 0 +end + +function write(str, ctx) + ctx.result = ctx.result .. str + ctx.line_len = ctx.line_len + #str +end + +function write_nolength(str, ctx) + ctx.result = ctx.result .. str +end + +function space_here(ctx) + return math.max(0, ctx.max_width - ctx.line_len) +end + +function space_newline(ctx) + return math.max(0, ctx.max_width - #ctx.next_indent) +end + +-- +-- Main function +-- + +return function(val) + if val == nil then + return nil + else + local ctx = new_context() + local piece = translate(val, ctx) + piece = clean(piece, ctx) + display(piece, ctx) + return (C.e .. ctx.result .. C.e) + end +end