summaryrefslogtreecommitdiff
path: root/source/luametatex/source/lua/lmtenginelib.c
blob: f8df06657b8edfdcc4b36372269d09efd4de4685 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"

engine_state_info lmt_engine_state = {
    .lua_init         = 0,
    .lua_only         = 0,
    .luatex_banner    = NULL,
    .engine_name      = NULL,
    .startup_filename = NULL,
    .startup_jobname  = NULL,
    .dump_name        = NULL,
    .utc_time         = 0,
    .permit_loadlib   = 0,
};

/*tex
    We assume that the strings are proper \UTF\ and in \MSWINDOWS\ we handle wide characters to get
    that right.
*/

typedef struct environment_state_info {
    char **argv;
    int    argc;
    int    npos;
    char  *flag;
    char  *value;
    char  *name;
    char  *ownpath;
    char  *ownbase;
    char  *ownname;
    char  *owncore;
    char  *input_name;
    int    luatex_lua_offset;
} environment_state_info;

static environment_state_info lmt_environment_state = {
    .argv              = NULL,
    .argc              = 0,
    .npos              = 0,
    .flag              = NULL,
    .value             = NULL,
    .name              = NULL,
    .ownpath           = NULL,
    .ownbase           = NULL,
    .ownname           = NULL,
    .owncore           = NULL,
    .input_name        = NULL,
    .luatex_lua_offset = 0,
};

/*tex todo: make helpers in loslibext which has similar code */

static void enginelib_splitnames(void)
{
    char *p = lmt_memory_strdup(lmt_environment_state.ownpath); /*tex We need to make copies! */
    /*
    printf("ownpath = %s\n",environment_state.ownpath);
    printf("ownbase = %s\n",environment_state.ownbase);
    printf("ownname = %s\n",environment_state.ownname);
    printf("owncore = %s\n",environment_state.owncore);
    */
    /*
        We loose some here but not enough to worry about. Maybe eventually we will use our own
        |basename| and |dirname| anyway.
    */
    lmt_environment_state.ownbase = aux_basename(lmt_memory_strdup(p));
    lmt_environment_state.ownname = aux_basename(lmt_memory_strdup(p));
    lmt_environment_state.ownpath = aux_dirname(lmt_memory_strdup(p)); /* We could use p and not free later, but this is cleaner. */
    /* */
    for (size_t i = 0; i < strlen(lmt_environment_state.ownname); i++) {
        if (lmt_environment_state.ownname[i] == '.') {
            lmt_environment_state.ownname[i] = '\0';
            break ;
        }
    }
    lmt_environment_state.owncore = lmt_memory_strdup(lmt_environment_state.ownname);
    /*
    printf("ownpath = %s\n",environment_state.ownpath);
    printf("ownbase = %s\n",environment_state.ownbase);
    printf("ownname = %s\n",environment_state.ownname);
    printf("owncore = %s\n",environment_state.owncore);
    */
    lmt_memory_free(p);
}

/*tex A bunch of internalized strings: see |linterface.h |.*/

/* declare_shared_lua_keys; */
/* declare_metapost_lua_keys; */

char *tex_engine_input_filename(void)
{
    /*tex When npos equals zero we have no filename i.e. nothing that doesn't start with |--|. */
    return lmt_environment_state.npos > 0 && lmt_environment_state.npos < lmt_environment_state.argc ? lmt_environment_state.argv[lmt_environment_state.npos] : NULL;
}

/*tex

    Filenames can have spaces in which case (double) quotes are used to indicate the bounds of the
    string. At the \TEX\ level curly braces are also an option but these are dealt with in the
    scanner.

    Comment: maybe we should also support single quotes, so that we're consistent with \LUA\ quoting.

*/

static char *enginelib_normalize_quotes(const char* name, const char* mesg)
{
    char *ret = lmt_memory_malloc(strlen(name) + 3);
    if (ret) {
        int must_quote = strchr(name, ' ') != NULL;
        /* Leave room for quotes and NUL. */
        int quoted = 0;
        char *p = ret;
        if (must_quote) {
            *p++ = '"';
        }
        for (const char *q = name; *q; q++) {
            if (*q == '"') {
                quoted = ! quoted;
            } else {
                *p++ = *q;
            }
        }
        if (must_quote) {
            *p++ = '"';
        }
        *p = '\0';
        if (quoted) {
            tex_emergency_message("system", "unbalanced quotes in %s %s\n", mesg, name);
            tex_emergency_exit();
        }
    }
    return ret;
}

/*

    We support a minimum set of options but more can be supported by supplying an (startup)
    initialization script and/or by setting values in the |texconfig| table. At some point we might
    provide some default initiazation script but that's for later. In fact, a bug in \LUATEX\ <
    1.10 made some of the command line options get lost anyway due to setting their values before
    checking the config table (probably introduced at some time). As no one noticed that anyway,
    removing these from the commandline is okay.

    Part of the commandline handler is providing (minimal) help information and reporting credits
    (more credits can be found in the source file). Here comes the basic help.

    At some point I will likely add a |--permitloadlib| flag and block loading of libraries when
    that flag is not given so that we satisfy operating systems and/or distributions that have some
    restrictions on loading libraries. It also means that the optional modules will be (un)locked,
    but we can control that in the runners so it's no big deal because we will never depend on
    external code for the \CONTEXT\ core features.

*/

