File Coverage

blib/lib/Language/Zcode/Parser/Generic.pm
Criterion Covered Total %
statement 115 139 82.7
branch 9 28 32.1
condition 4 12 33.3
subroutine 25 26 96.1
pod 4 4 100.0
total 157 209 75.1


line stmt bran cond sub pod time code
1             package Language::Zcode::Parser::Generic;
2            
3 2     2   12 use strict;
  2         5  
  2         61  
4 2     2   10 use warnings;
  2         3  
  2         50  
5 2     2   2042 use IO::File;
  2         28366  
  2         463  
6            
7 2     2   868 use Language::Zcode::Util;
  2         5  
  2         1280  
8            
9             =head1 Language::Zcode::Parser::Generic
10            
11             Base class for Z-code parsers.
12            
13             A Parser reads and parses a Z-code file into a big Perl hash.
14            
15             For finding where the subroutines start and end, you can either depend on
16             an external call to txd, a 1992 C program, or a beta pure Perl version.
17            
18             Everything else is done in pure Perl.
19            
20             =cut
21            
22             =head2 new (class, args...)
23            
24             Base class does nothing with args
25            
26             =cut
27            
28             sub new {
29 2     2 1 6 my ($class, @arg) = @_;
30 2         15 bless {}, $class;
31             }
32            
33             =head2 find_zfile (filename)
34            
35             If the input filename is not found AND the user did not enter, e.g., '.z5' at
36             the end of the filename, the system will try to find a file ending with .z[1-9]
37             or .dat.
38            
39             Multiple or no matches -> return false
40            
41             =cut
42            
43             sub find_zfile {
44 0     0 1 0 my ($self, $infile) = @_;
45 0 0       0 return $infile if -e $infile;
46            
47 0         0 my $fn = ""; # filename to return
48 0 0       0 if ($infile !~ /\.(z[1-9]|dat)$/i) {
49 0         0 my @files = glob("$infile.z[1-9]");
50 0 0       0 push @files, "$infile.dat" if -e "$infile.dat";
51 0 0       0 if (@files == 0) {
    0          
52 0         0 warn "No file $infile.z[1-9] or $infile.dat\n";
53             } elsif (@files > 1) {
54 0         0 warn "Too many files match $infile.z[1-9] or $infile.dat: @files\n";
55             } else {
56 0         0 $fn = $files[0];
57             }
58             } else {
59 0         0 warn "File '$infile' not found\n";
60             }
61            
62 0         0 return $fn;
63             }
64            
65             =head2 read_memory (infile)
66            
67             Reads the given Z-code file into memory
68            
69             =cut
70            
71             sub read_memory {
72 2     2 1 582 my ($self, $infile) = @_;
73             # Read in actual Z file
74 2 50       19 my $ZFILE = new IO::File "<$infile" or die "Zfile: $!";
75 2         271 binmode $ZFILE;
76 2         66 my $size = -s $infile;
77 2         7 my $q = "";
78             # Read it all into one big string, split it into an array
79 2         64 my $err = read($ZFILE, $q, $size);
80 2 50       9 die "Problem reading Z file from Perl: $!" unless defined $err;
81 2         3432 @Language::Zcode::Util::Memory = unpack('C*', $q);
82 2         415 close($ZFILE);
83             }
84            
85             =head2 parse_header
86            
87             Parse Z-code header.
88            
89             Creates %Constants, which stores a bunch of constants
90             like the Z version number, where in memory things are stored, etc.
91            
92             =cut
93            
94             sub parse_header {
95 2     2 1 15 my $self = shift;
96            
97             # see spec section 11:
98 2     2   14 use constant HEADER_SIZE => 64;
  2         5  
  2         145  
99            
100             # These are all addresses in the header of various Z constants
101 2     2   22 use constant VERSION_NUMBER => 0x00;
  2         4  
  2         87  
102 2     2   10 use constant RELEASE_NUMBER => 0x02;
  2         4  
  2         143  
103 2     2   11 use constant PAGED_MEMORY_ADDRESS => 0x04;
  2         5  
  2         89  
104 2     2   12 use constant FIRST_INSTRUCTION_ADDRESS => 0x06;
  2         3  
  2         92  
105 2     2   10 use constant DICTIONARY_ADDRESS => 0x08;
  2         4  
  2         100  
106 2     2   138 use constant OBJECT_TABLE_ADDRESS => 0x0a;
  2         5  
  2         94  
107 2     2   10 use constant GLOBAL_VARIABLE_ADDRESS => 0x0c;
  2         4  
  2         89  
108 2     2   9 use constant STATIC_MEMORY_ADDRESS => 0x0e;
  2         4  
  2         79  
109 2     2   11 use constant SERIAL_CODE => 0x12;
  2         3  
  2         92  
110 2     2   10 use constant ABBREV_TABLE_ADDRESS => 0x18;
  2         3  
  2         93  
111 2     2   10 use constant FILE_LENGTH => 0x1a;
  2         4  
  2         76  
112 2     2   10 use constant CHECKSUM => 0x1c;
  2         3  
  2         95  
113 2     2   12 use constant INTERPRETER_NUMBER => 0x1e;
  2         5  
  2         90  
114 2     2   10 use constant INTERPRETER_VERSION => 0x1f;
  2         11  
  2         83  
115 2     2   9 use constant ROUTINES_OFFSET => 0x28;
  2         4  
  2         91  
116 2     2   10 use constant STRINGS_OFFSET => 0x2a;
  2         2  
  2         107  
117            
118             # interpreter version name; "P" for Plotz
119 2     2   59 use constant INTERPRETER_CODE => ord "P";
  2         4  
  2         1570  
120            
121 2         5 my %info;
122 2         4 my $version = $Language::Zcode::Util::Memory[VERSION_NUMBER];
123 2 50 33     85 if ($version < 1 or $version > 8) {
    50 33        
      33        
124 0         0 die "This does not appear to be a valid game file.\n";
125             } elsif (($version < 3 or $version > 5) and $version != 8) {
126 0         0 die "Sorry, only z-code versions 3,4,5 and 8 are supported at present...\nAnd even those need work! :)\n"
127             }
128            
129 2         7 $info{version} = $version;
130 2         10 $info{release_number} = Language::Zcode::Util::get_word_at(RELEASE_NUMBER);
131 2         7 $info{paged_memory_address} = Language::Zcode::Util::get_word_at(PAGED_MEMORY_ADDRESS);
132 2         96 $info{first_instruction_address} = Language::Zcode::Util::get_word_at(FIRST_INSTRUCTION_ADDRESS);
133 2         7 $info{dictionary_address} = Language::Zcode::Util::get_word_at(DICTIONARY_ADDRESS);
134 2         10 $info{object_table_address} = Language::Zcode::Util::get_word_at(OBJECT_TABLE_ADDRESS);
135 2         7 $info{global_variable_address} = Language::Zcode::Util::get_word_at(GLOBAL_VARIABLE_ADDRESS);
136 2         6 $info{static_memory_address} = Language::Zcode::Util::get_word_at(STATIC_MEMORY_ADDRESS);
137             # see zmach06e.txt
138 2         9 $info{abbrev_table_address} = Language::Zcode::Util::get_word_at(ABBREV_TABLE_ADDRESS);
139 2         5 my $c = "";
140 2         8 for (SERIAL_CODE .. SERIAL_CODE + 5) {
141 12         31 $c .= chr Language::Zcode::Util::get_byte_at($_);
142             }
143 2         8 $info{serial_code} = qq{"$c"};
144            
145             # set object/dictionary "constants" for this version...
146 2 50       8 if ($version <= 3) {
147             # 13.3, 13.4
148 0         0 $info{encoded_word_length} = 6;
149            
150             # 12.3.1
151 0         0 $info{object_bytes} = 9;
152 0         0 $info{attribute_bytes} = 4;
153 0         0 $info{pointer_size} = 1;
154 0         0 $info{max_properties} = 31; # 12.2
155 0         0 $info{max_objects} = 255; # 12.3.1
156             } else {
157 2         6 $info{encoded_word_length} = 9;
158            
159             # 12.3.2
160 2         6 $info{object_bytes} = 14;
161 2         5 $info{attribute_bytes} = 6;
162 2         5 $info{pointer_size} = 2;
163 2         4 $info{max_properties} = 63; # 12.2
164 2         8 $info{max_objects} = 65535; # 12.3.2
165             }
166 2 50       12 die("check your math!")
167             if (($info{attribute_bytes} + ($info{pointer_size} * 3) + 2)
168             != $info{object_bytes});
169            
170 2         9 my $flen = Language::Zcode::Util::get_word_at(FILE_LENGTH);
171 2 50 33     21 if ($version <= 3) {
    50          
172             # see 11.1.6
173 0         0 $flen *= 2;
174             } elsif ($version == 4 || $version == 5) {
175 2         4 $flen *= 4;
176             } else {
177 0         0 $flen *= 8;
178             }
179 2         5 $info{file_length} = $flen;
180            
181 2         8 $info{file_checksum} = Language::Zcode::Util::get_word_at(CHECKSUM);
182            
183             # Packed string/routine calculation
184 2         23 my %packed_mult = (1=>2, 2=>2, 3=>2, 4=>4, 5=>4, 6=>4, 7=>4, 8=>8);
185 2         7 $info{packed_multiplier} = $packed_mult{$version};
186 2 50       7 if ($version >= 6) {
187 0         0 $info{routines_offset} = &Language::Zcode::Util::get_word_at(ROUTINES_OFFSET);
188 0         0 $info{strings_offset} = &Language::Zcode::Util::get_word_at(STRINGS_OFFSET);
189             } else {
190 2         5 $info{routines_offset} = 0;
191 2         5 $info{strings_offset} = 0;
192             }
193            
194 2         23 %Language::Zcode::Util::Constants = %info;
195            
196             # Now set any data that we know will be true in the output program
197             # interpreter number
198             # &set_byte_at(INTERPRETER_NUMBER, $interpreter_id);
199 2         12 &Language::Zcode::Util::set_byte_at(INTERPRETER_VERSION, INTERPRETER_CODE);
200            
201 2         13 return;
202             }
203            
204             1;