File Coverage

blib/lib/TestML/Runtime.pm
Criterion Covered Total %
statement 236 268 88.0
branch 71 98 72.4
condition 16 21 76.1
subroutine 50 65 76.9
pod 0 17 0.0
total 373 469 79.5


line stmt bran cond sub pod time code
1             package TestML::Runtime;
2              
3 27     27   16439 use TestML::Base;
  27         31  
  27         136  
4              
5             has testml => ();
6             has bridge => ();
7             has library => ();
8             has compiler => ();
9             has skip => ();
10              
11             has function => ();
12             has error => ();
13             has global => ();
14             has base => ();
15              
16 27     27   105 use File::Basename();
  27         26  
  27         315  
17 27     27   74 use File::Spec();
  27         25  
  27         40504  
18              
19             sub BUILD {
20 39     39 0 355 my ($self) = @_;
21 39         61 $TestML::Runtime::Singleton = $self;
22 39   66     1590 $self->{base} ||= File::Basename::dirname($0);
23             }
24              
25             sub run {
26 23     23 0 39 my ($self) = @_;
27 23         143 $self->compile_testml;
28 23         1736 $self->initialize_runtime;
29 23         126 $self->run_function($self->{function}, []);
30             }
31              
32             # TODO Functions should have return values
33             sub run_function {
34 34     34 0 50 my ($self, $function, $args) = @_;
35              
36 34         176 $self->apply_signature($function, $args);
37              
38 34         74 my $parent = $self->function;
39 34         54 $self->{function} = $function;
40              
41 34         39 for my $statement (@{$function->statements}) {
  34         81  
42 165 100       19582 if (ref($statement) eq 'TestML::Assignment') {
43 55         150 $self->run_assignment($statement);
44             }
45             else {
46 110         242 $self->run_statement($statement);
47             }
48             }
49 34         7370 $self->{function} = $parent;
50 34         79 return;
51             }
52              
53             sub apply_signature {
54 34     34 0 52 my ($self, $function, $args) = @_;
55 34         93 my $signature = $function->signature;
56              
57 34 50 66     136 die sprintf(
58             "Function received %d args but expected %d",
59             scalar(@$args),
60             scalar(@$signature),
61             ) if @$signature and @$args != @$signature;
62              
63 34         69 $function->setvar('Self', $function);
64 34         132 for (my $i = 0; $i < @$signature; $i++) {
65 27         22 my $arg = $args->[$i];
66 27 50       39 $arg = $self->run_expression($arg)
67             if ref($arg) eq 'TestML::Expression';
68 27         29 $function->setvar($signature->[$i], $arg);
69             }
70             }
71              
72             sub run_statement {
73 110     110 0 134 my ($self, $statement) = @_;
74 110   100     226 my $blocks = $self->select_blocks($statement->points || []);
75 110         173 for my $block (@$blocks) {
76 135 100       5727 $self->function->setvar('Block', $block) if $block != 1;
77 135         280 my $result = $self->run_expression($statement->expr);
78 135 100       251 if (my $assert = $statement->assert) {
79 121         275 $self->run_assertion($result, $assert);
80             }
81             }
82             }
83              
84             sub run_assignment {
85 55     55 0 64 my ($self, $assignment) = @_;
86 55         119 $self->function->setvar(
87             $assignment->name,
88             $self->run_expression($assignment->expr),
89             );
90             }
91              
92             sub run_assertion {
93 121     121 0 129 my ($self, $left, $assert) = @_;
94 121         229 my $method = 'assert_' . $assert->name;
95              
96 121         237 $self->function->getvar('TestNumber')->{value}++;
97              
98 121 100       228 if ($assert->expr) {
99 103         202 $self->$method($left, $self->run_expression($assert->expr));
100             }
101             else {
102 18         47 $self->$method($left);
103             }
104             }
105              
106             sub run_expression {
107 374     374 0 314 my ($self, $expr) = @_;
108              
109 374         277 my $context = undef;
110 374         348 $self->{error} = undef;
111 374 100       1137 if ($expr->isa('TestML::Expression')) {
112 85         67 my @calls = @{$expr->calls};
  85         151  
113 85 50       159 die if @calls <= 1;
114 85         144 $context = $self->run_call(shift(@calls));
115 85         118 for my $call (@calls) {
116 130 100       218 if ($self->error) {
117             next unless
118 11 100 66     38 $call->isa('TestML::Call') and
119             $call->name eq 'Catch';
120             }
121 126         193 $context = $self->run_call($call, $context);
122             }
123             }
124             else {
125 289         405 $context = $self->run_call($expr);
126             }
127 374 50       722 if ($self->error) {
128 0         0 die $self->error;
129             }
130 374         690 return $context;
131             }
132              
133             sub run_call {
134 500     500 0 408 my ($self, $call, $context) = @_;
135              
136 500 100       1200 if ($call->isa('TestML::Object')) {
137 126         158 return $call;
138             }
139 374 100       843 if ($call->isa('TestML::Function')) {
140 11         14 return $call;
141             }
142 363 100       754 if ($call->isa('TestML::Point')) {
143 132         231 return $self->get_point($call->name);
144             }
145 231 50       389 if ($call->isa('TestML::Call')) {
146 231         347 my $name = $call->name;
147 231   50     379 my $callable =
148             $self->function->getvar($name) ||
149             $self->lookup_callable($name) ||
150             die "Can't locate '$name' callable";
151 231 100       665 if ($callable->isa('TestML::Object')) {
152 66         78 return $callable;
153             }
154 165 100 100     287 return $callable unless $call->args or defined $context;
155 161   100     370 $call->{args} ||= [];
156 161         132 my $args = [map $self->run_expression($_), @{$call->args}];
  161         214  
157 161 100       316 unshift @$args, $context if $context;
158 161 100       339 if ($callable->isa('TestML::Callable')) {
159 150         147 my $value = eval { $callable->value->(@$args) };
  150         254  
160 150 100       250 if ($@) {
161 7         14 $self->{error} = $@;
162 7         24 return TestML::Error->new(value => $@);
163             }
164 143 50       329 die "'$name' did not return a TestML::Object object"
165             unless UNIVERSAL::isa($value, 'TestML::Object');
166 143         439 return $value;
167             }
168 11 50       21 if ($callable->isa('TestML::Function')) {
169 11         18 return $self->run_function($callable, $args);
170             }
171 0         0 die;
172             }
173 0         0 die;
174             }
175              
176             sub lookup_callable {
177 43     43 0 61 my ($self, $name) = @_;
178 43         44 for my $library (@{$self->function->getvar('Library')->value}) {
  43         78  
179 74 100       343 if ($library->can($name)) {
180 43     150   145 my $function = sub { $library->$name(@_) };
  150         372  
181 43         153 my $callable = TestML::Callable->new(value => $function);
182 43         103 $self->function->setvar($name, $callable);
183 43         207 return $callable;
184             }
185             }
186 0         0 return;
187             }
188              
189             sub get_point {
190 132     132 0 128 my ($self, $name) = @_;
191 132         207 my $value = $self->function->getvar('Block')->{points}{$name};
192 132 50       221 defined $value or return;
193 132 50 66     554 if ($value =~ s/\n+\z/\n/ and $value eq "\n") {
194 0         0 $value = '';
195             }
196 132         130 $value =~ s/^\\//gm;
197 132         266 return TestML::Str->new(value => $value);
198             }
199              
200             sub select_blocks {
201 110     110 0 100 my ($self, $wanted) = @_;
202 110 100       277 return [1] unless @$wanted;
203 39         60 my $selected = [];
204              
205 39         45 OUTER: for my $block (@{$self->function->data}) {
  39         80  
206 100         76 my %points = %{$block->points};
  100         190  
207 100 50       183 next if exists $points{SKIP};
208 100 50       156 if (exists $points{ONLY}) {
209 0         0 for my $point (@$wanted) {
210 0 0       0 return [] unless exists $points{$point};
211             }
212 0         0 $selected = [$block];
213 0         0 last;
214             }
215 100         112 for my $point (@$wanted) {
216 178 100       333 next OUTER unless exists $points{$point};
217             }
218 64         76 push @$selected, $block;
219 64 50       137 last if exists $points{LAST};
220             }
221 39         63 return $selected;
222             }
223              
224             sub compile_testml {
225 23     23 0 38 my ($self) = @_;
226              
227 23 50       110 die "'testml' document required but not found"
228             unless $self->testml;
229 23 100       67 if ($self->testml !~ /\n/) {
230 20         57 my ($file, $dir) = File::Basename::fileparse($self->testml);
231 20         87 $self->{testml} = $file;
232 20         242 $self->{base} = File::Spec->catdir($self->{base}, $dir);
233 20         60 $self->{testml} = $self->read_testml_file($self->testml);
234             }
235 23 50       181 $self->{function} = $self->compiler->new->compile($self->testml)
236             or die "TestML document failed to compile";
237             }
238              
239             sub initialize_runtime {
240 23     23 0 43 my ($self) = @_;
241              
242 23         122 $self->{global} = $self->function->outer;
243              
244 23         107 $self->{global}->setvar(Block => TestML::Block->new);
245 23         96 $self->{global}->setvar(Label => TestML::Str->new(value => '$BlockLabel'));
246 23         61 $self->{global}->setvar(True => $TestML::Constant::True);
247 23         52 $self->{global}->setvar(False => $TestML::Constant::False);
248 23         54 $self->{global}->setvar(None => $TestML::Constant::None);
249 23         73 $self->{global}->setvar(TestNumber => TestML::Num->new(value => 0));
250 23         137 $self->{global}->setvar(Library => TestML::List->new);
251              
252 23         64 my $library = $self->function->getvar('Library');
253 23         184 for my $lib ($self->bridge, $self->library) {
254 46 100       123 if (ref($lib) eq 'ARRAY') {
255 23         152 $library->push($_->new) for @$lib;
256             }
257             else {
258 23         202 $library->push($lib->new);
259             }
260             }
261             }
262              
263             sub get_label {
264 124     124 0 138 my ($self) = @_;
265 124 50       196 my $label = $self->function->getvar('Label') or return;
266 124 50       228 $label = $label->value or return;
267 124         532 $label =~ s/\$(\w+)/$self->replace_label($1)/ge;
  115         219  
268 124         394 return $label;
269             }
270              
271             sub replace_label {
272 115     115 0 184 my ($self, $var) = @_;
273 115         186 my $block = $self->function->getvar('Block');
274 115 100       326 return $block->label if $var eq 'BlockLabel';
275 25 100       39 if (my $v = $block->points->{$var}) {
276 12         13 $v =~ s/\n.*//s;
277 12         32 $v =~ s/^\s*(.*?)\s*$/$1/;
278 12         26 return $v;
279             }
280 13 50       20 if (my $v = $self->function->getvar($var)) {
281 13         20 return $v->value;
282             }
283             }
284              
285             sub read_testml_file {
286 40     40 0 90 my ($self, $file) = @_;
287 40         142 my $path = File::Spec->catfile($self->base, $file);
288 40 50       1210 open my $fh, $path
289             or die "Can't open '$path' for input: $!";
290 40         134 local $/;
291 40         944 return <$fh>;
292             }
293              
294             #-----------------------------------------------------------------------------
295             package TestML::Function;
296              
297 27     27   137 use TestML::Base;
  27         57  
  27         97  