static void enginelib_show_help(void)
{
    puts(
        "Usage: " luametatex_name_lowercase " --lua=FILE [OPTION]... [TEXNAME[.tex]] [COMMANDS]\n"
        "   or: " luametatex_name_lowercase " --lua=FILE [OPTION]... \\FIRST-LINE\n"
        "   or: " luametatex_name_lowercase " --lua=FILE [OPTION]... &FMT ARGS\n"
        "\n"
        "Run " luametatex_name_camelcase " on TEXNAME, usually creating TEXNAME.pdf. Any remaining COMMANDS"
        "are processed as luatex input, after TEXNAME is read.\n"
        "\n"
        "Alternatively, if the first non-option argument begins with a backslash,\n"
        luametatex_name_camelcase " interprets all non-option arguments as an input line.\n"
        "\n"
        "Alternatively, if the first non-option argument begins with a &, the next word\n"
        "is taken as the FMT to read, overriding all else. Any remaining arguments are\n"
        "processed as above.\n"
        "\n"
        "If no arguments or options are specified, prompt for input.\n"
        "\n"
        "The following regular options are understood:\n"
        "\n"
        "  --credits           display credits and exit\n"
        "  --fmt=FORMAT        load the format file FORMAT\n"
        "  --help              display help and exit\n"
        "  --ini               be ini" luametatex_name_lowercase ", for dumping formats\n"
        "  --jobname=STRING    set the job name to STRING\n"
        "  --lua=FILE          load and execute a lua initialization script\n"
        "  --version           display version and exit\n"
        "\n"
        "Alternate behaviour models can be obtained by special switches\n"
        "\n"
        "  --luaonly           run a lua file, then exit\n"
        "\n"
        "Loading libraries from Lua is blocked unless one explicitly permits it:\n"
        "\n"
        "  --permitloadlib     permit loading of external libraries (coming)\n"
        "\n"
        "See the reference manual for more information about the startup process.\n"
        "\n"
        "Email bug reports to " luametatex_bug_address ".\n"
    );
    exit(EXIT_SUCCESS);
}

/*tex

    This is the minimal version info display.  The credits option provides a bit more information.
*/

static void enginelib_show_version_info(void)
{
    tex_print_version_banner();
    puts(
        "\n"
        "\n"
        "Execute '" luametatex_name_lowercase " --credits' for credits and version details.\n"
        "\n"
        "There is NO warranty. Redistribution of this software is covered by the terms\n"
        "of the GNU General Public License, version 2 or (at your option) any later\n"
        "version. For more information about these matters, see the file named COPYING\n"
        "and the LuaMetaTeX source.\n"
        "\n"
        "Functionality : level " LMT_TOSTRING(luametatex_development_id) "\n"
        "Support       : " luametatex_support_address "\n"
        "Copyright     : The Lua(Meta)TeX Team(s) (2005-2022+)\n"
        "\n"
        "The LuaMetaTeX project is related to ConTeXt development. This macro package\n"
        "tightly integrates TeX and MetaPost in close cooperation with Lua. Updates will\n"
        "happen in sync with ConTeXt and when needed. Don't be fooled by unchanged dates:\n"
        "long term stability is the objective."
    );
    exit(EXIT_SUCCESS);
}

/*tex

    We only mention the most relevelant credits here. The first part is there to indicate a bit of
    history. A very large part of the code, of course, comes from Don Knuths original \TEX, and the
    same is true for most documentation!

    Most of the \ETEX\ extensions are present too. Much of the expansion and protrusion code
    originates in \PDFTEX\ but we don't have any of its backend code. From \OMEGA\ (\ALEPH) we took
    bits and pieces too, for instance the basics of handling directions but at this point we only
    have two directions left (that don't need much code). One features that sticks are the left-
    and right boxes.

    The \METAPOST\ library is an important component and also add quite some code. Here we use a
    stripped down version of the version 2 library with some extra additions.

    We take \LUA\ as it is. In the meantime we went from \LUA\ 5.2 to 5.3 to 5.4 and will follow up
    on what makes sense. For as far as possible no changes are made but there are some configuration
    options in use. We use an \UTF8\ aware setup. Of course \LPEG\ is part of the deal.

    The lean and mean \PDF\ library is made for \LUATEX\ and we use that one here too. In
    \LUAMETATEX\ we use some of its helpers to implement for instance md5 and sha support. In
    \LUAMETATEX\ there are some more than mentioned here but they are {\em not} part of the default
    binary. Some libraries mentioned below can become loaded on demand.

*/

