summaryrefslogtreecommitdiff
path: root/source/luametatex/source/tex/texerrors.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/luametatex/source/tex/texerrors.c')
-rw-r--r--source/luametatex/source/tex/texerrors.c704
1 files changed, 704 insertions, 0 deletions
diff --git a/source/luametatex/source/tex/texerrors.c b/source/luametatex/source/tex/texerrors.c
new file mode 100644
index 000000000..3252d2c50
--- /dev/null
+++ b/source/luametatex/source/tex/texerrors.c
@@ -0,0 +1,704 @@
+/*
+ See license.txt in the root of this project.
+*/
+
+# include "luametatex.h"
+
+# include <string.h>
+
+/*tex
+
+ When something anomalous is detected, \TEX\ typically does something like this (in \PASCAL\
+ lingua):
+
+ \starttyping
+ print_err("Something anomalous has been detected");
+ help(
+ "This is the first line of my offer to help.\n"
+ "This is the second line. I'm trying to\n"
+ "explain the best way for you to proceed."
+ );
+ error();
+ \stoptyping
+
+ A two-line help message would be given using |help2|, etc.; these informal helps should use
+ simple vocabulary that complements the words used in the official error message that was
+ printed. (Outside the U.S.A., the help messages should preferably be translated into the local
+ vernacular. Each line of help is at most 60 characters long, in the present implementation, so
+ that |max_print_line| will not be exceeded.)
+
+ The |print_err| procedure supplies a |!| before the official message, and makes sure that the
+ terminal is awake if a stop is going to occur. The |error| procedure supplies a |.| after the
+ official message, then it shows the location of the error; and if |interaction =
+ error_stop_mode|, it also enters into a dialog with the user, during which time the help message
+ may be printed.
+
+*/
+
+error_state_info lmt_error_state = {
+ .last_error = NULL,
+ .last_lua_error = NULL,
+ .last_warning_tag = NULL,
+ .last_warning = NULL,
+ .last_error_context = NULL,
+ .help_text = NULL,
+ .print_buffer = "",
+ .intercept = 0,
+ .last_intercept = 0,
+ .interaction = 0,
+ .default_exit_code = 0,
+ .set_box_allowed = 0,
+ .history = 0,
+ .error_count = 0,
+ .err_old_setting = 0,
+ .in_error = 0,
+ .long_help_seen = 0,
+ .context_indent = 4,
+ .padding = 0,
+ .line_limits = {
+ .maximum = max_error_line,
+ .minimum = min_error_line,
+ .size = min_error_line,
+ .top = 0,
+ },
+ .half_line_limits = {
+ .maximum = max_half_error_line,
+ .minimum = min_half_error_line,
+ .size = min_half_error_line,
+ .top = 0,
+ },
+} ;
+
+/*tex
+ Because a |text_can| can be assembled we make a copy. There are not many cases where this is
+ really needed but there are seldom errors anyway so we can neglect this duplication of data.
+*/
+
+inline static void tex_aux_update_help_text(const char* str)
+{
+ if (lmt_error_state.help_text) {
+ lmt_memory_free(lmt_error_state.help_text);
+ lmt_error_state.help_text = NULL;
+ }
+ if (str) {
+ lmt_error_state.help_text = lmt_memory_strdup(str);
+ }
+}
+
+/*tex
+
+ The previously defines structure collects all relevant variables: the current level of
+ interaction: |interaction|, states like |last_error|, |last_lua_error|, |last_warning_tag|,
+ |last_warning_str| and |last_error_context|, and temporary variables like |err_old_setting| and
+ |in_error|.
+
+ This is a variant on |show_runaway| that is used when we delegate error handling to a \LUA\
+ callback. (Maybe some day that will be default.)
+
+*/
+
+static void tex_aux_set_last_error_context(void)
+{
+ int saved_selector = lmt_print_state.selector;
+ int saved_new_line_char = new_line_char_par;
+ int saved_new_string_line = lmt_print_state.new_string_line;
+ lmt_print_state.selector = new_string_selector_code;
+ new_line_char_par = 10;
+ lmt_print_state.new_string_line = 10;
+ tex_show_validity();
+ tex_show_context();
+ lmt_memory_free(lmt_error_state.last_error_context);
+ lmt_error_state.last_error_context = tex_take_string(NULL);
+ lmt_print_state.selector = saved_selector;
+ new_line_char_par = saved_new_line_char;
+ lmt_print_state.new_string_line = saved_new_string_line;
+}
+
+static void tex_aux_flush_error(void)
+{
+ if (lmt_error_state.in_error) {
+ lmt_print_state.selector = lmt_error_state.err_old_setting;
+ lmt_memory_free(lmt_error_state.last_error);
+ lmt_error_state.last_error = tex_take_string(NULL);
+ if (lmt_error_state.last_error) {
+ int callback_id = lmt_callback_defined(show_error_message_callback);
+ if (callback_id > 0) {
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
+ } else {
+ tex_print_str(lmt_error_state.last_error);
+ }
+ }
+ lmt_error_state.in_error = 0;
+ }
+}
+
+static int tex_aux_error_callback_set(void)
+{
+ int callback_id = lmt_callback_defined(show_error_message_callback);
+ return lmt_lua_state.lua_instance && callback_id > 0 ? callback_id : 0;
+}
+
+static void tex_aux_start_error(void)
+{
+ if (tex_aux_error_callback_set()) {
+ lmt_error_state.err_old_setting = lmt_print_state.selector;
+ lmt_print_state.selector = new_string_selector_code;
+ lmt_error_state.in_error = 1 ;
+ lmt_memory_free(lmt_error_state.last_error);
+ lmt_error_state.last_error = NULL;
+ } else {
+ tex_print_nlp();
+ tex_print_str("! ");
+ }
+}
+
+/*tex
+
+ \TEX\ is careful not to call |error| when the print |selector| setting might be unusual. The
+ only possible values of |selector| at the time of error messages are:
+
+ \startitemize
+ \startitem |no_print|: |interaction=batch_mode| and |log_file| not yet open; \stopitem
+ \startitem |term_only|: |interaction>batch_mode| and |log_file| not yet open; \stopitem
+ \startitem |log_only|: |interaction=batch_mode| and |log_file| is open; \stopitem
+ \startitem |term_and_log|: |interaction>batch_mode| and |log_file| is open. \stopitem
+ \stopitemize
+
+*/
+
+void tex_fixup_selector(int logopened)
+{
+ if (lmt_error_state.interaction == batch_mode) {
+ lmt_print_state.selector = logopened ? logfile_selector_code : no_print_selector_code ;
+ } else {
+ lmt_print_state.selector = logopened ? terminal_and_logfile_selector_code : terminal_selector_code;
+ }
+}
+
+/*tex
+
+ The variable |history| records the worst level of error that has been detected. It has four
+ possible values: |spotless|, |warning_issued|, |error_message_issued|, and |fatal_error_stop|.
+
+ Another variable, |error_count|, is increased by one when an |error| occurs without an
+ interactive dialog, and it is reset to zero at the end of every paragraph. If |error_count|
+ reaches 100, \TEX\ decides that there is no point in continuing further.
+
+ The value of |history| is initially |fatal_error_stop|, but it will be changed to |spotless|
+ if \TEX\ survives the initialization process.
+
+*/
+
+void tex_initialize_errors(void)
+{
+ lmt_error_state.interaction = error_stop_mode;
+ lmt_error_state.set_box_allowed = 1;
+ if (lmt_error_state.half_line_limits.size > lmt_error_state.line_limits.size) {
+ lmt_error_state.half_line_limits.size = lmt_error_state.line_limits.size/2;
+ }
+ if (lmt_error_state.half_line_limits.size <= 30) {
+ lmt_error_state.half_line_limits.size = 31;
+ } else if (lmt_error_state.half_line_limits.size >= (lmt_error_state.line_limits.size - 15)) {
+ lmt_error_state.half_line_limits.size = lmt_error_state.line_limits.size - 16;
+ }
+}
+
+/*tex
+
+ It is possible for |error| to be called recursively if some error arises when |get_token| is
+ being used to delete a token, and/or if some fatal error occurs while \TEX\ is trying to fix
+ a non-fatal one. But such recursion is never more than two levels deep.
+
+ Individual lines of help are recorded in the string |help_text|. There can be embedded
+ newlines.
+
+ The |jump_out| procedure just cuts across all active procedure levels and exits the program.
+ It is used when there is no recovery from a particular error. The exit code can be overloaded.
+
+ We don't close the lua state because we then have to collect lots of garbage and it really
+ slows doen the run. It's not needed anyway, as we exit.
+
+*/
+
+static int tex_aux_final_exit(int code)
+{
+ exit(code);
+ return 0; /* unreachable */
+}
+
+int tex_normal_exit(void)
+{
+ tex_terminal_update();
+ /* lua_close(lua_state.lua_instance); */
+ lmt_main_state.ready_already = output_disabled_state;
+ if (lmt_error_state.history != spotless && lmt_error_state.history != warning_issued) {
+ return tex_aux_final_exit(EXIT_FAILURE);
+ } else {
+ return tex_aux_final_exit(lmt_error_state.default_exit_code);
+ }
+}
+
+static void tex_aux_jump_out(void)
+{
+ tex_close_files_and_terminate(1);
+ tex_normal_exit();
+}
+
+/*tex
+
+ This completes the job of error reporting, that is, in good old \TEX. But in \LUATEX\ it
+ doesn't make sense to suport this model of error handling, also because one cannot backtrack
+ over \LUA\ actions, so it would be a cheat. But we can keep the modes.
+
+*/
+
+static void tex_aux_error(int type)
+{
+ int callback_id = lmt_callback_defined(intercept_tex_error_callback);
+ tex_aux_flush_error();
+ if (lmt_error_state.history < error_message_issued && type != warning_error_type) {
+ lmt_error_state.history = error_message_issued;
+ }
+ if (lmt_lua_state.lua_instance && callback_id > 0) {
+ tex_aux_set_last_error_context();
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dd->d", lmt_error_state.interaction, type, &lmt_error_state.interaction);
+ lmt_error_state.error_count = 0;
+ tex_terminal_update();
+ switch (lmt_error_state.interaction) {
+ case batch_mode: /* Q */
+ --lmt_print_state.selector;
+ return;
+ case nonstop_mode: /* R */
+ return;
+ case scroll_mode: /* S */
+ return;
+ case error_stop_mode: /* carry on */
+ break;
+ default: /* exit */
+ lmt_error_state.interaction = scroll_mode;
+ if (type != warning_error_type) {
+ tex_aux_jump_out();
+ }
+ break;
+ }
+ } else {
+ tex_print_char('.');
+ tex_show_context();
+ }
+ if (type != warning_error_type) {
+ ++lmt_error_state.error_count;
+ if (lmt_error_state.error_count == 100) {
+ tex_print_message("That makes 100 errors; please try again.");
+ lmt_error_state.history = fatal_error_stop;
+ tex_aux_jump_out();
+ }
+ }
+ /*tex
+ We assume that the callback handles the log file too. Otherwise we put the help message in
+ the log file.
+ */
+ if (callback_id == 0) {
+ if (lmt_error_state.interaction > batch_mode) {
+ /*tex Avoid terminal output: */
+ --lmt_print_state.selector;
+ }
+ tex_print_nlp();
+ if (lmt_error_state.help_text) {
+ tex_print_str(lmt_error_state.help_text);
+ tex_print_nlp();
+ }
+ if (lmt_error_state.interaction > batch_mode) {
+ /*tex Re-enable terminal output: */
+ ++lmt_print_state.selector;
+ }
+ }
+ tex_print_ln();
+}
+
+/*tex
+
+ In anomalous cases, the print selector might be in an unknown state; the following subroutine
+ is called to fix things just enough to keep running a bit longer.
+
+*/
+
+static void tex_aux_normalize_selector(void)
+{
+ if (lmt_fileio_state.log_opened) {
+ lmt_print_state.selector = terminal_and_logfile_selector_code;
+ } else {
+ lmt_print_state.selector = terminal_selector_code;
+ }
+ if (! lmt_fileio_state.job_name) {
+ tex_open_log_file();
+ }
+ if (lmt_error_state.interaction == batch_mode) {
+ /*tex It becomes no or terminal. */
+ --lmt_print_state.selector;
+ }
+}
+
+/*tex The following procedure prints \TEX's last words before dying: */
+
+static void tex_aux_succumb_error(void)
+{
+ if (lmt_error_state.interaction == error_stop_mode) {
+ /*tex No more interaction: */
+ lmt_error_state.interaction = scroll_mode;
+ }
+ if (lmt_fileio_state.log_opened) {
+ tex_aux_error(succumb_error_type);
+ }
+ lmt_error_state.history = fatal_error_stop;
+ /*tex Irrecoverable error: */
+ tex_aux_jump_out();
+}
+
+/*tex This prints |s|, and that's it. */
+
+void tex_fatal_error(const char *helpinfo)
+{
+ tex_aux_normalize_selector();
+ tex_handle_error(
+ succumb_error_type,
+ "Emergency stop",
+ helpinfo
+ );
+}
+
+/*tex Here is the most dreaded error message. We stop due to finiteness. */
+
+void tex_overflow_error(const char *s, int n)
+{
+ tex_aux_normalize_selector();
+ tex_handle_error(
+ succumb_error_type,
+ "TeX capacity exceeded, sorry [%s=%i]",
+ s, n,
+ "If you really absolutely need more capacity, you can ask a wizard to enlarge me."
+ );
+}
+
+/*tex
+
+ The program might sometime run completely amok, at which point there is no choice but to stop.
+ If no previous error has been detected, that's bad news; a message is printed that is really
+ intended for the \TEX\ maintenance person instead of the user (unless the user has been
+ particularly diabolical). The index entries for \quotation {this can't happen} may help to
+ pinpoint the problem.
+
+*/
+
+int tex_confusion(const char *s)
+{
+ /*tex A consistency check violated; |s| tells where: */
+ tex_aux_normalize_selector();
+ if (lmt_error_state.history < error_message_issued) {
+ tex_handle_error(
+ succumb_error_type,
+ "This can't happen (%s)",
+ s,
+ "I'm broken. Please show this to someone who can fix me."
+ );
+ } else {
+ tex_handle_error(
+ succumb_error_type,
+ "I can't go on meeting you like this",
+ "One of your faux pas seems to have wounded me deeply ... in fact, I'm barely\n"
+ "conscious. Please fix it and try again."
+ );
+ }
+ return 0;
+}
+
+/*tex
+
+ When the program is interrupted we just quit. Here is the hook to deal with it.
+
+*/
+
+void aux_quit_the_program(void) /*tex No |tex_| prefix here! */
+{
+ tex_handle_error(
+ succumb_error_type,
+ "Forced stop",
+ NULL
+ );
+}
+
+/*tex
+
+ The |back_error| routine is used when we want to replace an offending token just before issuing
+ an error message. This routine, like |back_input|, requires that |cur_tok| has been set. We
+ disable interrupts during the call of |back_input| so that the help message won't be lost.
+
+*/
+
+static void tex_aux_back_error(void)
+{
+ tex_back_input(cur_tok);
+ tex_aux_error(back_error_type);
+}
+
+/*tex Back up one inserted token and call |error|. */
+
+static void tex_aux_insert_error(void)
+{
+ tex_back_input(cur_tok);
+ lmt_input_state.cur_input.token_type = inserted_text;
+ tex_aux_error(insert_error_type);
+}
+
+int tex_normal_error(const char *t, const char *p)
+{
+ if (lmt_engine_state.lua_only) {
+ /*tex Normally ending up here means that we call the wrong error function. */
+ tex_emergency_message(t, p);
+ } else {
+ tex_aux_normalize_selector();
+ if (! tex_aux_error_callback_set()) {
+ tex_print_nlp();
+ tex_print_str("! ");
+ }
+ tex_print_str("error");
+ if (t) {
+ tex_print_format(" (%s)", t);
+ }
+ tex_print_str(": ");
+ if (p) {
+ tex_print_str(p);
+ }
+ lmt_error_state.history = fatal_error_stop;
+ tex_print_str("\n");
+ }
+ return tex_aux_final_exit(EXIT_FAILURE);
+}
+
+void tex_normal_warning(const char *t, const char *p)
+{
+ if (strcmp(t, "lua") == 0) {
+ int callback_id = lmt_callback_defined(intercept_lua_error_callback);
+ int saved_new_line_char = new_line_char_par;
+ new_line_char_par = 10;
+ if (lmt_lua_state.lua_instance && callback_id) {
+ (void) lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
+ /* error(); */
+ } else {
+ tex_handle_error(
+ normal_error_type,
+ p ? p : "unspecified lua error",
+ "The lua interpreter ran into a problem, so the remainder of this lua chunk will\n"
+ "be ignored."
+ );
+ }
+ new_line_char_par = saved_new_line_char;
+ } else {
+ int callback_id = lmt_callback_defined(show_warning_message_callback);
+ if (callback_id > 0) {
+ /*tex Free the last ones, */
+ lmt_memory_free(lmt_error_state.last_warning);
+ lmt_memory_free(lmt_error_state.last_warning_tag);
+ lmt_error_state.last_warning = lmt_memory_strdup(p);
+ lmt_error_state.last_warning_tag = lmt_memory_strdup(t);
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
+ } else {
+ tex_print_ln();
+ tex_print_str("warning");
+ if (t) {
+ tex_print_format(" (%s)", t);
+ }
+ tex_print_str(": ");
+ if (p) {
+ tex_print_str(p);
+ }
+ tex_print_ln();
+ }
+ if (lmt_error_state.history == spotless) {
+ lmt_error_state.history = warning_issued;
+ }
+ }
+}
+
+int tex_formatted_error(const char *t, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(lmt_error_state.print_buffer, print_buffer_size, fmt, args);
+ return tex_normal_error(t, lmt_error_state.print_buffer);
+ /*
+ va_end(args);
+ return 0;
+ */
+}
+
+void tex_formatted_warning(const char *t, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(lmt_error_state.print_buffer, print_buffer_size, fmt, args);
+ tex_normal_warning(t, lmt_error_state.print_buffer);
+ va_end(args);
+}
+
+void tex_emergency_message(const char *t, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(lmt_error_state.print_buffer, print_buffer_size, fmt, args);
+ fprintf(stdout,"%s : %s\n",t,lmt_error_state.print_buffer);
+ va_end(args);
+}
+
+int tex_emergency_exit(void)
+{
+ return tex_aux_final_exit(EXIT_FAILURE);
+}
+
+/*tex A prelude to more abstraction and maybe using sprint etc.*/
+
+static void tex_aux_do_handle_error_type(
+ int type
+) {
+ switch (type) {
+ case normal_error_type:
+ case eof_error_type:
+ case condition_error_type:
+ case runaway_error_type:
+ case warning_error_type:
+ tex_aux_error(type);
+ break;
+ case back_error_type:
+ tex_aux_back_error();
+ break;
+ case insert_error_type:
+ tex_aux_insert_error();
+ break;
+ case succumb_error_type:
+ tex_aux_succumb_error();
+ break;
+ }
+}
+
+void tex_handle_error_message_only(
+ const char *message
+)
+{
+ tex_aux_start_error();
+ tex_print_str(message);
+ if (tex_aux_error_callback_set()) {
+ lmt_error_state.in_error = 0;
+ lmt_memory_free(lmt_error_state.last_error);
+ lmt_error_state.last_error = lmt_memory_strdup(message);
+ }
+}
+
+/*tex
+
+ We had about 15 specific tuned message handlers as a prelude to a general template based one
+ and that one has arrived (we also have a print one, beginning 2021 only partially applied as
+ I'm undecided). We can now call a translation callback where we remap similar to how we do it
+ in ConTeXt but I;'m nor that sure if users really need it. The english is probably the least
+ problematic part of an error so first I will perfect the tracing bit.
+
+*/
+
+/*
+ %c int char
+ %s *char string
+ %q *char 'string'
+ %i int integer
+ %e backslash (tex escape)
+ %C int int symbolic representation of cmd chr
+ %E *char \cs
+ %S int tex cs string
+ %M int mode
+ %T int tex string
+ %% percent
+
+*/
+
+extern void tex_handle_error(error_types type, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format); /* hm, weird, no number */
+ /*tex Todo: a translation callback: |str, 1 => str|. */
+ tex_aux_start_error();
+ while (1) {
+ int chr = *format++;
+ switch (chr) {
+ case '\0':
+ goto DONE;
+ case '%':
+ {
+ chr = *format++;
+ switch (chr) {
+ case '\0':
+ goto DONE;
+ case 'c':
+ tex_print_char(va_arg(args, int));
+ break;
+ case 's':
+ tex_print_str(va_arg(args, char *));
+ break;
+ case 'q':
+ tex_print_char('\'');
+ tex_print_str(va_arg(args, char *));
+ tex_print_char('\'');
+ break;
+ case 'm':
+ tex_print_cs_checked(va_arg(args, int));
+ break;
+ case 'i':
+ tex_print_int(va_arg(args, int));
+ break;
+ case 'e':
+ tex_print_str_esc(NULL);
+ break;
+ case 'C':
+ {
+ int cmd = va_arg(args, int);
+ int val = va_arg(args, int);
+ tex_print_cmd_chr((singleword) cmd, val); /* inlining doesn't work */
+ break;
+ }
+ case 'E':
+ tex_print_str_esc(va_arg(args, char *));
+ break;
+ case 'S':
+ {
+ halfword cs = va_arg(args, int);
+ tex_print_cs(cs);
+ break;
+ }
+ case 'M':
+ {
+ halfword mode = va_arg(args, int);
+ tex_print_str(tex_string_mode(mode));
+ break;
+ }
+ case 'T':
+ {
+ strnumber s = va_arg(args, int);
+ tex_print_tex_str(s);
+ break;
+ }
+ case '%':
+ tex_print_char('%');
+ break;
+ default:
+ /* ignore bad one */
+ break;
+ }
+ }
+ break;
+ default:
+ tex_print_char(chr); /* todo: utf */
+ break;
+ }
+ }
+ DONE:
+ /*tex Todo: a translation callback: |str, 2 => str|. */
+ tex_aux_update_help_text(va_arg(args, char *));
+ tex_aux_do_handle_error_type(type);
+ va_end(args);
+}