File Coverage

inc/TestML/Runtime.pm
Criterion Covered Total %
statement 204 262 77.8
branch 48 104 46.1
condition 9 21 42.8
subroutine 41 63 65.0
pod 0 17 0.0
total 302 467 64.6


line stmt bran cond sub pod time code
1             package TestML::Runtime;
2              
3 3     3   15 use TestML::Base;
  3         4  
  3         19  
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         2 $TestML::Runtime::Singleton = $self;
19 1 50 33     42 $self->{base} ||= $0 =~ m!(.*)/! ? $1 : ".";
20             }
21              
22             sub run {
23 1     1 0 2 my ($self) = @_;
24 1         16 $self->compile_testml;
25 1         230 $self->initialize_runtime;
26 1         8 $self->run_function($self->{function}, []);
27             }
28              
29             # TODO Functions should have return values
30             sub run_function {
31 1     1 0 2 my ($self, $function, $args) = @_;
32              
33 1         8 $self->apply_signature($function, $args);
34              
35 1         3 my $parent = $self->function;
36 1         2 $self->{function} = $function;
37              
38 1         3 for my $statement (@{$function->statements}) {
  1         5  
39 3 100       10 if (ref($statement) eq 'TestML::Assignment') {
40 2         8 $self->run_assignment($statement);
41             }
42             else {
43 1         6 $self->run_statement($statement);
44             }
45             }
46 1         278 $self->{function} = $parent;
47 1         4 return;
48             }
49              
50             sub apply_signature {
51 1     1 0 2 my ($self, $function, $args) = @_;
52 1         4 my $signature = $function->signature;
53              
54 1 50 33     6 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         6 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 1     1 0 3 my ($self, $statement) = @_;
71 1   50     5 my $blocks = $self->select_blocks($statement->points || []);
72 1         3 for my $block (@$blocks) {
73 7 50       21793 $self->function->setvar('Block', $block) if $block != 1;
74 7         26 my $result = $self->run_expression($statement->expr);
75 7 50       32 if (my $assert = $statement->assert) {
76 7         28 $self->run_assertion($result, $assert);
77             }
78             }
79             }
80              
81             sub run_assignment {
82 2     2 0 3 my ($self, $assignment) = @_;
83 2         6 $self->function->setvar(
84             $assignment->name,
85             $self->run_expression($assignment->expr),
86             );
87             }
88              
89             sub run_assertion {
90 7     7 0 19 my ($self, $left, $assert) = @_;
91 7         22 my $method = 'assert_' . $assert->name;
92              
93 7         26 $self->function->getvar('TestNumber')->{value}++;
94              
95 7 50       24 if ($assert->expr) {
96 7         21 $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 16     16 0 25 my ($self, $expr) = @_;
105              
106 16         20 my $context = undef;
107 16         33 $self->{error} = undef;
108 16 100       87 if ($expr->isa('TestML::Expression')) {
109 14         16 my @calls = @{$expr->calls};
  14         38  
110 14 50       40 die if @calls <= 1;
111 14         37 $context = $self->run_call(shift(@calls));
112 14         38 for my $call (@calls) {
113 21 50       61 if ($self->error) {
114             next unless
115 0 0 0     0 $call->isa('TestML::Call') and
116             $call->name eq 'Catch';
117             }
118 21         51 $context = $self->run_call($call, $context);
119             }
120             }
121             else {
122 2         10 $context = $self->run_call($expr);
123             }
124 16 50       55 if ($self->error) {
125 0         0 die $self->error;
126             }
127 16         52 return $context;
128             }
129              
130             sub run_call {
131 37     37 0 57 my ($self, $call, $context) = @_;
132              
133 37 100       210 if ($call->isa('TestML::Object')) {
134 2         18 return $call;
135             }
136 35 50       158 if ($call->isa('TestML::Function')) {
137 0         0 return $call;
138             }
139 35 100       139 if ($call->isa('TestML::Point')) {
140 14         40 return $self->get_point($call->name);
141             }
142 21 50       65 if ($call->isa('TestML::Call')) {
143 21         53 my $name = $call->name;
144 21   50     57 my $callable =
145             $self->function->getvar($name) ||
146             $self->lookup_callable($name) ||
147             die "Can't locate '$name' callable";
148 21 50       120 if ($callable->isa('TestML::Object')) {
149 0         0 return $callable;
150             }
151 21 50 66     56 return $callable unless $call->args or defined $context;
152 21   100     97 $call->{args} ||= [];
153 21         25 my $args = [map $self->run_expression($_), @{$call->args}];
  21         49  
154 21 50       64 unshift @$args, $context if $context;
155 21 50       70 if ($callable->isa('TestML::Callable')) {
156 21         33 my $value = eval { $callable->value->(@$args) };
  21         49  
157 21 50       92 if ($@) {
158 0         0 $self->{error} = $@;
159 0         0 return TestML::Error->new(value => $@);
160             }
161 21 50       67 die "'$name' did not return a TestML::Object object"
162             unless UNIVERSAL::isa($value, 'TestML::Object');
163 21         134 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 3     3 0 5 my ($self, $name) = @_;
175 3         4 for my $library (@{$self->function->getvar('Library')->value}) {
  3         9  
176 4 100       34 if ($library->can($name)) {
177 3     21   15 my $function = sub { $library->$name(@_) };
  21         116  
178 3         15 my $callable = TestML::Callable->new(value => $function);
179 3         9 $self->function->setvar($name, $callable);
180 3         21 return $callable;
181             }
182             }
183 0         0 return;
184             }
185              
186             sub get_point {
187 14     14 0 30 my ($self, $name) = @_;
188 14         37 my $value = $self->function->getvar('Block')->{points}{$name};
189 14 50       32 defined $value or return;
190 14 50 33     132 if ($value =~ s/\n+\z/\n/ and $value eq "\n") {
191 0         0 $value = '';
192             }
193 14         29 $value =~ s/^\\//gm;
194 14         45 return TestML::Str->new(value => $value);
195             }
196              
197             sub select_blocks {
198 1     1 0 3 my ($self, $wanted) = @_;
199 1 50       4 return [1] unless @$wanted;
200 1         2 my $selected = [];
201              
202 1         2 OUTER: for my $block (@{$self->function->data}) {
  1         4  
203 7         9 my %points = %{$block->points};
  7         16  
204 7 50       17 next if exists $points{SKIP};
205 7 50       13 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 7         12 for my $point (@$wanted) {
213 14 50       31 next OUTER unless exists $points{$point};
214             }
215 7         12 push @$selected, $block;
216 7 50       18 last if exists $points{LAST};
217             }
218 1         3 return $selected;
219             }
220              
221             sub compile_testml {
222 1     1 0 3 my ($self) = @_;
223              
224 1 50       7 die "'testml' document required but not found"
225             unless $self->testml;
226 1 50       6 if ($self->testml !~ /\n/) {
227 1 50       3 $self->testml =~ /(?:(.*)\/)?(.*)/ or die;
228 1         5 $self->{testml} = $2;
229 1 50       7 $self->{base} .= '/' . $1 if $1;
230 1         4 $self->{testml} = $self->read_testml_file($self->testml);
231             }
232 1 50       28 $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         9 $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         4 $self->{global}->setvar(True => $TestML::Constant::True);
244 1         4 $self->{global}->setvar(False => $TestML::Constant::False);
245 1         4 $self->{global}->setvar(None => $TestML::Constant::None);
246 1         6 $self->{global}->setvar(TestNumber => TestML::Num->new(value => 0));
247 1         10 $self->{global}->setvar(Library => TestML::List->new);
248              
249 1         3 my $library = $self->function->getvar('Library');
250 1         15 for my $lib ($self->bridge, $self->library) {
251 2 100       6 if (ref($lib) eq 'ARRAY') {
252 1         12 $library->push($_->new) for @$lib;
253             }
254             else {
255 1         12 $library->push($lib->new);
256             }
257             }
258             }
259              
260             sub get_label {
261 7     7 0 12 my ($self) = @_;
262 7 50       19 my $label = $self->function->getvar('Label') or return;
263 7 50       17 $label = $label->value or return;
264 7         43 $label =~ s/\$(\w+)/$self->replace_label($1)/ge;
  7         24  
265 7         38 return $label;
266             }
267              
268             sub replace_label {
269 7     7 0 16 my ($self, $var) = @_;
270 7         20 my $block = $self->function->getvar('Block');
271 7 50       36 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 2     2 0 4 my ($self, $file) = @_;
284 2         26 my $path = $self->base . '/' . $file;
285 2 50       3931 open my $fh, $path
286             or die "Can't open '$path' for input: $!";
287 2         24 local $/;
288 2         204 return <$fh>;
289             }
290              
291             #-----------------------------------------------------------------------------
292             package TestML::Function;
293              
294 3     3   27 use TestML::Base;
  3         4  
  3         21  
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 26 100   26   125 sub outer { @_ == 1 ? $outer->{$_[0]} : ($outer->{$_[0]} = $_[1]) }
304              
305             sub getvar {
306 62     62   92 my ($self, $name) = @_;
307 62         121 while ($self) {
308 83 100       234 if (my $object = $self->namespace->{$name}) {
309 59         190 return $object;
310             }
311 24         52 $self = $self->outer;
312             }
313 3         20 undef;
314             }
315              
316             sub setvar {
317 20     20   41 my ($self, $name, $value) = @_;
318 20         46 $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 3     3   19 use TestML::Base;
  3         5  
  3         14  
330              
331             has name => ();
332             has expr => ();
333              
334             #-----------------------------------------------------------------------------
335             package TestML::Statement;
336              
337 3     3   14 use TestML::Base;
  3         14  
  3         12  
338              
339             has expr => ();
340             has assert => ();
341             has points => ();
342              
343             #-----------------------------------------------------------------------------
344             package TestML::Expression;
345              
346 3     3   16 use TestML::Base;
  3         5  
  3         12  
347              
348             has calls => [];
349              
350             #-----------------------------------------------------------------------------
351             package TestML::Assertion;
352              
353 3     3   17 use TestML::Base;
  3         6  
  3         13  
354              
355             has name => ();
356             has expr => ();
357              
358             #-----------------------------------------------------------------------------
359             package TestML::Call;
360              
361 3     3   17 use TestML::Base;
  3         5  
  3         12  
362              
363             has name => ();
364             has args => ();
365              
366             #-----------------------------------------------------------------------------
367             package TestML::Callable;
368              
369 3     3   15 use TestML::Base;
  3         7  
  3         20  
370             has value => ();
371              
372             #-----------------------------------------------------------------------------
373             package TestML::Block;
374              
375 3     3   14 use TestML::Base;
  3         5  
  3         11  
376              
377             has label => '';
378             has points => {};
379              
380             #-----------------------------------------------------------------------------
381             package TestML::Point;
382              
383 3     3   18 use TestML::Base;
  3         8  
  3         12  
384              
385             has name => ();
386              
387             #-----------------------------------------------------------------------------
388             package TestML::Object;
389              
390 3     3   16 use TestML::Base;
  3         3  
  3         12  
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 3     3   16 use TestML::Base;
  3         6  
  3         18  
410             extends 'TestML::Object';
411              
412 21     21   57 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 3     3   16 use TestML::Base;
  3         5  
  3         12  
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 3     3   19 use TestML::Base;
  3         5  
  3         11  
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 3     3   16 use TestML::Base;
  3         6  
  3         14  
450             extends 'TestML::Object';
451             has value => [];
452 0     0   0 sub list { $_[0] }
453             sub push {
454 3     3   6 my ($self, $elem) = @_;
455 3         4 push @{$self->value}, $elem;
  3         8  
456             }
457              
458             #-----------------------------------------------------------------------------
459             package TestML::None;
460              
461 3     3   14 use TestML::Base;
  3         8  
  3         11  
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 3     3   27 use TestML::Base;
  3         6  
  3         12  
473             extends 'TestML::Object';
474              
475             #-----------------------------------------------------------------------------
476             package TestML::Error;
477              
478 3     3   17 use TestML::Base;
  3         11  
  3         19  
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;