File Coverage

blib/lib/Repl/Core/Eval.pm
Criterion Covered Total %
statement 212 261 81.2
branch 108 178 60.6
condition 19 40 47.5
subroutine 18 20 90.0
pod 0 9 0.0
total 357 508 70.2


line stmt bran cond sub pod time code
1             package Repl::Core::Eval;
2            
3             # Pragma's.
4 1     1   4240 use strict;
  1         5  
  1         40  
5 1     1   5 use warnings;
  1         2  
  1         32  
6 1     1   4 no warnings 'recursion';
  1         2  
  1         33  
7            
8             # Uses.
9 1     1   503 use Repl::Core::BasicContext;
  1         3  
  1         24  
10 1     1   485 use Repl::Core::CompositeContext;
  1         3  
  1         22  
11 1     1   5 use Repl::Core::Pair;
  1         2  
  1         16  
12 1     1   463 use Repl::Core::CommandRepo;
  1         2  
  1         21  
13 1     1   462 use Repl::Core::MacroRepo;
  1         3  
  1         26  
14 1     1   595 use Repl::Core::Lambda;
  1         2  
  1         33  
15            
16 1     1   1291 use Time::HiRes qw/gettimeofday/;
  1         2035  
  1         4  
17 1     1   222 use Carp;
  1         2  
  1         3265  
