File Coverage

blib/lib/Lox/Resolver.pm
Criterion Covered Total %
statement 37 169 21.8
branch 6 36 16.6
condition 0 3 0.0
subroutine 13 39 33.3
pod 0 36 0.0
total 56 283 19.7


line stmt bran cond sub pod time code
1             package Lox::Resolver;
2 1     1   6 use strict;
  1         1  
  1         26  
3 1     1   4 use warnings;
  1         2  
  1         93  
4              
5             BEGIN {
6 1     1   4 my @constants = qw(CLASS FUNCTION INITIALIZER METHOD NONE SUBCLASS);
7 1         3 my %constant_values = map { $constants[$_] => $_ } 0..$#constants;
  6         12  
8 1         5 require constant;
9 1         1691 constant->import(\%constant_values);
10             }
11              
12             our $VERSION = 0.02;
13              
14             sub new {
15 2     2 0 4 my ($class, $interpreter) = @_;
16 2         11 return bless {
17             current_function => NONE,
18             current_class => NONE,
19             interpreter => $interpreter,
20             scopes => [],
21             }, $class;
22             }
23              
24 0     0 0 0 sub current_function :lvalue { $_[0]->{current_function} }
25 0     0 0 0 sub current_class :lvalue { $_[0]->{current_class} }
26 0     0 0 0 sub interpreter { $_[0]->{interpreter} }
27 3     3 0 11 sub scopes { $_[0]->{scopes} }
28              
29             sub run {
30 2     2 0 3 my ($self, $stmts) = @_;
31 2         6 $self->resolve($stmts);
32             }
33              
34             sub visit_block_stmt {
35 0     0 0 0 my ($self, $stmt) = @_;
36 0         0 $self->begin_scope;
37 0         0 $self->resolve($stmt->statements);
38 0         0 $self->end_scope;
39 0         0 return undef;
40             }
41              
42             sub visit_break_stmt {
43 0     0 0 0 my ($self, $stmt) = @_;
44 0         0 return undef;
45             }
46              
47             sub visit_class_stmt {
48 0     0 0 0 my ($self, $stmt) = @_;
49 0         0 my $enclosing_class = $self->current_class;
50 0         0 $self->current_class = CLASS;
51 0         0 $self->declare($stmt->name);
52 0         0 $self->define($stmt->name);
53              
54 0 0       0 if (my $sc = $stmt->superclass) {
55 0 0       0 if($stmt->name->lexeme eq $sc->name->lexeme) {
56 0         0 Lox::error($sc->name, 'A class cannot inherit from itself');
57             }
58 0         0 $self->current_class = SUBCLASS;
59 0         0 $self->resolve($sc);
60 0         0 $self->begin_scope();
61 0         0 $self->scopes->[-1]{super} = 1;
62             }
63              
64 0         0 $self->begin_scope;
65 0         0 $self->scopes->[-1]->{this} = 1;
66 0         0 foreach my $method ($stmt->methods->@*) {
67 0 0       0 my $declaration = $method->name->lexeme eq 'init' ? INITIALIZER : METHOD;
68 0         0 $self->resolve_function($method, $declaration);
69             }
70 0         0 $self->end_scope;
71 0 0       0 $self->end_scope if $stmt->superclass;
72 0         0 $self->current_class = $enclosing_class;
73 0         0 return undef;
74             }
75              
76             sub visit_expression_stmt {
77 0     0 0 0 my ($self, $stmt) = @_;
78 0         0 $self->resolve($stmt->expression);
79 0         0 return undef;
80             }
81              
82             sub visit_if_stmt {
83 0     0 0 0 my ($self, $stmt) = @_;
84 0         0 $self->resolve($stmt->condition);
85 0         0 $self->resolve($stmt->then_branch);
86 0 0       0 if ($stmt->else_branch) {
87 0         0 $self->resolve($stmt->else_branch);
88             }
89 0         0 return undef;
90             }
91              
92             sub visit_print_stmt {
93 1     1 0 2 my ($self, $stmt) = @_;
94 1         14 $self->resolve($stmt->expression);
95 1         3 return undef;
96             }
97              
98             sub visit_return_stmt {
99 0     0 0 0 my ($self, $stmt) = @_;
100 0 0       0 if ($stmt->value) {
101 0 0       0 if ($self->current_function == INITIALIZER) {
102 0         0 Lox::error($stmt->keyword, 'Cannot return a value from an initializer');
103             }
104 0         0 $self->resolve($stmt->value);
105             }
106 0         0 return undef;
107             }
108              
109             sub visit_function_stmt {
110 0     0 0 0 my ($self, $stmt) = @_;
111 0         0 $self->declare($stmt->name);
112 0         0 $self->define($stmt->name);
113 0         0 $self->resolve_function($stmt, FUNCTION);
114 0         0 return undef;
115             }
116              
117             sub visit_get_expr {
118 0     0 0 0 my ($self, $expr) = @_;
119 0         0 $self->resolve($expr->object);
120 0         0 return undef;
121             }
122              
123             sub resolve {
124 4     4 0 7 my ($self, $stmt_or_expr) = @_;
125 4 100       9 if (ref $stmt_or_expr ne 'ARRAY') {
126 2         4 $stmt_or_expr = [$stmt_or_expr];
127             }
128 4         15 $_->accept($self) for (@$stmt_or_expr);
129 4         7 return undef;
130             }
131              
132             sub resolve_function {
133 0     0 0 0 my ($self, $stmt, $type) = @_;
134 0         0 my $enclosing_function = $self->current_function;
135 0         0 $self->current_function = $type;
136 0         0 $self->begin_scope;
137 0         0 for my $param ($stmt->params->@*) {
138 0         0 $self->declare($param);
139 0         0 $self->define($param);
140             }
141 0         0 $self->resolve($stmt->body);
142 0         0 $self->end_scope;
143 0         0 $self->current_function = $enclosing_function;
144             }
145              
146             sub begin_scope {
147 0     0 0 0 my $self = shift;
148 0         0 push $self->scopes->@*, {};
149 0         0 return undef;
150             }
151              
152             sub end_scope {
153 0     0 0 0 my $self = shift;
154 0         0 pop $self->scopes->@*;
155 0         0 return undef;
156             }
157              
158             sub visit_var_stmt {
159 1     1 0 8 my ($self, $stmt) = @_;
160 1         4 $self->declare($stmt->name);
161 1 50       3 if (my $init = $stmt->initializer) {
162 1         4 $self->resolve($init);
163             }
164 1         2 $self->define($stmt->name);
165 1         2 return undef;
166             }
167              
168             sub visit_while_stmt {
169 0     0 0 0 my ($self, $stmt) = @_;
170 0         0 $self->resolve($stmt->condition);
171 0         0 $self->resolve($stmt->body);
172 0         0 return undef;
173             }
174              
175             sub visit_assign_expr {
176 0     0 0 0 my ($self, $expr) = @_;
177 0         0 $self->resolve($expr->value);
178 0         0 $self->resolve_local($expr, $expr->name);
179 0         0 return undef;
180             }
181              
182             sub visit_binary_expr {
183 0     0 0 0 my ($self, $expr) = @_;
184 0         0 $self->resolve($expr->left);
185 0         0 $self->resolve($expr->right);
186 0         0 return undef;
187             }
188              
189             sub visit_call_expr {
190 0     0 0 0 my ($self, $expr) = @_;
191 0         0 $self->resolve($expr->callee);
192 0         0 for my $argument ($expr->arguments->@*) {
193 0         0 $self->resolve($argument);
194             }
195 0         0 return undef;
196             }
197              
198             sub visit_function_expr {
199 0     0 0 0 my ($self, $expr) = @_;
200 0         0 $self->resolve_function($expr, FUNCTION);
201 0         0 return undef;
202             }
203              
204             sub visit_grouping_expr {
205 0     0 0 0 my ($self, $expr) = @_;
206 0         0 $self->resolve($expr->expression);
207 0         0 return undef;
208             }
209              
210 1     1 0 2 sub visit_literal_expr { undef }
211              
212             sub visit_logical_expr {
213 0     0 0 0 my ($self, $expr) = @_;
214 0         0 $self->resolve($expr->left);
215 0         0 $self->resolve($expr->right);
216 0         0 return undef;
217             }
218              
219             sub visit_set_expr {
220 0     0 0 0 my ($self, $expr) = @_;
221 0         0 $self->resolve($expr->value);
222 0         0 $self->resolve($expr->object);
223 0         0 return undef;
224             }
225              
226             sub visit_unary_expr {
227 0     0 0 0 my ($self, $expr) = @_;
228 0         0 $self->resolve($expr->right);
229 0         0 return undef;
230             }
231              
232             sub declare {
233 1     1 0 7 my ($self, $name_token) = @_;
234 1 50       5 return undef unless $self->scopes->@*;
235              
236 0         0 my $scope = $self->scopes->[-1];
237 0 0       0 if (exists $scope->{$name_token->lexeme}) {
238 0         0 Lox::error($name_token,
239             'Variable with this name already declared in this scope');
240             }
241              
242 0         0 return $self->scopes->[-1]{$name_token->lexeme} = 0;
243             }
244              
245             sub define {
246 1     1 0 3 my ($self, $name_token) = @_;
247 1 50       2 return undef unless $self->scopes->@*;
248 0         0 return $self->scopes->[-1]{$name_token->lexeme} = 1;
249             }
250              
251             sub visit_super_expr {
252 0     0 0 0 my ($self, $expr) = @_;
253 0 0       0 if ($self->current_class == NONE) {
    0          
254 0         0 Lox::error($expr->keyword, 'Cannot use \'super\' outside of a class');
255             }
256             elsif ($self->current_class != SUBCLASS) {
257 0         0 Lox::error($expr->keyword,
258             'Cannot use \'super\' in a class with no superclass');
259             }
260 0         0 $self->resolve_local($expr, $expr->keyword);
261 0         0 return undef;
262             }
263              
264             sub visit_this_expr {
265 0     0 0 0 my ($self, $expr) = @_;
266 0 0       0 if ($self->current_class == NONE) {
267 0         0 Lox::error($expr->keyword, 'Cannot use \'this\' outside of a class');
268             }
269 0         0 $self->resolve_local($expr, $expr->keyword);
270 0         0 return undef;
271             }
272              
273             sub visit_variable_expr {
274 1     1 0 2 my ($self, $expr) = @_;
275 1 50       3 return undef unless $self->scopes->@*;
276              
277 0           my $value = $self->scopes->[-1]{$expr->name->lexeme};
278 0 0 0       if (defined $value && $value == 0) {
279 0           Lox::error($expr->name,
280             'Cannot read local variable in its own initializer');
281             }
282 0           $self->resolve_local($expr, $expr->name);
283 0           return undef;
284             }
285              
286             sub resolve_local {
287 0     0 0   my ($self, $expr, $name_token) = @_;
288 0           for (my $i = $#{$self->scopes}; $i >= 0; $i--) {
  0            
289 0 0         if (exists $self->scopes->[$i]{$name_token->lexeme}) {
290 0           $self->interpreter->resolve($expr, $#{$self->scopes} - $i);
  0            
291 0           return;
292             }
293             }
294             # not found assume it is global
295             }
296              
297             1;