lmtmujs.c /size: 19 Kb    last modification: 2025-02-21 11:03
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 we keep control over 
315    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 over what happens. Another
374    benefit is that we don't need memory management when fetching data from files.
375*/
376
377static void mujslib_file_finalize(js_State *J, void *p)
378{
379    int *id = p;
380    (void) J;
381    if (*id) {
382        lua_State *L = lmt_lua_state.lua_instance;
383        int top = lua_gettop(L);
384        lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.close_file_id);
385        lua_pushinteger(L, *id);
386        if (lua_pcall(L, 1, 0, 0)) {
387            tex_formatted_warning("mujs", "close file: %s\n", lua_tostring(L, -1));
388        }
389        lua_settop(L,top);
390    }
391}
392
393static void mujslib_file_close(js_State *J)
394{
395    if (mujslib_state.instance) {
396        if (mujslib_state.close_file_id) {
397    	    int *id = mujslib_state.js_touserdata(J, 0, "File");
398            if (*id) {
399                mujslib_file_finalize(J, id);
400            }
401        } else {
402            tex_normal_warning("mujs", "missing callback: close file");
403        }
404    }
405	mujslib_state.js_pushundefined(J);
406}
407
408static void mujslib_file_read(js_State *J)
409{
410    if (mujslib_state.instance) {
411        if (mujslib_state.read_file_id) {
412            int *id = mujslib_state.js_touserdata(J, 0, "File");
413            if (*id) {
414                lua_State *L = lmt_lua_state.lua_instance;
415                int top = lua_gettop(L);
416                int n = 1;
417                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.read_file_id);
418                lua_pushinteger(L, *id);
419                if (mujslib_state.js_isstring(J, 1)) {
420                    const char *how = mujslib_state.js_tostring(J, 1);
421                    if (how) {
422                        lua_pushstring(L, how);
423                        n = 2;
424                    }
425                } else if (mujslib_state.js_isnumber(J, 1)) {
426                    int how = mujslib_state.js_tointeger(J, 1);
427                    if (how) {
428                        lua_pushinteger(L, how);
429                        n = 2;
430                    }
431                }
432                if (lua_pcall(L, n, 1, 0)) {
433                    tex_formatted_warning("mujs", "close file: %s\n", lua_tostring(L, -1));
434                } else {
435                    const char *result = strdup(lua_tostring(L, -1));
436                    if (result) {
437            	        mujslib_state.js_pushstring(J, result);
438                        lua_settop(L, top);
439                        return;
440                    }
441                }
442                lua_settop(L, top);
443            }
444        } else {
445            tex_normal_warning("mujs", "missing callback: read file");
446        }
447    }
448	mujslib_state.js_pushundefined(J);
449}
450
451static void mujslib_file_seek(js_State *J)
452{
453    if (mujslib_state.instance) {
454        if (mujslib_state.seek_file_id) {
455            int *id = mujslib_state.js_touserdata(J, 0, "File");
456            if (*id) {
457                lua_State *L = lmt_lua_state.lua_instance;
458                int top = lua_gettop(L);
459                int n = 2;
460                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.seek_file_id);
461                lua_pushinteger(L, *id);
462                /* no checking here */
463                lua_pushstring(L, mujslib_state.js_tostring(J, 1));
464                if (mujslib_state.js_isnumber(J, 2)) {
465                    lua_pushinteger(L, mujslib_state.js_tointeger(J, 2));
466                    n = 3;
467                }
468                if (lua_pcall(L, n, 1, 0)) {
469                    tex_formatted_warning("mujs", "seek file: %s\n", lua_tostring(L, -1));
470                } else if (lua_type(L, -1) == LUA_TNUMBER) {
471         	        mujslib_state.js_pushnumber(J, lua_tonumber(L, -1));
472                    lua_settop(L, top);
473                    return;
474                }
475                lua_settop(L, top);
476            }
477        } else {
478            tex_normal_warning("mujs", "missing callback: seek file");
479        }
480    }
481	mujslib_state.js_pushundefined(J);
482}
483
484static void mujslib_file_new(js_State *J)
485{
486    if (mujslib_state.instance) {
487        if (mujslib_state.open_file_id) {
488    	    const char *name = mujslib_state.js_tostring(J, 1);
489            if (name) {
490                lua_State *L = lmt_lua_state.lua_instance;
491                int top = lua_gettop(L);
492                lua_rawgeti(L, LUA_REGISTRYINDEX, mujslib_state.open_file_id);
493                lua_pushstring(L, name);
494                if (lua_pcall(L, 1, 1, 0)) {
495                    tex_formatted_warning("mujs", "open file: %s\n", lua_tostring(L, -1));
496                } else {
497                    int *id = malloc(sizeof(int));
498                    if (id) {
499                        *((int*) id) = (int) lua_tointeger(L, -1);
500                        lua_settop(L, top);
501                        if (id) {
502	                        mujslib_state.js_currentfunction(J);
503	                        mujslib_state.js_getproperty(J, -1, "prototype");
504	                        mujslib_state.js_newuserdata(J, "File", id, mujslib_file_finalize);
505                            return;
506                        }
507                    }
508                }
509                lua_settop(L, top);
510            }
511        } else {
512            tex_normal_warning("mujs", "missing callback: open file");
513        }
514    }
515 	mujslib_state.js_pushnull(J);
516}
517
518/* Setting things up. */
519
520static void mujslib_file_initialize(js_State *J)
521{
522	mujslib_state.js_getglobal(J, "Object");
523	mujslib_state.js_getproperty(J, -1, "prototype");
524	mujslib_state.js_newuserdata(J, "File", stdin, NULL);
525	{
526		mujslib_state.js_newcfunction(J, mujslib_file_read, "File.prototype.read", 0);
527		mujslib_state.js_defproperty(J, -2, "read", JS_DONTENUM);
528		mujslib_state.js_newcfunction(J, mujslib_file_seek, "File.prototype.seek", 0);
529		mujslib_state.js_defproperty(J, -2, "seek", JS_DONTENUM);
530		mujslib_state.js_newcfunction(J, mujslib_file_close, "File.prototype.close", 0);
531		mujslib_state.js_defproperty(J, -2, "close", JS_DONTENUM);
532	}
533	mujslib_state.js_newcconstructor(J, mujslib_file_new, mujslib_file_new, "File", 1);
534	mujslib_state.js_defglobal(J, "File", JS_DONTENUM);
535}
536
537static int mujslib_initialize(lua_State *L)
538{
539    if (! mujslib_state.initialized) {
540        const char *filename = lua_tostring(L, 1);
541        if (filename) {
542
543            lmt_library lib = lmt_library_load(filename);
544
545            mujslib_state.js_newstate        = lmt_library_find(lib, "js_newstate");
546            mujslib_state.js_freestate       = lmt_library_find(lib, "js_freestate");
547            mujslib_state.js_setreport       = lmt_library_find(lib, "js_setreport");
548
549            mujslib_state.js_newcfunction    = lmt_library_find(lib, "js_newcfunction");
550            mujslib_state.js_newuserdata     = lmt_library_find(lib, "js_newuserdata");
551            mujslib_state.js_newcconstructor = lmt_library_find(lib, "js_newcconstructor");
552
553            mujslib_state.js_pushundefined   = lmt_library_find(lib, "js_pushundefined");
554            mujslib_state.js_pushnull        = lmt_library_find(lib, "js_pushnull");
555            mujslib_state.js_pushnumber      = lmt_library_find(lib, "js_pushnumber");
556            mujslib_state.js_pushstring      = lmt_library_find(lib, "js_pushstring");
557
558            mujslib_state.js_dostring        = lmt_library_find(lib, "js_dostring");
559            mujslib_state.js_dofile          = lmt_library_find(lib, "js_dofile");
560
561            mujslib_state.js_tostring        = lmt_library_find(lib, "js_tostring");
562            mujslib_state.js_tointeger       = lmt_library_find(lib, "js_tointeger");
563            mujslib_state.js_touserdata      = lmt_library_find(lib, "js_touserdata");
564
565            mujslib_state.js_getglobal       = lmt_library_find(lib, "js_getglobal");
566            mujslib_state.js_setglobal       = lmt_library_find(lib, "js_setglobal");
567            mujslib_state.js_defglobal       = lmt_library_find(lib, "js_defglobal");
568
569            mujslib_state.js_getproperty     = lmt_library_find(lib, "js_getproperty");
570            mujslib_state.js_setproperty     = lmt_library_find(lib, "js_setproperty");
571            mujslib_state.js_defproperty     = lmt_library_find(lib, "js_defproperty");
572
573            mujslib_state.js_isstring        = lmt_library_find(lib, "js_isstring");
574            mujslib_state.js_isnumber        = lmt_library_find(lib, "js_isnumber");
575            mujslib_state.js_isundefined     = lmt_library_find(lib, "js_isundefined");
576
577            mujslib_state.js_currentfunction = lmt_library_find(lib, "js_currentfunction");
578
579            mujslib_state.initialized = lmt_library_okay(lib);
580
581            mujslib_start();
582
583            mujslib_file_initialize(mujslib_state.instance);
584        }
585    }
586    lua_pushboolean(L, mujslib_state.initialized && mujslib_state.instance);
587    return 1;
588}
589
590static struct luaL_Reg mujslib_function_list[] = {
591    { "initialize",   mujslib_initialize     }, 
592    { "reset",        mujslib_reset          },
593    { "execute",      mujslib_execute        },
594    { "dofile",       mujslib_dofile         },
595    { "setfindfile",  mujslib_set_find_file  },
596    { "setopenfile",  mujslib_set_open_file  },
597    { "setclosefile", mujslib_set_close_file },
598    { "setreadfile",  mujslib_set_read_file  },
599    { "setseekfile",  mujslib_set_seek_file  },
600    { "setconsole",   mujslib_set_console    },
601    { NULL,           NULL                   },
602};
603
604int luaopen_mujs(lua_State *L)
605{
606    lmt_library_register(L, "mujs", mujslib_function_list);
607    return 0;
608}
609