diff options
Diffstat (limited to 'source/luametatex/source/lua/lmtenginelib.c')
-rw-r--r-- | source/luametatex/source/lua/lmtenginelib.c | 1146 |
1 files changed, 1146 insertions, 0 deletions
diff --git a/source/luametatex/source/lua/lmtenginelib.c b/source/luametatex/source/lua/lmtenginelib.c new file mode 100644 index 000000000..f8df06657 --- /dev/null +++ b/source/luametatex/source/lua/lmtenginelib.c @@ -0,0 +1,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"; +} |