Perl provides two statements die and warn to enable the system to return a brief description of an error when an unusual situation arises during the course of execution. If the unusual situation is such that the program cannot continue any more, the die statement can be used to kill the program as well as inform the reason why the program cannot execute any more. The warn function is used when an unusual or error situation arises, but the error is forgivable and the program can continue running in spite of the problem.
The following program computes n!, the factorial of n. We know that n! is not computable for negative integers or rational numbers. The following program prompts the user to enter a non-negative integer n and computes n!. However, if the user enters something inappropriate, the program automatically dies.
Program 2.31
#!/usr/bin/perl
#file factorial1.pl
my ($n, $errorStmt);
print "Please enter a non-negative integer>> ";
$n =
chomp $n;
$errorStmt =
"n! cannot be computed for a negative integer or a non-integer!\n";
die $errorStmt if (($n < 0) or ($n =~ /[.]/));
print &factorial($n) . "\n";
#subroutine to compute factorial
sub factorial{
my ($no) = @_;
if (($no == 0) or ($no == 1)){
return 1;
}
else{
return ($no * &factorial ($no -1));
}
}
The program asks the user to enter a non-negative integer, and reads the user’s input. However, it is quite possible that the user does not enter a non-negative integer although the program explicitly asks for one. A user can do so absent-mindedly, or if the user does not understand the program’s instruction. In this case, the instruction is clear and simple, but in the case of more complex instructions, there is room to be misunderstood. Here, the die statement is used in conjunction with validation of the input.
die $errorStmt if (($n < 0) or ($n =~ /[.]/));
Any single or simple statement in Perl can have a modifier such as an if clause. There are several modifiers that are allowed. This is not an if statement; the statement is die with an if clause as modifier.
die is called with one argument. It is the string to be printed when die is invoked. In this case, the string to be printed is $errorStmt. The conditional associated with the if modifier is
(($n < 0) or ($n =~ /[.]/));
The die statement is invoked if the value read, $n, is less than 0 of if it contains a period inside. We are making the simplistic assumption that the presence of a period indicates it is an number with a decimal point in it. Of course, one can enter a string that has non-digits, several decimal points, etc., but we are not worried about an exhaustive validation of the input here.
The main program calls the factorial subroutine with $n as the sole argument and prints the returned value. The call to the subroutine has an ampersand (&) in front. The & is not essential when invoking functions, but some programmers use it to indicate explicitly that it is a call to a subroutine.
The factorial subroutine is recursive since there is a call to itself embedded in its body. The incoming argument list @_ contains only one element. It is fished out and is called $no locally inside the body of the subroutine. As mentioned earlier, writing proper termination conditions is of utmost importance in writing a recursive function. Otherwise, the subroutine will continue for ever. Here, if $no is 0 or 1, the value returned by the factorial subroutine is 1. This corresponds to the mathematical facts
0! = 1! = 1.
If the value of $no is larger than 1, the value returned is computed by the following expression.
$no * &factorial ($no -1)
The call to the function we are writing, viz., factorial in this expression makes this subroutine recursive. It must be emphasized that the argument given to the recursive call to factorial is $no -1. This argument is one less than the value of $no, the argument passed to the original call to factorial. This ensures that as factorial is called again and again recursively, the value of the argument comes down by 1 each time. Since the argument becomes smaller every time and starts with a non-negative integer, after a finite number of recursive calls to factorial, the argument becomes 1. At this point, the call to factorial immediately quits, returning 1 due to the termination condition. This call is our savior ensuring proper termination of the recursive process. When the last call returns 1, the call just prior to it performs the multiplication to produce 2!. This call returns the value, and the call prior to it computes 3!. Similarly, 4!, 5!, , etc., are computed. Finally, the first call to factorial performs the multiplication that computes n! and returns it to the main program. This is the value printed by the print statement in the bottom of the main program.
The die statement is frequently used with file open or socket opening, among other situations. If we are trying to open a file to read, write or append to, and the file cannot be opened for the purpose at hand, quite frequently the program should not proceed any more. The following program is a modified version of the file copying program first discussed in Section 1.6.2. To copy a file, we need to open a file for reading and open another file for writing at the same time. We read a line from the first file and write it to the second. This continues till all lines have been copied.
Program 2.32
#!/usr/bin/perl
#file copyDie.pl
#copying everything from one file to another
use strict;
my ($oldfilename, $newfilename);
print "Please give me a file name >> ";
$oldfilename =
chomp ($oldfilename);
$newfilename = $oldfilename . ".new";
open (IN, $oldfilename) or die "Cannot open $oldfilename for reading: $!";
open (OUT, ">$newfilename") ||
die "Cannot open $newfilename for writing: $!";;
while (
print OUT $_;
}
close (IN) or die "Cannot close $oldfilename: $!";;
close (OUT) or die "Cannot close $newfilename: $!";;
print "File `$newfilename' is an exact copy of file `$oldfilename'\n";
The program prompts for a file name, chomps the \n that follows to obtain the string containing the file name only. The opening of the two filehandles is done by the following statements.
open (IN, $oldfilename) or die "Cannot open $oldfilename for reading: $!";
open (OUT, ">$newfilename") || die "Cannot open $newfilename for writing: $!";;
These statements show the use of die as well as the use of orand || as a means of controlling the flow of execution. Let us look at the first of these two statements. The or operator connects two full-fledged statements
open (IN, $oldfilename)
and
die "Cannot open $oldfilename for reading: $!"
each one of which can be written as independent statements terminated by ;. The first statement is an open statement that returns a non-false or true value if successful. If unsuccessful in opening a file for reading for whatever reasons, it returns false. If the first statement is successful, or is happy and skips looking at the second statement. Thus, the die statement is skipped if the file can be opened for reading. However, if the file cannot be opened, the die statement is executed. The die statement takes a string as its argument. The string contains a programmer written error message. It also uses the system generated error string $!. This special variable contains the description of the error as Perl sees it. The second statement uses the || version of logical disjunction. The program contains two additional such die statements that work with the closeing of filehandles.
In this program, we see two versions of the logical disjunction operation: or and ||. Both are essentially the same except that or has much lower precedence than ||. or is also a more readable version of the logical disjunction. Similarly, and is a more readable version of the logical conjunction. open is considered a list operator because it takes a list of two elements as an argument. The precedence of the logical operators written in words is much lower than most operators. As a result, when the logical operators written as word are used, there is less need to parenthesize the operands.
The $! when used in a string context, prints an error message. In a non-string context it prints an error number. A problematic run of the program is given below along with an error message.
%copyDie.pl
Please give me a file name >> abc.pl
Cannot open abc.pl for reading:
No such file or directory at copyDie.pl line 13,
The printout of the program has been broken into two lines so that it does not print outside the margin.
Sometimes when an error occurs and the error is not so serious in that the program can continue although something happened that was not quite expected. In such a case, it makes sense to type a warning instead of fieing. For example, we can rewrite the factorial program given above so that we allow a user to enter a non-negative relational number, i.e., a number with decimal digits in it. We truncate it off to make an integer out of it and proceed with factorial computation. However, if the number entered is negative the program dies. The program is given below.
Program 2.33
#!/usr/bin/perl
#file factorial3.pl
my ($n, $errorStmt1, $errorStmt2);
print "Please enter a non-negative integer>> ";
$n =
chomp $n;
$errorStmt = "ERROR: n! cannot be computed for a negative integer: $!\n";
$warnStmt =
"Warning: n! cannot be computed for a rational, but will truncate: $!\n";
die $errorStmt if ($n < 0);
if ($n =~ /^\d+[.]\d+$/){
warn $warnStmt;
$n = int ($n);
}
print "Computing $n!\n";
print &factorial($n) . "\n";
#subroutine to compute factorial
sub factorial{
my ($no) = @_;
if (($no == 0) or ($no == 1)){
return 1;
}
else{
return ($no * &factorial ($no -1));
}
}
The die statement checks to see if $n < 0 and kills the program if so. The warn statement is inside the if block. It is invoked if the conditional given below is satisfied.
$n =~ /^\d+[.]\d+$/
This requires the value of $n to be a string that contains one or more digits, followed by the decimal point, followed by one or more digits. If the condition is satisfied, the warning message is printed. It is followed by truncating the $n by keeping its integer part using the built-in function int. The factorial subroutine is called with the value of $n, whether the original or modified.