File Coverage

blib/lib/Lox/Parser.pm
Criterion Covered Total %
statement 120 277 43.3
branch 30 98 30.6
condition 3 13 23.0
subroutine 35 49 71.4
pod 0 39 0.0
total 188 476 39.5


line stmt bran cond sub pod time code
1             package Lox::Parser;
2 1     1   18 use strict;
  1         2  
  1         24  
3 1     1   13 use warnings;
  1         1  
  1         27  
4 1     1   4 use Lox::Bool;
  1         2  
  1         70  
5 1     1   376 use Lox::Expr;
  1         2  
  1         23  
6 1     1   5 use Lox::Nil;
  1         1  
  1         64  
7 1     1   341 use Lox::Stmt;
  1         2  
  1         22  
8 1     1   324 use Lox::String;
  1         2  
  1         22  
9 1     1   6 use Lox::TokenType;
  1         1  
  1         2945  
10             our $VERSION = 0.02;
11              
12             sub new {
13 2     2 0 4 my ($class, $args) = @_;
14             return bless {
15 2   50     10 tokens => ($args->{tokens} || die 'requires an arrayref of tokens'),
16             errors => [],
17             current=> 0,
18             }, $class;
19             }
20              
21 148     148 0 186 sub tokens { $_[0]->{tokens} }
22 2     2 0 5 sub errors :lvalue { $_[0]->{errors} }
23 156     156 0 503 sub current :lvalue { $_[0]->{current} }
24              
25             sub parse {
26 2     2 0 3 my $self = shift;
27 2         3 my @statements;
28 2         4 while (!$self->is_at_end) {
29 2         6 push @statements, $self->declaration;
30             }
31 2         13 return \@statements;
32             }
33              
34             sub declaration {
35 2     2 0 13 my $self = shift;
36 2         3 my $dec = eval {
37 2 50 33     14 if ($self->check(FUN) && $self->check(IDENTIFIER, 1)) {
    50          
    100          
38 0         0 $self->advance;
39 0         0 $self->function_stmt('function');
40             }
41             elsif ($self->match(CLASS)) {
42 0         0 $self->class_declaration;
43             }
44 1         3 elsif ($self->match(VAR)) { $self->var_declaration }
45 1         4 else { $self->statement }
46             };
47 2 50       6 unless ($@) {
48 2         6 return $dec;
49             }
50 0         0 $self->synchronize;
51 0         0 return undef;
52             }
53              
54             sub class_declaration {
55 0     0 0 0 my $self = shift;
56 0         0 my $name = $self->consume(IDENTIFIER, 'Expect class name');
57              
58 0         0 my $superclass = undef;
59 0 0       0 if ($self->match(LESS)) {
60 0         0 $self->consume(IDENTIFIER, 'Expect superclass name');
61 0         0 $superclass = Lox::Expr::Variable->new({name => $self->previous});
62             }
63              
64 0         0 $self->consume(LEFT_BRACE, 'Expect \'{\' before class body');
65 0         0 my @methods = ();
66 0   0     0 while (!$self->check(RIGHT_BRACE) && !$self->is_at_end) {
67 0         0 push @methods, $self->function_stmt('method');
68             }
69 0         0 $self->consume(RIGHT_BRACE, 'Expect \'}\' after class body');
70 0         0 return Lox::Stmt::Class->new({
71             superclass => $superclass,
72             methods => \@methods,
73             name => $name,
74             });
75             }
76              
77             sub statement {
78 1     1 0 3 my $self = shift;
79 1 50       3 if ($self->match(FOR)) {
80 0         0 return $self->for_statement;
81             }
82 1 50       4 if ($self->match(BREAK)) {
83 0         0 return $self->break_statement;
84             }
85 1 50       3 if ($self->match(IF)) {
86 0         0 return $self->if_statement;
87             }
88 1 50       3 if ($self->match(WHILE)) {
89 0         0 return $self->while_statement;
90             }
91 1 50       3 if ($self->match(LEFT_BRACE)) {
92 0         0 return Lox::Stmt::Block->new({statements => $self->block});
93             }
94 1 50       3 if ($self->match(PRINT)) {
95 1         3 return $self->print_statement;
96             }
97 0 0       0 if ($self->match(RETURN)) {
98 0         0 return $self->return_statement;
99             }
100 0         0 return $self->expression_statement;
101             }
102              
103             sub for_statement {
104 0     0 0 0 my $self = shift;
105 0         0 $self->consume(LEFT_PAREN, "Expect '(' after 'for'");
106              
107 0         0 my $initializer;
108 0 0       0 if ($self->match(SEMICOLON)) {
    0          
109 0         0 $initializer = undef;
110             }
111             elsif ($self->match(VAR)) {
112 0         0 $initializer = $self->var_declaration;
113             }
114             else {
115 0         0 $initializer = $self->expression_statement;
116             }
117              
118 0         0 my $condition = Lox::Expr::Literal->new({value => 1});
119 0 0       0 if (!$self->check(SEMICOLON)) {
120 0         0 $condition = $self->expression;
121             }
122 0         0 $self->consume(SEMICOLON, 'Expect ";" after loop condition');
123              
124 0         0 my $increment = undef;
125 0 0       0 if (!$self->check(RIGHT_PAREN)) {
126 0         0 $increment = $self->expression;
127             }
128 0         0 $self->consume(RIGHT_PAREN, 'Expect ")" after for clauses');
129              
130 0         0 $self->{looping}++;
131 0         0 my $body = $self->statement;
132 0 0       0 if ($increment) {
133 0         0 $body = Lox::Stmt::Block->new({statements =>
134             [$body, Lox::Stmt::Expression->new({expression => $increment})]});
135             }
136 0         0 $body = Lox::Stmt::While->new({condition => $condition, body => $body});
137              
138 0 0       0 if ($initializer) {
139 0         0 $body = Lox::Stmt::Block->new({statements => [$initializer, $body]});
140             }
141 0         0 $self->{looping}--;
142              
143 0         0 return $body;
144             }
145              
146             sub if_statement {
147 0     0 0 0 my $self = shift;
148 0         0 $self->consume(LEFT_PAREN, "Expect '(' after 'if'");
149 0         0 my $condition = $self->expression;
150 0         0 $self->consume(RIGHT_PAREN, "Expect ')' after condition");
151              
152 0         0 my $then_branch = $self->statement;
153 0         0 my $else_branch = undef;
154 0 0       0 if ($self->match(ELSE)) {
155 0         0 $else_branch = $self->statement;
156             }
157 0         0 return Lox::Stmt::If->new({
158             condition => $condition,
159             then_branch => $then_branch,
160             else_branch => $else_branch,
161             });
162             }
163              
164             sub break_statement {
165 0     0 0 0 my $self = shift;
166             $self->error($self->previous, 'Can only break out of loops')
167 0 0       0 unless $self->{looping};
168 0         0 $self->consume(SEMICOLON, 'Expect ";" after "break"');
169 0         0 return Lox::Stmt::Break->new({});
170             }
171              
172             sub var_declaration {
173 1     1 0 2 my $self = shift;
174 1         3 my $name = $self->consume(IDENTIFIER, "Expect variable name");
175 1         2 my $init = undef;
176 1 50       2 if ($self->match(EQUAL)) {
177 1         3 $init = $self->expression;
178             }
179 1         2 $self->consume(SEMICOLON, 'Expect ";" after variable declaration');
180 1         12 return Lox::Stmt::Var->new({name => $name, initializer => $init});
181             }
182              
183             sub while_statement {
184 0     0 0 0 my $self = shift;
185 0         0 $self->consume(LEFT_PAREN, "Expect '(' after 'while'");
186 0         0 my $condition = $self->expression;
187 0         0 $self->consume(RIGHT_PAREN, "Expect ')' after condition");
188 0         0 $self->{looping}++;
189 0         0 my $while = Lox::Stmt::While->new({
190             condition => $condition,
191             body => $self->statement,
192             });
193 0         0 $self->{looping}--;
194 0         0 return $while;
195             }
196              
197             sub expression_statement {
198 0     0 0 0 my $self = shift;
199 0         0 my $value = $self->expression;
200 0         0 $self->consume(SEMICOLON, 'Expect \';\' after expression');
201 0         0 return Lox::Stmt::Expression->new({expression => $value});
202             }
203              
204             sub function_stmt {
205 0     0 0 0 my ($self, $kind) = @_;
206 0         0 my $name = $self->consume(IDENTIFIER, "Expect $kind name");
207 0         0 my ($parameters, $body) = $self->parse_function($kind);
208 0         0 return Lox::Stmt::Function->new({
209             name => $name,
210             params => $parameters,
211             body => $body,
212             });
213             }
214              
215             sub function_expr {
216 0     0 0 0 my ($self) = @_;
217 0         0 my ($parameters, $body) = $self->parse_function('lambda');
218 0         0 return Lox::Expr::Function->new({
219             params => $parameters,
220             body => $body,
221             });
222             }
223              
224             sub parse_function {
225 0     0 0 0 my ($self, $kind) = @_;
226 0         0 $self->consume(LEFT_PAREN, "Expect '(' after $kind declaration");
227 0         0 my @parameters;
228 0 0       0 if (!$self->check(RIGHT_PAREN)) {
229 0         0 do {
230 0 0       0 if (@parameters >= 255) {
231 0         0 $self->error($self->peek, "Cannot have more than 255 parameters");
232             }
233              
234 0         0 push @parameters, $self->consume(IDENTIFIER, "Expect parameter name");
235             } while ($self->match(COMMA));
236             }
237 0         0 $self->consume(RIGHT_PAREN, "Expect ')' after parameters");
238              
239 0         0 $self->consume(LEFT_BRACE, "Expect '{' before $kind body");
240 0         0 $self->{functioning}++;
241 0         0 my $body = $self->block;
242 0         0 $self->{functioning}--;
243 0         0 return \@parameters, $body;
244             }
245              
246             sub block {
247 0     0 0 0 my $self = shift;
248 0         0 my @statements;
249 0   0     0 while (!$self->check(RIGHT_BRACE) && !$self->is_at_end) {
250 0         0 push @statements, $self->declaration;
251             }
252 0         0 $self->consume(RIGHT_BRACE, "Expect '}' after block");
253 0         0 return \@statements;
254             }
255              
256             sub print_statement {
257 1     1 0 3 my $self = shift;
258 1         2 my $value = $self->expression;
259 1         2 $self->consume(SEMICOLON, 'Expect ";" after value');
260 1         9 return Lox::Stmt::Print->new({expression => $value});
261             }
262              
263             sub return_statement {
264 0     0 0 0 my $self = shift;
265 0         0 my $keyword = $self->previous;
266             $self->error($keyword, 'Can only return from functions')
267 0 0       0 unless $self->{functioning};
268 0 0       0 my $value = $self->check(SEMICOLON) ? undef : $self->expression;
269 0         0 $self->consume(SEMICOLON, 'Expect ";" after return value');
270 0         0 return Lox::Stmt::Return->new({keyword => $keyword, value => $value});
271             }
272              
273             sub assignment {
274 2     2 0 4 my $self = shift;
275 2         4 my $expr = $self->_or;
276 2 50       4 if ($self->match(EQUAL)) {
277 0         0 my $equals = $self->previous;
278 0         0 my $value = $self->assignment;
279             # we parsed the left side THEN found an equals sign
280             # returns a new expr using the left side input
281 0 0       0 if (ref $expr eq 'Lox::Expr::Variable') {
    0          
282 0         0 return Lox::Expr::Assign->new({name => $expr->name, value => $value});
283             }
284             elsif (ref $expr eq 'Lox::Expr::Get') {
285 0         0 return Lox::Expr::Set->new({
286             object => $expr->object,
287             value => $value,
288             name => $expr->name,
289             });
290             }
291 0         0 $self->error($equals, 'Invalid assignment target');
292             }
293 2         5 return $expr;
294             }
295              
296             sub _or {
297 2     2   3 my $self = shift;
298 2         3 my $expr = $self->_and;
299              
300 2         3 while ($self->match(OR)) {
301 0         0 $expr = Lox::Expr::Logical->new({
302             left => $expr,
303             operator => $self->previous,
304             right => $self->_and,
305             });
306             }
307 2         3 return $expr;
308             }
309              
310             sub _and {
311 2     2   2 my $self = shift;
312 2         4 my $expr = $self->equality;
313              
314 2         3 while ($self->match(AND)) {
315 0         0 $expr = Lox::Expr::Logical->new({
316             left => $expr,
317             operator => $self->previous,
318             right => $self->_and,
319             });
320             }
321 2         3 return $expr;
322             }
323              
324 2     2 0 4 sub expression { shift->assignment }
325              
326             sub equality {
327 2     2 0 3 my $self = shift;
328 2         5 my $expr = $self->comparison;
329 2         4 while ($self->match(BANG_EQUAL, EQUAL_EQUAL)) {
330 0         0 $expr = Lox::Expr::Binary->new({
331             left => $expr,
332             operator => $self->previous,
333             right => $self->comparison,
334             });
335             }
336 2         4 return $expr;
337             }
338              
339             sub comparison {
340 2     2 0 2 my $self = shift;
341 2         5 my $expr = $self->addition;
342 2         4 while ($self->match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) {
343 0         0 $expr = Lox::Expr::Binary->new({
344             left => $expr,
345             operator => $self->previous,
346             right => $self->addition,
347             });
348             }
349 2         4 return $expr;
350             }
351              
352             sub addition {
353 2     2 0 2 my $self = shift;
354 2         5 my $expr = $self->multiplication;
355 2         3 while ($self->match(MINUS, PLUS)) {
356 0         0 $expr = Lox::Expr::Binary->new({
357             left => $expr,
358             operator => $self->previous,
359             right => $self->multiplication,
360             });
361             }
362 2         3 return $expr;
363             }
364              
365             sub multiplication {
366 2     2 0 10 my $self = shift;
367 2         6 my $expr = $self->unary;
368 2         11 while ($self->match(SLASH, STAR)) {
369 0         0 $expr = Lox::Expr::Binary->new({
370             left => $expr,
371             operator => $self->previous,
372             right => $self->unary,
373             });
374             }
375 2         4 return $expr;
376             }
377              
378             sub unary {
379 2     2 0 2 my $self = shift;
380 2 50       16 if ($self->match(BANG, MINUS)) {
381 0         0 my $expr = Lox::Expr::Unary->new({
382             operator => $self->previous,
383             right => $self->unary,
384             });
385 0         0 return $expr;
386             }
387 2         5 return $self->call;
388             }
389              
390             sub call {
391 2     2 0 4 my $self = shift;
392 2         3 my $expr = $self->primary;
393 2         4 while (1) {
394 2 50       3 if ($self->match(LEFT_PAREN)) {
    50          
395 0         0 $expr = $self->finish_call($expr);
396             }
397             elsif ($self->match(DOT)) {
398 0         0 $expr = Lox::Expr::Get->new({
399             object => $expr,
400             name => $self->consume(IDENTIFIER,'Expect property name after \'.\''),
401             });
402             }
403             else {
404 2         4 last;
405             }
406             }
407 2         8 return $expr;
408             }
409              
410             sub finish_call {
411 0     0 0 0 my ($self, $callee) = @_;
412 0         0 my @args;
413 0 0       0 if (!$self->check(RIGHT_PAREN)) {
414 0         0 do {
415 0 0       0 $self->error($self->peek, 'Cannot have more than 255 arguments')
416             if @args >= 255;
417 0         0 push @args, $self->expression;
418             } while ($self->match(COMMA));
419             }
420 0         0 my $paren = $self->consume(RIGHT_PAREN, 'Expect ")" after arguments');
421 0         0 return Lox::Expr::Call->new({
422             arguments => \@args,
423             callee => $callee,
424             paren => $paren,
425             });
426             }
427              
428             sub primary {
429 2     2 0 3 my $self = shift;
430 2 50       3 if ($self->match(FALSE)) {
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    0          
    0          
431 0         0 return Lox::Expr::Literal->new({value => $False});
432             }
433             elsif ($self->match(TRUE)) {
434 0         0 return Lox::Expr::Literal->new({value => $True});
435             }
436             elsif ($self->match(NIL)) {
437 0         0 return Lox::Expr::Literal->new({value => $Nil});
438             }
439             elsif ($self->match(NUMBER)) {
440 0         0 return Lox::Expr::Literal->new({value => $self->previous->{literal}});
441             }
442             elsif ($self->match(STRING)) {
443 1         4 return Lox::Expr::Literal->new({value => Lox::String->new($self->previous->{literal})});
444             }
445             elsif ($self->match(SUPER)) {
446 0         0 my $keyword = $self->previous;
447 0         0 $self->consume(DOT, 'Expect \'.\' after \'super\'');
448 0         0 my $method = $self->consume(IDENTIFIER, 'Expect superclass method name');
449 0         0 return Lox::Expr::Super->new({keyword => $keyword, method => $method});
450             }
451             elsif ($self->match(THIS)) {
452 0         0 return Lox::Expr::This->new({keyword => $self->previous});
453             }
454             elsif ($self->match(IDENTIFIER)) {
455 1         2 return Lox::Expr::Variable->new({name => $self->previous});
456             }
457             elsif ($self->match(LEFT_PAREN)) {
458 0         0 my $expr = $self->expression;
459 0         0 $self->consume(RIGHT_PAREN, 'Expect ")" after expression');
460 0         0 return Lox::Expr::Grouping->new({expression => $expr});
461             }
462             elsif ($self->match(FUN)) {
463 0         0 return $self->function_expr;
464             }
465 0         0 $self->error($self->peek, 'Expect expression');
466             }
467              
468             sub match {
469 44     44 0 74 my ($self, @types) = @_;
470 44         49 for my $t (@types) {
471 58 100       68 if ($self->check($t)) {
472 5         10 $self->advance;
473 5         13 return 1;
474             }
475             }
476 39         89 return undef;
477             }
478              
479             sub consume {
480 3     3 0 13 my ($self, $type, $msg) = @_;
481 3 50       6 return $self->advance if $self->check($type);
482 0         0 $self->error($self->peek, $msg);
483             }
484              
485             sub check {
486 63     63 0 72 my ($self, $type, $offset) = @_;
487 63 50       78 return $self->is_at_end ? undef : $self->peek($offset)->{type} == $type;
488             }
489              
490             sub advance {
491 8     8 0 8 my $self = shift;
492 8 50       9 $self->current++ unless $self->is_at_end;
493 8         14 return $self->previous;
494             }
495              
496 75     75 0 88 sub is_at_end { shift->peek->{type} == EOF }
497              
498             sub peek {
499 138     138 0 149 my ($self, $offset) = @_;
500 138   50     164 return $self->tokens->[ $self->current + ($offset//0) ];
501             }
502              
503             sub previous {
504 10     10 0 11 my $self = shift;
505 10         12 return $self->tokens->[ $self->current - 1];
506             }
507              
508             sub error {
509 0     0 0   my ($self, $token, $msg) = @_;
510 0           push $self->errors->@*, [$token, $msg];
511 0           die $msg;
512             }
513              
514             sub synchronize {
515 0     0 0   my $self = shift;
516 0           $self->advance;
517 0           while (!$self->is_at_end) {
518 0 0         return if $self->previous->{type} == SEMICOLON;
519 0           my $next = $self->peek;
520 0 0         return if grep { $next == $_ } CLASS,FUN,VAR,FOR,IF,WHILE,PRINT,RETURN;
  0            
521 0           $self->advance;
522             }
523             }
524              
525             1;