🐳 chore(lapis): 增加 库
parent
0227bd5075
commit
50453c05f3
@ -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)
|
|
||||||
@ -1,627 +0,0 @@
|
|||||||
#define LUA_LIB
|
|
||||||
#include <lua.h>
|
|
||||||
#include <lauxlib.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <float.h>
|
|
||||||
|
|
||||||
// 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) | "[" <anychar except ']'> "]" */
|
|
||||||
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' */
|
|
||||||
@ -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 = {
|
||||||
|
"<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
|
||||||
Loading…
Reference in New Issue