298              
299             has type => 'Func'; # Functions are TestML typed objects
300             has signature => []; # Input variable names
301             has namespace => {}; # Lexical scoped variable stash
302             has statements => []; # Exexcutable code statements
303             has data => []; # Data section scoped to this function
304              
305             my $outer = {};
306 598 100   598   1766 sub outer { @_ == 1 ? $outer->{$_[0]} : ($outer->{$_[0]} = $_[1]) }
307              
308             sub getvar {
309 849     849   734 my ($self, $name) = @_;
310 849         1172 while ($self) {
311 1281 100       1653 if (my $object = $self->namespace->{$name}) {
312 788         1423 return $object;
313             }
314 493         565 $self = $self->outer;
315             }
316 61         259 undef;
317             }
318              
319             sub setvar {
320 384     384   402 my ($self, $name, $value) = @_;
321 384         538 $self->namespace->{$name} = $value;
322             }
323              
324             sub forgetvar {
325 0     0   0 my ($self, $name) = @_;
326 0         0 delete $self->namespace->{$name};
327             }
328              
329             #-----------------------------------------------------------------------------
330             package TestML::Assignment;
331              
332 27     27   123 use TestML::Base;
  27         44  
  27         830  
333              
334             has name => ();
335             has expr => ();
336              
337             #-----------------------------------------------------------------------------
338             package TestML::Statement;
339              
340 27     27   140 use TestML::Base;
  27         33  
  27         77  
