lmtmujs.c /size: 20 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5/*tex
6    The \CLANGUAGE\ interface looks quite a bit like the \LUA\ interface. This module will only
7    provide print-to-tex functions and no other interfacing. It really makes no sense to provide
8    more. An interesting lightweight interface could map onto \LUA\ calls but there is no gain
9    either. Consider it an experiment that might attract kids to \TEX, just because \JAVASCRIPT\
10    looks familiar. We have only one instance. Of course we could use some userdata object but I
11    don't think it is worth the effort and one would like to be persistent across calls. (We used
12    to have multiple \LUA\ instances which made no sense either.)
13
14    url: https://mujs.com/index.html
15
16    Keep in mind: we don't have a \JAVASCRIPT\ interoreter embedded because this is just a small
17    minimal interface to code that {\em can} be loaded at runtime, if present at all.
18
19*/
20
21# include "luametatex.h"
22# include "lmtoptional.h"
23
24typedef struct js_State js_State;
25
26typedef void (*js_CFunction) (js_State *J);
27typedef void (*js_Report)    (js_State *J, const char *message);
28typedef void (*js_Finalize)  (js_State *J, void *p);
29
30typedef enum js_states {
31	JS_STRICT = 1,
32} js_states;
33
34typedef enum js_properties {
35	JS_READONLY = 1,
36	JS_DONTENUM = 2,
37	JS_DONTCONF = 4,
38} js_properties;
39
40/*tex A couple of status variables: */
41
42typedef struct mujslib_state_info {
43
44    js_State *instance;
45    int       initialized;
46    int       find_file_id;
47    int       open_file_id;
48    int       close_file_id;
49    int       read_file_id;
50    int       seek_file_id;
51    int       console_id;
52    int       padding;
53
54    struct js_State * (*js_newstate) (
55        void  *alloc,
56        void  *actx,
57        int    flags
58    );
59
60    void (*js_freestate) (
61        js_State *J
62    );
63
64    void (*js_setreport) (
65        js_State  *J,
66        js_Report  report
67    );
68
69    int (*js_dostring) (
70        js_State   *J,
71        const char *source
72    );
73
74    void (*js_newcfunction) (
75        js_State     *J,
76        js_CFunction  fun,
77        const char   *name,
78        int           length
79    );
80
81    void (*js_newuserdata) (
82        js_State    *J,
83        const char  *tag,
84        void        *data,
85        js_Finalize finalize
86    );
87
88    void (*js_newcconstructor) (
89        js_State     *J,
90        js_CFunction  fun,
91        js_CFunction  con,
92        const char   *name,
93        int           length
94    );
95
96    int (*js_dofile) (
97        js_State   *J,
98        const char *filename
99    );
100
101    void (*js_currentfunction) (
102        js_State *J
103    );
104
105    void         (*js_getglobal)     (js_State *J, const char *name                   );
106    void         (*js_setglobal)     (js_State *J, const char *name                   );
107    void         (*js_defglobal)     (js_State *J, const char *name, int atts         );
108
109    void         (*js_getproperty)   (js_State *J, int idx, const char *name          );
110    void         (*js_setproperty)   (js_State *J, int idx, const char *name          );
111    void         (*js_defproperty)   (js_State *J, int idx, const char *name, int atts);
112
113    void         (*js_pushundefined) (js_State *J                                     );
114    void         (*js_pushnull)      (js_State *J                                     );
115    void         (*js_pushnumber)    (js_State *J, double v                           );
116    void         (*js_pushstring)    (js_State *J, const char *v                      );
117
118    const char * (*js_tostring)      (js_State *J, int idx                            );
119    int          (*js_tointeger)     (js_State *J, int idx                            );
120    void       * (*js_touserdata)    (js_State *J, int idx, const char *tag           );
121
122    int          (*js_isnumber)      (js_State *J, int idx                            );
123    int          (*js_isstring)      (js_State *J, int idx                            );
124    int          (*js_isundefined)   (js_State *J, int idx                            );
125
126} mujslib_state_info;
127
128static mujslib_state_info mujslib_state = {
129
130    .initialized         = 0,
131    .instance            = NULL,
132    .find_file_id        = 0,
133    .open_file_id        = 0,
134    .close_file_id       = 0,
135    .read_file_id        = 0,
136    .seek_file_id        = 0,
137    .console_id          = 0,
138    .padding             = 0,
139
140    .js_newstate         = NULL,
141    .js_freestate        = NULL,
142    .js_setreport        = NULL,
143    .js_dostring         = NULL,
144    .js_newcfunction     = NULL,
145    .js_newuserdata      = NULL,
146    .js_newcconstructor  = NULL,
147    .js_dofile           = NULL,
148    .js_currentfunction  = NULL,
149
150    .js_getglobal        = NULL,
151    .js_setglobal        = NULL,
152    .js_defglobal        = NULL,
153
154    .js_getproperty      = NULL,
155    .js_setproperty      = NULL,
156    .js_defproperty      = NULL,
157
158    .js_pushundefined    = NULL,
159    .js_pushnull         = NULL,
160    .js_pushnumber       = NULL,
161    .js_pushstring       = NULL,
162
163    .js_tostring         = NULL,
164    .js_tointeger        = NULL,
165    .js_touserdata       = NULL,
166
167    .js_isnumber         = NULL,
168    .js_isstring         = NULL,
169    .js_isundefined      = NULL,
170
171};
172
173/*tex A few callbacks: */
174
175static int mujslib_register_function(lua_State * L, int old_id)
176{
177    if (! (lua_isfunction(L, -1) || lua_isnil(L, -1))) {
178        return 0;
179    } else {
180        lua_pushvalue(L, -1);
181        if (old_id) {
182            luaL_unref(L, LUA_REGISTRYINDEX, old_id);
183        }
184        return luaL_ref(L, LUA_REGISTRYINDEX);
185    }
186}
187
188static int mujslib_set_find_file(lua_State *L)
189{
190    mujslib_state.find_file_id = mujslib_register_function(L, mujslib_state.find_file_id);
191    return 0;
192}
193
194static int mujslib_set_open_file(lua_State *L)
195{
196    mujslib_state.open_file_id = mujslib_register_function(L, mujslib_state.open_file_id);
197    return 0;
198}
199
200static int mujslib_set_close_file(lua_State *L)
201{
202    mujslib_state.close_file_id = mujslib_register_function(L, mujslib_state.close_file_id);
203    return 0;
204}
205
206static int mujslib_set_read_file(lua_State *L)
207{
208    mujslib_state.read_file_id = mujslib_register_function(L, mujslib_state.read_file_id);
209    return 0;
210}
211
212static int mujslib_set_seek_file(lua_State *L)
213{
214    mujslib_state.seek_file_id = mujslib_register_function(L, mujslib_state.seek_file_id);
215    return 0;
216}
217
218static int mujslib_set_console(lua_State *L)
219{
220    mujslib_state.console_id = mujslib_register_function(L, mujslib_state.console_id);
221    return 0;
222}
223
224static char *mujslib_find_file(const char *fname, const char *fmode)
225{
226    if (mujslib_state.find_file_id) {
227        lua_State *L = lmt_lua_state.lua_instance; /* todo: pass */
228        lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.find_file_id);
229        lua_pushstring(L, fname);
230        lua_pushstring(L, fmode);
231        if (lua_pcall(L, 2, 1, 0)) {
232            tex_formatted_warning("mujs", "find file: %s\n", lua_tostring(L, -1));
233        } else {
234            char *s = NULL;
235            const char *x = lua_tostring(L, -1);
236            if (x) {
237                s = strdup(x);
238            }
239            lua_pop(L, 1);
240            return s;
241        }
242    } else {
243        tex_normal_warning("mujs", "missing callback: find file");
244    }
245    return NULL;
246}
247
248/*tex A few helpers: */
249
250static void mujslib_aux_texcprint(js_State *J, int ispartial)
251{
252    int c = default_catcode_table_preset;
253    int i = 0;
254    if (mujslib_state.js_isnumber(J, 1)) {
255        if (mujslib_state.js_isnumber(J, 2) || mujslib_state.js_isstring(J, 2)) {
256            c = mujslib_state.js_tointeger(J, 1);
257            i = 2;
258        } else {
259            i = 1;
260        }
261    } else if (mujslib_state.js_isstring(J, 1)) {
262        i = 1;
263    }
264    if (i) {
265        const char *s = mujslib_state.js_tostring(J, i);
266        if (s) {
267            lmt_cstring_print(c, s, ispartial);
268        }
269    } else {
270        tex_normal_warning("mujs", "invalid argument(s) for printing to tex");
271    }
272	mujslib_state.js_pushundefined(J); /* needed ? */
273}
274
275static void mujslib_aux_texprint(js_State *J)
276{
277    mujslib_aux_texcprint(J, 0); /* full line */
278}
279
280static void mujslib_aux_texsprint(js_State *J)
281{
282    mujslib_aux_texcprint(J, 1); /* partial line */
283}
284
285static void mujslib_aux_feedback(js_State *J, const char *category, const char *message)
286{
287    if (message) {
288        if (mujslib_state.console_id) {
289            lua_State *L = lmt_lua_state.lua_instance;
290            lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.console_id);
291            lua_pushstring(L, category);
292            lua_pushstring(L, message);
293            if (lua_pcall(L, 2, 0, 0)) {
294                tex_formatted_warning("mujs", "console: %s\n", lua_tostring(L, -1));
295            }
296        } else {
297            tex_print_message(message);
298        }
299    }
300	mujslib_state.js_pushundefined(J);
301}
302
303static void mujslib_aux_console(js_State *J)
304{
305    mujslib_aux_feedback(J, "console", mujslib_state.js_tostring(J, 1));
306}
307
308static void mujslib_aux_report(js_State *J, const char *s)
309{
310    mujslib_aux_feedback(J, "report", s);
311}
312
313/*tex
314    The interfaces: for loading files a finder callback is mandate so that
315    we keep control over what gets read from where.
316*/
317
318static int mujslib_execute(lua_State *L)
319{
320    if (mujslib_state.instance) {
321	    const char *s = lua_tostring(L, 1);
322        if (s) {
323           mujslib_state.js_dostring(mujslib_state.instance, s);
324        }
325    }
326    return 0;
327}
328
329static int mujslib_dofile(lua_State *L)
330{
331    if (mujslib_state.instance) {
332	    const char *name = lua_tostring(L, 1);
333        if (name) {
334            char *found = mujslib_find_file(name, "rb");
335            if (found) {
336               mujslib_state.js_dofile(mujslib_state.instance, found);
337            }
338            free(found);
339        }
340    } else {
341        tex_normal_warning("mujs", "missing callback: find file");
342    }
343    return 0;
344}
345
346static void mujslib_start(void)
347{
348    if (mujslib_state.instance) {
349        mujslib_state.js_freestate(mujslib_state.instance);
350    }
351    mujslib_state.instance = mujslib_state.js_newstate(NULL, NULL, JS_STRICT);
352    if (mujslib_state.instance) {
353        mujslib_state.js_newcfunction(mujslib_state.instance, mujslib_aux_texprint, "texprint", 2);
354        mujslib_state.js_setglobal   (mujslib_state.instance, "texprint");
355        mujslib_state.js_newcfunction(mujslib_state.instance, mujslib_aux_texsprint, "texsprint", 2);
356        mujslib_state.js_setglobal   (mujslib_state.instance, "texsprint");
357        mujslib_state.js_newcfunction(mujslib_state.instance, mujslib_aux_console, "console", 1);
358        mujslib_state.js_setglobal   (mujslib_state.instance, "console");
359        mujslib_state.js_setreport   (mujslib_state.instance, mujslib_aux_report);
360    }
361}
362
363static int mujslib_reset(lua_State *L)
364{
365    if (mujslib_state.initialized) {
366        mujslib_start();
367    }
368    lua_pushboolean(L, mujslib_state.initialized && mujslib_state.instance);
369    return 1;
370}
371
372/*tex
373    File handling: we go via the \LUA\ interface so that we have control
374    over what happens. Another benefit is that we don't need memory
375    management when fetching data from files.
376*/
377
378static void mujslib_file_finalize(js_State *J, void *p)
379{
380    int *id = p;
381    (void) J;
382    if (*id) {
383        lua_State *L = lmt_lua_state.lua_instance;
384        int top = lua_gettop(L);
385        lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.close_file_id);
386        lua_pushinteger(L, *id);
387        if (lua_pcall(L, 1, 0, 0)) {
388            tex_formatted_warning("mujs", "close file: %s\n", lua_tostring(L, -1));
389        }
390        lua_settop(L,top);
391    }
392}
393
394static void mujslib_file_close(js_State *J)
395{
396    if (mujslib_state.instance) {
397        if (mujslib_state.close_file_id) {
398    	    int *id = mujslib_state.js_touserdata(J, 0, "File");
399            if (*id) {
400                mujslib_file_finalize(J, id);
401            }
402        } else {
403            tex_normal_warning("mujs", "missing callback: close file");
404        }
405    }
406	mujslib_state.js_pushundefined(J);
407}
408
409static void mujslib_file_read(js_State *J)
410{
411    if (mujslib_state.instance) {
412        if (mujslib_state.read_file_id) {
413            int *id = mujslib_state.js_touserdata(J, 0, "File");
414            if (*id) {
415                lua_State *L = lmt_lua_state.lua_instance;
416                int top = lua_gettop(L);
417                int n = 1;
418                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.read_file_id);
419                lua_pushinteger(L, *id);
420                if (mujslib_state.js_isstring(J, 1)) {
421                    const char *how = mujslib_state.js_tostring(J, 1);
422                    if (how) {
423                        lua_pushstring(L, how);
424                        n = 2;
425                    }
426                } else if (mujslib_state.js_isnumber(J, 1)) {
427                    int how = mujslib_state.js_tointeger(J, 1);
428                    if (how) {
429                        lua_pushinteger(L, how);
430                        n = 2;
431                    }
432                }
433                if (lua_pcall(L, n, 1, 0)) {
434                    tex_formatted_warning("mujs", "close file: %s\n", lua_tostring(L, -1));
435                } else {
436                    const char *result = strdup(lua_tostring(L, -1));
437                    if (result) {
438            	        mujslib_state.js_pushstring(J, result);
439                        lua_settop(L, top);
440                        return;
441                    }
442                }
443                lua_settop(L, top);
444            }
445        } else {
446            tex_normal_warning("mujs", "missing callback: read file");
447        }
448    }
449	mujslib_state.js_pushundefined(J);
450}
451
452static void mujslib_file_seek(js_State *J)
453{
454    if (mujslib_state.instance) {
455        if (mujslib_state.seek_file_id) {
456            int *id = mujslib_state.js_touserdata(J, 0, "File");
457            if (*id) {
458                lua_State *L = lmt_lua_state.lua_instance;
459                int top = lua_gettop(L);
460                int n = 2;
461                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.seek_file_id);
462                lua_pushinteger(L, *id);
463                /* no checking here */
464                lua_pushstring(L, mujslib_state.js_tostring(J, 1));
465                if (mujslib_state.js_isnumber(J, 2)) {
466                    lua_pushinteger(L, mujslib_state.js_tointeger(J, 2));
467                    n = 3;
468                }
469                if (lua_pcall(L, n, 1, 0)) {
470                    tex_formatted_warning("mujs", "seek file: %s\n", lua_tostring(L, -1));
471                } else if (lua_type(L, -1) == LUA_TNUMBER) {
472         	        mujslib_state.js_pushnumber(J, lua_tonumber(L, -1));
473                    lua_settop(L, top);
474                    return;
475                }
476                lua_settop(L, top);
477            }
478        } else {
479            tex_normal_warning("mujs", "missing callback: seek file");
480        }
481    }
482	mujslib_state.js_pushundefined(J);
483}
484
485static void mujslib_file_new(js_State *J)
486{
487    if (mujslib_state.instance) {
488        if (mujslib_state.open_file_id) {
489    	    const char *name = mujslib_state.js_tostring(J, 1);
490            if (name) {
491                lua_State *L = lmt_lua_state.lua_instance;
492                int top = lua_gettop(L);
493                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.open_file_id);
494                lua_pushstring(L, name);
495                if (lua_pcall(L, 1, 1, 0)) {
496                    tex_formatted_warning("mujs", "open file: %s\n", lua_tostring(L, -1));
497                } else {
498                    int *id = malloc(sizeof(int));
499                    if (id) {
500                        *((int*) id) = (int) lua_tointeger(L, -1);
501                        lua_settop(L, top);
502                        if (id) {
503	                        mujslib_state.js_currentfunction(J);
504	                        mujslib_state.js_getproperty(J, -1, "prototype");
505	                        mujslib_state.js_newuserdata(J, "File", id, mujslib_file_finalize);
506                            return;
507                        }
508                    }
509                }
510                lua_settop(L, top);
511            }
512        } else {
513            tex_normal_warning("mujs", "missing callback: open file");
514        }
515    }
516 	mujslib_state.js_pushnull(J);
517}
518
519/* Setting things up. */
520
521static void mujslib_file_initialize(js_State *J)
522{
523	mujslib_state.js_getglobal(J, "Object");
524	mujslib_state.js_getproperty(J, -1, "prototype");
525	mujslib_state.js_newuserdata(J, "File", stdin, NULL);
526	{
527		mujslib_state.js_newcfunction(J, mujslib_file_read, "File.prototype.read", 0);
528		mujslib_state.js_defproperty(J, -2, "read", JS_DONTENUM);
529		mujslib_state.js_newcfunction(J, mujslib_file_seek, "File.prototype.seek", 0);
530		mujslib_state.js_defproperty(J, -2, "seek", JS_DONTENUM);
531		mujslib_state.js_newcfunction(J, mujslib_file_close, "File.prototype.close", 0);
532		mujslib_state.js_defproperty(J, -2, "close", JS_DONTENUM);
533	}
534	mujslib_state.js_newcconstructor(J, mujslib_file_new, mujslib_file_new, "File", 1);
535	mujslib_state.js_defglobal(J, "File", JS_DONTENUM);
536}
537
538static int mujslib_initialize(lua_State *L)
539{
540    if (! mujslib_state.initialized) {
541        const char *filename = lua_tostring(L, 1);
542        if (filename) {
543
544            lmt_library lib = lmt_library_load(filename);
545
546            mujslib_state.js_newstate        = lmt_library_find(lib, "js_newstate");
547            mujslib_state.js_freestate       = lmt_library_find(lib, "js_freestate");
548            mujslib_state.js_setreport       = lmt_library_find(lib, "js_setreport");
549
550            mujslib_state.js_newcfunction    = lmt_library_find(lib, "js_newcfunction");
551            mujslib_state.js_newuserdata     = lmt_library_find(lib, "js_newuserdata");
552            mujslib_state.js_newcconstructor = lmt_library_find(lib, "js_newcconstructor");
553
554            mujslib_state.js_pushundefined   = lmt_library_find(lib, "js_pushundefined");
555            mujslib_state.js_pushnull        = lmt_library_find(lib, "js_pushnull");
556            mujslib_state.js_pushnumber      = lmt_library_find(lib, "js_pushnumber");
557            mujslib_state.js_pushstring      = lmt_library_find(lib, "js_pushstring");
558
559            mujslib_state.js_dostring        = lmt_library_find(lib, "js_dostring");
560            mujslib_state.js_dofile          = lmt_library_find(lib, "js_dofile");
561
562            mujslib_state.js_tostring        = lmt_library_find(lib, "js_tostring");
563            mujslib_state.js_tointeger       = lmt_library_find(lib, "js_tointeger");
564            mujslib_state.js_touserdata      = lmt_library_find(lib, "js_touserdata");
565
566            mujslib_state.js_getglobal       = lmt_library_find(lib, "js_getglobal");
567            mujslib_state.js_setglobal       = lmt_library_find(lib, "js_setglobal");
568            mujslib_state.js_defglobal       = lmt_library_find(lib, "js_defglobal");
569
570            mujslib_state.js_getproperty     = lmt_library_find(lib, "js_getproperty");
571            mujslib_state.js_setproperty     = lmt_library_find(lib, "js_setproperty");
572            mujslib_state.js_defproperty     = lmt_library_find(lib, "js_defproperty");
573
574            mujslib_state.js_isstring        = lmt_library_find(lib, "js_isstring");
575            mujslib_state.js_isnumber        = lmt_library_find(lib, "js_isnumber");
576            mujslib_state.js_isundefined     = lmt_library_find(lib, "js_isundefined");
577
578            mujslib_state.js_currentfunction = lmt_library_find(lib, "js_currentfunction");
579
580            mujslib_state.initialized = lmt_library_okay(lib);
581
582            mujslib_start();
583
584            mujslib_file_initialize(mujslib_state.instance);
585        }
586    }
587    lua_pushboolean(L, mujslib_state.initialized && mujslib_state.instance);
588    return 1;
589}
590
591static struct luaL_Reg mujslib_function_list[] = {
592    { "initialize",   mujslib_initialize     }, /* mandate */
593    { "reset",        mujslib_reset          },
594    { "execute",      mujslib_execute        },
595    { "dofile",       mujslib_dofile         },
596    { "setfindfile",  mujslib_set_find_file  }, /* mandate */
597    { "setopenfile",  mujslib_set_open_file  }, /* mandate */
598    { "setclosefile", mujslib_set_close_file }, /* mandate */
599    { "setreadfile",  mujslib_set_read_file  }, /* mandate */
600    { "setseekfile",  mujslib_set_seek_file  },
601    { "setconsole",   mujslib_set_console    },
602    { NULL,           NULL                   },
603};
604
605int luaopen_mujs(lua_State *L)
606{
607    lmt_library_register(L, "mujs", mujslib_function_list);
608    return 0;
609}
610