static void enginelib_show_credits(void)
{
    tex_print_version_banner();
    puts(
        "\n"
        "\n"
        "Here we mention those involved in the bits and pieces that define " luametatex_name_camelcase ". More details of\n"
        "what comes from where can be found in the manual and other documents (that come with ConTeXt).\n"
        "\n"
        "  luametatex : Hans Hagen, Alan Braslau, Mojca Miklavec, Wolfgang Schuster, Mikael Sundqvist\n"
        "\n"
        "It is a follow up on:\n"
        "\n"
        "  luatex     : Hans Hagen, Hartmut Henkel, Taco Hoekwater, Luigi Scarso\n"
        "\n"
        "This program itself builds upon the code from:\n"
        "\n"
        "  tex        : Donald Knuth\n"
        "\n"
        "We also took a few features from:\n"
        "\n"
        "  etex       : Peter Breitenlohner, Phil Taylor and friends\n"
        "\n"
        "The font expansion and protrusion code is derived from:\n"
        "\n"
        "  pdftex     : Han The Thanh and friends\n"
        "\n"
        "Part of the bidirectional text flow model is inspired by:\n"
        "\n"
        "  omega      : John Plaice and Yannis Haralambous\n"
        "  aleph      : Giuseppe Bilotta\n"
        "\n"
        "Graphic support is originates in:\n"
        "\n"
        "  metapost   : John Hobby, Taco Hoekwater, Luigi Scarso, Hans Hagen and friends\n"
        "\n"
        "All this is opened up with:\n"
        "\n"
        "  lua        : Roberto Ierusalimschy, Waldemar Celes and Luiz Henrique de Figueiredo\n"
        "  lpeg       : Roberto Ierusalimschy\n"
        "\n"
        "A few libraries are embedded, of which we mention:\n"
        "\n"
# ifdef MI_MALLOC_VERSION
        "  mimalloc   : Daan Leijen (https://github.com/microsoft/mimalloc)\n" /* not enabled for arm yet */
# endif
        "  miniz      : Rich Geldreich etc\n"
        "  pplib      : Paweł Jackowski (with partial code from libraries)\n"
        "  md5        : Peter Deutsch (with partial code from pplib libraries)\n"
        "  sha2       : Aaron D. Gifford (with partial code from pplib libraries)\n"
        "  socket     : Diego Nehab (partial and adapted)\n"
        "  libcerf    : Joachim Wuttke (adapted for MSVC)\n"
        "  decnumber  : Mike Cowlishaw from IBM (one of the number models in MP)\n"
        "  avl        : Richard (adapted a bit to fit in)\n"
        "  hjn        : Raph Levien (derived from TeX's hyphenator, but adapted again)\n"
        "\n"
        "The code base contains more names and references. Some libraries are partially adapted or\n"
        "have been replaced. The MetaPost library has additional functionality, some of which is\n"
        "experimental. The LuaMetaTeX project relates to ConTeXt. This LuaMetaTeX 2+ variant is a\n"
        "lean and mean variant of LuaTeX 1+ but the core typesetting functionality is the same and\n"
        "and has been extended in many aspects.\n"
        "\n"
        "There is a lightweight subsystem for optional libraries but here we also delegate as much\n"
        "as possibe to Lua. A few interfaces are provided bny default, others can be added using a\n"
        "simple foreign interface subsystem. Although this is provided an dconsidered part of the\n"
        "LuaMetaTeX engine it is not something ConTeXt depends (and will) depend on.\n"
        "\n"
        "version   : " luametatex_version_string " | " LMT_TOSTRING(luametatex_development_id) "\n"
        "format id : " LMT_TOSTRING(luametatex_format_fingerprint) "\n"
# ifdef __DATE__
        "date      : " __TIME__ " | " __DATE__ "\n"
# endif
# ifdef LMT_COMPILER_USED
        "compiler  : " LMT_COMPILER_USED "\n"
# endif
    );
    exit(EXIT_SUCCESS);
}

/*tex

    Some properties of the command line (and startup call) are reflected in variables that start
    with \type {self}.

*/

static void enginelib_prepare_cmdline(int zero_offset)
{
    lua_State *L = lmt_lua_state.lua_instance;
    /*tex We keep this reorganized |arg| table, which can start at -3! */
    lua_createtable(L, lmt_environment_state.argc, 0);
    for (lua_Integer i = 0; i < lmt_environment_state.argc; i++) {
        lua_set_string_by_index(L, (int) (i - zero_offset), lmt_environment_state.argv[i]);
    }
    lua_setglobal(L, "arg");
    /* */
    lua_getglobal(L, "os");
    lua_set_string_by_key(L, "selfbin",  lmt_environment_state.argv[0]);
    lua_set_string_by_key(L, "selfpath", lmt_environment_state.ownpath);
    lua_set_string_by_key(L, "selfdir",  lmt_environment_state.ownpath); /* for old times sake */
    lua_set_string_by_key(L, "selfbase", lmt_environment_state.ownbase);
    lua_set_string_by_key(L, "selfname", lmt_environment_state.ownname);
    lua_set_string_by_key(L, "selfcore", lmt_environment_state.owncore);
    lua_createtable(L, lmt_environment_state.argc, 0);
    for (lua_Integer i = 0; i < lmt_environment_state.argc; i++) {
        lua_set_string_by_index(L, (int) i, lmt_environment_state.argv[i]);
    }
    lua_setfield(L, -2, "selfarg");
}

/*tex

    Argument checking is somewhat tricky because it can interfere with the used console (shell). It
    makes sense to combine this with the \LUA\ command line parser code but even that is no real way
    out. For instance, on \MSWINDOWS\ we need to deal with wide characters.

    The code below is as independent from libraries as possible and differs from the the code used
    in other \TEX\ engine.  We issue no warnings and silently recover, because in the end the macro
    package (and its \LUA\ code) can deal with that.

*/

static void enginelib_check_option(char **options, int i)
{
    char *option = options[i];
    char *n = option;
    lmt_environment_state.flag = NULL;
    lmt_environment_state.value = NULL;
    if (*n == '-') {
        n++;
    } else {
        goto NOTHING;
    }
    if (*n == '-') {
        n++;
    } else {
        goto NOTHING;
    }
    if (*n == '\0') {
        return;
    }
    {
        char *v = strchr(n, '=');
        size_t l = (int) (v ? (v - n) : strlen(n));
        lmt_environment_state.flag = lmt_memory_malloc(l + 1);
        if (lmt_environment_state.flag) {
            memcpy(lmt_environment_state.flag, n, l);
            lmt_environment_state.flag[l] = '\0';
            if (v) {
                v++;
                l = (int) strlen(v);
                lmt_environment_state.value = lmt_memory_malloc(l + 1);
                if (lmt_environment_state.value) {
                    memcpy(lmt_environment_state.value, v, l);
                    lmt_environment_state.value[l] = '\0';
                }
            }
        }
        return;
    }
  NOTHING:
    if (lmt_environment_state.name == NULL && i > 0) {
        lmt_environment_state.name = option;
        lmt_environment_state.npos = i;
    }
}