341              
342             has expr => ();
343             has assert => ();
344             has points => ();
345              
346             #-----------------------------------------------------------------------------
347             package TestML::Expression;
348              
349 27     27   103 use TestML::Base;
  27         29  
  27         72  
350              
351             has calls => [];
352              
353             #-----------------------------------------------------------------------------
354             package TestML::Assertion;
355              
356 27     27   99 use TestML::Base;
  27         29  
  27         79  
357              
358             has name => ();
359             has expr => ();
360              
361             #-----------------------------------------------------------------------------
362             package TestML::Call;
363              
364 27     27   96 use TestML::Base;
  27         29  
  27         90  
365              
366             has name => ();
367             has args => ();
368              
369             #-----------------------------------------------------------------------------
370             package TestML::Callable;
371              
372 27     27   95 use TestML::Base;
  27         28  
  27         109  
373             has value => ();
374              
375             #-----------------------------------------------------------------------------
376             package TestML::Block;
377              
378 27     27   101 use TestML::Base;
  27         27  
  27         87  
379              
380             has label => '';
381             has points => {};
382              
383             #-----------------------------------------------------------------------------
384             package TestML::Point;
385              
386 27     27   97 use TestML::Base;
  27         30  
  27         83  
387              
388             has name => ();
389              
390             #-----------------------------------------------------------------------------
391             package TestML::Object;
392              
393 27     27   93 use TestML::Base;
  27         28  
  27         86  
