File Coverage

blib/lib/Error/Show.pm
Criterion Covered Total %
statement 240 338 71.0
branch 54 108 50.0
condition 36 84 42.8
subroutine 16 18 88.8
pod 4 6 66.6
total 350 554 63.1


line stmt bran cond sub pod time code
1             package Error::Show;
2              
3 4     4   453101 use 5.024000;
  4         13  
4 4     4   20 use strict;
  4         13  
  4         97  
5 4     4   13 use warnings;
  4         15  
  4         247  
6 4     4   54 use feature "say";
  4         17  
  4         631  
7              
8              
9              
10             our $VERSION = 'v0.5.0';
11              
12 4     4   1750 use constant::more DEBUG=>undef;
  4         3636  
  4         24  
13             use constant::more {
14 4         90 PACKAGE=> 0,
15             FILENAME=> 1,
16             LINE=> 2,
17             SUBROUTINE=> 3,
18             HASARGS=> 4,
19             WANTARRAY=> 5,
20             EVALTEXT=> 6,
21             IS_REQUIRE=> 7,
22             HINTS=> 8,
23             BITMASK=> 9,
24             HINT_HASH=> 10,
25             MESSAGE=> 11,
26             SEQUENCE=> 12,
27             CODE_LINES=> 13,
28 4     4   593 };
  4         7  