/*tex

    The |lmt| suffix is actually a \CONTEXT\ thing but it permits us to have \LUA\ files for
    \LUAMETATEX\ and \LUATEX\ alongside. The ones for this engine can use a more recent variant of
    \LUA\ and thereby be not compatible. Especially syntax extension complicates this like using
    |<const>| in \LUA 5.4+ or before that bitwise operators in \LUA\ 5.3 (not/never in \LUAJIT).

*/

const char *suffixes[] = { "lmt", "lua", NULL };

static void enginelib_parse_options(void)
{
    /*tex We add 5 chars (separator and suffix) so we reserve 6. */
    char *firstfile = (char*) lmt_memory_malloc(strlen(lmt_environment_state.ownpath) + strlen(lmt_environment_state.owncore) + 6);
    for (int i = 0; suffixes[i]; i++) {
        sprintf(firstfile, "%s/%s.%s", lmt_environment_state.ownpath, lmt_environment_state.owncore, suffixes[i]);
        /* stat */
        if (aux_is_readable(firstfile)) {
            lmt_memory_free(lmt_engine_state.startup_filename);
            lmt_engine_state.startup_filename = firstfile;
            lmt_environment_state.luatex_lua_offset = 0;
            lmt_engine_state.lua_only = 1;
            lmt_engine_state.lua_init = 1;
            return;
        }
    }
    lmt_memory_free(firstfile);
    firstfile = NULL;
    /* */
    for (int i = 1;;) {
        if (i == lmt_environment_state.argc || *lmt_environment_state.argv[i] == '\0') {
            break;
        }
        enginelib_check_option(lmt_environment_state.argv, i);
        i++;
        if (! lmt_environment_state.flag) {
            continue;
        }
        if (strcmp(lmt_environment_state.flag, "luaonly") == 0) {
            lmt_engine_state.lua_only = 1;
            lmt_environment_state.luatex_lua_offset = i;
            lmt_engine_state.lua_init = 1;
        } else if (strcmp(lmt_environment_state.flag, "lua") == 0) {
            if (lmt_environment_state.value) {
                lmt_memory_free(lmt_engine_state.startup_filename);
                lmt_engine_state.startup_filename = lmt_memory_strdup(lmt_environment_state.value);
                lmt_environment_state.luatex_lua_offset = i - 1;
                lmt_engine_state.lua_init = 1;
            }
        } else if (strcmp(lmt_environment_state.flag, "jobname") == 0) {
            if (lmt_environment_state.value) {
                lmt_memory_free(lmt_engine_state.startup_jobname);
                lmt_engine_state.startup_jobname = lmt_memory_strdup(lmt_environment_state.value);
            }
        } else if (strcmp(lmt_environment_state.flag, "fmt") == 0) {
            if (lmt_environment_state.value) {
                lmt_memory_free(lmt_engine_state.dump_name);
                lmt_engine_state.dump_name = lmt_memory_strdup(lmt_environment_state.value);
            }
        } else if (! lmt_engine_state.permit_loadlib && strcmp(lmt_environment_state.flag, "permitloadlib") == 0) {
            lmt_engine_state.permit_loadlib = 1;
        } else if (strcmp(lmt_environment_state.flag, "ini") == 0) {
            lmt_main_state.run_state = initializing_state;
        } else if (strcmp(lmt_environment_state.flag, "help") == 0) {
            enginelib_show_help();
        } else if (strcmp(lmt_environment_state.flag, "version") == 0) {
            enginelib_show_version_info();
        } else if (strcmp(lmt_environment_state.flag, "credits") == 0) {
            enginelib_show_credits();
        }
        lmt_memory_free(lmt_environment_state.flag);
        lmt_environment_state.flag = NULL;
        if (lmt_environment_state.value) {
            lmt_memory_free(lmt_environment_state.value);
            lmt_environment_state.value = NULL;
        }
    }
    /*tex This is an attempt to find |input_name| or |dump_name|. */
    if (lmt_environment_state.argv[lmt_environment_state.npos]) { /* aka name */
        if (lmt_engine_state.lua_only) {
            if (! lmt_engine_state.startup_filename) {
                lmt_engine_state.startup_filename = lmt_memory_strdup(lmt_environment_state.argv[lmt_environment_state.npos]);
                lmt_environment_state.luatex_lua_offset = lmt_environment_state.npos;
            }
        } else if (lmt_environment_state.argv[lmt_environment_state.npos][0] == '&') {
            /*tex This is historic but and might go away. */
            if (! lmt_engine_state.dump_name) {
                lmt_engine_state.dump_name = lmt_memory_strdup(lmt_environment_state.argv[lmt_environment_state.npos] + 1);
            }
        } else if (lmt_environment_state.argv[lmt_environment_state.npos][0] == '*') {
            /*tex This is historic but and might go away. */
            if (! lmt_environment_state.input_name) {
                lmt_environment_state.input_name = lmt_memory_strdup(lmt_environment_state.argv[lmt_environment_state.npos] + 1);
            }
        } else if (lmt_environment_state.argv[lmt_environment_state.npos][0] == '\\') {
            /*tex We have a command but this and might go away. */
        } else {
            /*tex We check for some suffixes first. */
            firstfile = lmt_memory_strdup(lmt_environment_state.argv[lmt_environment_state.npos]);
            for (int i = 0; suffixes[i]; i++) {
                if (strstr(firstfile, suffixes[i]) == firstfile + strlen(firstfile) - 4){
                    if (lmt_engine_state.startup_filename) {
                        lmt_memory_free(firstfile);
                    } else {
                        lmt_engine_state.startup_filename = firstfile;
                        lmt_environment_state.luatex_lua_offset = lmt_environment_state.npos;
                        lmt_engine_state.lua_only = 1;
                        lmt_engine_state.lua_init = 1;
                    }
                    goto DONE;
                }
            }
            if (lmt_environment_state.input_name) {
                lmt_memory_free(firstfile);
            } else {
                lmt_environment_state.input_name = firstfile;
            }
        }
    }
  DONE:
    /*tex Finalize the input filename. */
    if (lmt_environment_state.input_name) {
        /* probably not ok */
        lmt_environment_state.argv[lmt_environment_state.npos] = enginelib_normalize_quotes(lmt_environment_state.input_name, "argument");
    }
}

