File Coverage

lib/Games/Checkers/PDNParser.pm
Criterion Covered Total %
statement 15 70 21.4
branch 0 30 0.0
condition 0 18 0.0
subroutine 5 8 62.5
pod 0 3 0.0
total 20 129 15.5


line stmt bran cond sub pod time code
1             # Games::Checkers, Copyright (C) 1996-2012 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16 1     1   1070 use strict;
  1         2  
  1         23  
17 1     1   4 use warnings;
  1         1  
  1         27  
18              
19             package Games::Checkers::PDNParser;
20              
21 1     1   4 use Games::Checkers::Board;
  1         2  
  1         13  
22 1     1   4 use Games::Checkers::Rules;
  1         1  
  1         21  
23 1     1   380 use IO::File;
  1         6981  
  1         924  
24              
25             sub new ($$;$) {
26 0     0 0   my $class = shift;
27 0           my $filename = shift;
28 0           my $variant = shift;
29              
30             -r "$filename.$_" and $filename .= ".$_"
31 0   0       for qw(pdn.gz pdn.xz pdn.bz2 pdn gz xz bz2);
32 0 0         my $file_to_open =
    0          
    0          
33             $filename =~ /\.gz$/ ? "zcat $filename |" :
34             $filename =~ /\.xz$/ ? "xzcat $filename |" :
35             $filename =~ /\.bz2$/ ? "bzcat $filename |" :
36             $filename;
37 0           my $fd = new IO::File $file_to_open;
38 0 0         die "Can't open PDN for reading ($filename)\n" unless $fd;
39              
40 0           my $self = { fn => $filename, fd => $fd, lineno => 0, variant => $variant };
41 0           bless $self, $class;
42 0           return $self;
43             }
44              
45             sub error_prefix {
46 0     0 0   my $self = shift;
47 0           "Error parsing $self->{fn}, line $self->{lineno}, corrupted record:\n";
48             }
49              
50             sub next_record ($) {
51 0     0 0   my $self = shift;
52              
53 0           my $values = {};
54              
55 0           my $line;
56 0           my $not_end = 0;
57 0           while ($line = $self->{fd}->getline) {
58 0           $self->{lineno}++;
59 0 0         next if $line =~ /^\s*(([#;]|{.*}|\(.*\))\s*)?$/;
60 0           $not_end = 1;
61 0 0         if ($line =~ /\[(\w+)\s+"?(.*?)"?\]/) {
62 0           $values->{$1} = $2;
63 0           next;
64             }
65 0           last;
66             }
67 0 0         return undef unless $not_end;
68              
69 0           for (qw(White Black)) {
70 0 0         next unless $values->{$_};
71 0           $values->{$_} = join(' ', reverse split(/\s*,\s*/, $values->{$_}));
72             }
73              
74 0           my $result = $values->{Result};
75 0 0         die $self->error_prefix . "\tNon empty named value 'Result' is missing\n"
76             unless $result;
77 0           my $lineno = $self->{lineno};
78              
79 0   0       my $variant = $values->{GameType} || $self->{variant};
80 0           Games::Checkers::Rules::set_variant($variant);
81 0           my $board = Games::Checkers::Board->new($values->{FEN});
82              
83 0           my $move_string = "";
84 0   0       while (!$move_string || ($line = $self->{fd}->getline) && $self->{lineno}++) {
      0        
85 0           $line =~ s/[\r\n]+$/ /;
86 0           $move_string .= $line;
87 0 0         last if $line =~ /\Q$result\E\s*$/;
88              
89             # tolerate some broken PDNs without trailing result separator
90 0           my $next_char = $self->{fd}->getc;
91 0 0         last unless defined $next_char;
92 0           $self->{fd}->ungetc(ord($next_char));
93 0 0         last if $next_char eq "[";
94             }
95              
96             # tolerate some broken PDNs without result separator
97             # warn $self->error_prefix . "\tSeparator ($result) from line $lineno is not found, continuing anyway\n"
98             # unless $line =~ /\Q$result\E\s*$/;
99              
100 0           $move_string =~ s/\Q$result\E\s*$//;
101 0           $move_string =~ s/{[^}]*}//g; # remove comments
102 0           $move_string =~ s/\([^\)]*(\)[^(]*)?\)//g; # remove comments
103 0           $move_string =~ s/([x:*-])\s+(\d|\w)/$1$2/gi; # remove alignment spaces
104 0           my @move_verge_strings = split(/(?:\s+|\d+\.+\s*)+/, $move_string);
105 0   0       shift @move_verge_strings while @move_verge_strings && !$move_verge_strings[0];
106              
107             # tolerate some broken PDNs with no real moves, like: 1. - - 2. - -
108 0   0       pop @move_verge_strings while @move_verge_strings && $move_verge_strings[0] eq '-';
109              
110             my @move_verges = map {
111 0 0         die $self->error_prefix . "\tIncorrect move notation ($_)\n"
  0            
112             unless /^(\d+|\w\d)([x:*-])(\d+|\w\d)((?!-)\2(\d+|\w\d))*$/i;
113             [
114             $2 eq "-" ? 0 : 1,
115             map {
116 0 0         /^\d+$/ ? $board->num_to_loc($_) : $board->str_to_loc($_)
  0 0          
117             } split(/[x:*-]/)
118             ]
119             } @move_verge_strings;
120              
121 0           return [ \@move_verges, $values, $variant, $board ];
122             }
123              
124             1;