File Coverage

blib/lib/Karel/Parser.pm
Criterion Covered Total %
statement 84 85 98.8
branch 14 14 100.0
condition 5 6 83.3
subroutine 31 32 96.8
pod 2 2 100.0
total 136 139 97.8


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 9     9   110043 use warnings;
  9         16  
  9         291  
14 9     9   37 use strict;
  9         11  
  9         184  
15              
16 9     9   580 use Moo;
  9         11105  
  9         50  
17 9     9   7370 use Marpa::R2;
  9         1127133  
  9         451  
18 9     9   555 use namespace::clean;
  9         9820  
  9         79  
19              
20             { package # Hide from CPAN.
21             Karel::Parser::Actions;
22              
23 9     9   2117 use parent 'Exporter';
  9         15  
  9         74  
24             our @EXPORT_OK = qw{ def concat left forward pick drop stop repeat
25             While If first_ch negate call list defs run };
26              
27 47     47   1336 sub def { [ $_[1], $_[2] ] }
28 80     80   283754 sub concat { $_[1] . $_[2] }
29 39     39   25601 sub left { [ l => $_[1] ] }
30 6     6   8593 sub forward { [ f => $_[1] ] }
31 9     9   241 sub pick { [ p => $_[1] ] }
32 4     4   16671 sub drop { [ d => $_[1] ] }
33 2     2   8184 sub stop { [ q => $_[1] ] }
34 25     25   717 sub repeat { complex_command( r => @_ ) }
35 7     7   194 sub While { complex_command( w => @_ ) }
36 19         89 sub If { [ i => @{ $_[1] }[ 0, 1 ],
37             ref $_[1][2] ? $_[1][2] : [['x']], # else
38 19 100   19   452 [ @{ $_[1] }[ -2, -1 ] ] ] }
  19         70  
39 25     25   823 sub first_ch { substr $_[1], 0, 1 }
40 17     17   438 sub negate { '!' . $_[1] }
41 32     32   870 sub call { $_[0]{ $_[1][0] } = 1;
42 32         67 [ 'c', $_[1][0], [ @{ $_[1] }[ 1, 2 ] ] ] }
  32         118  
43 100     100   2747 sub list { [ grep defined, @_[ 1 .. $#_ ] ] }
44              
45             sub defs {
46 26     26   681 my $unknown = shift;
47 26         52 my %h;
48 26         72 for my $command (@_) {
49 47         84 $h{ $command->[0][0] } = [ $command->[0][1], @{ $command }[ 1, 2 ] ];
  47         166  
50             }
51 26         78 return [ \%h, $unknown ]
52             }
53              
54             sub run {
55 15     15   457 shift;
56 15         41 $_[0][-1][0] -= length 'run ';
57 15         46 [ @_ ]
58             }
59              
60             sub complex_command {
61 32     32   84 my $cmd = shift;
62 32         56 [ $cmd => @{ $_[1] }[ 0, 1 ], [ @{ $_[1] }[ 2, 3 ] ] ]
  32         76  
  32         125  
63             }
64              
65             }
66              
67             my %terminals = (
68             octothorpe => '#',
69             drop_mark => 'drop-mark',
70             pick_mark => 'pick-mark',
71             );
72              
73             $terminals{$_} = $_
74             for qw( command left forward stop repeat while if else end done
75             wall mark there a facing not North East South West x times
76             s is isn t no );
77              
78              
79             my $dsl = << '__DSL__';
80              
81             :default ::= action => ::undef
82             lexeme default = latm => 1
83              
84             START ::= Defs action => ::first
85             | (Run SC) Command action => run
86             Run ::= 'run' action => [ values, start, length ]
87              
88             Defs ::= Def+ separator => SC action => defs
89             Def ::= Def2 action => [ values, start, length ]
90             Def2 ::= (SCMaybe) (command) (SC) CommandDef (SC) Prog (SC) (end)
91             action => def
92             NewCommand ::= CommandDef action => [ values, start, length ]
93             CommandDef ::= alpha valid_name action => concat
94             Prog ::= Commands action => ::first
95             Commands ::= Command+ separator => SC action => list
96             Command ::= Left action => left
97             | Forward action => forward
98             | Drop_mark action => drop
99             | Pick_mark action => pick
100             | Stop action => stop
101             | Repeat action => repeat
102             | While action => While
103             | If action => If
104             | NewCommand action => call
105             Left ::= left action => [ start, length ]
106             Forward ::= forward action => [ start, length ]
107             Drop_mark ::= drop_mark action => [ start, length ]
108             Pick_mark ::= pick_mark action => [ start, length ]
109             Stop ::= stop action => [ start, length ]
110             Repeat ::= (repeat SC) Num (SC Times SC) Prog (SC done)
111             action => [ values, start, length ]
112             While ::= (while SC) Condition (SC) Prog (done)
113             action => [ values, start, length ]
114             If ::= (if SC) Condition (SC) Prog (done)
115             action => [ values, start, length ]
116             | (if SC) Condition (SC) Prog (SC else SC) Prog (done)
117             action => [ values, start, length ]
118             Condition ::= (there quote s SC a SC) Covering
119             action => ::first
120             | (there SC is SC a SC) Covering
121             action => ::first
122             | (Negation SC) Covering action => negate
123             | (facing SC) Wind action => ::first
124             | (not SC facing SC) Wind action => negate
125             Negation ::= (there SC isn quote t SC a)
126             | (there SC is SC no)
127             | (there quote s SC no)
128             Covering ::= mark action => first_ch
129             | wall action => first_ch
130             Wind ::= North action => first_ch
131             | East action => first_ch
132             | South action => first_ch
133             | West action => first_ch
134             Num ::= non_zero action => ::first
135             | non_zero digits action => concat
136             Times ::= times
137             | x
138             Comment ::= (octothorpe non_lf lf)
139             SC ::= SpComm+
140             SCMaybe ::= SpComm*
141             SpComm ::= Comment
142             || space
143              
144             alpha ~ [a-z]
145             valid_name ~ [-a-z_0-9]+
146             non_zero ~ [1-9]
147             digits ~ [0-9]+
148             space ~ [\s]+
149             quote ~ [']
150             non_lf ~ [^\n]*
151             lf ~ [\n]
152              
153             __DSL__
154             $dsl .= join "\n", map "$_ ~ '$terminals{$_}'", keys %terminals;
155              
156              
157             has parser => ( is => 'ro' );
158              
159             has _grammar => ( is => 'lazy' );
160              
161 27     27   465 sub _dsl { $dsl }
162              
163 39     39   486 sub _action_class { 'Karel::Parser::Actions' }
164              
165 37     37   127 sub _terminals { \%terminals }
166              
167             =item my @terminal_strings = $self->terminals(@terminals)
168              
169             Returns the strings correscponding to the given terminal symbols.
170             E.g., C<< $self->terminals('octothorpe') >> returns C<#>.
171              
172             =cut
173              
174             sub terminals {
175 9     9 1 637 my $self = shift;
176 9   66     41 return map $self->_terminals->{$_} // $_, @_
177             }
178              
179             sub _build__grammar {
180 34     34   2311 my ($self) = @_;
181 34         252 my $g = 'Marpa::R2::Scanless::G'->new({ source => \$self->_dsl });
182 34         3670896 return $g
183             }
184              
185             =item my ($new_commands, $unknown) = $parser->parse($definition)
186              
187             C<$new_commands> is a hash that you can use to teach the robot:
188              
189             $robot->_learn($_, $new_commands->{$_}, $definition) for keys %$new_commands;
190              
191             C<$unknwon> is a hash whose keys are all the non-basic commands needed
192             to run the parsed programs.
193              
194             When the input starts with C, it should contain just one
195             command. The robot's C function uses it to parse commands you
196             run, as simple C<[[ 'c', $command ]]> doesn't work for core commands
197             (C, C, etc.).
198              
199             If there's an error, an exception is thrown. It's a hash ref with the
200             following keys:
201              
202             =over 4
203              
204             =item -
205              
206             B: lists the available terminals. There are several special
207             values: C (white space), C (letter starting a word),
208             C newline, C (anything but a newline), C
209             (character that can occur in a command name starting from the 2nd
210             position: a letter, digit, underscore, or a dash), C (single
211             quote), C (1-9).
212              
213             =item -
214              
215             B: the last successfully parsed command.
216              
217             =item -
218              
219             B: position (line, column) where the parsing stopped.
220              
221             =back
222              
223             =cut
224              
225             sub parse {
226 49     49 1 107028 my ($self, $input) = @_;
227 49         1255 my $recce = 'Marpa::R2::Scanless::R'
228             ->new({ grammar => $self->_grammar,
229             semantics_package => $self->_action_class,
230             });
231              
232 49         15704 my ($line, $column);
233             eval {
234 49         224 $recce->read(\$input);
235 49 100       111 1 } or do {
  47         93425  
236 2         4487 my $exception = $@;
237 2         10 ($line, $column) = $recce->line_column;
238             };
239              
240 49         289 my $value = $recce->value;
241 49 100 100     2519 if ($line || ! $value) {
242 9         63 my ($from, $length) = $recce->last_completed('Command');
243 9         2961 my @expected = $self->terminals(@{ $recce->terminals_expected });
  9         48  
244 9         134 my $E = bless { expected => \@expected }, ref($self) . '::Exception';
245 9 100       55 my $last = $recce->substring($from, $length) if defined $from;
246 9 100       348 $E->{last_completed} = $last if $last;
247 9 100       33 if ($line) {
248 2         9 $E->{pos} = [ $line, $column ];
249             } else {
250 7         30 $E->{pos} = [ $recce->line_column ];
251             }
252 9         325 die $E
253             }
254 40 100       2013 return $input =~ /^run / ? $value : @$$value
255             }
256              
257              
258             { package
259             Karel::Parser::Exception;
260              
261 9     9   9586 use overload '""' => sub { use Data::Dumper; Dumper \@_ };
  9     9   15  
  9     0   776  
  9         45  
  9         14  
  9         119  
  0         0  
262              
263             }
264              
265             =back
266              
267             =cut
268              
269             __PACKAGE__