/*tex

    Being a general purpose typesetting system, a \TEX\ system normally has its own way of dealing
    with language, script, country etc.\ specific properties. It is for that reason that we disable
    locales.

*/

static void enginelib_set_locale(void)
{
    setlocale(LC_ALL, "C");
}

static void enginelib_update_options(void)
{
    int starttime = -1;
    int utc = -1;
    int permitloadlib =  -1;
    if (! lmt_environment_state.input_name) {
        tex_engine_get_config_string("jobname", &lmt_environment_state.input_name);
    }
    if (! lmt_engine_state.dump_name) {
        tex_engine_get_config_string("formatname", &lmt_engine_state.dump_name);
    }
    tex_engine_get_config_number("starttime", &starttime);
    if (starttime >= 0) {
        aux_set_start_time(starttime);
    }
    tex_engine_get_config_boolean("useutctime", &utc);
    if (utc >= 0 && utc <= 1) {
        lmt_engine_state.utc_time = utc;
    }
    tex_engine_get_config_boolean("permitloadlib", &permitloadlib);
    if (permitloadlib >= 0) {
        lmt_engine_state.permit_loadlib = permitloadlib;
    }
}

/*tex

    We have now arrived at the main initializer. What happens after this is determined by what
    callbacks are set. The engine can behave as just a \LUA\ interpreter, startup the \TEX\
    machinery in so called virgin mode, or load a format and carry on from that.

*/

void tex_engine_initialize(int ac, char **av)
{
    /*tex Save to pass along to topenin. */
    lmt_print_state.selector = terminal_selector_code;
    lmt_environment_state.argc = aux_utf8_setargv(&lmt_environment_state.argv, av, ac);
    /* initializations */
    lmt_engine_state.lua_only = 0;
    lmt_engine_state.lua_init = 0;
    lmt_engine_state.startup_filename = NULL;
    lmt_engine_state.startup_jobname = NULL;
    lmt_engine_state.engine_name = luametatex_name_lowercase;
    lmt_engine_state.dump_name = NULL;
    lmt_engine_state.luatex_banner = lmt_memory_strdup(lmt_version_state.banner);
    /* preparations */
    lmt_environment_state.ownpath = aux_utf8_getownpath(lmt_environment_state.argv[0]);
    enginelib_splitnames();
    aux_set_run_time();
    /*tex
        Some options must be initialized before options are parsed. We don't need that many as we
        can delegate to \LUA.
    */
    /*tex Parse the commandline. */
    enginelib_parse_options();
    /*tex Forget about locales. */
    enginelib_set_locale();
    /*tex Initialize the \LUA\ instance and keys. */
    lmt_initialize();
    /*tex This can be redone later. */
    lmt_initialize_functions(0);
    lmt_initialize_properties(0);
    /*tex For word handlers. */
    lmt_initialize_languages();
    /*tex Here start the key definitions (will become functions). */
    lmt_initialize_interface();
    lmt_nodelib_initialize();
    lmt_tokenlib_initialize();
    lmt_fontlib_initialize();
    /*tex Collect arguments. */
    enginelib_prepare_cmdline(lmt_environment_state.luatex_lua_offset);
    if (lmt_engine_state.startup_filename && ! aux_is_readable(lmt_engine_state.startup_filename)) {
        lmt_memory_free(lmt_engine_state.startup_filename);
        lmt_engine_state.startup_filename = NULL;
    }
    /*tex
        Now run the file (in \LUATEX\ there is a special \TEX\ table pushed with limited
        functionality (initialize, run, finish) but the normal tex helpers are not unhidden so
        basically one has no \TEX. We no longer have that.
    */
    if (lmt_engine_state.startup_filename) {
        lua_State *L = lmt_lua_state.lua_instance;
        if (lmt_engine_state.lua_only) {
            if (luaL_loadfile(L, lmt_engine_state.startup_filename)) {
                tex_emergency_message("lua error", "startup file: %s", lmt_error_string(L, -1));
                tex_emergency_exit();
            } else if (lua_pcall(L, 0, 0, 0)) {
                tex_emergency_message("lua error", "function call: %s", lmt_error_string(L, -1));
                lmt_traceback(L);
                tex_emergency_exit();
            } else {
                /*tex We're okay. */
                exit(lmt_error_state.default_exit_code);
            }
        } else {
            /*tex a normal tex run */
            if (luaL_loadfile(L, lmt_engine_state.startup_filename)) {
                tex_emergency_message("lua error", "startup file: %s", lmt_error_string(L, -1));
                tex_emergency_exit();
            } else if (lua_pcall(L, 0, 0, 0)) {
                tex_emergency_message("lua error", "function call: %s", lmt_error_string(L, -1));
                lmt_traceback(L);
                tex_emergency_exit();
            }
            enginelib_update_options();
            tex_check_fmt_name();
        }
    } else if (lmt_engine_state.lua_init) {
        tex_emergency_message("startup error", "no valid startup file given, quitting");
        tex_emergency_exit();
    } else {
        tex_check_fmt_name();
    }
}

