File Coverage

inc/TestML/Runtime.pm
Criterion Covered Total %
statement 195 262 74.4
branch 47 104 45.1
condition 11 21 52.3
subroutine 40 63 63.4
pod 0 17 0.0
total 293 467 62.7


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