File Coverage

blib/lib/Decl/Semantics/Code.pm
Criterion Covered Total %
statement 145 206 70.3
branch 39 62 62.9
condition 10 24 41.6
subroutine 16 21 76.1
pod 15 15 100.0
total 225 328 68.6


line stmt bran cond sub pod time code
1             package Decl::Semantics::Code;
2            
3 12     12   67 use warnings;
  12         25  
  12         362  
4 12     12   112 use strict;
  12         25  
  12         345  
5            
6 12     12   62 use base qw(Decl::Node);
  12         22  
  12         9290  
7 12     12   192 use Decl::Util;
  12         29  
  12         1372  
8 12     12   79 use Data::Dumper;
  12         27  
  12         42858  
9            
10             =head1 NAME
11            
12             Decl::Semantics::Code - implements some code (perl or otherwise) in a declarative framework.
13            
14             =head1 VERSION
15            
16             Version 0.01
17            
18             =cut
19            
20             our $VERSION = '0.01';
21            
22            
23             =head1 SYNOPSIS
24            
25             This class serves two purposes: first, it's an example of what a semantic node class should look like, and second, it
26             will probably end up being the class that builds most of the code references in a declarative program.
27            
28             =head2 defines(), tags_defined()
29            
30             Called by Decl::Semantics during import, to find out what tags this plugin claims to implement and the
31             parsers used to build its content.
32            
33             =cut
34 0     0 1 0 sub defines { ('on', 'do', 'perl', 'sub'); }
35             our %build_handlers = ( perl => { node => sub { Decl::Semantics::Code->new (@_) }, body => 'none' } );
36 12     12 1 80 sub tags_defined { Decl->new_data(<
37             on
38             do
39             sub
40             filter
41             perl (body=text)
42             EOF
43            
44             =head2 build_payload, build_macro_payload, fixvars, fixcall, fixevent, fixfind, make_code, make_macro_code, parse_select, make_select, make_dml, make_output, make_ifnew
45            
46             The C function is then called when this object's payload is built (i.e. in the stage when we're adding semantics to our
47             parsed syntax). The payload of a code object is its callable code result.
48            
49             The C function builds code in an event context; it actually calls C, which does the same in an arbitrary
50             node context that you supply (but that defaults to the event context of the code node).
51            
52             The parent's payload will always have been created by the time this function is called.
53            
54             The C function is by far the most complex of our code generators, as it has to find an iterator source and build a while loop,
55             or a DBI database and build a query and select loop. The parsing is split out into C in order to make it usable from elsewhere.
56            
57             The C function handles the non-select DBI keywords (just 'insert', 'update', and 'delete').
58            
59             The C functions munge various things around in our code generation scheme.
60            
61             The C function handles text blocks for output delineated with "<<".
62            
63             The C is probably going overboard with specific select tweaks; I really need to start thinking harder about real macros in the code.
64            
65             =cut
66            
67 17     17 1 123 sub fixvars { '$self->{v}->{\'' . $_[0] . '\'}' }
68             sub fixcall {
69 4 0 33 4 1 37 return '$self->' . $_[0] if ($_[0] eq 'output' ||
      33        
70             $_[0] eq 'write' ||
71             $_[0] eq 'log');
72 0         0 '$cx->' . $_[0]
73             }
74 2     2 1 13 sub fixevent { '$cx->do(\'' . $_[0] . '\')' }
75            
76 0     0 1 0 sub fixfind { '$self->find_context(' . $_[0] . ')' }
77            
78             our $next_counter = 1;
79            
80             sub parse_select { # 2011-08-27 - factored out of make_select below - made possible once more by the magic of unit testing!
81 2     2 1 4 my ($foreach) = @_;
82 2         4 my @vars = ();
83 2         3 my $keyword = '';
84            
85 2 100       14 if ($foreach =~ /^\s*(.*?)\s+in\s+(.*?)\s*$/) {
86 1         4 my ($target, $source) = ($1, $2);
87 1         6 @vars = split /\s*[, ]\s*/, $target;
88 1         8 return ('foreach', $target, $source, @vars);
89             }
90            
91 1 50       12 if ($foreach =~ /^\s*(.*?)\s+from\s+(.*?)\s*$/) {
92 0         0 my ($target, $source) = ($1, $2);
93 0         0 my $t = $target;
94 0         0 $t =~ s/^(distinct|all)\s+//;
95 0         0 @vars = map { s/^.* //; $_ } split (/\s*,\s*/, $t);
  0         0  
  0         0  
96 0         0 return ('select', $target, $source, @vars)
97             }
98 1 50       5 if ($foreach !~ /\s/) {
99 1         7 return ('foreach', '', $foreach);
100             }
101            
102 0         0 return ('error');
103             }
104            
105             sub make_select {
106 2     2 1 6 my ($self, $foreach, $keyword) = @_;
107 2         8 my $cx = $self->event_context;
108            
109 2         5 my ($target, $source);
110 2         4 my @vars = ();
111 2         4 my @last_vars = ();
112            
113 2         6 ($keyword, $target, $source, @vars) = parse_select($foreach);
114 2         5 @last_vars = map { '_last_' . $_ . '_value' } @vars;
  2         7  
115            
116 2 50       13 if ($keyword eq 'error') {
117 0         0 $self->error("'^foreach/select $foreach' can't be parsed");
118 0         0 return 'if (0) {';
119             }
120            
121 2         3 my $unique = $next_counter++;
122 2         2 my $ret;
123            
124 2 50       6 if ($keyword eq 'foreach') { # Normal data
125 2         10 my ($datasource, $type) = $self->find_data($source); # TODO: error handling if source not found.
126            
127 2 100 66     18 if (not $target and $datasource->is ('data')) {
128             # Take target from definition of data source.
129 1         6 push @vars, $datasource->parmlist;
130 1         5 push @last_vars, map { '_last_' . $_ . '_value' } $datasource->parmlist;
  2         5  
131             }
132            
133 2 50       10 if ($type eq 'text') {
    50          
134 0         0 my $my = '';
135 0 0       0 if (@vars) {
136 0         0 $target = 'my $' . shift @vars;
137 0 0       0 $my = 'my ($' . join (', $', @vars) . '); ' if @vars;
138 0 0       0 $my .= 'my ($' . join (', $', @last_vars) . '); ' if @last_vars;
139             } else {
140 0         0 $target = '$_';
141             }
142 0         0 $ret .= '{ ';
143 0         0 $ret .= 'my @text_node = $self->find_data(\'' . $source . '\'); ';
144 0         0 $ret .= 'my $iterator = $text_node[0]->iterate; ';
145 0         0 $ret .= 'while (' . $target . ' = $iterator->next) { ';
146 0         0 $ret .= $my;
147             } elsif ($type eq 'data') {
148 2         4 $ret .= '{ ';
149 2         5 $ret .= 'my @data_node = $self->find_data(\'' . $source . '\'); ';
150 2         4 $ret .= 'my $iterator = $data_node[0]->iterate; ';
151 2         3 $ret .= 'while (my $line = $iterator->next) { ';
152 2         6 $ret .= 'my ($' . join (', $', @vars) . ') = @$line;';
153 2         14 $ret .= 'my ($' . join (', $', @last_vars) . ') = @$line;';
154             } else {
155 0         0 $self->error ("node foreach not implemented yet");
156 0         0 $ret = 'if (0) {';
157             }
158             } else { # Database (e.g. DBI) select
159             # This is kind of the default mode for DBI; absent specification to the contrary, we find the first database handle and use it.
160             # But "source" is where everything is coming from, so if we can munge in in some way, this is where that will happen.
161            
162 0 0       0 if ($vars[0] eq '*') {
163 0         0 $ret .= '{ ';
164 0         0 $ret .= 'my $dbh = $self->find_context(\'database\')->payload; ';
165 0         0 $ret .= 'my $sth = $dbh->prepare ("select ' . $target . ' from ' . $source . '"); ';
166 0         0 $ret .= '$sth->execute(); ';
167 0         0 $ret .= 'while (my $row = $sth->fetchrow_hashref()) {';
168             } else {
169 0         0 $ret .= '{ ';
170 0         0 $ret .= 'my $dbh = $self->find_context(\'database\')->payload; ';
171 0         0 $ret .= 'my $sth = $dbh->prepare ("select ' . $target . ' from ' . $source . '"); ';
172 0         0 $ret .= '$sth->execute(); ';
173 0         0 $ret .= 'my ($' . join (', $', @vars) . '); ';
174 0         0 $ret .= 'my ($' . join (', $', @last_vars) . '); ';
175 0         0 $ret .= '$sth->bind_columns (\$'. join (', \$', @vars) . '); ';
176 0         0 $ret .= 'while ($sth->fetch()) {';
177             }
178             }
179            
180 2         15 $ret;
181             }
182            
183             sub make_dml {
184 0     0 1 0 my ($self, $foreach, $keyword) = @_;
185 0         0 my $cx = $self->event_context;
186             }
187            
188             sub make_output {
189 1     1 1 2 my ($output, $flag) = @_;
190            
191 1         2 my $r;
192 1 50       4 if ($flag eq '"') {
193 0         0 $r = '$self->output(<<"EOF");' . "\n";
194             } else {
195 1         2 $r = '$self->output($Decl::template_engine->express(<<\'EOF\', $cx));' . "\n";
196             }
197 1         3 $r .= $output;
198 1         1 $r .= "EOF\n";
199 1         3 return $r;
200             }
201            
202             sub make_ifnew {
203 0     0 1 0 my ($v) = @_;
204 0         0 return 'if (not defined $_last_' . $v . '_value or $_last_' . $v . '_value ne $' . $v . ') {' . "\n" .
205             ' $_last_' . $v . '_value = $' . $v . ';' . "\n";
206             }
207            
208             sub make_code {
209 1     1 1 3 my $self = shift;
210 1         3 my $code = shift;
211            
212 1         6 make_macro_code($self, $code, undef, @_);
213             }
214            
215             sub make_macro_code {
216 20     20 1 55 my $self = shift;
217 20         43 my $code = shift;
218 20   66     93 my $outer_cx = shift || $self->event_context;
219            
220 20         162 my $sem = $outer_cx->semantics;
221 20         153 my $subs = $self->subs();
222            
223 20         56 my $preamble = 'my $cx = shift || $outer_cx;' . "\n";
224 20 100       85 if (@_) {
225 5         29 $preamble .= 'my ($' . join (', $', @_) . ') = @_;' . "\n\n"; # I love generating code.
226             }
227 20         95 foreach my $subname (keys %$subs) {
228 2         13 $preamble .= 'local *' . $subname . ' = $subs->{\'' . $subname . '\'}->{sub};' . "\n";
229             }
230 20         63 $code = $preamble . $code;
231 20         80 $code =~ s/\^db( *)->/\$self->find_context('database')->dbh->/g;
232 20         135 $code =~ s/\$\^(\w+)/fixvars($1)/ge;
  17         65  
233 20         75 $code =~ s/\^!(\w+)/fixevent($1)/ge;
  2         9  
234 20         53 $code =~ s/\^\((.*?)\)/fixfind($1)/ge; # TODO: balanced parens would be a lot more convincing in that regexp...
  0         0  
235 20         70 $code =~ s/\^foreach (.*) {{/$self->make_select($1, 'foreach')/ge;
  2         9  
236 20         56 $code =~ s/\^select (.*) {{/$self->make_select($1, 'select')/ge;
  0         0  
237 20         53 $code =~ s/\^if-new (.*) {/make_ifnew($1)/ge;
  0         0  
238 20         53 $code =~ s/\^(insert .*);/$self->make_dml($1)/ge;
  0         0  
239 20         52 $code =~ s/\^(delete .*);/$self->make_dml($1)/ge;
  0         0  
240 20         54 $code =~ s/\^(update .*);/$self->make_dml($1)/ge;
  0         0  
241 20         68 $code =~ s/\^(\w+)/fixcall($1)/ge;
  4         12  
242            
243 20         46 my $lcode = '';
244 20         48 my $mode = 0;
245 20         38 my $indent = 0;
246 20         31 my $output;
247 20         41 my $flag = '';
248 20         99 foreach my $line (split /\n/, $code) {
249 86 100       184 if ($mode) {
250 2         4 my $leader = substr($line, 0, $indent);
251 2 100       9 if ($leader =~ /^[\s<]*$/) {
252 1         4 $output .= substr($line, $indent) . "\n";
253             } else {
254 1         5 $lcode .= make_output($output, $flag);
255 1         2 $mode = 0;
256 1         4 $lcode .= $line . "\n";
257             }
258             } else {
259 84 100       204 if ($line =~ /^\s*<
260 1         2 my $olen = length($line);
261 1         6 $line =~ s/^\s*<
262 1         3 $flag = substr($line, 0, 1);
263 1         13 $line =~ s/$flag\s*//;
264 1         3 $indent = $olen - length ($line);
265 1         2 $output = $line . "\n";
266 1         3 $mode = 1;
267             } else {
268 83         236 $lcode .= $line . "\n";
269             }
270             }
271             }
272 20 50       85 if ($mode) {
273 0         0 $lcode .= make_output($output, $flag);
274             }
275            
276 20         3358 my $sub = eval "sub {" . $lcode . "\n}";
277 20 50       94 $self->error ($@) if $@; # TODO: man, this is just the wrong way to do this.
278 20 50       70 print STDERR $@ if $@;
279            
280 20 100       70 if (wantarray) {
281 19         155 return ($sub, $lcode);
282             } else {
283 1         10 return $sub;
284             }
285             }
286            
287             sub build_payload { # TODO: split this out into build_code and build_payload
288 25     25 1 58 my $self = shift;
289 25         49 my $is_event = shift; # @_ is now the list of 'my' variables the code expects, by name.
290 25         108 build_macro_payload($self, $is_event, undef, @_);
291 25 100       96 $self->{callable} = 'sub' if $self->is('sub');
292 25         63 $self;
293             }
294            
295             sub build_macro_payload {
296 28     28 1 57 my $self = shift;
297 28         55 my $is_event = shift;
298 28   33     242 my $cx = shift || $self->event_context;
299            
300 28 100       146 return $self if $self->{built};
301 27         84 $self->{built} = 1;
302            
303 27 100       104 if (!@_) { # Didn't get any 'my' variables explicitly defined.
304 15         107 @_ = $self->optionlist;
305             }
306            
307             # Here's the tricky part. We have to build some code and evaluate it when asked. This could get arbitrarily complex.
308             # If we have a code body, that's our code. If we have both a body and a "code" (i.e. a one-line bracketed body), then
309             # the "code" takes precedence (e.g. Wx toolbars).
310 27 50       218 if ($self->code) { # TODO: this wasn't covered by the unit tests!
    100          
311 0         0 my $code = $self->code;
312 0         0 $code =~ s/^{//;
313 0         0 $code =~ s/}$//;
314             #print "code is $code\n";
315 0         0 ($self->{sub}, $self->{gencode}) = make_macro_code ($self, $code, $cx, @_);
316             #print STDERR "1gencode is " . $self->{gencode} . "\n";
317 0         0 $self->{callable} = 1;
318 0         0 $self->{owncode} = 1;
319             } elsif ($self->body) {
320 19         88 ($self->{sub}, $self->{gencode}) = make_macro_code ($self, $self->body, $cx, @_);
321             #print STDERR "gencode is " . $self->{gencode} . "\n";
322            
323 19         113 $self->{callable} = 1;
324 19         48 $self->{owncode} = 1;
325             } else {
326             # No body means we're just going to build each of our children, and try to execute each of them in sequence when called.
327             # No body and no callable children means we're not callable either.
328             #print "making child-based caller:" . $self->myline . "\n";
329 8         19 my $child_code = 0;
330 8         48 foreach ($self->nodes) {
331 0         0 $_->build;
332 0   0     0 $child_code = $child_code || $_->{callable};
333             }
334            
335 8 50       47 $self->{callable} = $child_code ? 1 : 0;
336 8     0   65 $self->{sub} = sub { $self->go(); };
  0         0  
337 8         48 $self->{owncode} = 0;
338             }
339            
340 27 100       181 $self->{event} = $self->is ('on') ? 1 : 0;
341 27 100 33     208 if ($self->{callable} && ($is_event || ($self->is ('on') and $self->name))) {
      66        
342 4         15 $cx->register_event ($self->name, $self->{sub});
343             }
344            
345 27 50       142 $self->{payload} = $self->{sub} unless $self->{payload}; # TODO: this seems fishy.
346 27         79 return $self;
347             }
348            
349            
350             =head1 AUTHOR
351            
352             Michael Roberts, C<< >>
353            
354             =head1 BUGS
355            
356             Please report any bugs or feature requests to C, or through
357             the web interface at L. I will be notified, and then you'll
358             automatically be notified of progress on your bug as I make changes.
359            
360             =head1 LICENSE AND COPYRIGHT
361            
362             Copyright 2010 Michael Roberts.
363            
364             This program is free software; you can redistribute it and/or modify it
365             under the terms of either: the GNU General Public License as published
366             by the Free Software Foundation; or the Artistic License.
367            
368             See http://dev.perl.org/licenses/ for more information.
369            
370             =cut
371            
372             1; # End of Decl::Semantics::Code