/*tex

    For practical and historical reasons some of the initalization and checking is split. The
    mainbody routine call out to these functions. The timing is sort of tricky: we can use a start
    up script, that sets some configuration parameters, and for sure some callbacks, and these, in
    turn, are then responsible for follow up actions like telling where to find the format file
    (when a dump is loaded) or startup file (when we're in virgin mode). When we are in neither of
    these modes the engine is just a \LUA\ interpreter which means that only a subset of libraries
    is initialized.

*/

static void tex_engine_get_config_numbers(const char *name, int *minimum, int *maximum, int *size, int *step)
{
    lua_State *L = lmt_lua_state.lua_instance;
    if (L && size) {
        int stacktop = lua_gettop(L);
        if (lua_getglobal(L, "texconfig") == LUA_TTABLE) {
            switch (lua_getfield(L, -1, name)) {
                case LUA_TNUMBER:
                    if (size) {
                        *size = (int) lmt_roundnumber(L, -1);
                    }
                    break;
                case LUA_TTABLE:
                    if (size && lua_getfield(L, -1, "size")) {
                        *size = (int) lmt_roundnumber(L, -1);
                    }
                    lua_pop(L, 1);
                    if (size && lua_getfield(L, -1, "plus")) {
                        *size += (int) lmt_roundnumber(L, -1);
                    }
                    lua_pop(L, 1);
                    if (step && lua_getfield(L, -1, "step")) {
                        int stp = (int) lmt_roundnumber(L, -1);
                        if (stp > *step) {
                            *step = stp;
                        }
                    }
                    break;
            }
            if (minimum && *size < *minimum) {
                *size = *minimum;
            } else if (maximum && *size > *maximum) {
                *size = *maximum;
            }
        }
        lua_settop(L, stacktop);
    }
}

void tex_engine_set_memory_data(const char *name, memory_data *data)
{
    tex_engine_get_config_numbers(name, &data->minimum, &data->maximum, &data->size, &data->step);
}

void tex_engine_set_limits_data(const char *name, limits_data *data)
{
    tex_engine_get_config_numbers(name, &data->minimum, &data->maximum, &data->size, NULL);
}

void tex_engine_get_config_boolean(const char *name, int *target)
{
    lua_State *L = lmt_lua_state.lua_instance;
    if (L) {
        int stacktop = lua_gettop(L);
        if (lua_getglobal(L, "texconfig") == LUA_TTABLE) {
            switch (lua_getfield(L, -1, name)) {
                case LUA_TBOOLEAN:
                    *target = lua_toboolean(L, -1);
                    break;
                case LUA_TNUMBER:
                    *target = (lua_tointeger(L, -1) == 0 ? 0 : 1);
                    break;
            }
        }
        lua_settop(L, stacktop);
    }
}

void tex_engine_get_config_number(const char *name, int *target)
{
    tex_engine_get_config_numbers(name, NULL, NULL, target, NULL);
}

void tex_engine_get_config_string(const char *name, char **target)
{
    lua_State *L = lmt_lua_state.lua_instance;
    if (L) {
        int stacktop = lua_gettop(L);
        if (lua_getglobal(L, "texconfig") == LUA_TTABLE) {
            if (lua_getfield(L, -1, name) == LUA_TSTRING) {
                *target = lmt_memory_strdup(lua_tostring(L, -1));
            }
        }
        lua_settop(L, stacktop);
    }
}

int tex_engine_run_config_function(const char *name)
{
    lua_State *L = lmt_lua_state.lua_instance;
    if (L) {
        if (lua_getglobal(L, "texconfig") == LUA_TTABLE) {
            if (lua_getfield(L, -1, name) == LUA_TFUNCTION) {
                if (! lua_pcall(L, 0, 0, 0)) {
                    return 1;
                } else {
                    /*tex
                        We can't be more precise here as it's called before \TEX\ initialization
                        happens.
                    */
                    tex_emergency_message("lua", "this went wrong: %s\n", lmt_error_string(L, -1));
                    tex_emergency_exit();
                }
            }
        }
    }
    return 0;
}

void tex_engine_check_configuration(void)
{
    tex_engine_run_config_function("init");
}

void lmt_make_table(
    lua_State     *L,
    const char    *tab,
    const char    *mttab,
    lua_CFunction  getfunc,
    lua_CFunction  setfunc
)
{
    lua_pushstring(L, tab);          /*tex |[{<tex>},"dimen"]| */
    lua_newtable(L);                 /*tex |[{<tex>},"dimen",{}]| */
    lua_settable(L, -3);             /*tex |[{<tex>}]| */
    lua_pushstring(L, tab);          /*tex |[{<tex>},"dimen"]| */
    lua_gettable(L, -2);             /*tex |[{<tex>},{<dimen>}]| */
    luaL_newmetatable(L, mttab);     /*tex |[{<tex>},{<dimen>},{<dimen_m>}]| */
    lua_pushstring(L, "__index");    /*tex |[{<tex>},{<dimen>},{<dimen_m>},"__index"]| */
    lua_pushcfunction(L, getfunc);   /*tex |[{<tex>},{<dimen>},{<dimen_m>},"__index","getdimen"]| */
    lua_settable(L, -3);             /*tex |[{<tex>},{<dimen>},{<dimen_m>}]| */
    lua_pushstring(L, "__newindex"); /*tex |[{<tex>},{<dimen>},{<dimen_m>},"__newindex"]| */
    lua_pushcfunction(L, setfunc);   /*tex |[{<tex>},{<dimen>},{<dimen_m>},"__newindex","setdimen"]| */
    lua_settable(L, -3);             /*tex |[{<tex>},{<dimen>},{<dimen_m>}]| */
    lua_setmetatable(L, -2);         /*tex |[{<tex>},{<dimen>}]| : assign the metatable */
    lua_pop(L, 1);                   /*tex |[{<tex>}]| : clean the stack */
}

