File Coverage

blib/lib/Anarres/Mud/Driver/Program.pm
Criterion Covered Total %
statement 54 295 18.3
branch 2 76 2.6
condition 0 20 0.0
subroutine 12 49 24.4
pod 0 38 0.0
total 68 478 14.2


line stmt bran cond sub pod time code
1             package Anarres::Mud::Driver::Program;
2              
3 3     3   2676 use strict;
  3         8  
  3         152  
4 3     3   17 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS %PROGS);
  3         7  
  3         229  
5 3     3   20 use Exporter;
  3         6  
  3         137  
6 3     3   25 use Carp qw(:DEFAULT cluck);
  3         5  
  3         640  
7 3     3   20 use Data::Dumper;
  3         6  
  3         145  
8 3     3   17 use File::Basename;
  3         6  
  3         286  
9 3     3   1781 use String::Escape qw(quote printable);
  3         10701  
  3         246  
10 3     3   1266 use Anarres::Mud::Driver::Compiler::Type qw(:all);
  3         6  
  3         937  
11 3     3   1980 use Anarres::Mud::Driver::Program::Variable;
  3         9  
  3         136  
12 3     3   1689 use Anarres::Mud::Driver::Program::Method;
  3         9  
  3         156  
13 3     3   1831 use Anarres::Mud::Driver::Program::Efun qw(efuns efunflags);
  3         7  
  3         11932  
