File Coverage

inc/TestML/Runtime.pm
Criterion Covered Total %
statement 210 268 78.3
branch 45 98 45.9
condition 9 21 42.8
subroutine 43 65 66.1
pod 0 17 0.0
total 307 469 65.4


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