File Coverage

inc/TestML/Runtime.pm
Criterion Covered Total %
statement 200 262 76.3
branch 47 104 45.1
condition 7 21 33.3
subroutine 41 63 65.0
pod 0 17 0.0
total 295 467 63.1


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