14              
15             # This object is big and the 'context'-related stuff and possibly the
16             # 'generate'-related stuff could be split out.
17              
18             @ISA = qw(Exporter);
19             # Oddly enough, the PERL_* tags here must be in order.
20             @EXPORT_OK = (qw(package_to_path path_to_package
21             PERL_HEAD PERL_USE PERL_VARS PERL_SUBS PERL_TAIL
22             PERL_DOCS));
23             %EXPORT_TAGS = (
24             sections => [ grep { /^PERL_/ } @EXPORT_OK ],
25             all => \@EXPORT_OK,
26             );
27              
28             # To insert various things into the Perl code.
29             sub PERL_HEAD () { 0 }
30             sub PERL_USE () { 1 }
31             sub PERL_VARS () { 2 }
32             sub PERL_SUBS () { 3 }
33             sub PERL_TAIL () { 4 }
34             sub PERL_DOCS () { 5 }
35              
36             my $DEBUGLABELS = 0;
37              
38             %PROGS = (
39             "/foo/bar" => new Anarres::Mud::Driver::Program(Path=>"/foo/bar"),
40             );
41              
42             # Class methods
43              
44             sub new {
45 4     4 0 710 my $class = shift;
46 4 50       23 my $self = ($#_ == 0) ? { %{ (shift) } } : { @_ };
  0         0  
47              
48 4 50       18 confess "No Path in program" unless $self->{Path};
49              
50 4         10 $self->{Perl} = [ ];
51 4         7 $self->{PerlGlobals} = [ ];
52              
53 4         11 $self->{Inherits} = { };
54 4         9 $self->{Statics} = { };
55 4         7 $self->{Globals} = { };
56 4         7 $self->{Locals} = { };
57 4         10 $self->{Labels} = { };
58 4         7 $self->{LabelDefault} = undef;
59 4         16 $self->{Methods} = efuns;
60 4         12 $self->{MethodFlags} = efunflags;
61              
62 4         51 $self->{ScopeStack} = [ ];
63 4         8 $self->{LabelStack} = [ ];
64              
65 4         7 $self->{Warnings} = [ ];
66 4         5 $self->{Errors} = [ ];
67              
68 4         12 $self->{Label} = 0;
69              
70 4         11 $self->{Closures} = [ ];
71              
72 4         7 $self->{Classes} = { };
73              
74 4         20 return bless $self, $class;
75             }
76              
77             sub find { # find Anarres::Mud::Driver::Program $path
78 0     0 0   return $PROGS{$_[1]};
79             }
80              
81             sub path_to_package {
82 0     0 0   my $path = shift;
83 0           $path =~ s,/,::,g;
84 0           $path =~ s/\.c$//;
85 0           $path =~ s,^/*,,;
86 0           return "Anarres::Mud::Library::" . $path;
87             }
88              
89             sub package_to_path {
90 0     0 0   my $package = shift;
91 0 0         die "package_to_path: Invalid package name"
92             unless $package =~ s/^Anarres::Mud::Library//;
93 0           $package =~ s,::,/,g;
94 0           return $package;
95             }
96              
97             # Debugging methods
98              
99             sub warning {
100 0     0 0   my $self = shift;
101 0           print "WARNING: $_\n" foreach @_;
102 0           push(@{ $self->{Warnings} }, @_);
  0            
103             }
104              
105             sub error {
106 0     0 0   my $self = shift;
107 0           print "ERROR: $_\n" foreach @_;
108 0           push(@{ $self->{Errors} }, @_);
  0            
109             }
110              
111             # Instance query methods
112              
113 0     0 0   sub path { return $_[0]->{Path}; }
114 0     0 0   sub source { return $_[0]->{Source}; }
115 0     0 0   sub ppsource { return $_[0]->{PPSource}; }
116 0     0 0   sub package { return path_to_package $_[0]->{Path}; }
117              
118 0     0 0   sub methods { return values %{ $_[0]->{Methods} }; }
  0            
119             # sub locals { return values %{ $_[0]->{Globals} }; }
120 0     0 0   sub globals { return values %{ $_[0]->{Globals} }; }
  0            
121              
122             sub variable {
123 0     0 0   my ($self, $name) = @_;
124 0   0       return $self->{Locals}->{$name}
125             || $self->{Globals}->{$name}
126             || undef;
127             }
128              
129             # Instance modification methods
130              
131             sub closure {
132 0     0 0   my ($self, $clousure) = @_;
133 0           return (push(@{ $self->{Closures} }, $clousure) - 1);
  0            
134             }
135              
136             sub reset_labels {
137 0     0 0   my $self = shift;
138             # invoke for new method?
139 0 0         die "Label stack not empty" if @{ $self->{LabelStack} };
  0            
140 0           $self->{LabelDefault} = undef;
141 0           $self->{Labels} = { };
142 0           $self->{LabelCurrent} = undef;
143 0           $self->{LabelStack} = [ ];
144 0           $self->{BreakTarget} = undef;
145 0           $self->{BreakStack} = [ ];
146 0 0         print "Label stack reset\n" if $DEBUGLABELS;
147             }
148              
149             sub switch_start {
150 0     0 0   my ($self, $type) = @_; # Do something with 'type'
151 0           push(@{$self->{LabelStack}},
  0            
152             [
153             $self->{Labels},
154             $self->{LabelDefault},
155             ]);
156 0           $self->{LabelDefault} = undef;
157 0           $self->{Labels} = { };
158 0           push(@{$self->{BreakStack}}, $self->{BreakTarget});
  0            
159 0           $self->{BreakTarget} = $self->label(undef);
160 0           print "Start switch: Push labels: " .
161 0 0         scalar(@{ $self->{LabelStack} }) . "\n"
162             if $DEBUGLABELS;
163 0           return $self->{BreakTarget};
164             }
165              
166             sub switch_end {
167 0     0 0   my $self = shift;
168 0           my $ret = [ $self->{Labels}, $self->{LabelDefault} ];
169 0           my ($labels, $default) = @{ pop(@{ $self->{LabelStack} }) };
  0            
  0            
170 0           $self->{Labels} = { %{$self->{Labels}}, %$labels, };
  0            
171 0   0       $self->{LabelDefault} ||= $default;
172 0           $self->{BreakTarget} = pop(@{$self->{BreakStack}});
  0            
173 0           print "End switch: Pop labels: " .
174 0 0         scalar(@{ $self->{LabelStack} }) . "\n"
175             if $DEBUGLABELS;
176 0           return $ret;
177             }
178              
179             sub loop_start {
180 0     0 0   my $self = shift;
181 0           $self->{BreakTarget} = undef;
182 0           $self->{ContinueTarget} = $self->label(undef);
183             }
184              
185             sub loop_end {
186 0     0 0   my $self = shift;
187 0           $self->{BreakTarget} = pop(@{$self->{BreakStack}});
  0            
188 0           return $self->{BreakTarget}; # Make the return explicit
189             }
190              
191             # XXX This mechanism isn't currently used.
192             sub statement {
193 0     0 0   $_[0]->{LabelCurrent} = undef;
194             }
195              
196             sub label {
197 0     0 0   my ($self, $val) = @_;
198 0 0         return undef if $self->{LabelCurrent};
199 0           my $label = '__AMD_LABEL' . $self->{Label}++;
200 0 0         if (defined $val) {
201 0 0         print "Adding label $label => " . $val->dump . "\n"
202             if $DEBUGLABELS;
203 0           $self->{Labels}->{$label} = $val
204             }
205 0           return $label;
206             }
207              
208             sub default {
209 0     0 0   my $self = shift;
210 0 0         print "Adding DEFAULT label\n"
211             if $DEBUGLABELS;
212 0           return ($self->{LabelDefault} = $self->label(undef));
213             }
214              
215             # This should return a label in a switch or undef in a loop.
216             sub getbreaktarget {
217 0     0 0   $_[0]->{BreakTarget};
218             }
219              
220             sub save_locals {
221 0     0 0   my $self = shift;
222 0           my %saved = %{ $self->{Locals} };
  0            
223 0           push(@{$self->{ScopeStack}}, \%saved);
  0            
224             }
225              
226             sub restore_locals {
227 0     0 0   my $self = shift;
228 0           $self->{Locals} = pop(@{ $self->{ScopeStack} });
  0            
229             }
230              
231             # XXX Check that we don't declare a variable of type void.
232              
233             sub local {
234 0     0 0   my ($self, $name, $var) = @_;
235             # print STDERR "local($name, $var)\n";
236 0 0         return $self->{Locals}->{$name} unless $var;
237 0 0 0       $self->warning("Local $name masks previous definition")
      0        
238             if $self->{Locals}->{$name}
239             || $self->{Globals}->{$name}
240             || $self->{Statics}->{$name};
241             # print "Storing local variable " . $var->dump . "\n";
242 0           $self->{Locals}->{$name} = $var;
243 0           return ();
244             }
245              
246             sub global {
247 0     0 0   my ($self, $name, $var) = @_;
248             # print STDERR "global($name, $var)\n";
249 0 0         return $self->{Globals}->{$name} unless $var;
250 0 0 0       $self->error("Global $name masks previous definition in file XXX")
251             if $self->{Globals}->{$name}
252             || $self->{Statics}->{$name};
253             # print "Storing variable $name\n";
254 0           $self->{Globals}->{$name} = $var;
255 0           return ();
256             }
257              
258             sub static {
259 0     0 0   my ($self, $name, $var) = @_;
260             # print STDERR "static($name, $var)\n";
261 0 0         return $self->{Statics}->{$name} unless $var;
262 0 0         $self->error("Static $name masks previous definition in file XXX")
263             if $self->{Statics}->{$name};
264             # print "Storing variable $name\n";
265 0           $self->{Statics}->{$name} = $var;
266 0           return ();
267             }
268              
269             sub method {
270 0     0 0   my ($self, $name, $method) = @_;
271              
272             # print STDERR "method($name, $method)\n";
273              
274             # print STDERR "program->method($method)\n";
275              
276 0 0         unless ($method) {
277 0           $name =~ s/^.*:://; # XXX Remove and do properly.
278 0           my $ob = $self->{Methods}->{$name};
279 0 0         if (!$ob) {
280 0 0         $self->error("Method $name not found") unless $ob;
281             # warn "Autodefining method $name for bison yyparse";
282 0   0       $ob ||= new Anarres::Mud::Driver::Program::Method(
283             Type => T_INTEGER,
284             Name => $name,
285             Args => [],
286             Flags => M_UNKNOWN,
287             );
288 0           $self->{Methods}->{$name} = $ob;
289 0           $self->{MethodFlags}->{$name} = 0; # XXX UNDEFINED!
290             }
291 0           return $ob;
292             }
293              
294 0           my $proto = $self->{Methods}->{$name};
295 0 0         if ($proto) {
296             # XXX Check that types match!
297 0 0         warn "Method $name already defined"
298             if $proto->code;
299             }
300              
301             # print STDERR "Defining method $name\n";
302              
303             # XXX Check prototype match with superclass
304             # XXX Check sanity of modifiers
305              
306 0           $self->{Methods}->{$name} = $method;
307 0 0         $self->{MethodFlags}->{$name} = 0
308             unless exists $self->{MethodFlags}->{$name};
309              
310 0           return ();
311             }
312              
313             sub inherit {
314 0     0 0   my ($self, $name, $path) = @_;
315              
316 0           my $inh = $PROGS{$path};
317 0 0         return "Could not find inherited program '$path'" unless $inh;
318              
319 0 0         $name = basename($path, ".c") unless $name; # Also support DGD
320 0 0         return "Already inheriting file named $name"
321             if $self->{Inherits}->{$path};
322              
323 0           $self->{Inherits}->{$name} = $inh;
324              
325 0           my @errors;
326              
327 0           foreach ($inh->globals) {
328 0           my $err = $self->global($_);
329 0 0         push(@errors, $err), next if $err;
330             # Variable flags? Accessibility.
331             }
332              
333 0           foreach ($inh->methods) {
334 0 0         next if $_->flags & (M_EFUN | M_UNKNOWN | M_PRIVATE);
335 0           my $err = $self->method($_->name, $_); # XXX Mark inherited
336 0 0         push(@errors, $err) if $err;
337 0           $err = $self->method($name . "::" . $_->name, $_);
338 0 0         push(@errors, $err) if $err;
339             }
340              
341 0           return @errors;
342             }
343              
344             sub class {
345 0     0 0   my ($self, $cname, $fields) = @_;
346              
347 0 0         unless ($fields) {
348             # Search for the class; return a valid type for it.
349 0           my $class = $self->{Classes}->{$cname};
350 0 0         return $class if $class;
351 0           $self->error("No class named $cname");
352 0           return undef;
353             }
354              
355 0           my (%class, @types);
356 0           foreach (@$fields) {
357 0           my ($name, $type) = ($_->name, $_->type);
358 0           push(@types, $type);
359              
360 0 0         if ($class{$name}) {
361 0           $self->error("Field name $name multiply defined in class " .
362             $cname);
363 0           next;
364             }
365 0           $class{$name} = $type;
366             }
367              
368 0           my $type = T_CLASS($cname, @types);
369              
370 0           $self->{Classes}->{$cname} = {
371             Data => $fields,
372             Fields => \%class,
373             Type => $type,
374             };
375              
376             # print Dumper($fields);
377             # print STDERR "New class type is " . $$type . "\n";
378              
379 0           return 1;
380             }
381              
382             sub class_type {
383 0     0 0   my ($self, $cname) = @_;
384              
385 0           my $class = $self->class($cname);
386 0 0         unless ($class) {
387 0           $self->error("No such class $cname");
388 0           return T_FAILED;
389             }
390              
391 0           return $class->{Type};
392             }
393              
394             sub class_field_type {
395 0     0 0   my ($self, $cname, $fname) = @_;
396              
397 0           my $class = $self->{Classes}->{$cname};
398 0 0         unless ($class) {
399 0           $self->error("No such class $cname");
400 0           return T_FAILED;
401             }
402              
403 0           my $ftype = $class->{Fields}->{$fname};
404 0 0         unless ($ftype) {
405 0           $self->error("No such field $fname in class $cname");
406 0           return T_FAILED;
407             }
408              
409 0           return $ftype;
410             }
411              
412             # Debugging
413              
414             sub dump {
415 0     0 0   my ($self) = @_;
416              
417 0           my @inh = map { "(inherit " .
  0            
418             quote(printable $_) . " " .
419             quote(printable $self->{Inherits}->{$_}->path)
420             . ")" }
421 0           keys %{$self->{Inherits}};
422 0           my @glob = sort map { $_->dump(1) } values %{$self->{Globals}};
  0            
  0            
423 0           my @meth = sort keys %{$self->{Methods}};
  0            
424 0           @meth = grep { ! ($self->{MethodFlags}->{$_} & M_EFUN) } @meth;
  0            
425 0           @meth = map { $self->{Methods}->{$_}->dump(1) } @meth;
  0            
426              
427 0           my $out = "(program\n\t" . join("\n\t", @inh, @glob, @meth) . "\n)";
428              
429 0           return $out;
430             }
431              
432             # Semantics
433              
434             sub check {
435 0     0 0   my $self = shift;
436              
437 0           my @meth = grep { ! ($self->{MethodFlags}->{$_} & M_EFUN) }
  0            
438 0           keys %{$self->{Methods}};
439              
440 0           my $ret = 1;
441 0           foreach (@meth) {
442 0           my $tcm = $self->{Methods}->{$_}->check($self, 0);
443 0   0       $ret &&= $tcm;
444             }
445              
446 0           return $ret;
447             }
448              
449             # Output
450              
451             sub perl {
452 0     0 0   my ($self, $section, @code) = @_;
453 0 0         if (@code) {
454 0           push(@{ $self->{Perl}->[$section] }, @code);
  0            
455 0           return ();
456             }
457             else {
458 0           return join("\n", @{ $self->{Perl}->[$section] });
  0            
459             }
460             }
461              
462             sub perl_global {
463 0     0 0   my ($self, @globals) = @_;
464 0           push( @{ $self->{PerlGlobals} }, @globals);
  0            
465             }
466              
467             sub generate {
468 0     0 0   my ($self) = @_;
469              
470 0           my $path = $self->{Path};
471 0           my $package = $self->package;
472              
473 0           $self->perl(PERL_HEAD, "# program $path;");
474 0           $self->perl(PERL_HEAD, "package $package;");
475 0           $self->perl(PERL_USE, "use strict;");
476 0           $self->perl(PERL_USE, "use warnings;");
477              
478 0           $self->perl_global(q[$PROGRAM]);
479              
480 0 0         if (scalar %{ $self->{Inherits} }) {
  0            
481 0           my $inh = join " ",
482 0           map { $_->package }
483 0           values %{ $self->{Inherits} };
484 0           $self->perl_global(q[@ISA]);
485 0           $self->perl(PERL_VARS, qq[\@ISA = qw($inh);]);
486             }
487             else {
488 0           $self->perl(PERL_SUBS, qq[sub new { bless { }, shift; }\n]);
489             }
490              
491 0           $self->perl(PERL_USE, 'use vars qw(' .
492 0           join(" ", @{ $self->{PerlGlobals} }) .
493             ");");
494             # XXX $path forms part of a Perl program. Beware.
495 0           $self->perl(PERL_VARS,
496             '*PROGRAM = \$' . __PACKAGE__ . "::PROGS{'$path'};");
497 0           $self->perl(PERL_TAIL, '1;');
498 0           $self->perl(PERL_TAIL, '__END__');
499              
500             # These have a very large extent.
501 0           local *::methods = $self->{Methods};
502 0           local *::methodflags = $self->{MethodFlags};
503              
504             # Should we be doing these in order of definition? I've just
505             # put them into alpha order so I can find methods more easily
506             # in the generated Perl, but we lose definition order in the
507             # hash.
508 0           my @meth = map { $::methods{$_}->generate(0, $path) }
  0            
509 0           grep { ! ($::methodflags{$_} & M_EFUN) }
510             sort keys %::methods;
511              
512              
513 0           $self->perl(PERL_SUBS, @meth);
514              
515 0           my $out = '';
516 0           foreach (0..$#{$self->{Perl}}) {
  0            
517 0           $out .= "# === Section " .
518             $EXPORT_TAGS{sections}->[$_] . "\n";
519 0           $out .= $self->perl($_) . "\n\n";
520             }
521 0           return $out;
522             }
523              
524             1;