394              
395             has value => ();
396              
397             sub type {
398 8     8   11 my $type = ref($_[0]);
399 8 50       36 $type =~ s/^TestML::// or die "Can't find type of '$type'";
400 8         20 return $type;
401             }
402              
403 0     0   0 sub str { die "Cast from ${\ $_[0]->type} to Str is not supported" }
  0         0  
404 0     0   0 sub num { die "Cast from ${\ $_[0]->type} to Num is not supported" }
  0         0  
405 0     0   0 sub bool { die "Cast from ${\ $_[0]->type} to Bool is not supported" }
  0         0  
406 0     0   0 sub list { die "Cast from ${\ $_[0]->type} to List is not supported" }
  0         0  
407 0     0   0 sub none { $TestML::Constant::None }
408              
409             #-----------------------------------------------------------------------------
410             package TestML::Str;
411              
412 27     27   121 use TestML::Base;
  27         36  
  27         97  
413             extends 'TestML::Object';
414              
415 215     215   358 sub str { $_[0] }
416 0 0   0   0 sub num { TestML::Num->new(
417             value => ($_[0]->value =~ /^-?\d+(?:\.\d+)$/ ? ($_[0]->value + 0) : 0),
418             )}
419             sub bool {
420 5 100   5   12 length($_[0]->value) ? $TestML::Constant::True : $TestML::Constant::False
421             }
422 0     0   0 sub list { TestML::List->new(value => [split //, $_[0]->value]) }
423              
424             #-----------------------------------------------------------------------------
425             package TestML::Num;
426              
427 27     27   110 use TestML::Base;
  27         33  
  27         90  
428             extends 'TestML::Object';
429              
430 12     12   27 sub str { TestML::Str->new(value => $_[0]->value . "") }
431 0     0   0 sub num { $_[0] }
432 4 100   4   10 sub bool { ($_[0]->value != 0) ? $TestML::Constant::True : $TestML::Constant::False }
433             sub list {
434 0     0   0 my $list = [];
435 0         0 $#{$list} = int($_[0]) -1;
  0         0  
436 0         0 TestML::List->new(value =>$list);
437             }
438              
439             #-----------------------------------------------------------------------------
440             package TestML::Bool;
441              
442 27     27   113 use TestML::Base;
  27         31  
  27         88  
443             extends 'TestML::Object';
444              
445 0 0   0   0 sub str { TestML::Str->new(value => $_[0]->value ? "1" : "") }
446 0 0   0   0 sub num { TestML::Num->new(value => $_[0]->value ? 1 : 0) }
447 16     16   38 sub bool { $_[0] }
448              
449             #-----------------------------------------------------------------------------
450             package TestML::List;
451              
452 27     27   116 use TestML::Base;
  27         43  
  27         86  
453             extends 'TestML::Object';
454             has value => [];
455 40     40   72 sub list { $_[0] }
456             sub push {
457 69     69   74 my ($self, $elem) = @_;
458 69         70 push @{$self->value}, $elem;
  69         141  
459             }
460              
461             #-----------------------------------------------------------------------------
462             package TestML::None;
463              
464 27     27   100 use TestML::Base;
  27         31  
  27         84  
465             extends 'TestML::Object';
466              
467 0     0   0 sub str { TestML::Str->new(value => '') }
468 0     0   0 sub num { TestML::Num->new(value => 0) }
469 2     2   6 sub bool { $TestML::Constant::False }
470 0     0     sub list { TestML::List->new(value => []) }
471              
472             #-----------------------------------------------------------------------------
473             package TestML::Native;
474              
475 27     27   114 use TestML::Base;
  27         765  
  27         1497  
476             extends 'TestML::Object';
477              
478             #-----------------------------------------------------------------------------
479             package TestML::Error;
480              
481 27     27   120 use TestML::Base;
  27         1371  
  27         1287  
482             extends 'TestML::Object';
483              
484             #-----------------------------------------------------------------------------
485             package TestML::Constant;
486              
487             our $True = TestML::Bool->new(value => 1);
488             our $False = TestML::Bool->new(value => 0);
489             our $None = TestML::None->new;
490              
491             1;