static void *enginelib_aux_luaalloc(
    void   *ud,    /*tex Not used, but passed by \LUA. */
    void   *ptr,   /*tex The old pointer. */
    size_t  osize, /*tex The old size. */
    size_t  nsize  /*tex The new size. */
)
{
    (void) ud;
    lmt_lua_state.used_bytes += (int) (nsize - osize);
    if (lmt_lua_state.used_bytes > lmt_lua_state.used_bytes_max) {
        lmt_lua_state.used_bytes_max = lmt_lua_state.used_bytes;
    }
    /*tex Quite some reallocs happen in \LUA. */
    if (nsize == 0) {
        /* printf("free %i\n",(int) osize); */
        lmt_memory_free(ptr);
        return NULL;
    } else if (osize == 0) {
        /* printf("malloc %i\n",(int) nsize); */
        return lmt_memory_malloc(nsize);
    } else {
        /* printf("realloc %i -> %i\n",(int)osize,(int)nsize); */
        return lmt_memory_realloc(ptr, nsize);
    }
}

static int enginelib_aux_luapanic(lua_State *L)
{
    (void) L;
    tex_emergency_message("lua", "panic: unprotected error in call to Lua API (%s)\n", lmt_error_string(L, -1));
    return tex_emergency_exit();
}

static const luaL_Reg lmt_libs_lua_function_list[] = {
    { "_G",        luaopen_base      },
    { "package",   luaopen_package   },
    { "table",     luaopen_table     },
    { "io",        luaopen_io        },
    { "os",        luaopen_os        },
    { "string",    luaopen_string    },
    { "math",      luaopen_math      },
    { "debug",     luaopen_debug     },
    { "lpeg",      luaopen_lpeg      },
    { "utf8",      luaopen_utf8      },
    { "coroutine", luaopen_coroutine },
    { NULL,        NULL              },
};

static const luaL_Reg lmt_libs_extra_function_list[] = {
    { "md5",      luaopen_md5      },
    { "sha2",     luaopen_sha2     },
    { "aes",      luaopen_aes      },
    { "basexx",   luaopen_basexx   },
    { "lfs",      luaopen_filelib  }, /* for practical reasons we keep this namespace */
    { "fio",      luaopen_fio      },
    { "sio",      luaopen_sio      },
    { "sparse",   luaopen_sparse   },
    { "xzip",     luaopen_xzip     },
    { "xmath",    luaopen_xmath    },
    { "xcomplex", luaopen_xcomplex },
    { "xdecimal", luaopen_xdecimal },
    { NULL,       NULL             },
};

static const luaL_Reg lmt_libs_socket_function_list[] = {
    { "socket",   luaopen_socket_core },
    { "mime",     luaopen_mime_core   },
    { NULL,       NULL                },
};

static const luaL_Reg lmt_libs_more_function_list[] = {
    { "lua",      luaopen_lua    },
    { "luac",     luaopen_luac   },
    { "status",   luaopen_status },
    { "texio",    luaopen_texio  },
    { NULL,       NULL           },
};

static const luaL_Reg lmt_libs_tex_function_list[] = {
    { "tex",      luaopen_tex      },
    { "token",    luaopen_token    },
    { "node",     luaopen_node     },
    { "callback", luaopen_callback },
    { "font",     luaopen_font     },
    { "language", luaopen_language },
    { NULL,       NULL             },
};

static const luaL_Reg lmt_libs_mp_function_list[] = {
    { "mplib", luaopen_mplib },
    { NULL,    NULL          },
};

static const luaL_Reg lmt_libs_pdf_function_list[] = {
    { "pdfe",      luaopen_pdfe      },
    { "pdfdecode", luaopen_pdfdecode },
    { "pngdecode", luaopen_pngdecode },
    { NULL,        NULL              },
};

/*tex

    So, we have different library initialization lists for the the two \TEX\ modes (ini and normal)
    and \LUA\ mode (interpeter). It's not pretty yet but it might become better over time.

 */

static void enginelib_luaopen_liblist(lua_State *L, const luaL_Reg *lib)
{
    for (; lib->func; lib++) {
        luaL_requiref(L, lib->name, lib->func, 1);
        lua_setglobal(L, lib->name);
    }
}

/*tex

    In order to overcome (expected) debates about security we disable loading libraries unless
    explicitly enabled (as in \LUATEX). An exception are the optional libraries, but as these
    interfaces are rather bound to the cannonical \LUAMETATEX\ source code we can control these
    from \CONTEXT\ of needed because before users can run code, we can block support of these
    libraries. On the other hand, we have no reason to distrust the few that can (optionally) be
    used (they also cannot clash with different \LUA\ versions).

    \starttyping
    package.loadlib = nil|
    package.searchers[4] = nil
    package.searchers[3] = nil
    \stoptyping

*/

static int loadlib_warning(lua_State *L)
{
    (void) L;
    tex_normal_error("lua loadlib", "you can only load external libraries when --permitloadlib is given");
    return 0;
}

static void enginelib_disable_loadlib(lua_State *L)
{
    int top = lua_gettop(L);
    lua_getglobal(L, "package");
    lua_pushliteral(L, "loadlib");
    lua_pushcfunction(L, &loadlib_warning);
    lua_rawset(L, -3);
    lua_pushliteral(L, "searchers");
    lua_rawget(L, -2);
    lua_pushnil(L);
    lua_rawseti(L, -2, 4);
    lua_pushnil(L);
    lua_rawseti(L, -2, 3);
    lua_settop(L, top);
}