29              
30             #
31             # A list of top level file paths or scalar refs to check for syntax errors
32             #
33             my @IINC;
34             sub context;
35              
36             my %programs;
37            
38             sub import {
39 4     4   36 my $package=shift;
40             # Add support for reexporters that manipulate the export level
41 4   50     57 my @caller=caller($Exporter::ExportLevel//0);;
42 4         23 my @options=@_;
43              
44              
45             # Only have one sub to export and we only export it if the caller has a line
46             # number. Otherise we are being invoked from the CLI
47             #
48 4 50       14 if($caller[LINE]){
49 4     4   3522 no strict "refs";
  4         7  
  4         18916  
50 4         5 my $name;
51 4         8 $name=$caller[0]."::context";
52 4         6 *{$name}=\&{"context"};
  4         21  
  4         12  
53              
54 4         8 $name=$caller[0]."::streval";
55 4         24 *{$name}=\&{"streval"};
  4         22  
  4         22  
56              
57 4         7 $name=$caller[0]."::throw";
58 4         6 *{$name}=\&{"throw"};
  4         10  
  4         11  
59 4         3735 return;
60             }
61              
62             #
63             # CLI Options include
64             #
65              
66 0         0 require POSIX; #For _exit;
67 0         0 require IPC::Open3;
68 0         0 require Symbol;
69 0         0 my %options;
70              
71 0         0 my $clean=grep /clean/i, @options;
72 0         0 my $splain=grep /splain/i, @options;
73 0         0 my $do_warn=grep /warn/i, @options;
74 0         0 my $no_handler=grep /no_handler/i, @options;
75              
76 0 0       0 my @warn=$do_warn?():"-MError::Show::Internal";
77              
78              
79             #
80             # 1. Command line argument activation ie -MError::Show
81             #
82             # Find out any extra lib paths used. To do this we:
83             #
84             # a. fork/exec a new perl process using the value of $^X.
85             # b. The new process dumps the @INC array to STDOUT
86             # c. This process reads the output and stores in @IINC
87             #
88             # Only run it the first time its used
89             # Is this the best way? Not sure. At least this way there is no argument
90             # processing, perl process does it for us.
91             #
92            
93 0 0       0 @IINC=map {chomp; $_} do {
  0         0  
  0         0  
94 0 0       0 open my $fh, "-|", $^X . q| -E 'map print("$_\n"), @INC'| or die "$!";
95 0         0 <$fh>;
96             } unless @IINC;
97              
98             #
99             # 2. Extract the extra include paths
100             #
101             # Built up the 'extra' array of any include paths not already listed
102             # from the STDOUT dumping above
103             #
104 0         0 my @extra=map {("-I", $_)} grep {my $i=$_; !grep { $i eq $_} @IINC} @INC;
  0         0  
  0         0  
  0         0  
  0         0  
105              
106              
107              
108             #
109             # 3. Syntax checking the program
110             #
111             # Now we have the include paths sorted,
112             # a. fork/exec again, this time with the -c switch for perl to check syntax
113             # b. slurp STDERR from child process
114             # c. execute the context routine to parse and show more source code context
115             # d. print!
116             # The proc
117              
118 0         0 local $/=undef;
119 0         0 my $file=$0;
120              
121             #push @file, @ARGV;
122              
123             #my $runnable=not $^C;#$options{check};
124             #for my $file(@file){
125 0 0 0     0 die "Error::Show cannot process STDIN, -e and -E programs" if $file eq "-e" or $file eq "-E" or $file eq "-";
      0        
126 0 0       0 die "Error::Show cannot access \"$file\"" unless -f $file;
127 0         0 my @cmd= ($^X ,@warn, @extra, "-c", $file);
128              
129 0         0 my $pid;
130             my $result;
131 0         0 eval {
132 0         0 $pid=IPC::Open3::open3(my $chld_in, my $chld_out, my $chld_err = Symbol::gensym(), @cmd);
133 0         0 $result=<$chld_err>;
134 0         0 close $chld_in;
135 0         0 close $chld_out;
136 0         0 close $chld_err;
137 0         0 wait;
138             };
139 0 0 0     0 if(!$pid and $@){
140 0         0 die "Error::Show failed to syntax check";
141             }
142              
143              
144             #
145             # 4. Status code from child indicates success
146             # When 0 this means syntax was ok. Otherwise error
147             # Attempt to propogate code to exit status
148             #
149 0 0       0 my $code=$?>255? (0xFF & ~$?): $?;
150              
151 0         0 my $runnable=$?==0;
152              
153 0         0 my $status=context( $result, splain=>$splain, clean=>$clean)."\n";
154              
155 0 0       0 if($^C){
156 0 0       0 if($runnable){
157             #only print status if we want warnings
158 0 0       0 print STDERR $do_warn?$status: "$file syntax OK\n";
159              
160             }
161             else{
162             #Not runnable, thus syntax error. Always print
163 0         0 print STDERR $status;
164              
165             }
166 0         0 POSIX::_exit $code;
167              
168             }
169             else{
170             #not checking, we want to run
171 0 0       0 if($runnable){
172             # don't bother with warnings
173              
174             # v0.4.0
175             # Install an global handler, unless asked not to
176             #
177 0 0       0 unless($no_handler){
178             $SIG{__DIE__}=sub {
179             # propagate eval and parsing errors
180 0 0 0 0   0 die @_ if $^S or ! defined $^S;
181              
182             # Otherwise hard error
183 0         0 my @frames;
184 0         0 my $i=0;
185 0         0 push @frames , [caller $i++] while caller $i;
186 0         0 say STDERR Error::Show::context bless {error=>$_[0], frames=>\@frames}, "Error::Show::Exception";
187 0         0 exit;
188 0         0 };
189             }
190              
191              
192             }
193             else{
194             #Not runnable, thus syntax error. Always print
195 0         0 print STDERR $status;
196 0         0 POSIX::_exit $code;
197             }
198             }
199             }
200              
201              
202             sub process_string_error{
203              
204 8     8 0 12 my $error_in=shift;
205 8         53 my %opts=@_;
206              
207 8         64 require Scalar::Util;
208 8         26 my @error_lines;
209             my @errors;
210             #my @entry;
211 8         0 my %entry;
212              
213            
214              
215             # Convert the object (or string) error to string.. and process the line numbers etc.
216             # This is the easiest way to support multiple Execption types.
217             #
218 8         0 my $error;
219 8   33     43 $error||="$error_in";
220              
221             #local $_=$error;
222             #Substitue with a line number relative to the start marker
223             #Reported line numbers are 1 based, stored lines are 0 based
224             #my $translation=$opts{translation};
225             #my $start=$opts{start};
226              
227 8         15 my $i=0;
228 8         29 for(split "\n", $error){
229 9         11 DEBUG and say STDERR "ERROR LINE: ".$_;
230 9 50 33     81 if(/at (.*?) line (\d+)/
231             or /Missing right curly or square bracket at (.*?) (\d+) at end of line/){
232             #
233             # Group by file names
234             #
235 9         35 DEBUG and say STDERR "PROCESSING: ".$_;
236 9         11 DEBUG and say STDERR "file: $1 and line $2";
237 9   50     108 my $entry=$entry{$1}//=[];
238             #push @$entry, {file=>$1, line=>$2,message=>$_, sequence=>$i++};
239 9         24 my $a=[];
240 9         26 $a->[FILENAME]=$1;
241 9         27 $a->[LINE]=$2-1;
242 9         22 $a->[MESSAGE]=$_;
243 9 50       28 $a->[MESSAGE]=$opts{message} if $opts{message};
244 9         21 $a->[SEQUENCE]=$i++;
245 9 50       39 $a->[EVALTEXT]=$opts{program} if $opts{program};
246 9         29 push @$entry, $a;
247             }
248             }
249              
250              
251              
252             #Key is file name
253             # value is a hash of filename,line number, perl error string and the sequence number
254              
255 8         32 \%entry;
256              
257             }
258              
259             # Takes a hash ref 'normalized error' sources, cross reference with source
260             # files and internal caching of string eval, and generates context lines around
261             # target line number
262              
263             sub text_output {
264 27     27 0 38 my $info_ref=shift;
265 27         126 my %opts=@_;
266 27         51 my $total="";
267 27         30 DEBUG and say STDERR "Reverse flag in text output set to: $opts{reverse}";
268              
269             # Sort by sequence number
270             # Errors are stored by filename internally. Sort by sequence number.
271             #
272              
273             my @sorted_info=
274 1         3 sort {$a->[SEQUENCE] <=> $b->[SEQUENCE] }
275 27         63 map { $_->@* } values %$info_ref;
  28         100  
276              
277             # Reverse the order if we want the first error listed last
278             #
279 27 100       64 @sorted_info=reverse (@sorted_info) if $opts{reverse};
280              
281             # Process each of the errors in sequence
282 27         42 my $counter=0;
283 27   50     94 my $limit=$opts{limit}//100;
284 27         46 for my $info (@sorted_info){
285 28 50 33     62 last if $counter>=$limit and $limit >0;
286 28         35 $counter++;
287 28 50       69 unless(exists $info->[CODE_LINES]){
288 28         34 my @code;
289            
290 28 100       103 if(my @f=$info->[FILENAME] =~ /\(eval \d+\)/g){
291             # Not actually a file, this was an eval
292 4         8 my $prog=$programs{$f[0]};
293 4   50     24 @code=split "\n", $prog//"";
294              
295             # Remove the cached code once its been accessed, unless we really want to keep it
296 4 100       26 delete $programs{$f[0]} unless $opts{keep};
297             }
298             else {
299 24         35 @code=split "\n", do {
300 24 50       4307 open my $fh, "<", $info->[FILENAME] or warn "Could not open file for reading: $info->[FILENAME]";
301 24         131 local $/=undef;
302 24         1280 <$fh>;
303             };
304             }
305 28         106 $info->[CODE_LINES]=\@code;
306             }
307              
308             # At this point we have lines of code in an array
309             #
310            
311             #Find start mark and end mark
312             #
313 28         41 my $start_line=0;
314 28 100       71 if($opts{start_mark}){
315 5         7 my $counter=0;
316 5         6 my $start_mark=$opts{start_mark};
317 5         12 for($info->[CODE_LINES]->@*){
318 36 100       73 if(/$start_mark/){
319 4         5 $start_line+=$counter+1;
320 4         7 last;
321             }
322 32         30 $counter++;
323             }
324             # Don't include the start marker in the results
325             }
326              
327 28         65 my $end_line=$info->[CODE_LINES]->@*-1;
328              
329 28 100       54 if($opts{end_mark}){
330 5         5 my $counter=0;
331 5         5 my $end_mark=$opts{end_mark};
332 5         8 for (reverse($info->[CODE_LINES]->@*)){
333 22 100       36 if(/$end_mark/){
334 4         5 $end_line-=$counter;
335 4         5 last;
336             }
337 18         16 $counter++;
338             }
339             }
340              
341 28 50       58 $start_line+=$opts{start_offset} if $opts{start_offset};
342 28 50       71 $end_line-=$opts{end_offset } if $opts{end_offset};
343              
344             # preclamp the error line to within this range so that 'Unmatched ' errors
345             # at least show ssomething.
346             #
347 28 50       67 $info->[LINE]=$end_line if $info->[LINE]>$end_line;
348              
349 28         28 DEBUG and say "START LINE after offset: $start_line";
350 28         30 DEBUG and say "END LINE after offset: $end_line";
351             # At this point the file min and max lines we should consider are
352             # start_line and end line inclusive. The $start_line is also used as an
353             # offset to shift error sources
354             #
355              
356 28         63 my $min=$info->[LINE]-$opts{pre_lines};
357 28         50 my $max=$info->[LINE]+$opts{post_lines};
358              
359 28         51 my $target= $info->[LINE];#-$start_line;
360 28         35 DEBUG and say "TARGET: $target";
361              
362 28 100       51 $min=$min<$start_line ? $start_line: $min;
363              
364 28 100       45 $max=$max>$end_line?$end_line:$max;
365              
366             #
367             # format counter on the largest number to be expected
368             #
369 28         54 my $f_len=length("$max");
370              
371 28   50     60 my $indent=$opts{current_indent}//"";
372 28         102 my $out="$indent$info->[FILENAME]\n";
373            
374 28         48 my $format="$indent%${f_len}d% 2s %s\n";
375 28         38 my $mark="";
376              
377             #Change min and max to one based index
378             #$min++;
379             #$max--;
380 28         32 DEBUG and say STDERR "min before print $min";
381 28         39 DEBUG and say STDERR "max before print $max";
382 28         74 for my $l($min..$max){
383 268         342 $mark="";
384              
385 268         325 my $a=$l-$start_line+1;
386              
387             #Perl line number is 1 based
388 268 100       410 $mark="=>" if $l==$target;
389              
390              
391             # Print lines as per the index in file array
392 268         587 $out.=sprintf $format, $a, $mark, $info->[CODE_LINES][$l];
393             }
394              
395 28         67 $total.=$out;
396            
397             # Modifiy the message now with updated line numbers
398             # TODO: Tidy this up
399 28 50       91 $info->[MESSAGE]=~s/line (\d+)(?:\.|,)/(($1-1)>$max?$max:$1-1)-$start_line+1/e;
  6         32  
400              
401 28 100       96 $total.=$info->[MESSAGE]."\n" unless $opts{clean};
402              
403             }
404 27 50       57 if($opts{splain}){
405 0         0 $total=splain($total);
406             }
407 27         127 $total;
408             }
409              
410              
411             #
412             # Front end to the main processing sub. Configures and checks the inputs
413             #
414             my $msg= "Trace must be a ref to array of {file=>.., line=>..} pairs";
415             sub context{
416 10 50 33 10 1 169906 shift if(defined $_[0] and $_[0] eq __PACKAGE__);
417              
418              
419 10         21 my $error=shift;
420 10 100       21 return unless $error;
421              
422 8         38 my %opts=@_;
423              
424 8         13 my $out;
425 8         14 my $do_internal_frames=1;
426              
427             #return unless $opts{error} or $opts{frames} or $do_internal_frames;
428             #$opts{start_mark};#//=qr|.*|; #regex which matches the start of the code
429 8   50     44 $opts{pre_lines}//=5; #Number of lines to show before target line
430 8   50     38 $opts{post_lines}//=5; #Number of lines to show after target line
431 8   50     31 $opts{start_offset}//=0; #Offset past start mark to consider as min line
432 8   50     62 $opts{end_offset}//=0; #Offset before end to consider as max line
433 8   50     31 $opts{translation}//=0; #A static value added to the line numbering
434 8   50     48 $opts{indent}//=" ";
435 8   50     34 $opts{file}//="";
436 8         23 $opts{current_indent}="";
437              
438              
439              
440 8 100       20 unless($opts{reverse}){
441             # Show the actual error
442 7         15 $opts{clean}=undef;
443 7         35 my $info_ref=process_string_error $error, %opts ;
444 7         37 $out.=text_output $info_ref, %opts;
445 7         53 $opts{current_indent}.=$opts{indent};
446             }
447              
448              
449              
450              
451            
452             # Convert from supported exceptions classes to internal format
453 8         15 my $frames;
454 8   66     34 $frames||=eval {$error->{frames}}; # Error::Show::Exception
  8         64  
455 8   66     21 $frames||=eval {[$error->trace->frames]}; # Exception::Class::Base ok
  3         119  
456 8   66     24 $frames||=eval {$error->caller_stack}; # Exception::Base ok
  3         94  
457 8   66     20 $frames||=eval {[$error->getStackTrace]}; # Class::Throwable ok
  3         79  
458 8   66     16 $frames||=eval {\($error->frames)}; # Mojo::Exception ok
  3         93  
459 8   100     24 $frames||=[];
460              
461             #
462 8 100 66     71 if($do_internal_frames and @$frames==0){
463 3         6 my $i=0;
464              
465             #build call frames
466 3         3 my @frame;
467             my @stack;
468              
469 3         46 while(@frame=caller($i++)){
470 3         15 push @$frames, [@frame];
471             }
472             }
473            
474 8         13 my $dstf="Devel::StackTrace::Frame";
475              
476 8         50 require Scalar::Util;
477              
478              
479             #DEBUG and ;
480              
481             # Reverse the ordering of errors here if requested
482             #
483 8         20 my @frames_copy=$frames->@*;
484 8 100       27 @frames_copy=reverse @frames_copy if $opts{reverse};
485             # Check for trace kv pair. If this is present. We ignore the error
486             #
487             # Iterate through the list
488              
489             #my %_opts=%opts;
490 8         16 $opts{clean}=1;
491 8         12 my $i=0; #Sequence number
492 8         16 for my $e (@frames_copy) {
493              
494 19         36 my $a=[];
495 19 50 50     84 if((Scalar::Util::blessed($e)//"") eq "Devel::StackTrace::Frame"){
496             #Convert to an array
497 0         0 $a->[PACKAGE]=$e->package;
498 0         0 $a->[FILENAME]=$e->filename;
499 0         0 $a->[LINE]=$e->line;
500 0         0 $a->[SUBROUTINE]=$e->subroutine;
501 0         0 $a->[HASARGS]=$e->hasargs;
502 0         0 $a->[WANTARRAY]=$e->wantarray;
503 0         0 $a->[EVALTEXT]=$e->evaltext;
504 0         0 $a->[IS_REQUIRE]=$e->is_require;
505 0         0 $a->[HINTS]=$e->hints;
506 0         0 $a->[BITMASK]=$e->bitmask;
507 0         0 $a->[HINT_HASH]=$e->hints;
508             #$e=\@a;
509             }
510             else {
511             #Copy incase multiple calls to context on same error
512 19         80 @$a=$e->@*;
513             }
514              
515             # Skip over any frames from this package
516 19 50       91 next if $a->[PACKAGE] eq __PACKAGE__;
517              
518              
519 19   50     92 $a->[MESSAGE]//="";
520              
521             #Force a message if one is provided
522 19         34 $a->[LINE]--; #Make the error 0 based
523 19 50       69 $a->[MESSAGE]=$opts{message} if $opts{message};
524 19         34 $a->[SEQUENCE]=$i++;
525              
526             # Generate the context here
527             #
528 19         27 my %entry;
529 19         83 my $entry=$entry{$a->[FILENAME]}=[];
530 19         37 push @$entry, $a;
531 19         74 $out.= text_output \%entry, %opts;
532 19         159 $opts{current_indent}.=$opts{indent};
533             }
534 8 100       23 if($opts{reverse}){
535             # Show the actual error
536 1         2 $opts{clean}=undef;
537 1         6 my $info_ref=process_string_error $error, %opts ;
538 1         4 $out.=text_output $info_ref, %opts;
539 1         9 $opts{current_indent}.=$opts{indent};
540             }
541 8         62 $out;
542             }
543              
544              
545              
546             my ($chld_in, $chld_out, $chld_err);
547             my @cmd="splain";
548             my $pid;
549              
550             sub splain {
551 0     0 1 0 my $out;
552             #Attempt to open splain process if it isn't already
553 0 0       0 unless($pid){
554 0         0 eval{
555 0         0 $pid= IPC::Open3::open3($chld_in, $chld_out, $chld_err = Symbol::gensym(), @cmd);
556             #$chld_in->autoflush(1);
557              
558             };
559 0 0 0     0 if(!$pid and $@){
560 0         0 warn "Error::Show Could not splain the results";
561             }
562             };
563              
564             #Attempt to write to the process and read from it
565 0         0 eval {
566 0         0 print $chld_in $_[0], "\n";;
567 0         0 close $chld_in;
568 0         0 $out=<$chld_out>;
569 0         0 close $chld_out;
570 0         0 close $chld_err;
571             };
572              
573 0 0       0 if($@){
574 0         0 $pid=undef;
575 0         0 close $chld_in;
576 0         0 close $chld_out;
577 0         0 close $chld_err;
578 0         0 warn "Error::Show Could not splain the results";
579             }
580 0         0 $out;
581             }
582              
583             sub streval ($;$){
584              
585             # The program we want to execute
586 2     2 1 147936 my $code= $_[0];
587 2 50       11 if(ref($code) eq "CODE"){
588 0         0 return eval {$code->()};
  0         0  
589             }
590 2   33     12 my $package=$_[1]//caller;
591              
592              
593             # Wrap the eval in a sub. Here we can seperate syntax/complile errors and run
594             # time errors
595             #
596              
597 2         4 my $file;
598              
599             # Do eval to get current eval number and then calculate the NEXT eval number
600 2         316 my $number=eval '__FILE__=~ qr/(\d+)/; $1';
601 2         25 $number++;
602 2         4 $file="(eval $number)";
603 2         7 $programs{$file}=$code;
604 2         5 my @in_sub_frame;
605             # Attempt to compile
606             #
607             my $sub;
608             {
609 2         2 local $@;
  2         5  
610             #$sub=eval "sub {package $package; \@in_sub_frame=caller(0); local \$@; my \@res=eval {$code}; if(\$@){} \@res}";
611 2         363 $sub=eval "sub {package $package; \@in_sub_frame=caller(0); $code}";
612              
613             # Check for SYNTAX error
614             #
615 2         16 my $error=$@;
616 2 100 66     13 if(!defined($sub) or $error){
617 1 50       4 if(!ref $error){
618             # extract the filename (including the () )stored in the error
619 1         7 my $filename= $error=~/\(eval \d+\)/g;
620              
621 1         2 my @frame;
622             my @stack;
623              
624 1         2 my $i=1;
625 1         3 push @stack, [@frame]; #frame from actual eval
626 1         12 while(@frame=caller($i++)){
627 1         5 push @stack, [@frame];
628             }
629              
630 1         9 my $o=bless {error=>$error, frames=>\@stack}, "Error::Show::Exception";
631 1         7 die $o;#{error=>$error, frames=>\@stack};
632             }
633             else{
634 0         0 die $error;
635             }
636             }
637             }
638              
639              
640 1         2 my $result;
641             {
642             # Check for RUNTIME error
643 1         2 local $@;
  1         1  
644 1         1 my @frame;
645 1         2 $result=eval { $sub->(); };
  1         15  
646 1         1 my $error=$@;
647 1 50       4 if($error){
648 0 0       0 if(!ref $error){
649             # extract the filename stored in the error string
650 0         0 my $filename= $error=~/\(eval (\d+)\)/g;
651 0         0 my @stack;
652 0         0 my $i=1;
653 0         0 push @stack, [@in_sub_frame]; #frame from actual eval
654 0         0 while(@frame=caller($i++)){
655 0         0 push @stack, [@frame];
656             }
657              
658 0         0 my $o=bless {error=>$error, frames=>\@stack}, "Error::Show::Exception";
659 0         0 die $o;
660             }
661             else {
662             # Rethrow as is
663 0         0 die $error;
664             }
665             }
666             }
667              
668             # otherwise return the result
669 1         2 $result;
670             }
671              
672             sub throw {
673 2     2 1 457960 my $error=shift;
674 2   33     10 $error//=$@;
675 2         17 my @c=caller(0);
676            
677 2         6 my @frames;
678 2         5 my $i=1;
679 2         17 while(my @frame=caller($i++)){
680 8         75 push @frames, \@frame;
681             }
682              
683 2 50       10 unless(ref $error){
684             # Error is just a string. so we re create the file and line number
685             # from the the caller this sub
686             #
687 2         54 die bless {error=>"$error at $c[1] line $c[2]", frames=>\@frames}, "Error::Show::Exception";
688             }
689             else {
690             # rethrow
691 0         0 die bless {error=>$error, frames=>\@frames}, "Error::Show::Exception";
692             }
693             }
694              
695             package Error::Show::Exception;
696             use overload
697 20     20   1279 '""'=>sub { "$_[0]{error}" },
698 4     4   2546 'eq'=>sub { "$_[0]{error}" eq $_[1] };
  4     5   6411  
  4         54  
  5         28  
699              
700             1;
701             __END__