18            
19             sub new
20             {
21 1     1 0 10 my $invocant = shift;
22 1   33     6 my $class = ref($invocant) || $invocant;
23             # Initialize the token instance.
24 1         2 my $self = {};
25 1         10 $self->{CTX} = new Repl::Core::BasicContext;
26 1         9 $self->{CMDREPO} = new Repl::Core::CommandRepo;
27 1         8 $self->{MACREPO} = new Repl::Core::MacroRepo;
28 1         3 return bless($self, $class);
29             }
30            
31             # Getter and setter.
32             sub commandRepo
33             {
34 0     0 0 0 my $self = shift;
35 0         0 my $arg = shift;
36 0 0       0 $self->{CMDREPO} = $arg if $arg;
37 0         0 return $self->{CMDREPO};
38             }
39            
40             # Two parameters:
41             # - A name
42             # - A command instance.
43             sub registerCommand
44             {
45 12     12 0 13 my $self = shift;
46 12         13 my $name = shift;
47 12         10 my $cmd = shift;
48 12         37 $self->{CMDREPO}->registerCommand($name, $cmd);
49             }
50            
51            
52             # Getter and setter.
53             sub context
54             {
55 0     0 0 0 my $self = shift;
56 0         0 my $arg = shift;
57 0 0       0 $self->{CTX} = $arg if $arg;
58 0         0 return $self->{CTX};
59             }
60            
61             # The eval that should be called from the outside.
62             # A single parameter: the expression.
63             sub evalExpr
64             {
65 26     26 0 54 my ($self, $expr) = @_;
66 26         27 my $result;
67 26         51 eval {$result = $self->evalInContext($expr, $self->{CTX});};
  26         104  
68 26 100       132 croak sprintf("ERROR: An error occured while evaluating an expression.\n%s", cutat($@)) if($@);
69 25         93 return $result;
70             }
71            
72             # Internal, private eval.
73             sub evalInContext
74             {
75 653     653 0 929 my ($self, $expr, $ctx) = @_;
76 653         688 my $evalres = eval {
77 653 100       1119 if(!(ref($expr) eq 'ARRAY'))
78             {
79             # I. Atomic expressions.
80             ########################
81            
82 450 100 66     1605 if($expr =~ /\$(.+)/)
    50          
83             {
84 102         349 return $ctx->getBinding($1);
85             }
86             elsif(ref($expr) && $expr->isa("Repl::Core::Pair"))
87             {
88 0         0 my $left = $self->evalInContext($expr->getLeft(), $ctx);
89 0         0 my $right = $self->evalInContext($expr->getRight(), $ctx);
90 0         0 return new Repl::Core::Pair(LEFT=>$left, RIGHT=>$right);
91             }
92             else
93             {
94 348         625 return $expr;
95             }
96             }
97             else
98             {
99             # II. ARRAY composite expressions.
100             ##################################
101            
102             # An empty list is not evaluated, it evaluates to itself.
103 203 50       336 return $expr if ! scalar($expr);
104             # We have a non-empty list here.
105 203   50     470 my $cmdCandidate = @$expr[0] || '';
106 203         185 my $listsize = scalar(@{$expr});
  203         311  
107            
108             # 1. Special forms.
109            
110 203 50 100     2181 if("quote" eq $cmdCandidate)
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
    50          
111             {
112 0 0       0 return @$expr[1] if $listsize == 2;
113 0         0 die "ERROR: Quote bad format."
114             }
115             elsif("if" eq $cmdCandidate)
116             {
117 34 50 33     142 die "ERROR: If bad format" if $listsize < 3 || $listsize > 4;
118 34 100       351 if($self->boolEval($self->evalInContext($expr->[1], $ctx)))
    50          
119             {
120 26         60 return $self->evalInContext($expr->[2], $ctx);
121             }
122             elsif($listsize == 4)
123             {
124 8         19 return $self->evalInContext($expr->[3], $ctx);
125             }
126             else
127             {
128 0         0 return;
129             }
130             }
131             elsif("while" eq $cmdCandidate)
132             {
133 1 50 33     9 die "ERROR: While bad format" if $listsize < 2 || $listsize > 3;
134 1         4 my $result;
135 1         4 while($self->boolEval($self->evalInContext($expr->[1], $ctx)))
136             {
137 10 50       23 if($listsize == 3)
138             {
139 10         23 $result = $self->evalInContext($expr->[2], $ctx);
140             }
141             }
142 1         3 return $result;
143             }
144             elsif("and" eq $cmdCandidate)
145             {
146 5 50       13 die "ERROR: And bad format" if $listsize < 2;
147 5         18 for(my $i = 1; $i < $listsize; $i = $i + 1)
148             {
149 22         30 my $el = $expr->[$i];
150 22 100       42 return 0 if !$self->boolEval($self->evalInContext($el, $ctx));
151             }
152 3         8 return 1;
153             }
154             elsif("or" eq $cmdCandidate)
155             {
156 3 50       12 die "ERROR: Or bad format" if $listsize < 2;
157 3         10 for(my $i = 1; $i < $listsize;$i = $i + 1)
158             {
159 14         20 my $el = $expr->[$i];
160 14 100       34 return 1 if $self->boolEval($self->evalInContext($el, $ctx));
161             }
162 2         8 return 0;
163             }
164             elsif("not" eq $cmdCandidate)
165             {
166 2 50       7 die "ERROR: Not bad format." if $listsize != 2;
167 2 100       9 if($self->evalInContext($expr->[1], $ctx))
168             {
169 1         5 return 0;
170             }
171             else
172             {
173 1         3 return 1;
174             }
175             }
176             elsif("set" eq $cmdCandidate || "defvar" eq $cmdCandidate)
177             {
178 25         34 my $name;
179             my $value;
180            
181 25 100       63 if($listsize == 2)
    50          
182             {
183 4         8 my $paircand = $expr->[1];
184 4 50 33     31 if(ref($paircand) && $paircand->isa("Repl::Core::Pair"))
185             {
186 4         15 $name = $self->evalInContext($paircand->getLeft(), $ctx);
187 4         17 $value = $self->evalInContext($paircand->getRight(), $ctx);
188             }
189             else
190             {
191 0         0 die "ERROR: set bad format. Expected pair.";
192             }
193             }
194             elsif($listsize ==3)
195             {
196 21         77 $name = $self->evalInContext($expr->[1], $ctx);
197 21         44 $value = $self->evalInContext($expr->[2], $ctx);
198             }
199             else
200             {
201 0         0 die "ERROR: set is badly formed.";
202             }
203            
204 25 50       48 die "ERROR: set name should be scalar." if ref($name);
205            
206 25 100       44 if("set" eq $cmdCandidate)
207             {
208             # set.
209 14         42 $ctx->setBinding($name, $value);
210             }
211             else
212             {
213             # defvar.
214 11         45 $ctx->getRootContext()->defBinding($name, $value);
215             }
216 24         50 return $value;
217             }
218             elsif("let" eq $cmdCandidate || "let*" eq $cmdCandidate)
219             {
220 7 50       20 die "ERROR: bad let/let* form." if $listsize != 3;
221 7         15 my $bindings = $expr->[1];
222 7         13 my $letexpr = $expr->[2];
223 7         15 my $isletrec = ($cmdCandidate eq "let*");
224            
225 7 50       24 die "ERROR: bad let/let* form. Bindings should be array." if "ARRAY" ne ref($bindings);
226 7         14 my $bindingprep = [];
227 7         42 my $letctx = new Repl::Core::CompositeContext(new Repl::Core::BasicContext(), $ctx);
228            
229 7         17 foreach my $binding (@$bindings)
230             {
231 16 50 0     58 if(ref($binding) eq 'ARRAY')
    50          
    0          
232             {
233 0 0       0 die "ERROR: bad binding." if scalar(@$binding) != 2;
234 0         0 my $key = $binding->[0];
235 0         0 my $val = $self->evalInContext($$binding->[1], $letctx);
236 0 0       0 if($isletrec)
237             {
238 0         0 $letctx->defBinding($key,$val);
239             }
240             else
241             {
242 0         0 push(@$bindingprep, new Repl::Core::Pair(LEFT=>$key, RIGHT=>$val));
243             }
244             }
245             elsif(ref($binding) eq 'Repl::Core::Pair')
246             {
247 16         47 my $key = $binding->getLeft();
248 16         46 my $val = $self->evalInContext($binding->getRight(), $letctx);
249            
250 16 100       43 if($isletrec)
251             {
252 4         15 $letctx->defBinding($key,$val);
253             }
254             else
255             {
256 12         46 push(@$bindingprep, new Repl::Core::Pair(LEFT=>$key, RIGHT=>$val));
257             }
258             }
259             elsif(!ref($binding) && $binding)
260             {
261 0 0       0 if($isletrec)
262             {
263 0         0 $letctx->defBinding($binding, "");
264             }
265             else
266             {
267 0         0 push(@$bindingprep, new Repl::Core::Pair($binding, ""));
268             }
269             }
270             else
271             {
272 0         0 die "ERROR: bad let/let* binding list.";
273             }
274             }
275            
276 7 100       24 if(!$isletrec)
277             {
278 5         12 foreach my $pair (@$bindingprep)
279             {
280 12         34 $letctx->defBinding($pair->getLeft(), $pair->getRight());
281             }
282             }
283            
284             # Evaluate the let body.
285 7         20 return $self->evalInContext($letexpr, $letctx);
286             }
287             elsif ("get" eq $cmdCandidate)
288             {
289 0 0       0 die "ERROR: get format." if $listsize != 2;
290 0         0 my $name = $self->evalInContext($expr->[1], $ctx);
291 0 0       0 die "ERROR: get not string." if ref($name);
292 0         0 return $ctx->getBinding($name);
293             }
294             elsif("lambda" eq $cmdCandidate)
295             {
296 4 50       14 die "ERROR: lambda bad form." if $listsize != 3;
297 4         9 my $params = $expr->[1];
298 4         4 my $body = $expr->[2];
299            
300 4 50       12 die "ERROR: lambda param list." if(ref($params) ne 'ARRAY');
301 4 50       12 die "ERROR: lambda body." if !$body;
302 4         7 foreach my $param (@$params)
303             {
304 4 50 33     58 die "ERROR: Lambda bad parameter" if ref($param) || !$param;
305             }
306            
307             # Create a new parameter list copy.
308 4         21 my $paramlist = [@$params];
309 4         30 return new Repl::Core::Lambda($paramlist, $body, $ctx);
310             }
311             elsif("defun" eq $cmdCandidate)
312             {
313 1 50       8 die "ERROR: defun form." if $listsize != 4;
314 1         3 my $name = $expr->[1];
315 1         2 my $params = $expr->[2];
316 1         1 my $body = $expr->[3];
317            
318 1 50 33     6 die "ERROR: defun name." if(!$name || ref($name));
319 1 50       3 die "ERROR: defun params." if(ref($params) ne 'ARRAY');
320 1 50       4 die "ERROR: defun body." if !$body;
321 1         2 foreach my $param (@$params)
322             {
323 1 50 33     6 die "ERROR: defun bad parameter" if ref($param) || !$param;
324             }
325            
326 1         4 my $lambdamacro = ["lambda", $params, $body];
327 1         31 my $lambda = $self->evalInContext($lambdamacro, $ctx);
328 1         5 $ctx->getRootContext()->defBinding($name, $lambda);
329            
330 1         3 return $lambda;
331             }
332             elsif("timer" eq $cmdCandidate)
333             {
334 0 0       0 die "ERROR: timer form." if $listsize != 2;
335 0         0 my $start = gettimeofday() * 1000;
336 0         0 my $result = $self->evalInContext($expr->[1], $ctx);
337 0         0 my $stop = gettimeofday() * 1000;
338 0         0 return $stop - $start;
339             }
340             elsif($self->{MACREPO}->hasMacro($cmdCandidate))
341             {
342 0         0 my $macro = $self->{MACREPO}->getCommand($cmdCandidate);
343 0         0 my $transformed = $macro->transform(@$expr[1..($listsize -1)]);
344 0         0 return $self->eval($transformed, $ctx);
345             }
346            
347             # 2. All the other arrays are evaluated in a standard way.
348            
349 121         220 my $evallist = [];
350            
351 121         188 foreach my $el (@$expr)
352             {
353 347         1318 push @$evallist, $self->evalInContext($el, $ctx);
354            
355             }
356 121   50     301 $cmdCandidate = $evallist->[0] || '';
357            
358 121 50       237 die "ERROR: An empty list cannot be executed." if(scalar(@$evallist) == 0);
359            
360 121 50 66     542 if("eval" eq $cmdCandidate)
    100          
    100          
    100          
    100          
    100          
    50          
361             {
362 0 0       0 die "ERROR: bad eval form." if $listsize != 2;
363 0         0 return $self->evalInContext($evallist->[1], $ctx);
364             }
365             elsif("eq" eq $cmdCandidate)
366             {
367 15 50       34 die "ERROR: eq bad form." if$listsize != 3;
368 15         37 my $arg1 = $self->evalInContext($evallist->[1], $ctx);
369 15         49 my $arg2 = $self->evalInContext($evallist->[2], $ctx);
370 15         45 return $arg1 eq $arg2;
371             }
372             elsif("progn" eq $cmdCandidate)
373             {
374 6 50       15 die "ERROR: progn bad form" if $listsize < 2;
375 6         21 return $evallist->[$listsize - 1];
376             }
377             elsif("funcall" eq $cmdCandidate)
378             {
379 25 50       51 die "ERROR: funcall bad form." if $listsize < 2;
380 25         35 my $name = $evallist->[1];
381 25         30 my $lambda;
382            
383 25 100       67 if(ref($name) eq 'Repl::Core::Lambda')
    50          
384             {
385 24         29 $lambda = $name;
386             }
387             elsif(!ref($name))
388             {
389 1         7 my $obj = $ctx->getBinding($name);
390 1 50       5 if(ref($obj) eq 'Repl::Core::Lambda')
391             {
392 1         2 $lambda = $obj;
393             }
394             else
395             {
396 0         0 die "ERROR: function not found in the context.";
397             }
398             }
399             else
400             {
401 0         0 die "ERROR: first part of funcall is not a function name or a lambda.";
402             }
403            
404 25         29 my $callctx;
405 25         29 eval {$callctx = $lambda->createContext(@$evallist[2..($listsize-1)])};
  25         96  
406 25 50       51 die sprintf("ERROR in arglist.\n%s", $@) if $@;
407            
408 25         26 my $result;
409 25         25 eval {$result = $self->evalInContext($lambda->getExpr(), $callctx)};
  25         68  
410 25 50       75 die sprintf("ERROR in call of %s.\n%s", $name, $@) if $@;
411            
412 25         125 return $result;
413             }
414             elsif($self->{CMDREPO}->hasCommand($cmdCandidate))
415             {
416 51         165 my $cmd = $self->{CMDREPO}->getCommand($cmdCandidate);
417 51         57 my $result;
418 51         49 eval {$result = $cmd->execute($ctx, $evallist)};
  51         157  
419 51 50       83 if($@)
420             {
421             # Leave the line number in this case, the author of the command
422             # might need this information to pinpoint the location of the error.
423 0         0 die sprintf("ERROR: Command '%s' generated an error.\n%s", $cmdCandidate, $@);
424             }
425             else
426             {
427             # Simply return the result.
428 51         126 return $result;
429             }
430             }
431             elsif($ctx->isBound($cmdCandidate) && ref($ctx->getBinding($cmdCandidate)) eq "Repl::Core::Lambda")
432             {
433             # Convenience, funcall shorthand.
434             # When a name is used, the eval tries to use the lambda in the context.
435            
436 21         50 my $lambda = $ctx->getBinding($cmdCandidate);
437 21         67 my $macro = ["funcall", $lambda, @$evallist[1..($listsize-1)]];
438 21         51 return $self->evalInContext($macro, $ctx);
439             }
440             elsif(ref($cmdCandidate) eq "Repl::Core::Lambda")
441             {
442             # Convenience, funcall shorthand.
443             # A lambda as first argument.
444            
445 3         10 my $macro = ["funcall", @$evallist];
446 3         9 return $self->evalInContext($macro, $ctx);
447             }
448             else
449             {
450 0 0       0 if($cmdCandidate)
451             {
452 0         0 die sprintf("ERROR: The command name should evaluate to a string or a lambda.\nFound '%s' which cannot be interpreted as a function.", $cmdCandidate);
453             }
454             else
455             {
456 0         0 die "ERROR: The command name should evaluate to a string or a lambda. Found null.";
457             }
458             }
459             }
460             };
461 653 100       1073 if($@)
462             {
463 1         4 my $totalMsgLim = 1000;
464 1         2 my $entryMsgLim = 80;
465            
466 1         2 my $prettyexpr = $expr;
467 1         5 $prettyexpr = pretty($expr);
468            
469 1         5 my $msg = cutat($@);
470 1 50       5 if(length($msg) >= $totalMsgLim)
471             {
472 0 0       0 if(! ($msg =~ /.*-> \.\.\. $/))
473             {
474 0         0 $msg = $msg . "\n-> ...";
475             }
476             }
477             else
478             {
479 1         5 $msg = sprintf("%s-> %s", $msg, $prettyexpr);
480             }
481            
482 1         218 croak $msg;
483             }
484             else
485             {
486 652         1857 return $evalres;
487             }
488             }
489            
490             # Ordinary function, not a method.
491             # Cut of the "at ." part of the error message.
492             sub cutat
493             {
494 2     2 0 4 my $msg = shift;
495             # Note the 's' regexp option to allow . to match newlines.
496 2 50       18 if($msg =~ /\A(.*) at .+ line .*\Z/s)
497             {
498             # Cut of the "at line dddd." part.
499             # Because it will always point to this location here.
500 2         14 $msg = $1 . "\n";
501             }
502 2         101 return $msg;
503             }
504            
505             # Ordinary function, not a method.
506             # Pretty print an array (recursively).
507             sub pretty
508             {
509 3     3 0 5 my $arr = shift;
510 3 100       9 if(ref($arr) eq "ARRAY")
511             {
512 1         2 my $arrsize = scalar(@$arr);
513 1         2 my $i = 0;
514            
515 1         2 my $buf = "(";
516 1         3 foreach my $el (@$arr)
517             {
518 2         8 $buf = $buf . pretty($el);
519 2 100       8 $buf = $buf . " " if($i != ($arrsize - 1)) ;
520 2         14 $i = $i + 1;
521             }
522 1         2 $buf = $buf . ")";
523 1         3 return $buf;
524             }
525            
526 2         5 return $arr;
527             }
528            
529             sub boolEval
530             {
531 81     81 0 93 my $self = shift;
532 81         91 my $expr = shift;
533            
534 81 50       151 return 0 if !defined $expr;
535 81 100       310 return 1 if($expr =~ /true|ok|on|yes|y|t/i );
536 68 100       205 return 0 if($expr =~ /false|nok|off|no|n|f/i);
537 59 100       175 return 1 if ($expr != 0);
538 15 50       36 if(ref($expr) eq 'ARRAY')
539             {
540 0         0 return scalar($expr) > 0;
541             }
542 15         111 return 0;
543             }
544            
545             1;