summaryrefslogtreecommitdiff
path: root/source/luametatex/source/lua/lmtcallbacklib.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/luametatex/source/lua/lmtcallbacklib.c')
-rw-r--r--source/luametatex/source/lua/lmtcallbacklib.c615
1 files changed, 615 insertions, 0 deletions
diff --git a/source/luametatex/source/lua/lmtcallbacklib.c b/source/luametatex/source/lua/lmtcallbacklib.c
new file mode 100644
index 000000000..8724cdd6f
--- /dev/null
+++ b/source/luametatex/source/lua/lmtcallbacklib.c
@@ -0,0 +1,615 @@
+/*
+ See license.txt in the root of this project.
+*/
+
+# include "luametatex.h"
+
+/*tex
+
+ These are the supported callbacks (by name). This list must have the same size and order as the
+ array in |luatexcallbackids.h|! We could have kept the names private here and maybe they will
+ become that again. On the other hand we can now use them in reports.
+
+*/
+
+callback_state_info lmt_callback_state = {
+ .metatable_id = 0,
+ .padding = 0,
+ .values = { 0 },
+};
+
+/* todo: use lua keywords instead */
+
+static const char *callbacklib_names[total_callbacks] = {
+ "", /*tex empty on purpose */
+ "find_log_file",
+ "find_format_file",
+ "open_data_file",
+ "process_jobname",
+ "start_run",
+ "stop_run",
+ "define_font",
+ "pre_output_filter",
+ "buildpage_filter",
+ "hpack_filter",
+ "vpack_filter",
+ "hyphenate",
+ "ligaturing",
+ "kerning",
+ "glyph_run",
+ "pre_linebreak_filter",
+ "linebreak_filter",
+ "post_linebreak_filter",
+ "append_to_vlist_filter",
+ "alignment_filter",
+ "local_box_filter",
+ "packed_vbox_filter",
+ "mlist_to_hlist",
+ "pre_dump",
+ "start_file",
+ "stop_file",
+ "intercept_tex_error",
+ "intercept_lua_error",
+ "show_error_message",
+ "show_warning_message",
+ "hpack_quality",
+ "vpack_quality",
+ "insert_par",
+ "append_line_filter",
+ "build_page_insert",
+ /* "fire_up_output", */
+ "wrapup_run",
+ "begin_paragraph",
+ "paragraph_context",
+ /* "get_math_char", */
+ "math_rule",
+ "make_extensible",
+ "register_extensible",
+ "show_whatsit",
+ "get_attribute",
+ "get_noad_class",
+ "get_math_dictionary",
+ "show_lua_call",
+ "trace_memory",
+ "handle_overload",
+ "missing_character",
+ "process_character",
+};
+
+/*tex
+
+ This is the generic callback handler, inspired by the one described in the \LUA\ manual(s). It
+ got adapted over time and can also handle some userdata arguments.
+
+*/
+
+static int callbacklib_aux_run(lua_State *L, int id, int special, const char *values, va_list vl, int top, int base)
+{
+ int narg = 0;
+ int nres = 0;
+ if (special == 2) {
+ /*tex copy the enclosing table */
+ lua_pushvalue(L, -2);
+ }
+ for (narg = 0; *values; narg++) {
+ switch (*values++) {
+ case callback_boolean_key:
+ /*tex A boolean: */
+ lua_pushboolean(L, va_arg(vl, int));
+ break;
+ case callback_charnum_key:
+ /*tex A (8 bit) character: */
+ {
+ char cs = (char) va_arg(vl, int);
+ lua_pushlstring(L, &cs, 1);
+ }
+ break;
+ case callback_integer_key:
+ /*tex An integer: */
+ lua_pushinteger(L, va_arg(vl, int));
+ break;
+ case callback_line_key:
+ /*tex A buffer section, with implied start: */
+ lua_pushlstring(L, (char *) (lmt_fileio_state.io_buffer + lmt_fileio_state.io_first), (size_t) va_arg(vl, int));
+ break;
+ case callback_strnumber_key:
+ /*tex A \TEX\ string (indicated by an index): */
+ {
+ size_t len;
+ const char *s = tex_makeclstring(va_arg(vl, int), &len);
+ lua_pushlstring(L, s, len);
+ }
+ break;
+ case callback_lstring_key:
+ /*tex A \LUA\ string: */
+ {
+ lstring *lstr = va_arg(vl, lstring *);
+ lua_pushlstring(L, (const char *) lstr->s, lstr->l);
+ }
+ break;
+ case callback_node_key:
+ /*tex A \TEX\ node: */
+ lmt_push_node_fast(L, va_arg(vl, int));
+ break;
+ case callback_string_key:
+ /*tex A \CCODE\ string: */
+ lua_pushstring(L, va_arg(vl, char *));
+ break;
+ case '-':
+ narg--;
+ break;
+ case '>':
+ goto ENDARGS;
+ default:
+ ;
+ }
+ }
+ ENDARGS:
+ nres = (int) strlen(values);
+ if (special == 1) {
+ nres++;
+ } else if (special == 2) {
+ narg++;
+ }
+ {
+ lmt_lua_state.saved_callback_count++;
+ int i = lua_pcall(L, narg, nres, base);
+ if (i) {
+ /*tex
+ We can't be more precise here as it could be called before \TEX\ initialization is
+ complete.
+ */
+ lua_remove(L, top + 2);
+ lmt_error(L, "run callback", id, (i == LUA_ERRRUN ? 0 : 1));
+ lua_settop(L, top);
+ return 0;
+ }
+ }
+ if (nres == 0) {
+ return 1;
+ }
+ nres = -nres;
+ while (*values) {
+ int t = lua_type(L, nres);
+ switch (*values++) {
+ case callback_boolean_key:
+ switch (t) {
+ case LUA_TBOOLEAN:
+ *va_arg(vl, int *) = lua_toboolean(L, nres);
+ break;
+ case LUA_TNIL:
+ *va_arg(vl, int *) = 0;
+ break;
+ default:
+ return tex_formatted_error("callback", "boolean or nil expected, false or nil, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ /*
+ case callback_charnum_key:
+ break;
+ */
+ case callback_integer_key:
+ switch (t) {
+ case LUA_TNUMBER:
+ *va_arg(vl, int *) = lmt_tointeger(L, nres);
+ break;
+ default:
+ return tex_formatted_error("callback", "number expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ case callback_line_key:
+ switch (t) {
+ case LUA_TSTRING:
+ {
+ size_t len;
+ const char *s = lua_tolstring(L, nres, &len);
+ if (s && (len > 0)) {
+ int *bufloc = va_arg(vl, int *);
+ int ret = *bufloc;
+ if (tex_room_in_buffer(ret + (int) len)) {
+ strncpy((char *) (lmt_fileio_state.io_buffer + ret), s, len);
+ *bufloc += (int) len;
+ /* while (len--) { fileio_state.io_buffer[(*bufloc)++] = *s++; } */
+ while ((*bufloc) - 1 > ret && lmt_fileio_state.io_buffer[(*bufloc) - 1] == ' ') {
+ (*bufloc)--;
+ }
+ } else {
+ return 0;
+ }
+ }
+ /*tex We can assume no more arguments! */
+ }
+ break;
+ case LUA_TNIL:
+ /*tex We assume no more arguments! */
+ return 0;
+ default:
+ return tex_formatted_error("callback", "string or nil expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ case callback_strnumber_key:
+ switch (t) {
+ case LUA_TSTRING:
+ {
+ size_t len;
+ const char *s = lua_tolstring(L, nres, &len);
+ if (s) {
+ *va_arg(vl, int *) = tex_maketexlstring(s, len);
+ } else {
+ /*tex |len| can be zero */
+ *va_arg(vl, int *) = 0;
+ }
+ }
+ break;
+ default:
+ return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ case callback_lstring_key:
+ switch (t) {
+ case LUA_TSTRING:
+ {
+ size_t len;
+ const char *s = lua_tolstring(L, nres, &len);
+ if (s && len > 0) {
+ lstring *lsret = lmt_memory_malloc(sizeof(lstring));
+ if (lsret) {
+ lsret->s = lmt_memory_malloc((unsigned) (len + 1));
+ if (lsret->s) {
+ (void) memcpy(lsret->s, s, (len + 1));
+ lsret->l = len;
+ *va_arg(vl, lstring **) = lsret;
+ } else {
+ *va_arg(vl, int *) = 0;
+ }
+ } else {
+ *va_arg(vl, int *) = 0;
+ }
+ } else {
+ /*tex |len| can be zero */
+ *va_arg(vl, int *) = 0;
+ }
+ }
+ break;
+ default:
+ return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ case callback_node_key:
+ switch (t) {
+ case LUA_TUSERDATA:
+ *va_arg(vl, int *) = lmt_check_isnode(L, nres);
+ break;
+ default:
+ *va_arg(vl, int *) = null;
+ break;
+ }
+ break;
+ case callback_string_key:
+ switch (t) {
+ case LUA_TSTRING:
+ {
+ size_t len;
+ const char *s = lua_tolstring(L, nres, &len);
+ if (s) {
+ char *ss = lmt_memory_malloc((unsigned) (len + 1));
+ if (ss) {
+ memcpy(ss, s, (len + 1));
+ }
+ *va_arg(vl, char **) = ss;
+ } else {
+ *va_arg(vl, char **) = NULL;
+ // *va_arg(vl, int *) = 0;
+ }
+ }
+ break;
+ default:
+ return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ case callback_result_key:
+ switch (t) {
+ case LUA_TNIL:
+ *va_arg(vl, int *) = 0;
+ break;
+ case LUA_TBOOLEAN:
+ if (lua_toboolean(L, nres) == 0) {
+ *va_arg(vl, int *) = 0;
+ break;
+ } else {
+ return tex_formatted_error("callback", "string, false or nil expected, not: %s\n", lua_typename(L, t));
+ }
+ case LUA_TSTRING:
+ {
+ size_t len;
+ const char *s = lua_tolstring(L, nres, &len);
+ if (s) {
+ char *ss = lmt_memory_malloc((unsigned) (len + 1));
+ if (ss) {
+ memcpy(ss, s, (len + 1));
+ *va_arg(vl, char **) = ss;
+ } else {
+ *va_arg(vl, char **) = NULL;
+ // *va_arg(vl, int *) = 0;
+ }
+ } else {
+ *va_arg(vl, char **) = NULL;
+ // *va_arg(vl, int *) = 0;
+ }
+ }
+ break;
+ default:
+ return tex_formatted_error("callback", "string, false or nil expected, not: %s\n", lua_typename(L, t));
+ }
+ break;
+ default:
+ return tex_formatted_error("callback", "invalid value type returned\n");
+ }
+ nres++;
+ }
+ return 1;
+}
+
+/*tex
+ Especially the \IO\ related callbacks are registered once, for instance when a file is opened,
+ and (re)used later. These are dealt with here.
+*/
+
+int lmt_run_saved_callback_close(lua_State *L, int r)
+{
+ int ret = 0;
+ int stacktop = lua_gettop(L);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, r);
+ lua_push_key(close);
+ if (lua_rawget(L, -2) == LUA_TFUNCTION) {
+ ret = lua_pcall(L, 0, 0, 0);
+ if (ret) {
+ return tex_formatted_error("lua", "error in close file callback") - 1;
+ }
+ }
+ lua_settop(L, stacktop);
+ return ret;
+}
+
+int lmt_run_saved_callback_line(lua_State *L, int r, int firstpos)
+{
+ int ret = -1; /* -1 is error, >= 0 is buffer length */
+ int stacktop = lua_gettop(L);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, r);
+ lua_push_key(reader);
+ if (lua_rawget(L, -2) == LUA_TFUNCTION) {
+ lua_pushvalue(L, -2);
+ lmt_lua_state.file_callback_count++;
+ ret = lua_pcall(L, 1, 1, 0);
+ if (ret) {
+ ret = tex_formatted_error("lua", "error in read line callback") - 1;
+ } else if (lua_type(L, -1) == LUA_TSTRING) {
+ size_t len;
+ const char *s = lua_tolstring(L, -1, &len);
+ if (s && len > 0) {
+ while (len >= 1 && s[len-1] == ' ') {
+ len--;
+ }
+ if (len > 0) {
+ if (tex_room_in_buffer(firstpos + (int) len)) {
+ strncpy((char *) (lmt_fileio_state.io_buffer + firstpos), s, len);
+ ret = firstpos + (int) len;
+ } else {
+ tex_overflow_error("buffer", (int) len);
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ }
+ } else {
+ ret = -1;
+ }
+ }
+ lua_settop(L, stacktop);
+ return ret;
+}
+
+/*tex
+
+ Many callbacks have a specific handler, so they don't use the previously mentioned generic one.
+ The next bunch of helpers checks for them being set and deals invoking them as well as reporting
+ errors.
+
+*/
+
+int lmt_callback_okay(lua_State *L, int i, int *top)
+{
+ *top = lua_gettop(L);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
+ lua_pushcfunction(L, lmt_traceback); /* goes before function */
+ if (lua_rawgeti(L, -2, i) == LUA_TFUNCTION) {
+ lmt_lua_state.saved_callback_count++;
+ return 1;
+ } else {
+ lua_pop(L, 3);
+ return 0;
+ }
+}
+
+void lmt_callback_error(lua_State *L, int top, int i)
+{
+ lua_remove(L, top + 2);
+ lmt_error(L, "callback error", -1, (i == LUA_ERRRUN ? 0 : 1));
+ lua_settop(L, top);
+}
+
+int lmt_run_and_save_callback(lua_State *L, int i, const char *values, ...)
+{
+ int top = 0;
+ int ret = 0;
+ if (lmt_callback_okay(L, i, &top)) {
+ va_list args;
+ va_start(args, values);
+ ret = callbacklib_aux_run(L, i, 1, values, args, top, top + 2);
+ va_end(args);
+ if (ret > 0) {
+ ret = lua_type(L, -1) == LUA_TTABLE ? luaL_ref(L, LUA_REGISTRYINDEX) : 0;
+ }
+ lua_settop(L, top);
+ }
+ return ret;
+}
+
+int lmt_run_callback(lua_State *L, int i, const char *values, ...)
+{
+ int top = 0;
+ int ret = 0;
+ if (lmt_callback_okay(L, i, &top)) {
+ va_list args;
+ va_start(args, values);
+ ret = callbacklib_aux_run(L, i, 0, values, args, top, top + 2);
+ va_end(args);
+ lua_settop(L, top);
+ }
+ return ret;
+}
+
+void lmt_destroy_saved_callback(lua_State *L, int i)
+{
+ luaL_unref(L, LUA_REGISTRYINDEX, i);
+}
+
+static int callbacklib_callback_found(const char *s)
+{
+ if (s) {
+ for (int cb = 0; cb < total_callbacks; cb++) {
+ if (strcmp(callbacklib_names[cb], s) == 0) {
+ return cb;
+ }
+ }
+ }
+ return -1;
+}
+
+static int callbacklib_callback_register(lua_State *L)
+{
+ const char *s = lua_tostring(L, 1);
+ int cb = callbacklib_callback_found(s);
+ if (cb >= 0) {
+ switch (lua_type(L, 2)) {
+ case LUA_TFUNCTION:
+ lmt_callback_state.values[cb] = cb;
+ break;
+ case LUA_TBOOLEAN:
+ if (lua_toboolean(L, 2)) {
+ goto BAD; /*tex Only |false| is valid. */
+ }
+ // fall through
+ case LUA_TNIL:
+ lmt_callback_state.values[cb] = -1;
+ break;
+ }
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
+ lua_pushvalue(L, 2); /*tex the function or nil */
+ lua_rawseti(L, -2, cb);
+ lua_rawseti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
+ lua_pushinteger(L, cb);
+ return 1;
+ }
+ BAD:
+ lua_pushnil(L);
+ return 1;
+}
+
+void lmt_run_memory_callback(const char* what, int success)
+{
+ lmt_run_callback(lmt_lua_state.lua_instance, trace_memory_callback, "Sb->", what, success);
+ fflush(stdout);
+}
+
+/*tex
+
+ The \LUA\ library that deals with callbacks has some diagnostic helpers that makes it possible
+ to implement a higher level interface.
+
+*/
+
+static int callbacklib_callback_find(lua_State *L)
+{
+ const char *s = lua_tostring(L, 1);
+ if (s) {
+ int cb = callbacklib_callback_found(s);
+ if (cb >= 0) {
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
+ lua_rawgeti(L, -1, cb);
+ return 1;
+ }
+ }
+ lua_pushnil(L);
+ return 1;
+}
+
+static int callbacklib_callback_known(lua_State *L)
+{
+ const char *s = lua_tostring(L, 1);
+ lua_pushboolean(L, s && (callbacklib_callback_found(s) >= 0));
+ return 1;
+}
+
+static int callbacklib_callback_list(lua_State *L)
+{
+ lua_createtable(L, 0, total_callbacks);
+ for (int cb = 1; cb < total_callbacks; cb++) {
+ lua_pushstring(L, callbacklib_names[cb]);
+ lua_pushboolean(L, lmt_callback_defined(cb));
+ lua_rawset(L, -3);
+ }
+ return 1;
+}
+
+/* todo: language function calls */
+
+void lmt_push_callback_usage(lua_State *L)
+{
+ lua_createtable(L, 0, 9);
+ lua_push_integer_at_key(L, saved, lmt_lua_state.saved_callback_count);
+ lua_push_integer_at_key(L, file, lmt_lua_state.file_callback_count);
+ lua_push_integer_at_key(L, direct, lmt_lua_state.direct_callback_count);
+ lua_push_integer_at_key(L, function, lmt_lua_state.function_callback_count);
+ lua_push_integer_at_key(L, value, lmt_lua_state.value_callback_count);
+ lua_push_integer_at_key(L, local, lmt_lua_state.local_callback_count);
+ lua_push_integer_at_key(L, bytecode, lmt_lua_state.bytecode_callback_count);
+ lua_push_integer_at_key(L, message, lmt_lua_state.message_callback_count);
+ lua_push_integer_at_key(L, count,
+ lmt_lua_state.saved_callback_count
+ + lmt_lua_state.file_callback_count
+ + lmt_lua_state.direct_callback_count
+ + lmt_lua_state.function_callback_count
+ + lmt_lua_state.value_callback_count
+ + lmt_lua_state.local_callback_count
+ + lmt_lua_state.bytecode_callback_count
+ + lmt_lua_state.message_callback_count
+ );
+}
+
+static int callbacklib_callback_usage(lua_State *L)
+{
+ lmt_push_callback_usage(L);
+ return 1;
+}
+
+static const struct luaL_Reg callbacklib_function_list[] = {
+ { "find", callbacklib_callback_find },
+ { "known", callbacklib_callback_known },
+ { "register", callbacklib_callback_register },
+ { "list", callbacklib_callback_list },
+ { "usage", callbacklib_callback_usage },
+ { NULL, NULL },
+};
+
+int luaopen_callback(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_setfuncs(L, callbacklib_function_list, 0);
+ lua_newtable(L);
+ lmt_callback_state.metatable_id = luaL_ref(L, LUA_REGISTRYINDEX);
+ return 1;
+}