File Coverage

blib/lib/Lox/Interpreter.pm
Criterion Covered Total %
statement 65 218 29.8
branch 3 112 2.6
condition 1 32 3.1
subroutine 23 47 48.9
pod 0 36 0.0
total 92 445 20.6


line stmt bran cond sub pod time code
1             package Lox::Interpreter;
2 1     1   6 use feature 'say';
  1         1  
  1         157  
3 1     1   6 use strict;
  1         2  
  1         16  
4 1     1   5 use warnings;
  1         2  
  1         44  
5 1     1   325 use Lox::Bool;
  1         2  
  1         119  
6 1     1   340 use Lox::Callable;
  1         3  
  1         23  
7 1     1   391 use Lox::Environment;
  1         2  
  1         26  
8 1     1   338 use Lox::Function;
  1         2  
  1         24  
9 1     1   347 use Lox::Class;
  1         2  
  1         23  
10 1     1   338 use Lox::Nil;
  1         2  
  1         74  
11 1     1   405 use Lox::TokenType;
  1         2  
  1         2813  
12             our $VERSION = 0.02;
13              
14             sub new {
15 1     1 0 1056 my ($class, $args) = @_;
16 1   50     7 $args //= {};
17 1         7 my $globals = Lox::Environment->new({});
18 1         6 my $interpreter = bless {
19             environment => $globals,
20             globals => $globals,
21             locals => {},
22             %$args,
23             }, $class;
24             $interpreter->globals->define('clock', Lox::Callable->new({
25             arity => 0,
26 0     0   0 call => sub { time },
27 1         4 }));
28              
29 1         3 return $interpreter;
30             }
31              
32 1     1 0 3 sub environment :lvalue { $_[0]->{environment} }
33 2     2 0 20 sub globals { $_[0]->{globals} }
34 1     1 0 5 sub locals { $_[0]->{locals} }
35              
36             sub interpret {
37 2     2 0 4 my ($self, $stmts) = @_;
38 2         4 for (@$stmts) {
39 2         12 $self->execute($_);
40             }
41             }
42              
43             sub execute {
44 2     2 0 4 my ($self, $stmt) = @_;
45 2 50       7 $stmt->accept($self) unless $self->{breaking};
46             }
47              
48             sub resolve {
49 0     0 0 0 my ($self, $expr, $depth) = @_;
50 0         0 $self->locals->{"$expr"} = $depth;
51             }
52              
53             sub visit_break_stmt {
54 0     0 0 0 my ($self, $stmt) = @_;
55 0         0 $self->{breaking}++;
56 0         0 return undef;
57             }
58              
59             sub visit_class_stmt {
60 0     0 0 0 my ($self, $stmt) = @_;
61 0         0 my $superclass = undef;
62 0 0       0 if (my $sc = $stmt->superclass) {
63 0         0 $superclass = $self->evaluate($sc);
64 0 0       0 unless (ref $superclass eq 'Lox::Class') {
65 0         0 Lox::runtime_error($sc->name, 'Superclass must be a class');
66             }
67             }
68 0         0 $self->environment->define($stmt->name->lexeme, undef);
69              
70 0 0       0 if ($superclass) {
71 0         0 $self->environment = Lox::Environment->new({ enclosing => $self->environment });
72 0         0 $self->environment->define('super', $superclass);
73             }
74              
75 0         0 my %methods;
76 0         0 for my $method ($stmt->methods->@*) {
77 0         0 my $function = Lox::Function->new({
78             is_initializer => $method->name->lexeme eq 'init',
79             declaration => $method,
80             closure => $self->environment,
81             });
82 0         0 $methods{$method->name->lexeme} = $function;
83             }
84 0         0 my $klass = Lox::Class->new({
85             superclass => $superclass,
86             methods => \%methods,
87             name => $stmt->name->lexeme,
88             });
89              
90 0 0       0 if ($superclass) {
91 0         0 $self->environment = $self->environment->enclosing;
92             }
93              
94 0         0 $self->environment->assign($stmt->name, $klass);
95 0         0 return undef;
96             }
97              
98             sub visit_expression_stmt {
99 0     0 0 0 my ($self, $stmt) = @_;
100 0         0 $self->evaluate($stmt->expression);
101 0         0 return undef;
102             }
103              
104             sub visit_if_stmt {
105 0     0 0 0 my ($self, $stmt) = @_;
106 0 0       0 if ($self->is_truthy($self->evaluate($stmt->condition))) {
    0          
107 0         0 $self->execute($stmt->then_branch);
108             }
109             elsif ($stmt->else_branch) {
110 0         0 $self->execute($stmt->else_branch);
111             }
112             }
113              
114             sub visit_function_stmt {
115 0     0 0 0 my ($self, $stmt) = @_;
116 0         0 my $function = Lox::Function->new({
117             declaration => $stmt,
118             closure => $self->environment,
119             });
120 0         0 $self->environment->define($stmt->name->lexeme, $function);
121 0         0 return undef;
122             }
123              
124             sub visit_function_expr {
125 0     0 0 0 my ($self, $expr) = @_;
126 0         0 return Lox::Function->new({
127             declaration => $expr,
128             closure => $self->environment,
129             });
130             }
131              
132             sub visit_logical_expr {
133 0     0 0 0 my ($self, $expr) = @_;
134 0         0 my $left = $self->evaluate($expr->left);
135 0 0       0 if ($expr->operator->type == OR) {
136 0 0       0 return $left if $self->is_truthy($left);
137             }
138             else {
139 0 0       0 return $left if !$self->is_truthy($left);
140             }
141              
142 0         0 return $self->evaluate($expr->right);
143             }
144              
145             sub visit_set_expr {
146 0     0 0 0 my ($self, $expr) = @_;
147 0         0 my $object = $self->evaluate($expr->object);
148 0 0       0 if (ref $object ne 'Lox::Instance') {
149 0         0 Lox::runtime_error($expr->name, "Only instances have fields");
150             }
151              
152 0         0 my $value = $self->evaluate($expr->value);
153 0         0 $object->set($expr->name, $value);
154 0         0 return $value
155             }
156              
157             sub visit_super_expr {
158 0     0 0 0 my ($self, $expr) = @_;
159 0         0 my $distance = $self->locals->{"$expr"};
160 0         0 my $superclass = $self->environment->get_at($distance, 'super');
161 0         0 my $object = $self->environment->get_at($distance - 1, 'this');
162 0         0 my $method = $superclass->find_method($expr->method->lexeme);
163 0 0       0 unless ($method) {
164 0         0 Lox::runtime_error($expr->method,
165             sprintf 'Undefined property \'%s\'', $expr->method->lexeme);
166             }
167 0         0 return $method->bind($object);
168             }
169              
170             sub visit_this_expr {
171 0     0 0 0 my ($self, $expr) = @_;
172 0         0 return $self->look_up_variable($expr);
173             }
174              
175             sub visit_print_stmt {
176 1     1 0 3 my ($self, $stmt) = @_;
177 1         4 my $value = $self->evaluate($stmt->expression);
178 1         5 say $self->stringify($value);
179 1         12 return undef;
180             }
181              
182             sub visit_return_stmt {
183 0     0 0 0 my ($self, $stmt) = @_;
184 0 0       0 if ($stmt->value) {
185 0         0 $self->{returning} = $self->evaluate($stmt->value);
186             }
187 0         0 die "return\n";
188 0         0 return undef;
189             }
190              
191             sub visit_var_stmt {
192 1     1 0 2 my ($self, $stmt) = @_;
193 1         2 my $value = undef;
194 1 50       2 if ($stmt->initializer) {
195 1         2 $value = $self->evaluate($stmt->initializer);
196             }
197 1         3 $self->environment->define($stmt->name->{lexeme}, $value);
198 1         14 return undef;
199             }
200              
201             sub visit_while_stmt {
202 0     0 0 0 my ($self, $stmt) = @_;
203 0         0 while ($self->is_truthy($self->evaluate($stmt->condition))) {
204 0         0 $self->execute($stmt->body);
205 0 0       0 last if $self->{breaking};
206             }
207 0         0 return undef $self->{breaking};
208             }
209              
210             sub visit_block_stmt {
211 0     0 0 0 my ($self, $stmt) = @_;
212 0         0 $self->execute_block(
213             $stmt->statements,
214             Lox::Environment->new({ enclosing => $self->environment }));
215              
216 0         0 return undef;
217             }
218              
219             sub execute_block {
220 0     0 0 0 my ($self, $statements, $environment) = @_;
221 0         0 my $prev_environment = $self->environment;
222 0         0 $self->environment = $environment;
223 0         0 my $error;
224 0         0 for my $stmt (@$statements) {
225 0         0 eval { $self->execute($stmt) }; # so we can reset the env
  0         0  
226 0 0       0 if ($error = $@) {
227 0         0 last;
228             }
229             }
230 0         0 $self->environment = $prev_environment;
231 0 0       0 die $error if $error;
232 0         0 return undef;
233             }
234              
235             sub visit_literal_expr {
236 1     1 0 2 my ($self, $expr) = @_;
237 1         4 return $expr->value;
238             }
239              
240             sub visit_call_expr {
241 0     0 0 0 my ($self, $expr) = @_;
242 0         0 my $callee = $self->evaluate($expr->callee);
243 0         0 my @args;
244 0         0 for my $arg ($expr->arguments->@*) {
245 0         0 push @args, $self->evaluate($arg);
246             }
247 0 0 0     0 unless (ref $callee && $callee->isa('Lox::Callable')) {
248 0         0 Lox::runtime_error($expr->paren, 'Can only call functions and classes');
249             }
250              
251 0 0       0 if (@args!= $callee->arity) {
252 0         0 Lox::runtime_error($expr->paren,
253             sprintf 'Expected %d arguments but got %s',$callee->arity,scalar @args);
254             }
255 0   0     0 return $callee->call($self, \@args) // $Nil;
256             }
257              
258             sub visit_get_expr {
259 0     0 0 0 my ($self, $expr) = @_;
260 0         0 my $object = $self->evaluate($expr->object);
261              
262 0 0       0 if (ref $object eq 'Lox::Instance') {
263 0         0 return $object->get($expr->name);
264             }
265 0         0 Lox::runtime_error($expr->name, 'Only instances have properties');
266             }
267              
268             sub visit_grouping_expr {
269 0     0 0 0 my ($self, $expr) = @_;
270 0         0 return $self->evaluate($expr->expression);
271             }
272              
273             sub visit_unary_expr {
274 0     0 0 0 my ($self, $expr) = @_;
275 0         0 my $right = $self->evaluate($expr->right);
276              
277 0 0       0 if ($expr->operator->{type} == MINUS) {
278             # numbers are not objects
279 0 0       0 Lox::runtime_error($expr->operator, 'Operand must be a number')
280             if ref $right;
281 0         0 return -$right;
282             }
283             else {
284 0 0       0 return !($self->is_truthy($right) ? $True : $False);
285             }
286             }
287              
288             sub visit_assign_expr {
289 0     0 0 0 my ($self, $expr) = @_;
290 0         0 my $value = $self->evaluate($expr->value);
291 0         0 my $distance = $self->locals->{"$expr"};
292 0 0       0 if (defined $distance) {
293 0         0 $self->environment->assign_at($distance, $expr->name, $value);
294             }
295             else {
296 0         0 $self->globals->assign($expr->name, $value);
297             }
298 0         0 return $value;
299             }
300              
301             sub visit_variable_expr {
302 1     1 0 2 my ($self, $expr) = @_;
303 1         3 return $self->look_up_variable($expr);
304             }
305              
306             sub look_up_variable {
307 1     1 0 2 my ($self, $expr) = @_;
308 1         3 my $distance = $self->locals->{"$expr"};
309 1 50       5 return defined $distance
310             ? $self->environment->get_at($distance, $expr->name->lexeme)
311             : $self->globals->get($expr->name);
312             }
313              
314             sub visit_binary_expr {
315 0     0 0 0 my ($self, $expr) = @_;
316 0         0 my $left = $self->evaluate($expr->left);
317 0         0 my $right = $self->evaluate($expr->right);
318              
319 0         0 my $type = $expr->operator->{type};
320 0 0       0 if ($type == EQUAL_EQUAL) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
321 0 0       0 return $self->are_equal($left, $right) ? $True : $False;
322             }
323             elsif ($type == BANG_EQUAL) {
324 0 0       0 return !$self->are_equal($left, $right) ? $True : $False;
325             }
326             elsif ($type == GREATER) {
327             # numbers are not objects
328 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
329             if ref $left || ref $right;
330 0 0       0 return $left > $right ? $True : $False;
331             }
332             elsif ($type == GREATER_EQUAL) {
333 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
334             if ref $left || ref $right;
335 0 0       0 return $left >= $right ? $True : $False;
336             }
337             elsif ($type == LESS) {
338 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
339             if ref $left || ref $right;
340 0 0       0 return $left < $right ? $True : $False;
341             }
342             elsif ($type == LESS_EQUAL) {
343 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
344             if ref $left || ref $right;
345 0 0       0 return $left <= $right ? $True : $False;
346             }
347             elsif ($type == MINUS) {
348 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
349             if ref $left || ref $right;
350 0         0 return $left - $right;
351             }
352             elsif ($type == PLUS) {
353 0 0 0     0 if (ref $left || ref $right) {
354 0 0       0 if (ref $left eq ref $right) {
355 0 0       0 if (ref $left eq 'Lox::String') {
356 0         0 return Lox::String->new("$left" . "$right");
357             }
358             }
359             Lox::runtime_error(
360 0         0 $expr->operator, 'Operands must be two numbers or two strings');
361             }
362 0         0 return $left + $right; # Lox numbers are the only non-object values
363             }
364             elsif ($type == SLASH) {
365 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
366             if ref $left || ref $right;
367 0 0       0 return $right ? $left / $right : 'NaN';
368             }
369             elsif ($type == STAR) {
370 0 0 0     0 Lox::runtime_error($expr->operator, 'Operands must be two numbers')
371             if ref $left || ref $right;
372 0         0 return $left * $right;
373             }
374             }
375              
376             sub evaluate {
377 2     2 0 3 my ($self, $expr) = @_;
378 2         4 return $expr->accept($self);
379             }
380              
381             sub is_truthy {
382 0     0 0 0 my ($self, $value) = @_;
383 0 0       0 return !!$value if ref $value;
384 0         0 return 1;
385             }
386              
387             sub are_equal {
388 0     0 0 0 my ($self, $left, $right) = @_;
389 0 0       0 if (my $ltype = ref $left) {
    0          
390 0 0       0 if ($ltype eq ref $right) {
391 0         0 return "$left" eq "$right";
392             }
393 0         0 return undef;
394             }
395             elsif (ref $right) {
396 0         0 return undef;
397             }
398             else {
399 0         0 return $left == $right;
400             }
401             }
402              
403             sub stringify {
404 1     1 0 2 my ($self, $object) = @_;
405 1         24 return "$object";
406             }
407              
408             1;