File Coverage

blib/lib/Language/Zcode/Translator/Perl.pm
Criterion Covered Total %
statement 116 126 92.0
branch 49 60 81.6
condition 10 12 83.3
subroutine 12 14 85.7
pod 4 12 33.3
total 191 224 85.2


line stmt bran cond sub pod time code
1             package Language::Zcode::Translator::Perl;
2            
3 1     1   9 use strict;
  1         4  
  1         70  
4 1     1   7 use warnings;
  1         2  
  1         2978  
5            
6             =head1 NAME
7            
8             Language::Zcode::Translator::Perl - Translate Z-code into Perl code
9            
10             =cut
11            
12             @Language::Zcode::Translator::Perl::ISA = qw(Language::Zcode::Translator::Generic);
13             my $indent = ""; # indent subs for readability
14            
15             sub new {
16 1     1 0 2 my ($class, @arg) = @_;
17 1         6 bless {}, $class;
18             }
19            
20             # Write the beginning of the program
21             sub program_start {
22 1     1 1 757 my $self = shift;
23 1         4 my $top = <<'ENDTOP';
24             #!perl -w
25            
26             use strict;
27             use Getopt::Std;
28            
29             use Language::Zcode::Runtime::Opcodes; # Perl translation of complex opcodes
30             use Language::Zcode::Runtime::State; # save/restore game state
31             use Language::Zcode::Runtime::IO; # All IO stuff
32            
33             # Set constants
34             use vars qw(%Constants);
35             CONSTANTS_HERE
36            
37             ###############
38             # Read user input
39             my %opts;
40             my $Usage = <<"ENDUSAGE";
41             $0 [-r rows] [-c columns] [-t terminal] [-d]
42            
43             -r, -c say how big to make the screen
44             -t specifies a "dumb" terminal or slightly smarter "win32" terminal
45             (hopefully will be adding more terminals soon)
46             -d debug. Write information about which sub we're in, set \$DEBUG, etc.
47             ENDUSAGE
48             getopts("dr:c:t:", \%opts) or die "$Usage\n";
49             my $DEBUG = defined $opts{d};
50            
51             # Build and run the Z-machine
52             my $Z_Result = Language::Zcode::Runtime::Opcodes::Z_machine(%opts);
53            
54             # If Z_Result was an error, do a (non-eval'ed) die to really die.
55             die $Z_Result if $Z_Result;
56            
57             exit;
58             #############################################
59            
60             ENDTOP
61             # Version-dependent constants in Z-file become true constants in output
62             # file
63 21         58 my $cstr = join("",
64             "\%Constants = (\n",
65 1         18 map({ " $_ => $Language::Zcode::Util::Constants{$_},\n" }
66             sort keys %Language::Zcode::Util::Constants),
67             ");\n"
68             );
69 1         10 $top =~ s/^CONSTANTS_HERE/$cstr/m;
70 1         11 return $top;
71             }
72            
73             =pod
74            
75             =head3 routine_start
76            
77             This sub writes out a string that starts a sub.
78             Basically, we need to handle setting local variables the sub was
79             called with, and declaring an empty eval stack.
80            
81             The much more complicated situation is when we're restoring a game in which
82             this sub was in the call stack when @save was called. If sub A called B called
83             C, which saved, then when we restore the save, we'll start executing sub C,
84             right after the @save command - and we need to set the local variables and eval
85             stack in C to the values they had when we saved. When we return from C, z_call
86             will call B, which needs to start executing at the command right after the call
87             to C. But when we start executing B, for example, the local variables and eval
88             stack need to be set to the values they had when we called C. (We get that
89             information from the save file.)
90            
91             Arg 0 of the created sub will be an arrayref. It's empty for normal calls.
92             However, if we restored a game where this sub was in the call stack, then the
93             sub will be called with information giving the sub's state when it called the
94             next sub in the stack (or @save): namely, arg0 will then contain the PC where
95             we should resume execution, and the values to set the eval stack to.
96            
97             arg1-argn will contain input values for the local variables. If we're
98             restoring, those values will be the values from the appropriate frame
99             of the call stack.
100            
101             Note: it's legal to pass in too many or too few args.
102             Set only as many values as were passed in, & don't auto-expand array.
103             (Important pre-V5, when local var initial values may not be 0)
104            
105             =cut
106            
107             sub routine_start {
108 64     64 1 151 my ($self, $addr, @params) = @_;
109 64         151 my $name = "rtn$addr";
110 64         117 $indent = " " x 4;
111 64   33     1155 my $hex_address = sprintf("%x", ($name =~ /\d+/ && $&));
112 64         234 my $start = "sub $name {\n";
113 64         129 my $out_str = <<'ENDRTN1'; # single quotes make life a bit easier
114             my ($t1, $t2, @stack, @locv);
115             if (my @frame = @{shift @_}) {
116             @locv = @{$frame[1]}; @stack = @{$frame[2]}; goto "L$frame[0]";
117             } else {
118             @locv = (PUT_VALS_HERE);
119             @locv[0 .. ($#_ > $#locv ? $#locv : $#_)] = @_;
120             }
121             ENDRTN1
122 64         513 $out_str =~ s/PUT_VALS_HERE/join(", ", @params)/e; # default values
  64         466  
123 64         1004 $out_str =~ s/^(?!$)/$indent/gm;
124 64         1039 return "$start$out_str";
125             }
126            
127             sub routine_end {
128 64     64 1 916 $indent = "";
129 64         205 return "}\n\n";
130             }
131            
132            
133             ##############################################3
134             #
135             # Opcode translations...
136             # Z_* will later be replaced with values of %parsed
137             # _SW implements conversion to signed word.
138             # Branching "?(label)" and results "-> (result)" are not put into these
139             # translations because they're always handled the same way.
140             #
141             # SPEC 2.2: The operations of numerical comparison, multiplication,
142             # addition, subtraction, division, remainder-after-division and printing of
143             # numbers are signed; bitwise operations are unsigned.
144             #
145             # WARNING!!! If the same Z_* is found twice in the same translation,
146             # and that Z_* is translated to "pop@stack", bad things could happen!
147             # So use temporary variables.
148             # XXX Maybe I should fix this somehow, e.g. s/// add's translation to:
149             # $Z_A = $parsed{a}; $Z_B = $parsed{b}; _SW#\$Z_A# + _SW#$Z_B#;
150             # Then I can use $Z_FOO in the translation without fear.
151             # Only problem is things like make_var, especially var_to_lval
152             my %replace_trans = (
153             # Arithmetic ops
154             add => "_SW#Z_A# + _SW#Z_B#",
155             'sub' => "_SW#Z_A# - _SW#Z_B#",
156             mul => "_SW#Z_A# * _SW#Z_B#",
157             div => "int(_SW#Z_A# / _SW#Z_B#)",
158             # Perl: # (13 % -5) == -2; Zcode: 13 % -5 = (13 - (-5 * -2)) = 3
159             # How many times does $y fit into $x; always round towards zero!
160             # Use commas so we can later set $result = (..., ... , a%b)
161             mod=>'($t1 = _SW#Z_A#, $t2 = _SW#Z_B#, $t1 - $t2*int($t1/$t2))',
162            
163             # logical ops - make sure we get the right number of bits
164             'or' => "Z_A | Z_B",
165             'and'=> "Z_A & Z_B",
166             'not' => "0xffff & ~Z_VALUE",
167             log_shift => '($t1 = _SW#Z_PLACES#) > 0 ' .
168             '? Z_NUMBER << $t1 : (Z_NUMBER & 0xffff) >> -$t1',
169             # The |(...) fills in 1s from the left if bit fifteen (sign bit) is set
170             art_shift => 'do { $t2 = Z_NUMBER;
171             ($t1 = _SW#Z_PLACES#) > 0 ?
172             Z_NUMBER << $t1 :
173             ($t2 >> -$t1) | ($t2>>15 && ~(2**-$t1))}',
174            
175             # Jumps (conditional & unconditional)
176             jump => "goto LZ_LABEL",
177             # Branch instructions just write their conditions: they'll be added to later
178             jz => "Z_A == 0",
179             jg => "_SW#Z_A# > _SW#Z_B#",
180             jl => "_SW#Z_A# < _SW#Z_B#",
181             # jump if all given flags in the given bitmap are set
182             test => '(Z_BITMAP & ($t1 = Z_FLAGS)) == $t1',
183             # Zspec 1.1 'je 5' is illegal.
184             # I need to do _SW for the case of je -1 65535
185             # XXX If I move this to sub_trans, then I can use grep for > 2 args,
186             # and just test == for 2 args.
187             je => '$t1 = Z_A, grep {_SW#$t1# == _SW#$_#} (_ARG_LIST)',
188            
189             # Stack and Variables
190             # Note: this is where Z_VARIABLE lives - indirect variables. Beware!
191             'pop' => 'pop @stack',
192             'push' => 'push @stack, Z_VALUE',
193             pull => 'Z_VARIABLE = pop @stack',
194             store => "Z_VARIABLE = Z_VALUE",
195             load => "Z_VARIABLE",
196             # Spec15.html#inc: "This is signed, so -1 increments to 0."
197             # Spec15.html#dec: "This is signed, so 0 decrements to -1."
198             inc => "++Z_VARIABLE",
199             dec => "--Z_VARIABLE",
200             inc_chk => "_SW#++Z_VARIABLE# > _SW#Z_VALUE#",
201             dec_chk => "_SW#--Z_VARIABLE# < _SW#Z_VALUE#",
202            
203             # Memory access
204             loadb => '$PlotzMemory::Memory[(Z_ARRAY + Z_BYTE_INDEX) & 0xffff]',
205             loadw =>
206             '256*$PlotzMemory::Memory[$t1=(Z_ARRAY + 2*Z_WORD_INDEX) & 0xffff] +
207             $PlotzMemory::Memory[$t1 + 1]',
208             storeb =>
209             '$PlotzMemory::Memory[(Z_ARRAY + Z_BYTE_INDEX) & 0xffff] = Z_VALUE & 0xff',
210             storew => '$PlotzMemory::Memory[$t1 = (Z_ARRAY + 2*Z_WORD_INDEX) & 0xffff] =
211             ($t2 = Z_VALUE)>>8 & 0xff,
212             $PlotzMemory::Memory[$t1 + 1] = $t2 & 0xff',
213            
214             # Return
215             ret => "return Z_VALUE",
216             ret_popped => "return (pop \@stack)",
217             rtrue => "return 1",
218             rfalse => "return 0",
219            
220             # Print_*
221             # print is equivalent to print_addr with address of the literal string!
222             "print" => '# print "Z_PRINT_STRING"
223             &write_text(&decode_text(Z_LITERAL_STRING))',
224             print_ret => '# print "Z_PRINT_STRING"
225             &write_text(&decode_text(Z_LITERAL_STRING));
226             &newline();
227             return(1)',
228             print_num => "&write_text(_SW#Z_VALUE#)",
229             print_addr => "&write_text(&decode_text(Z_BYTE_ADDRESS_OF_STRING))",
230             # This is why we need to store entire program in memory
231             print_paddr => "&write_text(&decode_text(Z_PACKED_ADDRESS_OF_STRING))",
232             # XXX We're doing ASCII. Need to do ZSCII
233             print_char => "&write_zchar(Z_OUTPUT_CHARACTER_CODE)",
234             new_line => "&newline()",
235            
236             # Other I/O
237             "read" => "&z_read(Z_TEXT, Z_PARSE, Z_TIME, Z_ROUTINE)",
238             show_status => "&show_status()",
239             tokenise => "&z_tokenise(Z_TEXT, Z_PARSE, Z_DICTIONARY, Z_FLAG)",
240            
241             # Streams & windows & cursors
242             output_stream => "&output_stream(_SW#Z_NUMBER#)", # arg may be < 0
243             input_stream => "&input_stream(Z_NUMBER)",
244             split_window => "&split_window(Z_LINES)",
245             set_window => "&set_window(Z_WINDOW)",
246             erase_window => "&erase_window(_SW#Z_WINDOW#)", # arg may be < 0
247             get_cursor => "&get_cursor(Z_ARRAY)",
248             set_cursor => "&set_cursor(_SW#Z_LINE#, Z_COLUMN, Z_WINDOW)", # < 0 for v6
249             set_text_style => "&set_text_style(Z_STYLE)",
250            
251             # Objects
252             get_parent => "get_parent(Z_OBJECT)",
253             get_child => "get_child(Z_OBJECT)",
254             get_sibling => "get_sibling(Z_OBJECT)",
255             jin => "Z_OBJ2 == &get_object(&thing_location(Z_OBJ1, 'parent'))",
256             print_obj => "&write_text(&decode_text(&thing_location(Z_OBJECT, 'name')))",
257             insert_obj => "&insert_obj(Z_OBJECT, Z_DESTINATION)",
258             remove_obj => "&remove_obj(Z_OBJECT)",
259            
260             # Properties
261             get_prop => "&get_prop(Z_OBJECT, Z_PROPERTY)",
262             put_prop => "&put_prop(Z_OBJECT, Z_PROPERTY, Z_VALUE)",
263             get_next_prop => "&get_next_prop(Z_OBJECT, Z_PROPERTY)",
264             get_prop_addr => "&get_prop_addr(Z_OBJECT, Z_PROPERTY)",
265             get_prop_len => "&get_prop_len(Z_PROPERTY_ADDRESS)",
266            
267             # Attributes
268             set_attr => "&set_attr(Z_OBJECT, Z_ATTRIBUTE)",
269             clear_attr => "&clear_attr(Z_OBJECT, Z_ATTRIBUTE)",
270             test_attr => "&test_attr(Z_OBJECT, Z_ATTRIBUTE)",
271            
272             # Save/restore
273             # XXX Different for v1-3
274             save => '&save_state(Z_RESTORE_PC, \@locv, \@stack)',
275             restore => "&restore_state",
276             # Spec "save_undo": terp must return -1 if it doesn't implement save_undo
277             save_undo => "-1",
278             # Spec "restore_undo": illegal for a game to use this if save_undo
279             # returns -1.
280             restore_undo => "0",
281             restart => 'die "Restart\n"',
282            
283             # Misc
284             check_arg_count => '@_ >= Z_ARGUMENT_NUMBER',
285             quit => 'die "Quit\n"',
286             # Spec 15, 'piracy': "Interpreters are asked to be gullible"
287             piracy => "1",
288             random => "&z_random(_SW#Z_RANGE#)",
289             verify => "&z_verify()",
290             nop => 1,
291            
292             # Calls: Z subs are turned into Perl subs
293             # Use Perl's calling stack instead of building a separate one BUT do some
294             # bookkeeping (w/ extra args) to be able to save/restore machine state
295             call_1s =>
296             'z_call(Z_ROUTINE, \@locv, \@stack, Z_NEXT_PC, Z_RESULT_NUM, _ARG_LIST)',
297            
298             );
299            
300             # All call subs work the same! (store var will be set to undef for call_*n,
301             # and "result = " will be added to call_s).
302             @replace_trans{qw(call_2s call_vs call_vs2 call_1n call_2n call_vn call_vn2)} =
303             ($replace_trans{call_1s}) x 7;
304             #@replace_trans{ qw(call_2n call_vn call_vn2) } = ($replace_trans{call_1n}) x 3;
305            
306             # Translate Z opcode and ops into Perl
307             my %unimplemented; # keep track of unimplemented opcodes
308             sub translate_command {
309             # Keys to %parsed are based on the arguments in the opcode syntax list
310             # in LZ::Parser::Opcode. There's a few others I put in:
311             # - opcode, opcode address are the name & address of the opcode
312             # - result is variable name (or stack top) where we're supposed
313             # to store the result, if any
314             # - negate_jump means negate the condition of jump opcodes
315             # - jump_return means return this value (0 or 1) instead of branching
316             # if the branch condition is met
317             # - op is an arrayref to remaining arguments (used for e.g., call_*)
318 1387     1387 1 12439 my ($self, $href) = @_;
319 1387         14555 my %parsed = %$href;
320 1387 50       6384 my $opcode = $parsed{opcode} or return; # totally unknown opcode?
321 1387         2396 my $command = "OOPS. No Command Here\n"; # command to return
322            
323 1387         2712 my %sub_trans = (
324             # There's nothing to see here. Move along...
325             );
326            
327             # Build the actual line of Perl code
328             # XXX Only print labels we actually jump to? Requires separate pass.
329 1387         4527 my $label = "L$parsed{opcode_address}: ";
330             # Quetzal stores the byte BEFORE the next command as its restore_pc,
331             # so we'll eventually call a sub and try to goto that address.
332 1387 50       5859 $label .= "1; L$parsed{restore_pc}: " if exists $parsed{restore_pc};
333            
334             # Translate, e.g., "local1" to language-specific
335             # code representing second local variable
336             # Treat key "variable" specially - it's used in "indirect opcodes"
337             # (See make_var)
338 1387         3784 foreach my $key (keys %parsed) {
339 5926         11228 my %skip = map {$_=>1}
  59260         178724  
340             qw(args jump_return label literal_string negate_jump
341             next_pc opcode opcode_address print_string restore_pc);
342 5926 100       31242 if (!exists $skip{$key}) {
343             #warn "$key $parsed{$key}\n";
344 1696 50       23541 warn "undefined $key\n" if !defined $parsed{$key};
345 1696         6423 $parsed{$key} = $self->make_var($parsed{$key}, $key eq "variable");
346             }
347             }
348             # Pack addresses
349 1387         4281 foreach my $key (qw(packed_address_of_string routine)) {
350 2774 100       9386 if (exists $parsed{$key}) {
351 372         1581 $parsed{$key} = $self->packed_address_str($parsed{$key}, $key);
352             }
353             }
354            
355             # Turn variable number of args (if any) into a Perl list
356             # Btw, call_1n takes no args, so arg_list will be "" for call_1n, too
357 822         8147 my $arg_list = exists $parsed{args}
358 1387 100       4911 ? join(", ", map {$self->make_var($_)} @{$parsed{"args"}})
  355         987  
359             : "";
360            
361             # Turn Z ops into Perl ops
362 1387 50       5865 if (exists $replace_trans{$opcode}) {
    0          
363 1387         3393 local $_ = $replace_trans{$opcode};
364             # Put in actual arguments
365             # (Note that sometimes there's a letter before the Z,
366             # but never after the whole key.)
367             # If there are optional args, then some of the args won't exist.
368             # First handle things where we set an lval to an rval
369 1387         6419 s/Z_(\w+)\s+=\s+(.+)/$self->var_to_lval($parsed{lc $1}, $2)/e;
  316         1485  
370 1387         5536 s/([+-]{2})Z_(\w+)/$self->var_to_lval($parsed{lc $2}, $1)/e;
  72         390  
371 1387 100       7696 s/Z_(\w+)/exists $parsed{lc $1} ? $parsed{lc $1}:"undef"/ge;
  2284         19074  
372 1387         11251 s/_ARG_LIST/$arg_list/;
373 1387         4666 s/, (undef(, )?)?\)(;|$)/)$3/; # clean up unneeded args
374            
375             # Change numbers to signed/unsigned words.
376 1387         2993 s/_SW#(.*?)#/$self->signed_word($1)/ge;
  338         1282  
377            
378             # print "$parsed{opcode_address} $command\n";
379 1387         4089 $command = $_;
380            
381             } elsif (exists $sub_trans{$opcode}) {
382 0         0 $command = &{$sub_trans{$opcode}}();
  0         0  
383            
384             } else {
385 0 0       0 warn "Unimplemented opcode $opcode at $parsed{opcode_address}\n"
386             unless $unimplemented{$opcode}++;
387 0         0 $command = "&unimplemented_$opcode";
388             }
389            
390             # Handle commands that have a "-> (result)" argument
391             # (result has already been translated from e.g. 3 to 'local2')
392 1387 100       5291 $command = $self->var_to_lval($parsed{result}, $command)
393             if exists $parsed{result};
394            
395             # Handle branch instructions
396             # Do this AFTER store_result, so we get "goto L3 if $c = $a+$b"
397             # rather than "$c = goto L3 if $c=$a+$b"
398             # (Note: jump doesn't count as a branch instruction!)
399             # (This assumes Perl command is pretty simple)
400 1387 100       4072 if (exists $parsed{negate_jump}) {
401 155         285 my $action;
402 155 100       496 if (exists $parsed{jump_return}) {
403 2         7 $action = "return $parsed{jump_return}";
404             } else {
405 153 50       378 die "no label for command!" unless exists $parsed{label};
406 153         677 $action = "goto L$parsed{label}";
407             }
408 155 100       780 my $cond .= $parsed{negate_jump} ? "unless" : "if";
409 155         922 $command = "$action $cond $command";
410             }
411            
412 1387         4579 $command = "$indent$label$command;\n";
413            
414 1387         13666 return ($command);
415             }
416            
417             # Change the rval created by make_var to an lval (HACKISH!)
418             # Note that when we get called, make_var has already been called on the lval,
419             # generating possibly incorrect
420             # If $lval is an indirect variable (see make_var), then the variable
421             # is really an RVAL which returns a variable that should be treated as an LVAL!
422             # E.g., store [g0f] 17 means "set the variable represented by the number
423             # stored in global_var(15) to 17" so global_var(15) is still treated as an rval.
424             # If global_var(15) is 0 ("sp"), then treat sp as an LVAL, i.e.,
425             # push 17 onto the stack
426             # XXX Now that indirect var is treated differently, can I merge this
427             # XXX back into make_var, only called with an extra arg?
428             # Special case: if $rval is ++ or --, then inc/dec the lval.
429             sub var_to_lval {
430 479     479 0 7063 my ($self, $lval, $rval) = @_;
431 479         1080 local $_ = $lval; # for convenience in //'s.
432             # XXX what's correct protocol for store [sp] sp?
433             # Pop stack before reading indirect variable?
434 479         879 my $is_bracket = /bracket_var\(/;
435            
436             # $rval = "($rval) % 0x10000";
437            
438 479 100       3213 if ($is_bracket) { # add $rval to args to bracket_var
    100          
    100          
    50          
439 27         128 $rval =~ s/^[+-]{2}$/"$&"/; # Yuck!
440 27         169 s/\)$/, $rval)/;
441             } elsif (/global_var/) { # global_var(number) -> global_var(number, rval)
442 72         198 $rval =~ s/^[+-]{2}$/"$&"/; # Yuck!
443 72         657 s/\)$/, $rval)/;
444             } elsif (/locv/) { # $locv[num] -> $locv[num] = rval
445 349 100 100     2766 if ($rval eq "++" || $rval eq "--") {
446 37         268 my $op = substr($rval, 0, 1);
447             # XXX this wrong. Spec 15#dec/inc say this should be signed!
448             # XXX So it should really be:
449             # $_ = "($_ = ($_
450 37         202 $_ = "($_ = ($_ $op 1) & 0xffff)";
451             } else {
452 312         1304 $_ .= " = $rval";
453             }
454             } elsif (/stack/) { # pop @stack -> $stack[@stack] = rval
455             # push returns number of elements in array. I need the value I pushed.
456             # If indirect variable, this s/// won't happen.
457 31         320 s/pop\(\@stack\)/\$stack[\@stack]/;
458 31 100 100     217 if ($rval eq "++" || $rval eq "--") {
459 7         25 my $op = substr($rval, 0, 1);
460 7         30 $_ = "($_ = ($_ $op 1) & 0xffff)";
461             } else {
462 24         75 $_ .= " = $rval";
463             }
464             } else {
465 0         0 warn "Unexpected arg to var_to_lval $lval";
466             }
467            
468 479         2902 return $_;
469             }
470            
471             # Create a string describing a variable from the given string.
472             # (e.g., '$locv[2]' from 'local2')
473             # Indirect variables are a special case - stack doesn't get popped.
474             # Usually, it'll be a string describing the stack ("sp"), or a local
475             # or global variable. But if it's an expression in [], then
476             # e.g., [local2] means the value of the variable stored in
477             # $locv[2]. If $locv[2] is 11, then we really want the value stored in
478             # $locv[10]!
479             sub make_var {
480 2518     2518 0 6794 my ($self, $var, $is_indirect) = @_;
481 2518         6226 my $is_bracket = ($var =~ s/\[(.*)\]/$1/);
482            
483 2518         8985 local $_ = $var;
484 2518 100       18777 if (/^g([\da-f]+)$/) { # Global variable
    100          
    100          
    50          
485 161         475 my $var_num = hex($1);
486 161         606 $_ = "&global_var($var_num)";
487             } elsif (/^local([\da-f]+)$/) { # Local variable
488 672         2362 $_ = "\$locv[$1]";
489             } elsif ($_ eq "sp") { # Stack
490             # Spec Version 1.1 (draft7): "an indirect reference to the stack
491             # pointer does not push or pull the top item of the stack - it is read
492             # or written in place."
493             # ADK: From testing (winfrotz2002) it apears this is true for
494             # "load sp". "load [sp]" does pop the stack in getting the number
495             # of the variable to use. But if sp == 0, "load [sp]" still pops
496             # only once.
497 75 100 100     628 $_ = $is_indirect && !$is_bracket ? '$stack[-1]' : 'pop(@stack)';
498             } elsif (/^\d+$/) {
499             # Leave the numeric constant as it is
500             } else { # not a number? What is it?
501 0         0 warn "Unexpected arg to make_var: '$var'";
502             # keep the garbage in the output file
503             }
504            
505             # Get the value stored in the variable referenced by the current $_
506             # Pass in local variables & stack so we have their values.
507             # No, pass in *refs* to local variables & stack, in case the indirect
508             # var is an lval which references e.g. a local variable which we
509             # then need to set within indirect_var!
510 2518 100       7010 $_ = "bracket_var($_, \\\@locv, \\\@stack)"
511             if $is_bracket;
512            
513 2518         16233 return $_;
514             }
515            
516             # Convert num to signed_word & unsigned_word. Stolen from Games::Rezrov.
517            
518             # Signed word: if high bit is set, take ~ number, else just the number
519             # IF the expression we're sign'ing is just an integer constant,
520             # convert it to a signed word constant now.
521             # Otherwise, the term is a variable, so we just have to put in Perl code
522             # that will convert it at runtime
523             # Note that dzip and zip fail *differently* on test.inf wrt signed numbers!
524             sub signed_word {
525 338     338 0 900 my ($self, $exp) = @_;
526             # $exp =~ s/^\((.*)\)$/$1/
527             # or die "Unexpected expression '$exp' to signed_word\n";
528 338         470 my $ret;
529 338 100       1223 if ($exp =~ /^\d+$/) {
530 90 100       308 $ret = $exp & 0x8000 ? $exp - 0x10000 : $exp;
531             } else {
532             # XXX Aha! $ret = "($exp-0x8000) % 0x10000 - 0x8000"
533 248         702 $ret = "unpack('s', pack('s', $exp))";
534             }
535 338         2178 return $ret;
536             }
537            
538             # XXX might need to also explicity cast to unsigned
539             # when setting variables - see Games::Rezrov::StoryFile
540             sub unsigned_word {
541 0     0 0 0 return "unpack('S', pack('s', $_[1]))";
542             }
543            
544             sub newlineify {
545 0     0 0 0 my $s = pop;
546 0         0 $s =~ s/\n/\\n/g;
547 0         0 return $s;
548             }
549            
550             # Write memory to the file, as well as code to read it back
551             # (and to store original dynamic memory)
552             sub write_memory {
553 1     1 0 7 my ($self) = @_;
554             # Top of package
555 1         3 my $str = q(
556            
557             {
558             package PlotzMemory;
559            
560             use vars qw(@Memory);
561             my @Dynamic_Orig;
562            
563             sub get_byte_at { $Memory[$_[0]] }
564             sub set_byte_at { $Memory[$_[0]] = $_[1] & 0xff; }
565             sub get_word_at { ($Memory[$_[0]] << 8) + $Memory[$_[0] + 1]; }
566             sub set_word_at {
567             $Memory[$_[0]] = $_[1]>>8;
568             $Memory[$_[0] + 1] = $_[1] & 0xff;
569             }
570            
571             );
572            
573            
574             # change each byte to two hex digits
575 1         3 my $l = @Language::Zcode::Util::Memory;
576 1         4 my $flen = $Language::Zcode::Util::Constants{file_length}; # stated length
577             # Spec1.1: "Padding"
578             # The standard currently states that story file padding beyond the length
579             # specified in the header must be all zero bytes. Many Infocom story files
580             # in fact contain non-zero data in the padding, so interpreters must be
581             # sure to exclude the padding from checksum calculations.
582 1         2 my $hexed = "";
583 1         5 for (my $c = 0; $c < $l; $c+=16) {
584             # Add hex "line number" & \n's.
585 736         1552 my $len = $l - $c;
586 736 100       3122 $len = 16 if $len > 16;
587 736         9183 $hexed .= sprintf("%06x " . " %02x" x $len . "\n", $c,
588             @Language::Zcode::Util::Memory[$c .. $c + $len -1]);
589             }
590             # Actually, this is $#dynamic, not @dynamic
591 1         8 my $dynamic_size = $Language::Zcode::Util::Constants{static_memory_address} - 1;
592 1         346 $str .= <<"ENDUNPACK";
593             sub read_memory {
594             # (The map below removes address number and hexifies the other numbers)
595             my \$c = 0;
596             # Addr 0 1 2 3 4 5 6 7 8 9 a b c d e f
597             \@Memory = map {\$c++ % 17 ? hex : ()} qw(
598             $hexed
599             );
600             \@Dynamic_Orig = \@Memory[0 .. $dynamic_size];
601             }
602            
603             sub checksum {
604             my \$header_size = 0x40; # don't count header bytes.
605             my \$sum = 0;
606             for (\@Dynamic_Orig[\$header_size .. $dynamic_size -1],
607             \@Memory[$dynamic_size .. $flen-1])
608             {
609             \$sum += \$_;
610             }
611             # 512K * 256 = 128M: definitely less than 2G max integer size for Perl.
612             # so we don't need to do mod within the for loop
613             \$sum = \$sum % 0x10000;
614             return \$sum;
615             }
616            
617             sub get_dynamic_memory {
618             [\@Memory[0 .. $dynamic_size]];
619             }
620            
621             sub get_orig_dynamic_memory {
622             [\@Dynamic_Orig];
623             }
624            
625             my \$restore_mem_ref;
626             sub store_dynamic_memory {
627             \$restore_mem_ref = shift;
628             }
629            
630             # Reset memory EXCEPT the couple bits that get saved even during a restart.
631             sub reset_dynamic_memory {
632             my \$restoring = shift;
633             Language::Zcode::Runtime::IO::store_restart_bits();
634             \@Memory[0 .. $dynamic_size] =
635             \$restoring ? \@\$restore_mem_ref : \@Dynamic_Orig;
636             }
637            
638             } # End package PlotzMemory
639            
640             ENDUNPACK
641            
642 1         4178 return $str;
643             }
644            
645             # This functionality is supplied by the "use Language::Zcode::Runtime" at the
646             # top of the program (written in program_start)
647             sub library {
648 1     1 0 10 return "";
649             }
650            
651             1;