File Coverage

blib/lib/Repl/Core/Parser.pm
Criterion Covered Total %
statement 120 135 88.8
branch 46 62 74.1
condition 22 39 56.4
subroutine 12 12 100.0
pod 0 7 0.0
total 200 255 78.4


line stmt bran cond sub pod time code
1             package Repl::Core::Parser;
2            
3 1     1   42496 use strict;
  1         3  
  1         35  
4            
5 1     1   495 use Repl::Core::Buffer;
  1         3  
  1         28  
6 1     1   486 use Repl::Core::Token;
  1         3  
  1         26  
7 1     1   6 use Carp;
  1         1  
  1         102  
8 1     1   613 use Repl::Core::Pair;
  1         3  
  1         1843  
9            
10             # Parameters:
11             # - None.
12             sub new
13             {
14 3     3 0 24 my $invocant = shift;
15 3   33     22 my $class = ref($invocant) || $invocant;
16            
17 3         7 my $self = {};
18 3         11 return bless($self, $class);
19             }
20            
21             # Parameters:
22             # - A character buffer.
23             sub getNextToken
24             {
25 1603     1603 0 2088 my $self = shift;
26 1603         1540 my $buffer = shift;
27            
28 1603 100       3518 if(exists $self->{PUSHBACK})
29             {
30 791         1041 my $pushback = $self->{PUSHBACK};
31 791         1209 delete $self->{PUSHBACK};
32 791         1487 return $pushback;
33             }
34            
35 812 100       1990 if($buffer->eof())
36             {
37 39         117 return Repl::Core::Token->new(TYPE=>"eof" , VALUE=>"Unexpected end of expression encoutered.",
38             LINENO=>$buffer->getLineNo(), COLNO=>$buffer->getColNo());
39             }
40            
41             # Keep track of the start of the token.
42 773         2070 my $line = $buffer->getLineNo;
43 773         2147 my $col = $buffer->getColNo;
44             # We switch on the value of $char to see what we have to do next.
45 773         1709 my $char = $buffer->consumeChar;
46            
47 773 100       3416 if("(" eq $char)
    100          
    100          
    100          
    100          
    100          
    100          
48             {
49 112         385 return Repl::Core::Token->new(TYPE=>"beginlist", VALUE=>"(", LINENO=>$line, COLNO=>$col);
50             }
51             elsif(")" eq $char)
52             {
53 112         357 return Repl::Core::Token->new(TYPE=>"endlist", VALUE=>")", LINENO=>$line, COLNO=>$col);
54             }
55             elsif("'" eq $char)
56             {
57 1         6 return Repl::Core::Token->new(TYPE=>"quote", VALUE=>"'", LINENO=>$line, COLNO=>$col);
58             }
59             elsif("=" eq $char)
60             {
61 22         76 return Repl::Core::Token->new(TYPE=>"pair", VALUE=>"=", LINENO=>$line, COLNO=>$col);
62             }
63             elsif($char =~ /\s/)
64             {
65 234         415 my @whitebuf = ($char);
66 234   66     571 while(!$buffer->eof() && $buffer->peekChar() =~ /\s/)
67             {
68 11         29 push @whitebuf, $buffer->consumeChar();
69             }
70 234         1027 return Repl::Core::Token->new(TYPE=>"whitespace", VALUE=>join("", @whitebuf), LINENO=>$line, COLNO=>$col);
71             }
72             elsif(';' eq $char)
73             {
74             # Comments, skip until end of line.
75 4         11 my $peek = $buffer->peekChar();
76 4   66     10 while (!$buffer->eof() && "\n" ne $peek)
77             {
78 100         254 $buffer->consumeChar();
79 100         206 $peek = $buffer->peekChar();
80             }
81             # Consume newline as well.
82 4 50       17 $buffer->consumeChar() if("\n" eq $peek);
83 4         23 return Repl::Core::Token->new(TYPE=>"whitespace", VALUE=>sprintf(""), LINENO=>$line, COLNO=>$col);
84             }
85             elsif('"' eq $char)
86             {
87             # String literal encountered.
88             # Support for '\\', '\"', '\n' and '\t'.
89             # Note that the starting " is skipped, it is not added to the value.
90 5         10 my @stringbuf = ();
91 5   66     14 while(!$buffer->eof() && $buffer->peekChar() ne "\"")
92             {
93 23 100       54 if($buffer->peekChar() eq "\\")
94             {
95             # Consume the backslash.
96 3         9 $buffer->consumeChar;
97 3 100       10 if($buffer->peekChar() eq "n")
    100          
    50          
    0          
98             {
99             # We found a newline.
100 1         3 push @stringbuf, "\n";
101 1         3 $buffer->consumeChar();
102            
103             }
104             elsif($buffer->peekChar() eq "\\")
105             {
106             # We found a backslash.
107 1         2 push @stringbuf, "\\";
108 1         3 $buffer->consumeChar();
109             }
110             elsif($buffer->peekChar() eq "t")
111             {
112             # We found a tab.
113 1         4 push @stringbuf, "\t";
114 1         3 $buffer->consumeChar();
115             }
116             elsif($buffer->peekChar() eq '"')
117             {
118             # We found a double quote.
119 0         0 push @stringbuf, '"';
120 0         0 $buffer->consumeChar();
121             }
122             else
123             {
124             # Strict version: it produces an error.
125             # return Repl::Core::Token->new(TYPE=>"error", VALUE=>sprintf("Unknown quoted character %s found in string constant.", $buffer->peekChar()), LINENO=>$line, COLNO=>$col);
126            
127             # Relaxed version: it copies the sequence
128             # if it cannot be transated into a code.
129 0         0 push @stringbuf, "\\";
130 0         0 push @stringbuf, $buffer->consumeChar();
131             }
132             }
133             else
134             {
135 20         48 push @stringbuf, $buffer->consumeChar();
136             }
137             }
138            
139             # We examine the two finishing conditions of the preceding loop.
140             # The string is complete OR the buffer ended unexpectedly.
141 5 50       15 if($buffer->eof())
142             {
143             # EOF encountered, open string ...
144 0         0 return Repl::Core::Token->new(TYPE=>"eof", VALUE=>sprintf("Unclosed string encountered at line: %d, col: %d.",$line, $col), LINENO=>$line, COLNO=>$col);
145             }
146             else
147             {
148             # The string ended the normal way.
149             # Consume the closing ".
150 5         46 $buffer->consumeChar();
151 5         25 return Repl::Core::Token->new(TYPE=>"string", VALUE=>join("", @stringbuf), LINENO=>$line, COLNO=>$col);
152             }
153             }
154             else
155             {
156 283         572 my @literalbuf = ($char);
157 283   66     729 while(!$buffer->eof() && $buffer->peekChar() !~ m/[=\(\)\'\n\"\s;]/ )
158             {
159 474         1321 push @literalbuf, $buffer->consumeChar();
160             }
161 283         1275 return Repl::Core::Token->new(TYPE=>"string", VALUE=>join("", @literalbuf), LINENO=>$line, COLNO=>$col);
162             }
163             }
164            
165             # Parameters
166             # - A token instance.
167             sub pushBackToken
168             {
169 829     829 0 886 my $self = shift;
170 829         865 my $token = shift;
171            
172 829         1497 $self->{PUSHBACK} = $token;
173             }
174            
175             # Parameters:
176             # - A character buffer.
177             sub getNextNonWhitespaceToken
178             {
179 1364   33 1364 0 2678 my $self = shift || confess "Expected a method call on a Parser object.";
180 1364   33     2304 my $buffer = shift || confess "Expected 1 parameter, a Buffer object.";
181            
182 1364         2240 my $token = $self->getNextToken($buffer);
183 1364         3430 $token = $self->getNextToken($buffer) while($token->isWhitespace());
184            
185 1364         3285 return $token;
186             }
187            
188             # Parameters
189             # - A character buffer.
190             # Returns a token or an array of objects. A token result always indicates an error, parsing failed.
191             sub parseList
192             {
193 112     112 0 144 my $self = shift;
194 112         140 my $buffer = shift;
195            
196 112         196 my $token = $self->getNextNonWhitespaceToken($buffer);
197 112 50       288 if($token->isErroneous())
198             {
199 0         0 return $token;
200             }
201            
202 112 50       281 if($token->isBeginList())
203             {
204 112         271 my $line = $buffer->getLineNo();
205 112         271 my $col = $buffer->getColNo();
206 112         227 $token = $self->getNextNonWhitespaceToken($buffer);
207 112         178 my @result = ();
208            
209 112   33     280 while(!$token->isErroneous() && !$token->isEndList())
210             {
211 338         799 $self->pushBackToken($token);
212 338         663 my $expr = $self->parseExpression($buffer);
213 338 50 66     2804 if(UNIVERSAL::can($expr, 'isa') && $expr->isa("Repl::Core::Token"))
214             {
215 0 0       0 if($expr->isErroneous())
216             {
217 0         0 return $expr;
218             }
219             else
220             {
221 0         0 return Repl::Core::Token->new(TYPE=>"eof", VALUE=>"Syntax error in the list.", LINENO=>$line, COLNO=>$col);
222             }
223             } else
224             {
225 338         611 push @result, $expr;
226             }
227            
228 338         637 $token = $self->getNextNonWhitespaceToken($buffer);
229             }
230            
231 112 50       308 if($token->isErroneous())
232             {
233 0         0 return $token;
234             }
235            
236 112         486 return \@result;
237            
238             } else
239             {
240 0         0 return Repl::Core::Token->new(TYPE=>"error", VALUE=>sprintf("Syntax error, expected '(' but encountered: %s.", $token->getValue()),
241             LINENO=>$buffer->getLineNo(), COLNO=>$buffer->getColNo());
242             }
243            
244             }
245            
246             # Parameters
247             # - A character buffer.
248             sub parseExpression
249             {
250 402     402 0 1632 my $self = shift;
251 402         401 my $buffer = shift;
252            
253 402         651 my $token = $self->getNextNonWhitespaceToken($buffer);
254            
255 402 100 100     929 if($token->isErroneous())
    100          
    50          
256             {
257 1         3 return $token;
258             }
259             elsif($token->isBeginList() || $token->isString())
260             {
261 400         398 my $resultExpr;
262 400 100       884 if($token->isBeginList())
263             {
264             # List found.
265 112         214 $self->pushBackToken($token);
266 112         279 $resultExpr = $self->parseList($buffer);
267             }
268             else
269             {
270             # String found.
271 288         1070 $resultExpr = $token->getValue();
272             }
273            
274             # Return the error token on error.
275 400 50 66     4298 if(UNIVERSAL::can($resultExpr, 'isa') && $resultExpr->isa('Repl::Core::Token'))
276             {
277 0         0 return $resultExpr;
278             }
279            
280 400         793 my $peek = $self->getNextNonWhitespaceToken($buffer);
281 400 100       1021 if($peek->isPair())
282             {
283             # Yes, pairing found.
284 22         51 my $lvalueExpr = $self->parseExpression($buffer);
285 22 50 66     237 return $lvalueExpr if(UNIVERSAL::can($lvalueExpr, 'isa') && $lvalueExpr->isa('Repl::Core::Token'));
286 22         106 return Repl::Core::Pair->new(LEFT=>$resultExpr, RIGHT=>$lvalueExpr);
287             }
288             else
289             {
290             # No pairing found.
291 378         715 $self->pushBackToken($peek);
292 378         1242 return $resultExpr;
293             }
294             }
295             elsif($token->isQuote())
296             {
297 1         4 my $peekToken = $self->getNextToken($buffer);
298 1         5 $self->pushBackToken($peekToken);
299 1 50       4 if(!$peekToken->isWhitespace())
300             {
301 1         4 my $expr = $self->parseExpression($buffer);
302 1 50 33     9 if(UNIVERSAL::can($expr, 'isa' ) && $expr->isa("Repl::Core::Token"))
303             {
304 0         0 return $expr;
305             } else
306             {
307 1         10 return ["quote", $expr];
308             }
309             }
310             else
311             {
312 0         0 return $peekToken->getValue();
313             }
314            
315             } else
316             {
317 0         0 return Repl::Core::Token->new(TYPE=>"error", VALUE=>sprintf("Syntax error, expected a string or a list but encountered %s.", $token->getValue()), LINENO=>$token->getLineNo(), COLNO=>$token=>getColNo());
318             }
319             }
320            
321             # Parameters:
322             # - A string containing an expression.
323             sub parseString
324             {
325 38     38 0 21666 my $self = shift;
326 38         63 my $sentence = shift;
327            
328 38 100       118 if(exists $self->{PUSHBACK})
329             {
330 36         88 my $pushback = $self->{PUSHBACK};
331 36         146 delete $self->{PUSHBACK};
332             }
333            
334 38         161 my $buffer = Repl::Core::Buffer->new(SENTENCE=>$sentence);
335 38         108 return $self->parseExpression($buffer);
336             }
337            
338             1;