void lmt_initialize(void)
{
    lua_State *L = lua_newstate(enginelib_aux_luaalloc, NULL);
    if (L) {
        /*tex By default we use the generational garbage collector. */
        lua_gc(L, LUA_GCGEN, 0, 0);
        /* */
        lmt_lua_state.bytecode_max = -1;
        lmt_lua_state.bytecode_bytes = 0;
        lmt_lua_state.lua_instance = L;
        /* */
        lua_atpanic(L, &enginelib_aux_luapanic);
        /*tex Initialize the internalized strings. */
        lmt_initialize_shared_keys(L);
        lmt_initialize_metapost_keys(L);
        /*tex This initializes all the 'simple' libraries: */
        enginelib_luaopen_liblist(L, lmt_libs_lua_function_list);
        /*tex This initializes all the 'extra' libraries: */
        enginelib_luaopen_liblist(L, lmt_libs_extra_function_list);
        /*tex These are special: we extend them. */
        luaextend_os(L);
        luaextend_io(L);
        luaextend_string(L);
        /*tex Loading the socket library is a bit odd (old stuff). */
        enginelib_luaopen_liblist(L, lmt_libs_socket_function_list);
        /*tex This initializes the 'tex' related libraries that have some luaonly functionality */
        enginelib_luaopen_liblist(L, lmt_libs_more_function_list);
        /*tex This initializes the 'tex' related libraries. */
        if (! lmt_engine_state.lua_only) {
            enginelib_luaopen_liblist(L, lmt_libs_tex_function_list);
        }
        if (! lmt_engine_state.permit_loadlib) {
            enginelib_disable_loadlib(L);
        }
        /*tex Optional stuff. */
        luaopen_optional(L);
        /*tex This initializes the 'metapost' related libraries. */
        enginelib_luaopen_liblist(L, lmt_libs_mp_function_list);
        /*tex This initializes the 'pdf' related libraries. */
        enginelib_luaopen_liblist(L, lmt_libs_pdf_function_list);
        /*tex This one can become optional! */
        luaextend_xcomplex(L);
        /*tex We're nearly done! In this table we're going to put some info: */
        lua_createtable(L, 0, 0);
        lua_setglobal(L, "texconfig");
        /* Maybe this will embed the checkstack function that some libs need. */
     /* lua_checkstack(L, 1); */
    } else {
        tex_emergency_message("system", "the Lua state can't be created");
        tex_emergency_exit();
    }
}

int lmt_traceback(lua_State *L)
{
    const char *msg = lua_tostring(L, 1);
    luaL_traceback(L, L, msg ? msg : "<no message>", 1);
    return 1;
}

void lmt_error(
    lua_State  *L,
    const char *where,   /*tex The message has two parts. */
    int         detail,  /*tex A function slot or callback index or ... */
    int         is_fatal /*tex We quit if this is the case */
)
{
    char* err = NULL;
    if (lua_type(L, -1) == LUA_TSTRING) {
        const char *luaerr = lua_tostring(L, -1);
        size_t len = strlen(luaerr) + strlen(where) + 32; /*tex Add some slack. */
        err = (char *) lmt_memory_malloc((unsigned) len);
        if (err) {
            if (detail >= 0) {
                snprintf(err, len, "%s [%i]: %s", where, detail, luaerr);
            } else {
                snprintf(err, len, "%s: %s", where, luaerr);
            }
            if (lmt_error_state.last_lua_error) {
                lmt_memory_free(lmt_error_state.last_lua_error);
            }
        }
        lmt_error_state.last_lua_error = err;
    }
    if (is_fatal > 0) {
        /*
            Normally a memory error from lua. The pool may overflow during the |maketexlstring()|,
            but we are crashing anyway so we may as well abort on the pool size. It is probably
            too risky to show the error context now but we can imagine some more granularity.
        */
        tex_normal_error("lua", err ? err : where);
        /*tex
            This should never be reached, so there is no need to close, so let's make sure of
            that!
        */
        /* lua_close(L); */
    }
    else {
        tex_normal_warning("lua", err ? err : where);
    }
}

/*tex

    As with other dump related actions, this module provides its relevant properties. A dump is
    just that: variables written to a stream, and an undump reads instead. Some basic checking
    happens in these functions.

*/

void lmt_dump_engine_info(dumpstream f)
{
    /*tex We align |engine_name| to 4 bytes with one or more trailing |NUL|. */
    int x = (int) strlen(lmt_engine_state.engine_name);
    if (x > 0) {
        char *format_engine = lmt_memory_malloc((size_t) x + 5);
        if (format_engine) {
            memcpy(format_engine, lmt_engine_state.engine_name, (size_t) x + 1);
            for (int k = x; k <= x + 3; k++) {
                format_engine[k] = 0;
            }
            x = x + 4 - (x % 4);
            dump_int(f, x);
            dump_things(f, format_engine[0], x);
            lmt_memory_free(format_engine);
            return;
        }
    }
    tex_normal_error("system","dumping engine info failed");
}

void lmt_undump_engine_info(dumpstream f)
{
    int x;
    undump_int(f, x);
    if ((x > 1) && (x < 256)) {
        char *format_engine = lmt_memory_malloc((size_t) x);
        if (format_engine) {
            undump_things(f, format_engine[0], x);
            format_engine[x - 1] = 0;
            if (strcmp(lmt_engine_state.engine_name, format_engine)) {
                lmt_memory_free(format_engine);
                goto BAD;
            } else {
                lmt_memory_free(format_engine);
                return;
            }
        }
    }
  BAD:
    tex_fatal_undump_error("engine");
}

const char *lmt_error_string(lua_State* L, int index)
{
    const char *s = lua_tostring(L, index);
    return s ? s : "unknown error";
}