lmtcallbacklib.c /size: 22 Kb    last modification: 2025-02-21 11:03
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6
7/*tex
8
9    These are the supported callbacks (by name). This list must have the same size and order as the
10    array in |luatexcallbackids.h|! We could have kept the names private here and maybe they will
11    become that again. On the other hand we can now use them in reports.
12
13*/
14
15callback_state_info lmt_callback_state = {
16    .metatable_id = 0,
17    .padding      = 0,
18    .values       = { 0 },
19 /* .states       = { 0 }, */
20};
21
22/* todo: use lua keywords instead */
23
24static const char *callbacklib_names[total_callbacks] = {
25    "", /*tex empty on purpose */
26    "find_log_file",
27    "find_format_file",
28    "open_data_file",
29    "process_jobname",
30    "start_run",
31    "stop_run",
32    "define_font",
33    "quality_font",
34    "pre_output_filter",
35    "buildpage_filter",
36    "hpack_filter",
37    "vpack_filter",
38    "hyphenate",
39    "ligaturing",
40    "kerning",
41    "glyph_run",
42    "pre_linebreak_filter",
43    "linebreak_filter",
44    "post_linebreak_filter",
45    "append_to_vlist_filter",
46    "alignment_filter",
47    "local_box_filter",
48    "packed_vbox_filter",
49    "mlist_to_hlist",
50    "pre_dump",
51    "start_file",
52    "stop_file",
53    "intercept_tex_error",
54    "intercept_lua_error",
55    "show_error_message",
56    "show_warning_message",
57    "hpack_quality",
58    "vpack_quality",
59    "line_break",
60    "balance",
61    "show_vsplit",
62    "show_build",
63    "insert_par",
64    "append_line_filter",
65    "insert_distance",
66 /* "fire_up_output", */
67    "wrapup_run",
68    "begin_paragraph",
69    "paragraph_context",
70 /* "get_math_char", */
71    "math_rule",
72    "make_extensible",
73    "register_extensible",
74    "show_whatsit",
75    "get_attribute",
76    "get_noad_class",
77    "get_math_dictionary",
78    "show_lua_call",
79    "trace_memory",
80    "handle_overload",
81    "missing_character",
82    "process_character",
83    "linebreak_quality",
84    "paragraph_pass",
85    "handle_uleader",
86    "handle_uinsert",
87    "italic_correction",
88    "show_loners",
89    "tail_append",
90    "balance_boundary",
91    "balance_insert",
92};
93
94/*tex
95
96    This is the generic callback handler, inspired by the one described in the \LUA\ manual(s). It
97    got adapted over time and can also handle some userdata arguments.
98
99*/
100
101static int callbacklib_aux_run(lua_State *L, int id, int special, const char *values, va_list vl, int top, int base)
102{
103    int narg = 0;
104    int nres = 0;
105    if (special == 2) {
106        /*tex copy the enclosing table */
107        lua_pushvalue(L, -2);
108    }
109    for (narg = 0; *values; narg++) {
110        switch (*values++) {
111            case callback_boolean_key:
112                /*tex A boolean: */
113                lua_pushboolean(L, va_arg(vl, int));
114                break;
115            case callback_charnum_key:
116                /*tex A (8 bit) character: */
117                {
118                    char cs = (char) va_arg(vl, int);
119                    lua_pushlstring(L, &cs, 1);
120                }
121                break;
122            case callback_integer_key:
123                /*tex An integer: */
124                lua_pushinteger(L, va_arg(vl, int));
125                break;
126            case callback_line_key:
127                /*tex A buffer section, with implied start: */
128                lua_pushlstring(L, (char *) (lmt_fileio_state.io_buffer + lmt_fileio_state.io_first), (size_t) va_arg(vl, int));
129                break;
130            case callback_strnumber_key:
131                /*tex A \TEX\ string (indicated by an index): */
132                {
133                    size_t len;
134                    const char *s = tex_makeclstring(va_arg(vl, int), &len);
135                    lua_pushlstring(L, s, len);
136                }
137                break;
138            case callback_lstring_key:
139                /*tex A \LUA\ string: */
140                {
141                    lstring *lstr = va_arg(vl, lstring *);
142                    lua_pushlstring(L, (const char *) lstr->s, lstr->l);
143                }
144                break;
145            case callback_node_key:
146                /*tex A \TEX\ node: */
147                lmt_push_node_fast(L, va_arg(vl, int));
148                break;
149            case callback_string_key:
150                /*tex A \CCODE\ string: */
151                lua_pushstring(L, va_arg(vl, char *));
152                break;
153            case '-':
154                narg--;
155                break;
156            case '>':
157                goto ENDARGS;
158            default:
159                ;
160        }
161    }
162  ENDARGS:
163    nres = (int) strlen(values);
164    if (special == 1) {
165        nres++;
166    } else if (special == 2) {
167        narg++;
168    }
169    lmt_lua_state.saved_callback_count++;
170    {
171        int i = lua_pcall(L, narg, nres, base);
172        if (i) {
173            /*tex
174                We can't be more precise here as it could be called before \TEX\ initialization is
175                complete.
176            */
177            lua_remove(L, top + 2);
178            lmt_error(L, "run callback", id, (i == LUA_ERRRUN ? 0 : 1));
179            lua_settop(L, top);
180            return 0;
181        }
182    }
183    if (nres == 0) {
184        return 1;
185    }
186    nres = -nres;
187    while (*values) {
188        int t = lua_type(L, nres);
189        switch (*values++) {
190            case callback_boolean_key:
191                switch (t) {
192                    case LUA_TBOOLEAN:
193                        *va_arg(vl, int *) = lua_toboolean(L, nres);
194                        break;
195                    case LUA_TNIL:
196                        *va_arg(vl, int *) = 0;
197                        break;
198                    default:
199                        return tex_formatted_error("callback", "boolean or nil expected, false or nil, not: %s\n", lua_typename(L, t));
200                }
201                break;
202            /*
203            case callback_charnum_key:
204                break;
205            */
206            case callback_integer_key:
207                switch (t) {
208                    case LUA_TNUMBER:
209                        *va_arg(vl, int *) = lmt_tointeger(L, nres);
210                        break;
211                    default:
212                        return tex_formatted_error("callback", "number expected, not: %s\n", lua_typename(L, t));
213                }
214                break;
215            case callback_line_key:
216                switch (t) {
217                    case LUA_TSTRING:
218                        {
219                            size_t len;
220                            const char *s = lua_tolstring(L, nres, &len);
221                            if (s && (len > 0)) {
222                                int *bufloc = va_arg(vl, int *);
223                                int ret = *bufloc;
224                                if (tex_room_in_buffer(ret + (int) len)) {
225                                    strncpy((char *) (lmt_fileio_state.io_buffer + ret), s, len);
226                                    *bufloc += (int) len;
227                                    /* while (len--) {  fileio_state.io_buffer[(*bufloc)++] = *s++; } */
228                                    while ((*bufloc) - 1 > ret && lmt_fileio_state.io_buffer[(*bufloc) - 1] == ' ') {
229                                        (*bufloc)--;
230                                    }
231                               } else {
232                                    return 0;
233                               }
234                            }
235                            /*tex We can assume no more arguments! */
236                        }
237                        break;
238                    case LUA_TNIL:
239                        /*tex We assume no more arguments! */
240                        return 0;
241                    default:
242                        return tex_formatted_error("callback", "string or nil expected, not: %s\n", lua_typename(L, t));
243                }
244                break;
245            case callback_strnumber_key:
246                switch (t) {
247                    case LUA_TSTRING:
248                        {
249                            size_t len;
250                            const char *s = lua_tolstring(L, nres, &len);
251                            if (s) {
252                                *va_arg(vl, int *) = tex_maketexlstring(s, len);
253                            } else {
254                                /*tex |len| can be zero */
255                                *va_arg(vl, int *) = 0;
256                            }
257                        }
258                        break;
259                    default:
260                        return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
261               }
262                break;
263            case callback_lstring_key:
264                switch (t) {
265                    case LUA_TSTRING:
266                        {
267                            size_t len;
268                            const char *s = lua_tolstring(L, nres, &len);
269                            if (s && len > 0) {
270                                lstring *lsret = lmt_memory_malloc(sizeof(lstring));
271                                if (lsret) {
272                                    lsret->s = lmt_memory_malloc((unsigned) (len + 1));
273                                    if (lsret->s) {
274                                        (void) memcpy(lsret->s, s, (len + 1));
275                                        lsret->l = len;
276                                        *va_arg(vl, lstring **) = lsret;
277                                    } else {
278                                        *va_arg(vl, int *) = 0;
279                                    }
280                                } else {
281                                    *va_arg(vl, int *) = 0;
282                                }
283                            } else {
284                                /*tex |len| can be zero */
285                                *va_arg(vl, int *) = 0;
286                            }
287                        }
288                        break;
289                    default:
290                        return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
291                }
292                break;
293            case callback_node_key:
294                switch (t) {
295                    case LUA_TUSERDATA:
296                        *va_arg(vl, int *) = lmt_check_isnode(L, nres);
297                        break;
298                    default:
299                        *va_arg(vl, int *) = null;
300                        break;
301                }
302                break;
303            case callback_string_key:
304                switch (t) {
305                    case LUA_TSTRING:
306                        {
307                            size_t len;
308                            const char *s = lua_tolstring(L, nres, &len);
309                            if (s) {
310                                char *ss = lmt_memory_malloc((unsigned) (len + 1));
311                                if (ss) {
312                                    memcpy(ss, s, (len + 1));
313                                 }
314                                *va_arg(vl, char **) = ss;
315                            } else {
316                                *va_arg(vl, char **) = NULL;
317                             // *va_arg(vl, int *) = 0;
318                            }
319                        }
320                        break;
321                    default:
322                        return tex_formatted_error("callback", "string expected, not: %s\n", lua_typename(L, t));
323                }
324                break;
325            case callback_result_s_key:
326                switch (t) {
327                    case LUA_TNIL:
328                        *va_arg(vl, int *) = 0;
329                        break;
330                    case LUA_TBOOLEAN:
331                        if (lua_toboolean(L, nres) == 0) {
332                            *va_arg(vl, int *) = 0;
333                            break;
334                        } else {
335                            return tex_formatted_error("callback", "string, false or nil expected, not: %s\n", lua_typename(L, t));
336                        }
337                    case LUA_TSTRING:
338                        {
339                            size_t len;
340                            const char *s = lua_tolstring(L, nres, &len);
341                            if (s) {
342                                char *ss = lmt_memory_malloc((unsigned) (len + 1));
343                                if (ss) {
344                                    memcpy(ss, s, (len + 1));
345                                    *va_arg(vl, char **) = ss;
346                                } else {
347                                   *va_arg(vl, char **) = NULL;
348                                // *va_arg(vl, int *) = 0;
349                                }
350                            } else {
351                                *va_arg(vl, char **) = NULL;
352                             // *va_arg(vl, int *) = 0;
353                            }
354                        }
355                        break;
356                    default:
357                        return tex_formatted_error("callback", "string, false or nil expected, not: %s\n", lua_typename(L, t));
358                }
359                break;
360            case callback_result_i_key:
361                switch (t) {
362                    case LUA_TNUMBER:
363                        *va_arg(vl, int *) = lmt_tointeger(L, nres);
364                        break;
365                    default:
366                     /* *va_arg(vl, int *) = 0; */ /*tex We keep the value! */
367                        break;
368                }
369                break;
370            default:
371                return tex_formatted_error("callback", "invalid value type returned\n");
372        }
373        nres++;
374    }
375    return 1;
376}
377
378/*tex
379    Especially the \IO\ related callbacks are registered once, for instance when a file is opened,
380    and (re)used later. These are dealt with here.
381*/
382
383int lmt_run_saved_callback_close(lua_State *L, int r)
384{
385    int ret = 0;
386    int stacktop = lua_gettop(L);
387    lua_rawgeti(L, LUA_REGISTRYINDEX, r);
388    lua_push_key(close);
389    if (lua_rawget(L, -2) == LUA_TFUNCTION) {
390        ret = lua_pcall(L, 0, 0, 0);
391        if (ret) {
392            return tex_formatted_error("lua", "error in close file callback") - 1;
393        }
394    }
395    lua_settop(L, stacktop);
396    return ret;
397}
398
399int lmt_run_saved_callback_line(lua_State *L, int r, int firstpos)
400{
401    int ret = -1; /* -1 is error, >= 0 is buffer length */
402    int stacktop = lua_gettop(L);
403    lua_rawgeti(L, LUA_REGISTRYINDEX, r);
404    lua_push_key(reader);
405    if (lua_rawget(L, -2) == LUA_TFUNCTION) {
406        lua_pushvalue(L, -2);
407        lmt_lua_state.file_callback_count++;
408        ret = lua_pcall(L, 1, 1, 0);
409        if (ret) {
410            ret = tex_formatted_error("lua", "error in read line callback") - 1;
411        } else if (lua_type(L, -1) == LUA_TSTRING) {
412            size_t len;
413            const char *s = lua_tolstring(L, -1, &len);
414            if (s && len > 0) {
415                while (len >= 1 && s[len-1] == ' ') {
416                    len--;
417                }
418                if (len > 0) {
419                    if (tex_room_in_buffer(firstpos + (int) len)) {
420                        strncpy((char *) (lmt_fileio_state.io_buffer + firstpos), s, len);
421                        ret = firstpos + (int) len;
422                    } else {
423                        tex_overflow_error("buffer", (int) len);
424                        ret = 0;
425                    }
426                } else {
427                    ret = 0;
428                }
429            } else {
430                ret = 0;
431            }
432        } else {
433            ret = -1;
434        }
435    }
436    lua_settop(L, stacktop);
437    return ret;
438}
439
440/*tex
441
442    Many callbacks have a specific handler, so they don't use the previously mentioned generic one.
443    The next bunch of helpers checks for them being set and deals invoking them as well as reporting
444    errors.
445
446*/
447
448int lmt_callback_okay(lua_State *L, int i, int *top)
449{
450    *top = lua_gettop(L);
451    lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
452    lua_pushcfunction(L, lmt_traceback); /* goes before function */
453    if (lua_rawgeti(L, -2, i) == LUA_TFUNCTION) {
454        lmt_lua_state.saved_callback_count++;
455        return 1;
456    } else {
457        lua_pop(L, 3);
458        return 0;
459    }
460}
461
462void lmt_callback_error(lua_State *L, int top, int i)
463{
464    lua_remove(L, top + 2);
465    lmt_error(L, "callback error", -1, (i == LUA_ERRRUN ? 0 : 1));
466    lua_settop(L, top);
467}
468
469int lmt_run_and_save_callback(lua_State *L, int i, const char *values, ...)
470{
471    int top = 0;
472    int ret = 0;
473    if (lmt_callback_okay(L, i, &top)) {
474        va_list args;
475        va_start(args, values);
476        ret = callbacklib_aux_run(L, i, 1, values, args, top, top + 2);
477        va_end(args);
478        if (ret > 0) {
479            ret = lua_type(L, -1) == LUA_TTABLE ? luaL_ref(L, LUA_REGISTRYINDEX) : 0;
480        }
481        lua_settop(L, top);
482    }
483    return ret;
484}
485
486int lmt_run_callback(lua_State *L, int i, const char *values, ...)
487{
488    int top = 0;
489    int ret = 0;
490    if (lmt_callback_okay(L, i, &top)) {
491        va_list args;
492        va_start(args, values);
493        ret = callbacklib_aux_run(L, i, 0, values, args, top, top + 2);
494        va_end(args);
495        lua_settop(L, top);
496    }
497    return ret;
498}
499
500void lmt_destroy_saved_callback(lua_State *L, int i)
501{
502    luaL_unref(L, LUA_REGISTRYINDEX, i);
503}
504
505static int callbacklib_callback_found(const char *s)
506{
507    if (s) {
508        for (int cb = 0; cb < total_callbacks; cb++) {
509            if (strcmp(callbacklib_names[cb], s) == 0) {
510                return cb;
511            }
512        }
513    }
514    return -1;
515}
516
517static int callbacklib_callback_register(lua_State *L)
518{
519    const char *s = lua_tostring(L, 1);
520    int cb = callbacklib_callback_found(s);
521    if (cb >= 0) {
522     // if (lmt_callback_state.states[cb] & callback_frozen) {
523     //     /*tex Maybe issue a message. */
524     // } else {
525            switch (lua_type(L, 2)) {
526                case LUA_TFUNCTION:
527                    lmt_callback_state.values[cb] = cb;
528                 // lmt_callback_state.states[cb] = callback_set;
529                    break;
530                case LUA_TBOOLEAN:
531                    if (lua_toboolean(L, 2)) {
532                        goto BAD; /*tex Only |false| is valid. */
533                    }
534                    // fall through
535                case LUA_TNIL:
536                    lmt_callback_state.values[cb] = -1;
537                 // lmt_callback_state.states[cb] = 0;
538                    break;
539            }
540            lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
541            lua_pushvalue(L, 2); /*tex the function or nil */
542            lua_rawseti(L, -2, cb);
543            lua_rawseti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
544            lua_pushinteger(L, cb);
545            return 1;
546     // } 
547    }
548  BAD:
549    lua_pushnil(L);
550    return 1;
551}
552
553// static int callbacklib_callback_enable(lua_State *L)
554// {
555//     int cb = lmt_tointeger(L, 1);
556//     if (cb >= 0 && cb < total_callbacks) {
557//         if (lua_toboolean(L, 2)) {
558//             lmt_callback_state.states[cb] &= ~ callback_disabled;
559//         } else {
560//             lmt_callback_state.states[cb] |= callback_disabled;
561//         }
562//     }
563//     return 0;
564// }
565// 
566// static int callbacklib_callback_freeze(lua_State *L)
567// {
568//     int cb = lmt_tointeger(L, 1);
569//     if (cb >= 0 && cb < total_callbacks) {
570//         lmt_callback_state.states[cb] |= callback_frozen;
571//     }
572//     return 0;
573// }
574
575void lmt_run_memory_callback(const char* what, int success)
576{
577    lmt_run_callback(lmt_lua_state.lua_instance, trace_memory_callback, "Sb->", what, success);
578    fflush(stdout);
579}
580
581/*tex
582
583    The \LUA\ library that deals with callbacks has some diagnostic helpers that makes it possible
584    to implement a higher level interface.
585
586*/
587
588static int callbacklib_callback_find(lua_State *L)
589{
590    const char *s = lua_tostring(L, 1);
591    if (s) {
592        int cb = callbacklib_callback_found(s);
593        if (cb >= 0) {
594            lua_rawgeti(L, LUA_REGISTRYINDEX, lmt_callback_state.metatable_id);
595            lua_rawgeti(L, -1, cb);
596            return 1;
597        }
598    }
599    lua_pushnil(L);
600    return 1;
601}
602
603static int callbacklib_callback_known(lua_State *L)
604{
605    const char *s = lua_tostring(L, 1);
606    lua_pushboolean(L, s && (callbacklib_callback_found(s) >= 0));
607    return 1;
608}
609
610static int callbacklib_callback_list(lua_State *L)
611{
612    lua_createtable(L, 0, total_callbacks);
613    for (int cb = 1; cb < total_callbacks; cb++) {
614        lua_pushstring(L, callbacklib_names[cb]);
615        lua_pushboolean(L, lmt_callback_defined(cb));
616        lua_rawset(L, -3);
617    }
618    return 1;
619}
620
621/* todo: language function calls */
622
623void lmt_push_callback_usage(lua_State *L)
624{
625    lua_createtable(L, 0, 9);
626    lua_push_integer_at_key(L, saved,    lmt_lua_state.saved_callback_count);
627    lua_push_integer_at_key(L, file,     lmt_lua_state.file_callback_count);
628    lua_push_integer_at_key(L, direct,   lmt_lua_state.direct_callback_count);
629    lua_push_integer_at_key(L, function, lmt_lua_state.function_callback_count);
630    lua_push_integer_at_key(L, value,    lmt_lua_state.value_callback_count);
631    lua_push_integer_at_key(L, local,    lmt_lua_state.local_callback_count);
632    lua_push_integer_at_key(L, bytecode, lmt_lua_state.bytecode_callback_count);
633    lua_push_integer_at_key(L, message,  lmt_lua_state.message_callback_count);
634    lua_push_integer_at_key(L, count,
635        lmt_lua_state.saved_callback_count
636      + lmt_lua_state.file_callback_count
637      + lmt_lua_state.direct_callback_count
638      + lmt_lua_state.function_callback_count
639      + lmt_lua_state.value_callback_count
640      + lmt_lua_state.local_callback_count
641      + lmt_lua_state.bytecode_callback_count
642      + lmt_lua_state.message_callback_count
643    );
644}
645
646static int callbacklib_callback_usage(lua_State *L)
647{
648    lmt_push_callback_usage(L);
649    return 1;
650}
651
652static const struct luaL_Reg callbacklib_function_list[] = {
653    { "find",     callbacklib_callback_find     },
654    { "known",    callbacklib_callback_known    },
655    { "register", callbacklib_callback_register },
656 // { "enable",   callbacklib_callback_enable   },
657 // { "freeze",   callbacklib_callback_freeze   },
658    { "list",     callbacklib_callback_list     },
659    { "usage",    callbacklib_callback_usage    },
660    { NULL,       NULL                          },
661};
662
663int luaopen_callback(lua_State *L)
664{
665    lua_newtable(L);
666    luaL_setfuncs(L, callbacklib_function_list, 0);
667    lua_newtable(L);
668    lmt_callback_state.metatable_id = luaL_ref(L, LUA_REGISTRYINDEX);
669    return 1;
670}
671