File Coverage

blib/lib/Karel/Parser.pm
Criterion Covered Total %
statement 66 66 100.0
branch 12 12 100.0
condition 5 6 83.3
subroutine 27 27 100.0
pod 2 2 100.0
total 112 113 99.1


line stmt bran cond sub pod time code
1             package Karel::Parser;
2              
3             =head1 NAME
4              
5             Karel::Parser
6              
7             =head1 METHODS
8              
9             =over 4
10              
11             =cut
12              
13 8     8   69654 use warnings;
  8         9  
  8         199  
14 8     8   24 use strict;
  8         8  
  8         114  
15              
16 8     8   464 use Moo;
  8         9108  
  8         27  
17 8     8   5257 use Marpa::R2;
  8         782438  
  8         304  
18 8     8   412 use namespace::clean;
  8         7958  
  8         54  
19              
20             { package # Hide from CPAN.
21             Karel::Parser::Actions;
22              
23 8     8   1373 use parent 'Exporter';
  8         12  
  8         52  
24             our @EXPORT_OK = qw{ def concat left forward pick drop stop repeat
25             While If first_ch negate call list defs };
26              
27 42     42   833 sub def { [ $_[1], $_[2] ] }
28 69     69   159684 sub concat { $_[1] . $_[2] }
29 33     33   15978 sub left { ['l'] }
30 5     5   5185 sub forward { ['f'] }
31 9     9   176 sub pick { ['p'] }
32 4     4   10241 sub drop { ['d'] }
33 2     2   5045 sub stop { ['q'] }
34 21     21   449 sub repeat { ['r', $_[1], $_[2] ] }
35 7     7   156 sub While { ['w', $_[1], $_[2] ] }
36 18     18   365 sub If { ['i', $_[1], $_[2], $_[3]] }
37 24     24   613 sub first_ch { substr $_[1], 0, 1 }
38 17     17   342 sub negate { '!' . $_[1] }
39 27     27   534 sub call { $_[0]{ $_[1] } = 1; ['c', $_[1] ] }
  27         65  
40 89     89   1819 sub list { [ grep defined, @_[ 1 .. $#_ ] ] }
41             sub defs {
42 24     24   449 my $unknown = shift;
43 24         29 my %h;
44 24         117 $h{ $_->[0] }= $_->[1] for @_;
45 24         56 return [ \%h, $unknown ]
46             }
47              
48             }
49              
50             my %terminals = (
51             octothorpe => '#',
52             drop_mark => 'drop-mark',
53             pick_mark => 'pick-mark',
54             );
55              
56             $terminals{$_} = $_
57             for qw( command left forward stop repeat while if else end done
58             wall mark there a facing not North East South West x times
59             s is isn t no );
60              
61              
62             my $dsl = << '__DSL__';
63              
64             :default ::= action => ::undef
65             lexeme default = latm => 1
66              
67             START ::= Defs action => ::first
68             | ('run' SC) Command action => [value]
69              
70             Defs ::= Def+ separator => SC action => defs
71             Def ::= (SCMaybe) (command) (SC) NewCommand (SC) Prog (SC) (end)
72             action => def
73             NewCommand ::= alpha valid_name action => concat
74             Prog ::= Commands action => ::first
75             Commands ::= Command+ separator => SC action => list
76             Command ::= left action => left
77             | forward action => forward
78             | drop_mark action => drop
79             | pick_mark action => pick
80             | stop action => stop
81             | (repeat SC) Num (SC Times SC) Prog (SC done)
82             action => repeat
83             | (while SC) Condition (SC) Prog (done) action => While
84             | (if SC) Condition (SC) Prog (done) action => If
85             | (if SC) Condition (SC) Prog (else SC) Prog (done)
86             action => If
87             | NewCommand action => call
88             Condition ::= (there quote s SC a SC) Covering action => ::first
89             | (Negation SC) Covering action => negate
90             | (facing SC) Wind action => ::first
91             | (not SC facing SC) Wind action => negate
92             Negation ::= (there SC isn quote t SC a)
93             | (there SC is SC no)
94             | (there quote s SC no)
95             Covering ::= mark action => first_ch
96             | wall action => first_ch
97             Wind ::= North action => first_ch
98             | East action => first_ch
99             | South action => first_ch
100             | West action => first_ch
101             Num ::= non_zero action => ::first
102             | non_zero digits action => concat
103             Times ::= times
104             | x
105             Comment ::= (octothorpe non_lf lf)
106             SC ::= SpComm+
107             SCMaybe ::= SpComm*
108             SpComm ::= Comment
109             || space
110              
111             alpha ~ [a-z]
112             valid_name ~ [-a-z_0-9]+
113             non_zero ~ [1-9]
114             digits ~ [0-9]+
115             space ~ [\s]+
116             quote ~ [']
117             non_lf ~ [^\n]*
118             lf ~ [\n]
119              
120             __DSL__
121             $dsl .= join "\n", map "$_ ~ '$terminals{$_}'", keys %terminals;
122              
123              
124             has parser => ( is => 'ro' );
125              
126             has _grammar => ( is => 'lazy' );
127              
128 26     26   309 sub _dsl { $dsl }
129              
130 36     36   301 sub _action_class { 'Karel::Parser::Actions' }
131              
132 37     37   105 sub _terminals { \%terminals }
133              
134             =item my @terminal_strings = $self->terminals(@terminals)
135              
136             Returns the strings correscponding to the given terminal symbols.
137             E.g., C<< $self->terminals('octothorpe') >> returns C<#>.
138              
139             =cut
140              
141             sub terminals {
142 9     9 1 513 my $self = shift;
143 9   66     30 return map $self->_terminals->{$_} // $_, @_
144             }
145              
146             sub _build__grammar {
147 33     33   1437 my ($self) = @_;
148 33         200 my $g = 'Marpa::R2::Scanless::G'->new({ source => \$self->_dsl });
149 33         2526320 return $g
150             }
151              
152             =item my ($new_commands, $unknown) = $parser->parse($definition)
153              
154             C<$new_commands> is a hash that you can use to teach the robot:
155              
156             $robot->_learn($_, $new_commands->{$_}) for keys %$new_commands;
157              
158             C<$unknwon> is a hash whose keys are all the non-basic commands needed
159             to run the parsed programs.
160              
161             When the input starts with C, it should contain just one
162             command. The robot's C function uses it to parse commands you
163             run, as simple C<[[ 'c', $command ]]> doesn't work for core commands
164             (C, C, etc.).
165              
166             If there's an error, an exception is thrown. It's a hash ref with the
167             following keys:
168              
169             =over 4
170              
171             =item -
172              
173             B: lists the available terminals. There are several special
174             values: C (white space), C (letter starting a word),
175             C newline, C (anything but a newline), C
176             (character that can occur in a command name starting from the 2nd
177             position: a letter, digit, underscore, or a dash), C (single
178             quote), C (1-9).
179              
180             =item -
181              
182             B: the last successfully parsed command.
183              
184             =item -
185              
186             B: position (line, column) where the parsing stopped.
187              
188             =back
189              
190             =cut
191              
192             sub parse {
193 46     46 1 83734 my ($self, $input) = @_;
194 46         871 my $recce = 'Marpa::R2::Scanless::R'
195             ->new({ grammar => $self->_grammar,
196             semantics_package => $self->_action_class,
197             });
198              
199 46         10717 my ($line, $column);
200             eval {
201 46         180 $recce->read(\$input);
202 46 100       83 1 } or do {
  44         69133  
203 2         3510 my $exception = $@;
204 2         9 ($line, $column) = $recce->line_column;
205             };
206              
207 46         188 my $value = $recce->value;
208 46 100 100     1648 if ($line || ! $value) {
209 9         40 my ($from, $length) = $recce->last_completed('Command');
210 9         2465 my @expected = $self->terminals(@{ $recce->terminals_expected });
  9         36  
211 9         85 my $E = bless { expected => \@expected }, ref($self) . '::Exception';
212 9 100       40 my $last = $recce->substring($from, $length) if defined $from;
213 9 100       180 $E->{last_completed} = $last if $last;
214 9 100       22 if ($line) {
215 2         4 $E->{pos} = [ $line, $column ];
216             } else {
217 7         26 $E->{pos} = [ $recce->line_column ];
218             }
219 9         262 die $E
220             }
221 37 100       1261 return $input =~ /^run / ? $value : @$$value
222             }
223              
224              
225             =back
226              
227             =cut
228              
229             __PACKAGE__