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
|
|
68
|
use strict; |
|
21
|
|
|
|
|
18
|
|
|
21
|
|
|
|
|
459
|
|
14
|
21
|
|
|
21
|
|
60
|
use warnings; |
|
21
|
|
|
|
|
17
|
|
|
21
|
|
|
|
|
359
|
|
15
|
21
|
|
|
21
|
|
76
|
use LaTeXML::Global; |
|
21
|
|
|
|
|
17
|
|
|
21
|
|
|
|
|
1281
|
|
16
|
21
|
|
|
21
|
|
6930
|
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__ |