| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# /=====================================================================\ # |
|
2
|
|
|
|
|
|
|
# | LaTeXML::Common::Error | # |
|
3
|
|
|
|
|
|
|
# | Error handler | # |
|
4
|
|
|
|
|
|
|
# |=====================================================================| # |
|
5
|
|
|
|
|
|
|
# | Part of LaTeXML: | # |
|
6
|
|
|
|
|
|
|
# | Public domain software, produced as part of work done by the | # |
|
7
|
|
|
|
|
|
|
# | United States Government & not subject to copyright in the US. | # |
|
8
|
|
|
|
|
|
|
# |---------------------------------------------------------------------| # |
|
9
|
|
|
|
|
|
|
# | Bruce Miller #_# | # |
|
10
|
|
|
|
|
|
|
# | http://dlmf.nist.gov/LaTeXML/ (o o) | # |
|
11
|
|
|
|
|
|
|
# \=========================================================ooo==U==ooo=/ # |
|
12
|
|
|
|
|
|
|
package LaTeXML::Common::Error; |
|
13
|
21
|
|
|
21
|
|
70
|
use strict; |
|
|
21
|
|
|
|
|
22
|
|
|
|
21
|
|
|
|
|
459
|
|
|
14
|
21
|
|
|
21
|
|
61
|
use warnings; |
|
|
21
|
|
|
|
|
20
|
|
|
|
21
|
|
|
|
|
362
|
|
|
15
|
21
|
|
|
21
|
|
63
|
use LaTeXML::Global; |
|
|
21
|
|
|
|
|
22
|
|
|
|
21
|
|
|
|
|
1312
|
|
|
16
|
21
|
|
|
21
|
|
7001
|
use LaTeXML::Common::Object; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
use Time::HiRes; |
|
18
|
|
|
|
|
|
|
use Term::ANSIColor qw(:constants); |
|
19
|
|
|
|
|
|
|
use base qw(Exporter); |
|
20
|
|
|
|
|
|
|
our @EXPORT = ( |
|
21
|
|
|
|
|
|
|
# Error Reporting |
|
22
|
|
|
|
|
|
|
qw(&Fatal &Error &Warn &Info), |
|
23
|
|
|
|
|
|
|
# Progress reporting |
|
24
|
|
|
|
|
|
|
qw(&NoteProgress &NoteProgressDetailed &NoteBegin &NoteEnd), |
|
25
|
|
|
|
|
|
|
# Colored-logging related functions |
|
26
|
|
|
|
|
|
|
qw(&colorizeString) |
|
27
|
|
|
|
|
|
|
); |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
30
|
|
|
|
|
|
|
# Color setup |
|
31
|
|
|
|
|
|
|
$Term::ANSIColor::AUTORESET = 1; |
|
32
|
|
|
|
|
|
|
our $COLORIZED_LOGGING = -t STDERR; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
our %color_scheme = ( |
|
35
|
|
|
|
|
|
|
details => \&BOLD, |
|
36
|
|
|
|
|
|
|
success => \&GREEN, |
|
37
|
|
|
|
|
|
|
info => (defined &BRIGHT_BLUE ? \&BRIGHT_BLUE : \&BLUE), # bright only recently defined |
|
38
|
|
|
|
|
|
|
warning => \&YELLOW, |
|
39
|
|
|
|
|
|
|
error => sub { BOLD RED shift; }, |
|
40
|
|
|
|
|
|
|
fatal => sub { BOLD RED UNDERLINE shift; } |
|
41
|
|
|
|
|
|
|
); |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub colorizeString { |
|
44
|
|
|
|
|
|
|
my ($string, $alias) = @_; |
|
45
|
|
|
|
|
|
|
return $COLORIZED_LOGGING ? &{ $color_scheme{$alias} }($string) : $string; } |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
48
|
|
|
|
|
|
|
# Error reporting |
|
49
|
|
|
|
|
|
|
# Public API |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub Fatal { |
|
52
|
|
|
|
|
|
|
my ($category, $object, $where, $message, @details) = @_; |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Check if this is a known unsafe fatal and flag it if so (so that we reinitialize in daemon contexts) |
|
55
|
|
|
|
|
|
|
if ((($category eq 'internal') && ($object eq '')) || |
|
56
|
|
|
|
|
|
|
($category eq 'too_many_errors')) { |
|
57
|
|
|
|
|
|
|
$LaTeXML::UNSAFE_FATAL = 1; } |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# We'll assume that if the DIE handler is bound (presumably to this function) |
|
60
|
|
|
|
|
|
|
# we're in the outermost call to Fatal; we'll clear the handler so that we don't nest calls. |
|
61
|
|
|
|
|
|
|
die $message if $LaTeXML::IGNORE_ERRORS # Short circuit, w/no formatting, if in probing eval |
|
62
|
|
|
|
|
|
|
|| (($SIG{__DIE__} eq 'DEFAULT') && $^S); # Also missing class when parsing bindings(?!?!) |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# print STDERR "\nHANDLING FATAL:" |
|
65
|
|
|
|
|
|
|
# ." ignore=".($LaTeXML::IGNORE_ERRORS || '') |
|
66
|
|
|
|
|
|
|
# ." handler=".($SIG{__DIE__}||'') |
|
67
|
|
|
|
|
|
|
# ." parsing=".($^S||'') |
|
68
|
|
|
|
|
|
|
# ."\n"; |
|
69
|
|
|
|
|
|
|
my $inhandler = !$SIG{__DIE__}; |
|
70
|
|
|
|
|
|
|
my $ineval = 0; # whether we're in an eval should no longer matter! |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# This seemingly should be "local", but that doesn't seem to help with timeout/alarm/term? |
|
73
|
|
|
|
|
|
|
# It should be safe so long as the caller has bound it and rebinds it if necessary. |
|
74
|
|
|
|
|
|
|
$SIG{__DIE__} = 'DEFAULT'; # Avoid recursion while preparing the message. |
|
75
|
|
|
|
|
|
|
my $state = $STATE; |
|
76
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
if (!$inhandler) { |
|
79
|
|
|
|
|
|
|
local $LaTeXML::BAILOUT = $LaTeXML::BAILOUT; |
|
80
|
|
|
|
|
|
|
if (checkRecursiveError()) { |
|
81
|
|
|
|
|
|
|
$LaTeXML::BAILOUT = 1; |
|
82
|
|
|
|
|
|
|
push(@details, "Recursive Error!"); } |
|
83
|
|
|
|
|
|
|
$state->noteStatus('fatal') if $state && !$ineval; |
|
84
|
|
|
|
|
|
|
my $detail_level = (($verbosity <= 1) && ($category =~ /^(?:timeout|too_many_errors)$/)) ? 0 : 2; |
|
85
|
|
|
|
|
|
|
$message |
|
86
|
|
|
|
|
|
|
= generateMessage(colorizeString("Fatal:" . $category . ":" . ToString($object), 'fatal'), |
|
87
|
|
|
|
|
|
|
$where, $message, $detail_level, @details); |
|
88
|
|
|
|
|
|
|
# If we're about to (really) DIE, we'll bypass the usual status message, so add it here. |
|
89
|
|
|
|
|
|
|
# This really should be handled by the top-level program, |
|
90
|
|
|
|
|
|
|
# after doing all processing within an eval |
|
91
|
|
|
|
|
|
|
# BIZARRE: Note that die adds the "at " stuff IFF the message doesn't end w/ CR! |
|
92
|
|
|
|
|
|
|
$message .= $state->getStatusMessage . "\n" if $state && !$ineval; |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
else { # If we ARE in a recursive call, the actual message is $details[0] |
|
95
|
|
|
|
|
|
|
$message = $details[0] if $details[0]; } |
|
96
|
|
|
|
|
|
|
# If inside an eval, this won't actually die, but WILL set $@ for caller's use. |
|
97
|
|
|
|
|
|
|
die $message; } |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub checkRecursiveError { |
|
100
|
|
|
|
|
|
|
my @caller; |
|
101
|
|
|
|
|
|
|
for (my $frame = 2 ; @caller = caller($frame) ; $frame++) { |
|
102
|
|
|
|
|
|
|
if ($caller[3] =~ /^LaTeXML::(Global::ToString|Global::Stringify)$/) { |
|
103
|
|
|
|
|
|
|
# print STDERR "RECURSED ON $caller[3]\n"; |
|
104
|
|
|
|
|
|
|
return 1; } } |
|
105
|
|
|
|
|
|
|
return; } |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Note that "100" is hardwired into TeX, The Program!!! |
|
108
|
|
|
|
|
|
|
my $MAXERRORS = 100; # [CONSTANT] |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Should be fatal if strict is set, else warn. |
|
111
|
|
|
|
|
|
|
sub Error { |
|
112
|
|
|
|
|
|
|
my ($category, $object, $where, $message, @details) = @_; |
|
113
|
|
|
|
|
|
|
my $state = $STATE; |
|
114
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
115
|
|
|
|
|
|
|
if ($state && $state->lookupValue('STRICT')) { |
|
116
|
|
|
|
|
|
|
Fatal($category, $object, $where, $message, @details); } |
|
117
|
|
|
|
|
|
|
else { |
|
118
|
|
|
|
|
|
|
$state && $state->noteStatus('error'); |
|
119
|
|
|
|
|
|
|
print STDERR generateMessage(colorizeString("Error:" . $category . ":" . ToString($object), 'error'), |
|
120
|
|
|
|
|
|
|
$where, $message, 1, @details) |
|
121
|
|
|
|
|
|
|
if $verbosity >= -2; } |
|
122
|
|
|
|
|
|
|
if ($state && ($state->getStatus('error') || 0) > $MAXERRORS) { |
|
123
|
|
|
|
|
|
|
Fatal('too_many_errors', $MAXERRORS, $where, "Too many errors (> $MAXERRORS)!"); } |
|
124
|
|
|
|
|
|
|
return; } |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Warning message; results may be OK, but somewhat unlikely |
|
127
|
|
|
|
|
|
|
sub Warn { |
|
128
|
|
|
|
|
|
|
my ($category, $object, $where, $message, @details) = @_; |
|
129
|
|
|
|
|
|
|
my $state = $STATE; |
|
130
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
131
|
|
|
|
|
|
|
$state && $state->noteStatus('warning'); |
|
132
|
|
|
|
|
|
|
print STDERR generateMessage(colorizeString("Warning:" . $category . ":" . ToString($object), 'warning'), |
|
133
|
|
|
|
|
|
|
$where, $message, 0, @details) |
|
134
|
|
|
|
|
|
|
if $verbosity >= -1; |
|
135
|
|
|
|
|
|
|
return; } |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Informational message; results likely unaffected |
|
138
|
|
|
|
|
|
|
# but the message may give clues about subsequent warnings or errors |
|
139
|
|
|
|
|
|
|
sub Info { |
|
140
|
|
|
|
|
|
|
my ($category, $object, $where, $message, @details) = @_; |
|
141
|
|
|
|
|
|
|
my $state = $STATE; |
|
142
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
143
|
|
|
|
|
|
|
$state && $state->noteStatus('info'); |
|
144
|
|
|
|
|
|
|
print STDERR generateMessage(colorizeString("Info:" . $category . ":" . ToString($object), 'info'), |
|
145
|
|
|
|
|
|
|
$where, $message, -1, @details) |
|
146
|
|
|
|
|
|
|
if $verbosity >= 0; |
|
147
|
|
|
|
|
|
|
return; } |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
150
|
|
|
|
|
|
|
# Progress Reporting |
|
151
|
|
|
|
|
|
|
#********************************************************************** |
|
152
|
|
|
|
|
|
|
# Progress reporting. |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub NoteProgress { |
|
155
|
|
|
|
|
|
|
my (@stuff) = @_; |
|
156
|
|
|
|
|
|
|
my $state = $STATE; |
|
157
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
158
|
|
|
|
|
|
|
print STDERR @stuff if $verbosity >= 0; |
|
159
|
|
|
|
|
|
|
return; } |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub NoteProgressDetailed { |
|
162
|
|
|
|
|
|
|
my (@stuff) = @_; |
|
163
|
|
|
|
|
|
|
my $state = $STATE; |
|
164
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
165
|
|
|
|
|
|
|
print STDERR @stuff if $verbosity >= 1; |
|
166
|
|
|
|
|
|
|
return; } |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub NoteBegin { |
|
169
|
|
|
|
|
|
|
my ($stage) = @_; |
|
170
|
|
|
|
|
|
|
my $state = $STATE; |
|
171
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
172
|
|
|
|
|
|
|
if ($state && ($verbosity >= 0)) { |
|
173
|
|
|
|
|
|
|
$state->assignMapping('NOTE_TIMERS', $stage, [Time::HiRes::gettimeofday]); |
|
174
|
|
|
|
|
|
|
print STDERR "\n($stage..."; } |
|
175
|
|
|
|
|
|
|
return; } |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub NoteEnd { |
|
178
|
|
|
|
|
|
|
my ($stage) = @_; |
|
179
|
|
|
|
|
|
|
my $state = $STATE; |
|
180
|
|
|
|
|
|
|
my $verbosity = $state && $state->lookupValue('VERBOSITY') || 0; |
|
181
|
|
|
|
|
|
|
if (my $start = $state && $state->lookupMapping('NOTE_TIMERS', $stage)) { |
|
182
|
|
|
|
|
|
|
$state->assignMapping('NOTE_TIMERS', $stage, undef); |
|
183
|
|
|
|
|
|
|
if ($verbosity >= 0) { |
|
184
|
|
|
|
|
|
|
my $elapsed = Time::HiRes::tv_interval($start, [Time::HiRes::gettimeofday]); |
|
185
|
|
|
|
|
|
|
print STDERR sprintf(" %.2f sec)", $elapsed); } } |
|
186
|
|
|
|
|
|
|
return; } |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
189
|
|
|
|
|
|
|
# Handlers for perl's die & warn |
|
190
|
|
|
|
|
|
|
# We'll try to decode some common errors to make them more usable |
|
191
|
|
|
|
|
|
|
# for build systems. |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
my $quoted_re = qr/\"([^\"]*)\"/; # [CONSTANT] |
|
194
|
|
|
|
|
|
|
my $cantcall_re = qr/Can't call method/; # [CONSTANT] |
|
195
|
|
|
|
|
|
|
my $cantlocate_re = qr/Can't locate object method/; # [CONSTANT] |
|
196
|
|
|
|
|
|
|
my $undef_re = qr/Undefined subroutine/; # [CONSTANT] |
|
197
|
|
|
|
|
|
|
my $noself_re = qr/on an undefined value|without a package or object reference/; # [CONSTANT] |
|
198
|
|
|
|
|
|
|
my $via_re = qr/via package/; # [CONSTANT] |
|
199
|
|
|
|
|
|
|
my $at_re = qr/(at .*)/; # [CONSTANT] |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub perl_die_handler { |
|
202
|
|
|
|
|
|
|
my (@line) = @_; |
|
203
|
|
|
|
|
|
|
if ($LaTeXML::IGNORE_ERRORS) { # Just get out now, if we're ignoring errors within an eval. |
|
204
|
|
|
|
|
|
|
local $SIG{__DIE__} = undef; |
|
205
|
|
|
|
|
|
|
die @line; } |
|
206
|
|
|
|
|
|
|
# We try to find a meaningful name for where the error occurred; |
|
207
|
|
|
|
|
|
|
# That's the thing that is "misdefined", after all. |
|
208
|
|
|
|
|
|
|
# Not completely sure we're looking in the right place up the stack, though. |
|
209
|
|
|
|
|
|
|
if ($line[0] =~ /^$cantcall_re\s+$quoted_re\s+($noself_re)\s+$at_re$/) { |
|
210
|
|
|
|
|
|
|
my ($method, $kind, $where) = ($1, $2, $3); |
|
211
|
|
|
|
|
|
|
Fatal('misdefined', callerName(1), $where, |
|
212
|
|
|
|
|
|
|
"Can't call method '$method' $kind", @line[1 .. $#line]); } |
|
213
|
|
|
|
|
|
|
elsif ($line[0] =~ /^$undef_re\s+(\S+)\s+called $at_re$/) { |
|
214
|
|
|
|
|
|
|
my ($function, $where) = ($1, $2); |
|
215
|
|
|
|
|
|
|
Fatal('misdefined', callerName(1), $where, |
|
216
|
|
|
|
|
|
|
"Undefined subroutine '$function' called", @line[1 .. $#line]); } |
|
217
|
|
|
|
|
|
|
elsif ($line[0] =~ /^$cantlocate_re\s+$quoted_re\s+$via_re\s+$quoted_re\s+\(.*\)\s+$at_re/) { |
|
218
|
|
|
|
|
|
|
my ($method, $class, $where) = ($1, $2, $3); |
|
219
|
|
|
|
|
|
|
Fatal('misdefined', callerName(1), $where, |
|
220
|
|
|
|
|
|
|
"Can't locate method '$method' via '$class'", @line[1 .. $#line]); } |
|
221
|
|
|
|
|
|
|
elsif ($line[0] =~ /^Can't locate \S* in \@INC \(you may need to install the (\S*) module\) \(\@INC contains: ([^\)]*)\) $at_re$/) { |
|
222
|
|
|
|
|
|
|
my ($class, $inc, $where) = ($1, $2); |
|
223
|
|
|
|
|
|
|
Fatal('misdefined', callerName(1), $where, |
|
224
|
|
|
|
|
|
|
"Can't locate class '$class' (not installed or misspelled?)", @line[1 .. $#line]); } |
|
225
|
|
|
|
|
|
|
elsif ($line[0] =~ /^Can't use\s+(\w*)\s+\([^\)]*\) as (.*?) ref(?:\s+while "strict refs" in use)? at (.*)$/) { |
|
226
|
|
|
|
|
|
|
my ($gottype, $wanttype, $where) = ($1, $2, $3); |
|
227
|
|
|
|
|
|
|
Fatal('misdefined', callerName(1), $where, |
|
228
|
|
|
|
|
|
|
"Can't use $gottype as $wanttype reference", @line[1 .. $#line]); } |
|
229
|
|
|
|
|
|
|
elsif ($line[0] =~ /^File (.*?) had an error:/) { |
|
230
|
|
|
|
|
|
|
my ($file) = ($1); |
|
231
|
|
|
|
|
|
|
Fatal('misdefined', $file, undef, @line); } |
|
232
|
|
|
|
|
|
|
else { |
|
233
|
|
|
|
|
|
|
Fatal('perl', 'die', undef, "Perl died", @line); } |
|
234
|
|
|
|
|
|
|
return; } |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub perl_warn_handler { |
|
237
|
|
|
|
|
|
|
my (@line) = @_; |
|
238
|
|
|
|
|
|
|
return if $LaTeXML::IGNORE_ERRORS; |
|
239
|
|
|
|
|
|
|
if ($line[0] =~ /^Use of uninitialized value (.*?)(\s?+in .*?)\s+(at\s+.*?\s+line\s+\d+)\.$/) { |
|
240
|
|
|
|
|
|
|
my ($what, $how, $where) = ($1 || 'value', $2, $3); |
|
241
|
|
|
|
|
|
|
Warn('uninitialized', $what, $where, "Use of uninitialized value $what $how", @line[1 .. $#line]); } |
|
242
|
|
|
|
|
|
|
elsif ($line[0] =~ /^(.*?)\s+(at\s+.*?\s+line\s+\d+)\.$/) { |
|
243
|
|
|
|
|
|
|
my ($warning, $where) = ($1, $2); |
|
244
|
|
|
|
|
|
|
Warn('perl', 'warn', undef, $warning, $where, @line[1 .. $#line]); } |
|
245
|
|
|
|
|
|
|
else { |
|
246
|
|
|
|
|
|
|
Warn('perl', 'warn', undef, "Perl warning", @line); } |
|
247
|
|
|
|
|
|
|
return; } |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# The following handlers SHOULD report the problem, |
|
250
|
|
|
|
|
|
|
# even when within a "safe" eval that's ignoring errors. |
|
251
|
|
|
|
|
|
|
# Moreover, we'd really like to be able to throw all the way to |
|
252
|
|
|
|
|
|
|
# the top-level containing eval. How to do that? |
|
253
|
|
|
|
|
|
|
sub perl_interrupt_handler { |
|
254
|
|
|
|
|
|
|
my (@line) = @_; |
|
255
|
|
|
|
|
|
|
$LaTeXML::IGNORE_ERRORS = 0; # NOT ignored |
|
256
|
|
|
|
|
|
|
$LaTeXML::UNSAFE_FATAL = 1; |
|
257
|
|
|
|
|
|
|
Fatal('interrupt', 'interrupted', undef, "LaTeXML was interrupted", @_); |
|
258
|
|
|
|
|
|
|
return; } |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub perl_timeout_handler { |
|
261
|
|
|
|
|
|
|
my (@line) = @_; |
|
262
|
|
|
|
|
|
|
$LaTeXML::IGNORE_ERRORS = 0; # NOT ignored |
|
263
|
|
|
|
|
|
|
$LaTeXML::UNSAFE_FATAL = 1; |
|
264
|
|
|
|
|
|
|
Fatal('timeout', 'timedout', undef, "Conversion timed out", @_); |
|
265
|
|
|
|
|
|
|
return; } |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
sub perl_terminate_handler { |
|
268
|
|
|
|
|
|
|
my (@line) = @_; |
|
269
|
|
|
|
|
|
|
$LaTeXML::IGNORE_ERRORS = 0; # NOT ignored |
|
270
|
|
|
|
|
|
|
$LaTeXML::UNSAFE_FATAL = 1; |
|
271
|
|
|
|
|
|
|
Fatal('terminate', 'terminated', undef, "Conversion was terminated", @_); |
|
272
|
|
|
|
|
|
|
return; } |
|
273
|
|
|
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
274
|
|
|
|
|
|
|
# Internals |
|
275
|
|
|
|
|
|
|
# Synthesize an error message describing what happened, and where. |
|
276
|
|
|
|
|
|
|
# $detail specifies the level of detail |
|
277
|
|
|
|
|
|
|
# $detail == -1 : no context or stack |
|
278
|
|
|
|
|
|
|
# $detail == 0 : context, no stack |
|
279
|
|
|
|
|
|
|
# $detail == +1 : context & stack |
|
280
|
|
|
|
|
|
|
# including a level requesting full stack trace? |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub generateMessage { |
|
283
|
|
|
|
|
|
|
my ($errorcode, $where, $message, $detail, @extra) = @_; |
|
284
|
|
|
|
|
|
|
#---------------------------------------- |
|
285
|
|
|
|
|
|
|
# Generate location information; basic and for stack trace. |
|
286
|
|
|
|
|
|
|
# If we've been given an object $where, where the error occurred, use it. |
|
287
|
|
|
|
|
|
|
my $docloc = getLocation($where); |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# $message and each of @extra should be single lines |
|
290
|
|
|
|
|
|
|
@extra = grep { $_ ne '' } map { split("\n", $_) } grep { defined $_ } $message, @extra; |
|
291
|
|
|
|
|
|
|
# make 1st line be 1st line of message |
|
292
|
|
|
|
|
|
|
$message = shift(@extra); |
|
293
|
|
|
|
|
|
|
# $message =~ s/\n.*//g; |
|
294
|
|
|
|
|
|
|
# The initial portion of the message will consist of: |
|
295
|
|
|
|
|
|
|
$message = '' unless defined $message; |
|
296
|
|
|
|
|
|
|
my @lines = ( |
|
297
|
|
|
|
|
|
|
# Start with the error code & primary error message |
|
298
|
|
|
|
|
|
|
$errorcode . ' ' . $message, |
|
299
|
|
|
|
|
|
|
# Followed by single line location of where the message occurred (if we know) |
|
300
|
|
|
|
|
|
|
($docloc ? ($docloc) : ()), |
|
301
|
|
|
|
|
|
|
# and then any additional message lines supplied |
|
302
|
|
|
|
|
|
|
@extra); |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
#---------------------------------------- |
|
305
|
|
|
|
|
|
|
# Now add some additional context |
|
306
|
|
|
|
|
|
|
# NOTE: Should skip this for INFO |
|
307
|
|
|
|
|
|
|
# NOTE: Need to pass more of this onto the objects themselves.... |
|
308
|
|
|
|
|
|
|
# What should it be called? |
|
309
|
|
|
|
|
|
|
# showErrorContext() ????? |
|
310
|
|
|
|
|
|
|
$detail = 0 unless defined $detail; |
|
311
|
|
|
|
|
|
|
# Increment $detail if $verbosity > 0, unless $detail = -1, |
|
312
|
|
|
|
|
|
|
my $verbosity = ($STATE && $STATE->lookupValue('VERBOSITY')) || 0; |
|
313
|
|
|
|
|
|
|
if (($detail > -1) && ($verbosity > 0)) { |
|
314
|
|
|
|
|
|
|
$detail = 0 if defined $verbosity && $verbosity < -1; |
|
315
|
|
|
|
|
|
|
$detail++ if defined $verbosity && $verbosity > +1; } |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# FIRST line of stack trace information ought to look at the $where |
|
318
|
|
|
|
|
|
|
my $wheretype = ref $where; |
|
319
|
|
|
|
|
|
|
if ($detail <= 0) { } # No extra context |
|
320
|
|
|
|
|
|
|
elsif ($wheretype =~ /^XML::LibXML/) { |
|
321
|
|
|
|
|
|
|
push(@lines, "Node is " . Stringify($where)); } |
|
322
|
|
|
|
|
|
|
## Hmm... if we're being verbose or level is high, we might do this: |
|
323
|
|
|
|
|
|
|
### "Currently in ".$doc->getInsertionContext); } |
|
324
|
|
|
|
|
|
|
elsif ($wheretype =~ 'LaTeXML::Core::Gullet') { |
|
325
|
|
|
|
|
|
|
push(@lines, $where->showUnexpected); } # Or better? |
|
326
|
|
|
|
|
|
|
elsif ($wheretype =~ 'LaTeXML::Core::Stomach') { |
|
327
|
|
|
|
|
|
|
push(@lines, |
|
328
|
|
|
|
|
|
|
"Recently digested: " . join(' ', map { Stringify($_) } @LaTeXML::LIST)) |
|
329
|
|
|
|
|
|
|
if $verbosity > 1; } |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
#---------------------------------------- |
|
332
|
|
|
|
|
|
|
# Add Stack Trace, if that seems worthwhile. |
|
333
|
|
|
|
|
|
|
if (($detail > 1) && ($verbosity > 0)) { |
|
334
|
|
|
|
|
|
|
push(@lines, "Stack Trace:", stacktrace()); } |
|
335
|
|
|
|
|
|
|
elsif ($detail > -1) { |
|
336
|
|
|
|
|
|
|
my $nstack = ($detail > 1 ? undef : ($detail > 0 ? 4 : 1)); |
|
337
|
|
|
|
|
|
|
if (my @objects = objectStack($nstack)) { |
|
338
|
|
|
|
|
|
|
my $top = shift(@objects); |
|
339
|
|
|
|
|
|
|
push(@lines, "In " . trim(Stringify($$top[0])) . ' ' . Stringify($$top[1])); |
|
340
|
|
|
|
|
|
|
push(@objects, ['...']) if @objects && defined $nstack; |
|
341
|
|
|
|
|
|
|
push(@lines, join('', (map { ' <= ' . trim(Stringify($$_[0])) } @objects))) if @objects; |
|
342
|
|
|
|
|
|
|
} } |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# finally, join the result into a block of lines, indenting all but the 1st line. |
|
345
|
|
|
|
|
|
|
return "\n" . join("\n\t", @lines) . "\n"; } |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub Locator { |
|
348
|
|
|
|
|
|
|
my ($object) = @_; |
|
349
|
|
|
|
|
|
|
return ($object && $object->can('getLocator') ? $object->getLocator : "???"); } |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# A more organized abstraction along there likes of $where->whereAreYou |
|
352
|
|
|
|
|
|
|
# might be useful? |
|
353
|
|
|
|
|
|
|
sub getLocation { |
|
354
|
|
|
|
|
|
|
my ($where) = @_; |
|
355
|
|
|
|
|
|
|
my $wheretype = ref $where; |
|
356
|
|
|
|
|
|
|
if ($wheretype && ($wheretype =~ /^XML::LibXML/)) { |
|
357
|
|
|
|
|
|
|
my $box = $LaTeXML::DOCUMENT && $LaTeXML::DOCUMENT->getNodeBox($where); |
|
358
|
|
|
|
|
|
|
return Locator($box) if $box; } |
|
359
|
|
|
|
|
|
|
elsif ($wheretype && $where->can('getLocator')) { |
|
360
|
|
|
|
|
|
|
return $where->getLocator; } |
|
361
|
|
|
|
|
|
|
elsif (defined $where) { |
|
362
|
|
|
|
|
|
|
return $where; } |
|
363
|
|
|
|
|
|
|
# Otherwise, try to guess where the error came from! |
|
364
|
|
|
|
|
|
|
elsif ($LaTeXML::DOCUMENT) { # During construction? |
|
365
|
|
|
|
|
|
|
my $node = $LaTeXML::DOCUMENT->getNode; |
|
366
|
|
|
|
|
|
|
my $box = $LaTeXML::DOCUMENT->getNodeBox($node); |
|
367
|
|
|
|
|
|
|
return Locator($box) if $box; } |
|
368
|
|
|
|
|
|
|
if ($LaTeXML::BOX) { # In constructor? |
|
369
|
|
|
|
|
|
|
return Locator($LaTeXML::BOX); } |
|
370
|
|
|
|
|
|
|
if ($STATE && $STATE->getStomach) { |
|
371
|
|
|
|
|
|
|
my $gullet = $STATE->getStomach->getGullet; |
|
372
|
|
|
|
|
|
|
# NOTE: Problems here. |
|
373
|
|
|
|
|
|
|
# (1) With obsoleting Tokens as a Mouth, we can get pointless "Anonymous String" locators! |
|
374
|
|
|
|
|
|
|
# (2) If gullet is the source, we probably want to include next token, etc or |
|
375
|
|
|
|
|
|
|
return $gullet->getLocator(); } |
|
376
|
|
|
|
|
|
|
# # If in postprocessing |
|
377
|
|
|
|
|
|
|
# if($LaTeXML::Post::PROCESSOR && $LaTeXML::Post::DOCUMENT){ |
|
378
|
|
|
|
|
|
|
# return 'in '. $LaTeXML::Post::PROCESSOR->getName |
|
379
|
|
|
|
|
|
|
# . ' on '. $LaTeXML::Post::DOCUMENT->siteRelativeDestination; } |
|
380
|
|
|
|
|
|
|
return; } |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub callerName { |
|
383
|
|
|
|
|
|
|
my ($frame) = @_; |
|
384
|
|
|
|
|
|
|
my %info = caller_info(($frame || 0) + 2); |
|
385
|
|
|
|
|
|
|
return $info{sub}; } |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
sub callerInfo { |
|
388
|
|
|
|
|
|
|
my ($frame) = @_; |
|
389
|
|
|
|
|
|
|
my %info = caller_info(($frame || 0) + 2); |
|
390
|
|
|
|
|
|
|
return "$info{call} @ $info{file} line $info{line}"; } |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
#====================================================================== |
|
393
|
|
|
|
|
|
|
# This portion adapted from Carp; simplified (but hopefully still correct), |
|
394
|
|
|
|
|
|
|
# allow stringify overload, handle methods, make more concise! |
|
395
|
|
|
|
|
|
|
#====================================================================== |
|
396
|
|
|
|
|
|
|
my $MAXARGS = 8; # [CONSTANT] |
|
397
|
|
|
|
|
|
|
my $MAXLEN = 40; # Or more? [CONSTANT] |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
sub trim { |
|
400
|
|
|
|
|
|
|
my ($string) = @_; |
|
401
|
|
|
|
|
|
|
return $string unless defined $string; |
|
402
|
|
|
|
|
|
|
$string = substr($string, 0, $MAXLEN - 3) . "..." if (length($string) > $MAXLEN); |
|
403
|
|
|
|
|
|
|
$string =~ s/\n/\x{240D}/gs; # symbol for CR |
|
404
|
|
|
|
|
|
|
return $string; } |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub caller_info { |
|
407
|
|
|
|
|
|
|
my ($i) = @_; |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
my (%info, @args); |
|
410
|
|
|
|
|
|
|
{ package DB; |
|
411
|
|
|
|
|
|
|
@info{qw(package file line sub has_args wantarray evaltext is_require)} |
|
412
|
|
|
|
|
|
|
= caller($i); |
|
413
|
|
|
|
|
|
|
@args = @DB::args; } |
|
414
|
|
|
|
|
|
|
return () unless defined $info{package}; |
|
415
|
|
|
|
|
|
|
# Work out the effective sub name, or eval, or method ... |
|
416
|
|
|
|
|
|
|
my $call = ''; |
|
417
|
|
|
|
|
|
|
if (defined $info{evaltext}) { |
|
418
|
|
|
|
|
|
|
my $eval = $info{evaltext}; |
|
419
|
|
|
|
|
|
|
if ($info{is_require}) { |
|
420
|
|
|
|
|
|
|
$call = "require $eval"; } |
|
421
|
|
|
|
|
|
|
else { |
|
422
|
|
|
|
|
|
|
$eval =~ s/([\\\'])/\\$1/g; |
|
423
|
|
|
|
|
|
|
$call = "eval '" . trim($eval) . "'"; } } |
|
424
|
|
|
|
|
|
|
elsif ($info{sub} eq '(eval)') { |
|
425
|
|
|
|
|
|
|
$call = "eval {...}"; } |
|
426
|
|
|
|
|
|
|
else { |
|
427
|
|
|
|
|
|
|
$call = $info{sub}; |
|
428
|
|
|
|
|
|
|
my $method = $call; |
|
429
|
|
|
|
|
|
|
$method =~ s/^.*:://; |
|
430
|
|
|
|
|
|
|
# If $arg[0] is blessed, and `can' do $method, then we'll guess it's a method call? |
|
431
|
|
|
|
|
|
|
if ($info{has_args} && @args |
|
432
|
|
|
|
|
|
|
&& ref $args[0] && ((ref $args[0]) !~ /^(?:SCALAR|ARRAY|HASH|CODE|REF|GLOB|LVALUE)$/) |
|
433
|
|
|
|
|
|
|
&& $args[0]->can($method)) { |
|
434
|
|
|
|
|
|
|
$call = format_arg(shift(@args)) . "->" . $method; } } |
|
435
|
|
|
|
|
|
|
# Append arguments, if any. |
|
436
|
|
|
|
|
|
|
if ($info{has_args}) { |
|
437
|
|
|
|
|
|
|
@args = map { format_arg($_) } @args; |
|
438
|
|
|
|
|
|
|
if (@args > $MAXARGS) { |
|
439
|
|
|
|
|
|
|
$#args = $MAXARGS; push(@args, '...'); } |
|
440
|
|
|
|
|
|
|
$call .= '(' . join(',', @args) . ')'; } |
|
441
|
|
|
|
|
|
|
$info{call} = $call; |
|
442
|
|
|
|
|
|
|
return %info; } |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub format_arg { |
|
445
|
|
|
|
|
|
|
my ($arg) = @_; |
|
446
|
|
|
|
|
|
|
if (not defined $arg) { $arg = 'undef'; } |
|
447
|
|
|
|
|
|
|
elsif (ref $arg) { $arg = Stringify($arg); } # Allow overloaded stringify! |
|
448
|
|
|
|
|
|
|
elsif ($arg =~ /^-?[\d.]+\z/) { } # Leave numbers alone. |
|
449
|
|
|
|
|
|
|
else { # Otherwise, string, so quote |
|
450
|
|
|
|
|
|
|
$arg =~ s/'/\\'/g; # Slashify ' |
|
451
|
|
|
|
|
|
|
$arg =~ s/([[:cntrl:]])/ "\\".chr(ord($1)+ord('A'))/ge; |
|
452
|
|
|
|
|
|
|
$arg = "'$arg'" } |
|
453
|
|
|
|
|
|
|
return trim($arg); } |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# Semi-traditional (but reformatted) stack trace |
|
456
|
|
|
|
|
|
|
sub stacktrace { |
|
457
|
|
|
|
|
|
|
my $frame = 0; |
|
458
|
|
|
|
|
|
|
my $trace = ""; |
|
459
|
|
|
|
|
|
|
while (my %info = caller_info($frame++)) { |
|
460
|
|
|
|
|
|
|
next if $info{sub} =~ /^LaTeXML::Common::Error/; |
|
461
|
|
|
|
|
|
|
## $info{call} = '' if $info{sub} =~ /^LaTeXML::Common::Error::(?:Fatal|Error|Warn|Info)/; |
|
462
|
|
|
|
|
|
|
$trace .= "\t$info{call} @ $info{file} line $info{line}\n"; } |
|
463
|
|
|
|
|
|
|
return $trace; } |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
# Extract blessed `interesting' objects on stack. |
|
466
|
|
|
|
|
|
|
# Get a maximum of $maxdepth objects (if $maxdepth is defined). |
|
467
|
|
|
|
|
|
|
sub objectStack { |
|
468
|
|
|
|
|
|
|
my ($maxdepth) = @_; |
|
469
|
|
|
|
|
|
|
my $frame = 0; |
|
470
|
|
|
|
|
|
|
my @objects = (); |
|
471
|
|
|
|
|
|
|
while (1) { |
|
472
|
|
|
|
|
|
|
my (%info, @args); |
|
473
|
|
|
|
|
|
|
{ package DB; |
|
474
|
|
|
|
|
|
|
@info{qw(package file line sub has_args wantarray evaltext is_require)} = caller($frame++); |
|
475
|
|
|
|
|
|
|
@args = @DB::args; } |
|
476
|
|
|
|
|
|
|
last unless defined $info{package}; |
|
477
|
|
|
|
|
|
|
next if ($info{sub} eq '(eval)') || !$info{has_args} || !@args; |
|
478
|
|
|
|
|
|
|
my $self = $args[0]; |
|
479
|
|
|
|
|
|
|
# If $arg[0] is blessed, and `can' do $method, then we'll guess it's a method call? |
|
480
|
|
|
|
|
|
|
# We'll collect such objects provided they can ->getLocator |
|
481
|
|
|
|
|
|
|
if ((ref $self) && ((ref $self) !~ /^(?:SCALAR|ARRAY|HASH|CODE|REF|GLOB|LVALUE)$/)) { |
|
482
|
|
|
|
|
|
|
my $method = $info{sub}; |
|
483
|
|
|
|
|
|
|
$method =~ s/^.*:://; |
|
484
|
|
|
|
|
|
|
if ($self->can($method)) { |
|
485
|
|
|
|
|
|
|
next if @objects && ($self eq $objects[-1][0]); # but don't duplicate |
|
486
|
|
|
|
|
|
|
if ($self->can('getLocator')) { # Digestion object? |
|
487
|
|
|
|
|
|
|
push(@objects, [$self, Locator($self)]); } |
|
488
|
|
|
|
|
|
|
elsif ($self->isa('LaTeXML::Post::Processor') || $self->isa('LaTeXML::Post::Document')) { |
|
489
|
|
|
|
|
|
|
push(@objects, [$self, '->' . $method]); } |
|
490
|
|
|
|
|
|
|
last if $maxdepth && (scalar(@objects) >= $maxdepth); } } } |
|
491
|
|
|
|
|
|
|
return @objects; } |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
#********************************************************************** |
|
494
|
|
|
|
|
|
|
1; |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
__END__ |