File Coverage

blib/lib/LaTeXML/Common/Error.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


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__