File Coverage

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