Advanced Perl Programming by Sriram Srinivasan The unconfirmed error reports are from readers. They have not yet been approved or disproved by the author or editor and represent solely the opinion of the reader. Here's a key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification This page was updated December 22, 2003 UNCONFIRMED errors and comments from readers: {xix} http://tpj.com/tpj.com/tpj/contest should read http://tpj.com/tpj/contest {xix} The Obfuscated C Code Contest's URL is: http://www.ioccc.org/ The Perl Journal's Obfuscated Perl Contest URL has a duplicate "tpj.com", but there seem to be other problems. It *appears* to now be at http://fahrenheit-451.media.mit.edu/tpj/contest/ {xx} the FTPMAIL-section: The cd-command should end in "advanced_perl", not "advanced.perl", shouldn't it? [11] 5th paragraph: The way references to anonymous values behave in Perl is not at all the way pointers to anonymous values behave in C. In C, if you make a pointer to a variable that goes out of scope, the pointer will point to "garbage". In Perl, the garbage collector ensures that if a references refers to a variable that goes out of scope, that the reference will remain valid. Perl references behave like Java references. {13} line 8: Apostrophes around /bin/rm * should be back ticks. (14) Figure 1-2: The line: name sue should be: name Sue [14] last paragraph: There are two ways that C can implement "multidimensional arrays": either as arrays of arrays, or arrays of pointers to arrays. The way Perl implements multidimensional arrays is similar to C's arrays of pointers to arrays, like in the following code: int **a = malloc(10 * sizeof(int *)); for (i = 0; i < 10; i++) a[i] = malloc(10 * sizeof(int)); C also can have arrays of arrays, like in the following code: int a[10][10]; C's arrays of arrays are laid out in a contiguous stream of bytes. When the statement is made the Perl implements multidimensional arrays is a way similar to C, the author explains that pointers are used, as in the first example, but that the data is contiguous, as in the second example. The bottom line is that Perl's multidimensional arrays are totally unlike the vast majority of C multidimensional arrays, which are arrays of arrays. Perl's multidimensional arrays are, however, very much like Java's multidimensional arrays. {24} Figure 2-1: last line of C shaded box on left side of diagram should read } foo = {10, "good" }; {25} MAT2 should have 3 rows to be usable in Example 2-1, page 26. {26} line 11 in Example 2-1: push (@{$matrix_name}, \@row;) # insert the row-array into should read push (@{$matrix_name}, \@row); # insert the row-array into {28, 29} sample data files / sample code: The sample data files on 28 list student Garibaldi as id 52003; the sample code on 29 lists Garibaldi as id 42343. {29} para. 5: The example student structure doesn't list any courses, but the following text discusses foreign keys versus references as though courses were listed in the sample. The text also refers to HS201 as a course taken by Garibaldi; in fact, it's listed in the data files as a course taught by Schumacher and not taken by Garibaldi. [30] Example 2-3: In Ex 2-3, there needs to be an an elsif clause to read the professor's name into curr_prof->{name}. Try elsif ($line =~ /^Name:.*\s*(.*)/) { $curr_prof->{name} = $1; } (30) Example 2-3: On pages 30 and 31 in Ex 2-3 and 2-4, change all occurances of {name} to {Name}, to be consistent with the other hash keys especially since the student hash on page 29 uses "Name". {30} first code, before last line: The file F is opened but is not closed. {31} code line 4, Regex in Line 4: /(A-Za-z]+).*(\d+)-(\d+)/ This won't work for Starting-Hours >= 10, because the .* in front of the first (\d+) will get the first digit. The \d+ will only get the second digit. Make .* non-greedy (.*?) or change it to [^0-9]* to prevent it from "eating" digits. {31} lines 7 and 13 in Example 2.4: course_get_hours: sub course_get_hours{...} is missing. {31} Example 2-4, line 10: ... $course_taught\n"; should be ... $r1_courses->[$i]\n"; {31} first code line 10: The line "$to = 19 if $to == 7;" is not needed. It is handled by the next line that tests for $to <= 7. [31] line 12 of Example 2.4: "for $j" should start with $i+1 (instead of $i) to avoid checking overlaps of a course with itself. {31} line 12 of "Example 2.4"; What you have: for $j ($i+1 .. etc.) What it should be: foreach $j ($i+1 .. etc.) {31} second code, line -11: There is no variable $course_taught even through is it referenced in this print statement. Should be $r_courses->[$i]. [31] line -4 of Example 2.4: missing closing brace (opening { in line -8 is unbalanced) {33} line 1 of first code example: die "..." should terminate with $! instead of $:. {36} } elseif ($ref_type eq "SCALAR") { should read } elsif ($ref_type =~ /SCALAR/) { {38} first sentence after code: Example 14-4 does not, as stated, invoke the "cascade" method. [42] code under heading "Typeglobs": Change the lines: print "@potato\n"; #prints "Wow!" print @potato, "\n"; #prints "idahorusset" to print @potato, "\n"; #prints "Wow!" print "@potato\n"; #prints "idaho russet" (The comments are just a little off.) {53} line 11: comment should be: # Prints "Batman and Robin\n" {53} 3rd line of last example: comment should read: # prints "foo called\n" [54] Example 4-1, line 11: The line &($rhOptions{"_default_"}); appears to reference the function but it has no parameter list. should be &($rhOptions{"_default_"})(); {54} second code example: I could not get the reference call to the "default" subroutine to work in the dispatch table example: &{$rhOptions{"_default_"}}; Nor could I get the reader's (unofficial) correction to work. Copying the example code given in the book's code a block above the aforementioned line did, however, work: i.e. else { if(exists $rhOptions->{"_default_"}) { $rsub = $rhOptions->{_default_}; &$rsub(); } } [61] Example 4-3; First line of the routine "even_number_printer_gen" should be: my ($input) = $_{0}; or my ($input) = shift; {63} print $random_iter1(), " ", $random_iter2(), "\n" should be print $random_iter1->(), " ", $random_iter2->(), "\n" or print &$random_iter1(), " ", &$random_iter2(), "\n" {68} Figure 5-1: Lines 1 and 2 of the figure should be swapped to match the code on page 67. [71] under exceptions.pl; "catch takes a piece of code as a string (instead of as a block, as the previous example)" My question is: should the 'catch' in the referenced example have as its argument a block? It doesn't seem to. {75} section 5.5.1 under Example 5.2: It says: "If @patterns contains the three strings "^abc", "ghi", "efg$", for example, the code supplied to eval looks like this: while (<>) {if (/^foo/ && /bar$/ && /ghi/) {print $_;}}" The Perl code line should read: "while (<>) {if (/^abc/ && /ghi/ && /efg$/) {print $_;}}" {77} line 11: $tmp .= " " x (7 - length($tmp)); extends $tmp to its size of 7 characters. Of course, that is only necessary if $tmp might be shorter than 7 characters. But that is impossible, as $s is extended (in line 9) enough to guarantee the length of 7 characters for $tmp. The same applies to lines 13/15 and to the generating sourcecode on page 79. ?79? This one is stylistic--it doesn't actually matter on the code given, but given that using $` is, um, considered harmful, wouldn't the tab expansion code be better represented as: while ($buf =~ m/\t/g) { my $loc = pos($buf) - 1; substr ($buf,$loc,1) = ' ' x (8 - ($loc % 8)); } ?79? line -6: # expand leading tabs first-the common case The comment suggests that leading tabs are expanded first for efficiency, but it is almost always a bad idea to code for efficiency--at least until you've benchmarked the code. You might want to show how to expand leading tabs separately for instructional value, but then the comment should say that. [82] 1st paragraph in 'Java'; The author states that it is not possible to generate new java code on the fly. It is possible to get the compile class and make it compile a new .java file and then get the compiled class by Class.forName... compilerArgs = new String[5]; compilerArgs[0] = "-classpath"; compilerArgs[1] = "/usr/lib/java/jre/lib/rt.jar:..."; compilerArgs[2] = "-d"; compilerArgs[3] = outputDir; compilerArgs[4] = javaFilename; compilerClass = Class.forName( "com.sun.tools.javac.Main"); compilerObject = compilerClass.newInstance(); compileMethod = compilerClass.getMethod( "compile", new Class[] { (new String [] {}).getClass() }); compileResult = ( (Integer) compileMethod.invoke( compilerObject, new Object[] { compilerArgs } ) ).intValue(); if ( compileResult == 0 ) { System.out.println( "compile was successfully..."); } (83) Don Pardo's papers have left the URL given, and no forwarding information is provided. (85) 3rd code example: The line package B; should be in bold since it is the point of the example. {88} para. 1, lines 1-2: "Perl first looks for the file given to use() or require() in the current directory and then looks up the @INC built-in array to search the include paths." This sentence is incorrect. Perl searches for used or require'd files in @INC. By default, the current directory is in @INC, but it is at the end, so it is searched *after* all the other paths in @INC. (92) two thirds down the page: "Example 6-1 shows one way to do write this subroutine" ^^^^^^^^ {93} line -1: "The double colon gets translated to a filename separator, because the colon has a special significance for DOS filenames." Well...no. The double colon gets translated to a filename separator so that the package name becomes a valid path. Consider: OS Package -> Path Unix Foo::Bar -> Foo/Bar.pm DOS Foo::Bar -> Foo\Bar.pm Saying that the double colon is translated in order to accommodate DOS misapprehends both the translation mechanism and the history of Perl, which was originally developed for Unix, not DOS. (95) 2nd paragraph in "Accessing the Symbol Table": "We will also use this property is in Chapter 11," ^^ {105} (and following pages): The entries "hours_worked" and "overtime_hours_worked" are being used in "compute_ytd_income," but are nowhere defined. {109} It is not impossible to achieve the same effect in one line with the indirect notation. See the Camel book, p295. {111} para. 2, line 4: "allocate uses this to bless the object directly into the inherited class." Derived classes "inherit" things from base classes. You can reasonably say that a derived classes "inherits" its base class. But even granting some abuse of notation, it is wrong to refer to a derived class as the "inherited" class. This is particularly unfortunate in this paragraph, because it is essential that the user clearly understand the inheritance relationships in order to understand why allocate() shouldn't hardcode the class name. The sentence might be better written as "allocate uses this to bless the object directly into the HourlyEmployee class." {118} sub price has one too many }s (I think this is right, but don't want to rely on my own scant Perl knowledge. -efm) {119} After line 6 in Example 7-1, I had to add use StoreItem; before @ISA = qw(StoreItem); {121} In the "Resources" section of chapter 7, there is a reference to the comp.object FAQ. The URL given brings one to an empty directory; it appears that comp.object no longer has a FAQL. {121} "Resources" section; comp.object FAQ apparently is now archived at: http://www.faqs.org/faqs/by-newsgroup/comp/comp.object.html (127) 2nd line below Figure 8-2: "@free" should read "@_free". {130} code sample 2: Delete line 4: "my (@retcal);". {136} There is a general way to get interface inheritance in Perl. Define your data-structure in a way that uses an object in the class that you want to inherit from. (That is, use containment.) Do not include that package in @ISA. Use an AUTOLOAD subroutine for unknown calls that passes the base object to the class that you inherit from. BINGO! You have a module that inherits an interface even if the base class changes. {140} In table 9-1, in the right-hand column, the FETCH could maybe do with a: my $obj = shift; $obj->get_temp(); and the STORE: my ($obj, $t) = @_; $obj->set_temp($t); At the moment there's a mix of $obj and $ac. (142) In Table 9-2, perhaps you could add a ";" after lines 1 and 3 in column 1 to match the other lines. [143] sub FETCH {; If the first line requested from the tied array is the second line of the file the seek will fail. Change: if ($index > @$rl_offsets) { To either: if ($index > $#$rl_offsets) { Or: if ($index >= @$rl_offsets) { {144} Example 9-2, line 13: last if $last_index > $index; Perhaps it should read last if $last_index >= $index; so it does not overshoot the line. {156} code sample 1, line -1: syswrite(F, $msg, length($msg)); # can also use write() or print() This is bizarre and/or wrong. The ordinary way to code this is print F $msg; Using syswrite() might make the code run a bit faster, but it also invites bugs from programmers who forget (or don't know) that mixing syswrite() and print() is treacherous. write() is for outputting formats: it won't work at all. {160} Berkeley DB, 1st para, 2nd line: Is "B+Tree" correct? (Shouldn't the "+" be a "-"?) {162-163} lines -2 and -1: $sth = $dbh->prepare('insert into emptable (name, age) values (?, ?)'); and $sth->execute($id, $name, $age); These two lines seem inconsistent. There are 2 question marks in the prepare() call, but 3 parameters in the execute() call. {184} code sample, lines 9 and 10 (rule 2): $query =~ s/\\[# Hopefully \200 and \201 aren't being $query =~ s/\\["]/\201/g; # being used. The first line is not correct. It should probably appear as follows: $query =~ s/\\[']/\200/g; # Hopefully \200 and \201 aren't $query =~ s/\\["]/\201/g; # being used. (190) -8: (TCP/IP) "Transport" should read "Transmission." {191, 192, 194} In the code samples "LocalHost" should be "LocalAddress." {196} The code is a continuation of the same example but $main_sock changes to $main_socket. {204} second code, line 6: print "Server created. Waiting for events"; should be print "Server created. Waiting for events.\n"; [213] code in the BEGIN block: eval { require POSIX; POSIX->import(qw(F_SETFL O_NONBLOCK EAGAIN)); }; should be: eval { require POSIX; POSIX->import(qw(F_GETFL F_SETFL O_NONBLOCK EAGAIN)); }; F_GETFL is missing. [218] lines 5 and 7: "new_rpc_server" should read "new_server." [220] sub _incoming_msg; Using Perl 5.8 on Linux it appears that the behaviour of defined() on blank values has changed. I found I needed to add this last line to the _incoming_msg routine. On the "final" call to this routine, $msg is defined, but empty. sub _incoming_msg { my ($conn, $msg, $err) = @_; return if ($err); # Need better error handling. return unless defined($msg); if ($msg eq "") {return{}}; # perl 5.8 seems to treat defined differently {228} Lines 15, 18, 21 (Heading : Images as line 1): $image = $label->Bitmap(file => 'face.xbm'); etc Each of these lines must read $image = $label->Bitmap(-file => 'face.xbm'); etc.... ie, must have a hyphen before the word "file" (also on the CD). [231]: 2nd code block: $canvas->coords ($id, 10, 100); should be $canvas->coords ($id, 10, 100, 100, 100); # needs four coordinates. ?235, 246? Are the keys -expand, -fill, -side and -text correct? If yes, I'd liked to read an explanation for these "funny" negative keys somewhere. {238} second para below code: Change "explicitly using the add method" to "explicitly using the insert method." {239} second line of code: "[N$widget]" should be "[$widget]". {243} Example 14-7: Basically, the named parameters to the hlist methods should all start with a hyphen: "image =>" should be "-image =>", "text =>" should be "-text =>", "imagetext" should be "-imagetext", etc. [251] first footnote: www.neosoft.com is an ISP, not a resource for Tcl/TK. Now if I can just find a complete reference to the technique on page 239 about multiple list boxes with one scrollbar. Your example doesn't describe how to have the scrollbar actually send the yview command to multiple widgets, it just talks around it and implies that it's possible. Multiple text boxes can send the yscrollcommand to the scrollbar - that's fine. It's just getting the scrollbar to send the yview command to more than one text box needs an actual example. The text talks about keeping multiple boxes in synch, but then just describes how to get the yview command sent to a single widget. [282] Table 17-1, Directives Recognized by Jeeves: Table 17-1 mentions tHAT Jeeves recognizes "@IF, @ELSIF, @ELSE, @END" as directives for the Template Parser. These directives are not recognized by either Jeeves or the template parser. They are not found on CPAN or in the associated examples tar ball on your FTP site. The bad directives are also mentioned in the postscript documentation in the examples tar ball. {283} Regex containing "FOREACH" (line 11): } elsif ($line =~ /^\s*\@FOREACH\s*(\w*)\s*(.*)\s*/i { The last \s* is useless, because .* will "eat" all whitespace. Why not write (\w+) instead od (\w*) to ensure that at least one word-char. is present? (302) On page 302 of Advanced Perl Programming, there is a typo in the draw_mandel code. The code reads INIT: if (width > 400) { fprintf (stderr,"Width cannot exceed 400. Truncating.\n"; width = 400; } while it should read: INIT: if (width > 400) { fprintf (stderr,"Width cannot exceed 400. Truncating.\n"); width = 400; } The ) at the end of fprintf is missing. [314] 1st paragraph: not a bug, but a Bad Thing page 314, the author creates a custom perl interpreter called ex. The problem is, on each UN*X system, the standard text editor is vi, which can also be invoked as view (read-only mode) or ex (command line mode). Depending on your PATH setting, you might get either the text editor or the custom perl interpreter when you type: ex search.pl Yet, page 39 and page 127, the author frowns upon using the same identifier for a scalar and an array, for example. IMHO, this is confusing but not dangerous. On the other hand, the present ex problem is confusing *and* dangerous. The custom perl interpreter ought to get another name. {316} Ex. 19-3: Near the end of the example, there are these lines: for (num = 1; num <= 7; num++) { char buf[20];*buf = '\0'; that should be for (num = 1; num <= 7; num++) { char buf[20]; *buf = '\0'; I believe. There are also a lot of tabs in code (that's how I found this) that should be cleaned up. {317} last line: The URL might need to be changed to: "http://perl.apache.org/" <319-371> In the examples of chapter 20 there are many possible buffer overflows. All the strcpy should be strncpy. {325} lines 2 and 3: In Figure 20-2 I can see only one of the two mentioned pointers to "add's children. But maybe I didn't understand it. [330] 11th definition: The book lists svREFCNT_dev(SV *), but the actual function is SvREFCNT_dec(SV *). Note the capitalization of the first letter. See the prototype in Perl 5.6.0, sv.h. {330} 5th macro listed - table 20-1; APP 1st edition (aug 97) The listing for the fifth macro on page 330, table 20-1, reads as follows: 'sv_setpv (SV*, char *, int len' It is missing the terminating paren (and possibly a semi-colan). An additional, though probably more stylistic inconsistancy is that only some of the listings in this table are semi-colan terminated. {331} There is a reference to the function sv_dump(SV*). This function will only produce useful output if you have DEBUGGING #DEFINED when you compile Perl. If you do not have DEBUGGING defined, then the call will produce no output. {340} very first line, edition of 8/1997; "So if xhv_keys exceeds xhv_fill, Perl takes it ..." xhv_fill should be xhv_max (source: perl-5.6.1/hv.c:423) {355} Second code listing (3rd paragraph); APP - first edition (aug. '97) In the second code listing on page 355 (Ch 20, section 'The Called Side: Hand-Coding an XSUB, subsection 'Returning a variable list of results') has the following statement: sv_setpv(ST(i), "hello", 0); /* Like modifying $_[i] */ I'm not certain of the error here, but this call is inconsistant with the definition given in table 20-1 (page 330), which reads: sv_setpv(SV *, char *) Note two arguments in table 20-1, and three in the example (pg 355). Perhaps the example was intended to be a call to sv_setpvn ? Or perhaps the third argument in the example was not meant to be there? {390} paragraph numbered 22: In Appendix B, paragraph 22, the line ${"i"} = 10; # sets $foo to 10 should read either (most likely) ${"$i"} = 10; # sets $foo to 10 or (unlikely) ${"i"} = 10; # sets $i to 10 {page?} Possible bugs a reader found in the module Msg.pl: I tested it on AIX and perl5.004_01. - The BEGIN block does not work as expected. It seems that $blocking_supported in this block is some other value as the my $blocking_supported = 0; defined in the program. I also don't understand this, (it should work ??) - The function calls $conn->set_non_blocking(), $conn->set_blocking() will not work as intended; in these functions fcntl is called with the hash reference. It should be: fcntl($_[0]->{sock} , ... - On AIX when syswrite blocks, $! will be set to "Resource temporarily unavailable" instead of EAGAIN. So _err_will_block() will not work as expected. This is not a bug of course but a machine dependency. {example archive} There are a couple of things in the tetris.pl example which are in the examples archive for Advanced Perl Programming that don't seem to work quite right. My perl : 5.005_02 My Tk.pm : Tk800.012 At line 482 (the first statement in sub create_screen{}), the program as shipped does: $w_top= MainWindow->new('Tetris - Perl/Tk') ; which dies with "Odd number of argsMainWindow->new(Tetris - Perl/Tk)" This one can be fixed by: $w_top = MainWindow->new() ; $w_top->title('Tetris - Perl/Tk') ; The second one is little more subtle. On line 154, the comparison "$new_col > $MAX_COLS" isn't right--there *are* ten columns, but they're numbered starting from zero. You can demonstrate the problem with this by moving pieces to the right edge and rotating them. Depending on the shape of the piece, you can get the piece to run off the edge of the playfield and stick. To fix this, all you have to do is change the comparison to: ($new_col >= $MAX_COLS) {EXAMPLES} ftp://ftp.oreilly.com/published/oreilly/nutshell/advanced_perl/examples.tar.gz The file "examples/Jeeves/lib/Ast.pm": sub get_prop_value { my ($this, $prop_name) = $_[0]; return $this->{$prop_name}; } should be sub get_prop_value { my ($this, $prop_name) = @_; return $this->{$prop_name}; } Tetris Bug Fix: Bug = when object is on the right wall and you rotate - the object can be frozen before it hits bottom and the object will partially go outside the right boundry. sub rotate { # rotates the block counter_clockwise return if (!@cells); my $cell; # Calculate the pivot position around which to turn # The pivot is at (average x, average y) of all cells my $row_total = 0; my $col_total = 0; my ($row, $col); my @cols = map {$_ % $MAX_COLS} @cells; my @rows = map {int($_ / $MAX_COLS)} @cells; foreach (0 .. $#cols) { $row_total += $rows[$_]; $col_total += $cols[$_]; } my $pivot_row = int ($row_total / @cols + 0.5); # pivot row my $pivot_col = int ($col_total / @cols + 0.5); # pivot col # To position each cell counter_clockwise, we need to do a small # transformation. A row offset from the pivot becomes an equivalent # column offset, and a column offset becomes a negative row offset. my @new_cells = (); my @new_rows = (); my @new_cols = (); my ($new_row, $new_col); while (@rows) { $row = shift @rows; $col = shift @cols; # Calculate new $row and $col $new_col = $pivot_col + ($row - $pivot_row); $new_row = $pivot_row - ($col - $pivot_col); $cell = $new_row * $MAX_COLS + $new_col; # Check if the new row and col are invalid (is outside or #something # is already occupying that cell) # If valid, then no-one should be occupying it. if (($new_row < 0) || ($new_row > $MAX_ROWS) || ($new_col < 0) || ($new_col > $MAX_COLS -1 ) || $heap[$cell]) { return 0; } push (@new_rows, $new_row); push (@new_cols, $new_col); push (@new_cells, $cell); } # Move the UI tiles to the appropriate coordinates my $i= @new_rows-1; while ($i >= 0) { $new_row = $new_rows[$i]; $new_col = $new_cols[$i]; $w_heap->coords($tile_ids[$i], $new_col * $TILE_WIDTH, #x0 $new_row * $TILE_HEIGHT, #y0 ($new_col+1) * $TILE_WIDTH, #x1 ($new_row+1) * $TILE_HEIGHT); $i--; } @cells = @new_cells; 1; # Success }