File Coverage

blib/lib/Acme/Aheui.pm
Criterion Covered Total %
statement 159 190 83.6
branch 73 98 74.4
condition 11 13 84.6
subroutine 22 22 100.0
pod 2 4 50.0
total 267 327 81.6


line stmt bran cond sub pod time code
1             package Acme::Aheui;
2 1     1   49535 use utf8;
  1         2  
  1         6  
3 1     1   27 use strict;
  1         1  
  1         22  
4 1     1   4 use warnings;
  1         4  
  1         18  
5 1     1   536 use Term::ReadKey;
  1         3057  
  1         66  
6 1     1   5 use Encode qw/encode/;
  1         1  
  1         66  
7            
8             =encoding utf8
9            
10             =head1 NAME
11            
12             Acme::Aheui - an aheui interpreter
13            
14             =head1 VERSION
15            
16             Version 0.03
17            
18             =cut
19            
20             our $VERSION = '0.03';
21            
22            
23             =head1 SYNOPSIS
24            
25             use utf8;
26             use Acme::Aheui;
27             my $interpreter = Acme::Aheui->new( source => '아희' );
28             $interpreter->execute();
29            
30             =head1 DESCRIPTION
31            
32             An aheui interpreter.
33            
34             See aheui language specification at L
35            
36             Most logic is based on the reference implementation by Puzzlet Chung.
37             (L)
38            
39             =cut
40            
41             use constant {
42 1         1544 JONG_STROKE_NUMS =>
43             [0, 2, 4, 4, 2, 5, 5, 3, 5, 7, 9, 9, 7, 9,
44             9, 8, 4, 4, 6, 2, 4, 1, 3, 4, 3, 4, 4, 3],
45             REQUIRED_ELEM_NUMS =>
46             [0, 0, 2, 2, 2, 2, 1, 0, 1, 0, 1, 0, 2, 0, 1, 0, 2, 2, 0],
47 1     1   5 };
  1         1  
48            
49             =head1 PUBLIC METHODS
50            
51             =head2 new
52            
53             my $interpreter = Acme::Aheui->new( source => '아희' );
54            
55             This method will create and return C object.
56            
57             =cut
58            
59             sub new {
60 9     9 1 18239 my $class = shift;
61 9         23 my %args = @_;
62            
63 9   100     37 my $source = $args{source} || '';
64 9         17 my $codespace = build_codespace($source);
65            
66 9         38 my $self = {
67             _codespace => $codespace,
68             _stacks => [],
69             _stack_index => 0,
70             _x => 0,
71             _y => 0,
72             _dx => 0,
73             _dy => 1,
74             };
75 9         21 bless $self, $class;
76            
77 9         24 return $self;
78             }
79            
80             sub build_codespace {
81 9     9 0 12 my ($source) = @_;
82            
83 9         69 my @lines = split /\r?\n/, $source;
84 9         12 my @rows = ();
85 9         21 for my $line (@lines) {
86 25         26 my @row = ();
87 25         43 for my $char (split //, $line) {
88 112         109 my $disassembled = disassemble_hangul_char($char);
89 112         137 push @row, $disassembled;
90             }
91 25         42 push @rows, \@row;
92             }
93 9         18 return \@rows;
94             }
95            
96             sub disassemble_hangul_char {
97 112     112 0 99 my ($char) = @_;
98            
99 112 100       229 if ($char =~ /[가-힣]/) {
100 99         119 my $code = unpack 'U', $char;
101 99         79 $code -= 0xAC00;
102 99         138 my ($cho, $jung, $jong) = (int($code/28/21), ($code/28)%21, $code%28);
103 99         189 return {cho => $cho, jung => $jung, jong => $jong};
104             }
105             else {
106 13         29 return {cho => -1, jung => -1, jong => -1};
107             }
108             }
109            
110             =head2 execute
111            
112             $interpreter->execute();
113            
114             This method will execute the aheui program and return the exit code.
115             It may use C and/or C if the aheui program uses I/O.
116            
117             =cut
118            
119             sub execute {
120 6     6 1 21 my ($self) = @_;
121            
122 6 100       12 return 0 unless $self->_has_initial_command();
123 4         9 return $self->_loop_steps();
124             }
125            
126             sub _has_initial_command {
127 6     6   5 my ($self) = @_;
128            
129 6         7 for my $row (@{ $self->{_codespace} }) {
  6         15  
130 10         18 my $first_command = @$row[0];
131 10 100 100     42 if ($first_command && $$first_command{cho} != -1) {
132 4         11 return 1;
133             }
134             }
135 2         14 return 0;
136             }
137            
138             sub _loop_steps {
139 4     4   5 my ($self) = @_;
140            
141 4         5 while (1) {
142 76         62 my $codespace = $self->{_codespace};
143 76         78 my ($x, $y) = ($self->{_x}, $self->{_y});
144            
145 76 50       56 if ($x > $#{$$codespace[$y]}) {
  76         125  
146 0         0 $self->_move_cursor();
147 0         0 next;
148             }
149            
150 76         63 my $c = $$codespace[$y][$x];
151            
152 76 50 33     247 if (!$c || $c->{cho} == -1) {
153 0         0 $self->_move_cursor();
154 0         0 next;
155             }
156            
157 76         61 my $cho = $c->{cho};
158 76         56 my $jung = $c->{jung};
159 76         52 my $jong = $c->{jong};
160 76         61 my $si = $self->{_stack_index};
161            
162 76         100 my ($dx, $dy) = $self->_get_deltas_upon_jung($jung);
163 76         70 $self->{_dx} = $dx;
164 76         63 $self->{_dy} = $dy;
165            
166 76         61 my $stack = $self->{_stacks}->[$si];
167 76 100       85 my $elem_num = ($stack) ? scalar @{$stack} : 0;
  72         63  
168 76 50       96 if ($elem_num < REQUIRED_ELEM_NUMS->[$cho]) {
169 0         0 $self->{_dx} = -($self->{_dx});
170 0         0 $self->{_dy} = -($self->{_dy});
171             }
172             else {
173 76 50       255 if ($cho == 2) { # ㄴ
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
174 0         0 my $m = $self->_pop($si);
175 0         0 my $n = $self->_pop($si);
176 0         0 $self->_push($si, int($n/$m));
177             }
178             elsif ($cho == 3) { # ㄷ
179 8         12 my $m = $self->_pop($si);
180 8         10 my $n = $self->_pop($si);
181 8         11 $self->_push($si, $n+$m);
182             }
183             elsif ($cho == 16) { # ㅌ
184 3         5 my $m = $self->_pop($si);
185 3         4 my $n = $self->_pop($si);
186 3         6 $self->_push($si, $n-$m);
187             }
188             elsif ($cho == 4) { # ㄸ
189 6         9 my $m = $self->_pop($si);
190 6         9 my $n = $self->_pop($si);
191 6         11 $self->_push($si, $n*$m);
192             }
193             elsif ($cho == 5) { # ㄹ
194 0         0 my $m = $self->_pop($si);
195 0         0 my $n = $self->_pop($si);
196 0         0 $self->_push($si, $n%$m);
197             }
198             elsif ($cho == 6) { # ㅁ
199 18         29 my $v = $self->_pop($si);
200 18 100       34 if ($jong == 21) { # jongㅇ
    50          
201 2         4 $self->_output_number($v);
202             }
203             elsif ($jong == 27) { # jongㅎ
204 16         27 $self->_output_code_as_character($v);
205             }
206             }
207             elsif ($cho == 7) { # ㅂ
208 24         14 my $v = 0;
209 24 100       41 if ($jong == 21) { # jongㅇ
    100          
210 1         4 $v = $self->_get_input_number();
211             }
212             elsif ($jong == 27) { # jongㅎ
213 2         6 $v = $self->_get_input_character_as_code();
214             }
215             else { # the other jongs
216 21         20 $v = JONG_STROKE_NUMS->[$jong];
217             }
218 24         70 $self->_push($si, $v);
219             }
220             elsif ($cho == 8) { # ㅃ
221 12         16 $self->_duplicate($si);
222             }
223             elsif ($cho == 17) { # ㅍ
224 1         3 $self->_swap($si);
225             }
226             elsif ($cho == 9) { # ㅅ
227 0         0 $self->{_stack_index} = $jong;
228             }
229             elsif ($cho == 10) { # ㅆ
230 0         0 $self->_push($jong, $self->_pop($si));
231             }
232             elsif ($cho == 12) { # ㅈ
233 0         0 my $m = $self->_pop($si);
234 0         0 my $n = $self->_pop($si);
235 0 0       0 my $in = ($n >= $m) ? 1 : 0;
236 0         0 $self->_push($si, $in);
237             }
238             elsif ($cho == 14) { # ㅊ
239 0 0       0 if ($self->_pop($si) == 0) {
240 0         0 $self->{_dx} = -($self->{_dx});
241 0         0 $self->{_dy} = -($self->{_dy});
242             }
243             }
244             elsif ($cho == 18) { # ㅎ
245 4   100     7 my $ret = $self->_pop($si) || 0;
246 4         30 return $ret;
247             }
248             }
249            
250 72         828 $self->_move_cursor();
251             }
252             }
253            
254             sub _move_cursor {
255 101     101   11887 my ($self) = @_;
256 101         97 my $codespace = $self->{_codespace};
257            
258 101         103 $self->{_x} += $self->{_dx};
259 101         102 $self->{_y} += $self->{_dy};
260            
261 101         87 my $last_row_index = $#{ $codespace };
  101         112  
262 101 100       192 if ($self->{_y} < 0) {
263 2         3 $self->{_y} = $last_row_index;
264             }
265 101 100       139 if ($self->{_y} > $last_row_index) {
266 2         3 $self->{_y} = 0;
267             }
268            
269 101         83 my $last_char_index = $#{ @$codespace[$self->{_y}] };
  101         117  
270 101 100       145 if ($self->{_x} < 0) {
271 4         6 $self->{_x} = $last_char_index;
272             }
273 101 100 100     260 if ($self->{_x} > $last_char_index &&
274             $self->{_dx} != 0) {
275 3         6 $self->{_x} = 0;
276             }
277             }
278            
279             sub _get_deltas_upon_jung {
280 76     76   56 my ($self, $jung) = @_;
281            
282 76         58 my $dx = $self->{_dx};
283 76         59 my $dy = $self->{_dy};
284            
285 76 100       222 if ($jung == 0) {
    50          
    100          
    50          
    100          
    50          
    100          
    50          
    50          
    100          
    50          
286 28         35 return (1, 0); # ㅏ
287             }
288             elsif ($jung == 2) {
289 0         0 return (2, 0); # ㅑ
290             }
291             elsif ($jung == 4) {
292 16         17 return (-1, 0); # ㅓ
293             }
294             elsif ($jung == 6) {
295 0         0 return (-2, 0); # ㅕ
296             }
297             elsif ($jung == 8) {
298 12         13 return (0, -1); # ㅗ
299             }
300             elsif ($jung == 12) {
301 0         0 return (0, -2); # ㅛ
302             }
303             elsif ($jung == 13) {
304 16         21 return (0, 1); # ㅜ
305             }
306             elsif ($jung == 17) {
307 0         0 return (0, 2); # ㅠ
308             }
309             elsif ($jung == 18) {
310 0         0 return ($dx, -$dy); # ㅡ
311             }
312             elsif ($jung == 19) {
313 1         4 return (-$dx, -$dy); # ㅢ
314             }
315             elsif ($jung == 20) {
316 3         6 return (-$dx, $dy); # ㅣ
317             }
318             else {
319 0         0 return ($dx, $dy);
320             }
321             }
322            
323             sub _push {
324 257     257   32785 my ($self, $i, $n) = @_;
325            
326 257 50       344 if ($i == 27) { # ㅎ
327 0         0 return;
328             }
329             else {
330 257         185 push @{$self->{_stacks}->[$i]}, $n;
  257         500  
331             }
332             }
333            
334             sub _pop {
335 299     299   15358 my ($self, $i) = @_;
336 299         279 my $stack = $self->{_stacks}->[$i];
337            
338 299 100       476 if ($i == 21) { # ㅇ
    50          
339 9         15 return shift @$stack;
340             }
341             elsif ($i == 27) { # ㅎ
342 0         0 return;
343             }
344             else {
345 290         377 return pop @$stack;
346             }
347             }
348            
349             sub _duplicate {
350 39     39   69 my ($self, $i) = @_;
351 39         40 my $stack = $self->{_stacks}->[$i];
352            
353 39 100       91 if ($i == 21) { # ㅇ
    50          
354 1         3 my $first = $$stack[0];
355 1         3 unshift @$stack, $first;
356             }
357             elsif ($i == 27) { # ㅎ
358 0         0 return;
359             }
360             else {
361 38         39 my $last = $$stack[-1];
362 38         53 push @$stack, $last;
363             }
364             }
365            
366             sub _swap {
367 28     28   56 my ($self, $i) = @_;
368 28         33 my $stack = $self->{_stacks}->[$i];
369            
370 28 100       53 if ($i == 21) { # ㅇ
    50          
371 1         2 my $first = $$stack[0];
372 1         1 my $second = $$stack[1];
373 1         2 $$stack[0] = $second;
374 1         2 $$stack[1] = $first;
375             }
376             elsif ($i == 27) { # ㅎ
377 0         0 return;
378             }
379             else {
380 27         29 my $last = $$stack[-1];
381 27         26 my $next = $$stack[-2];
382 27         27 $$stack[-1] = $next;
383 27         40 $$stack[-2] = $last;
384             }
385             }
386            
387             sub _output_number {
388 2     2   3 my ($self, $number) = @_;
389            
390 2         45 print $number;
391             }
392            
393             sub _output_code_as_character {
394 16     16   14 my ($self, $code) = @_;
395            
396 16         34 my $unichar = pack 'U', $code;
397 16         38 print encode('utf-8', $unichar);
398             }
399            
400             sub _get_input_character_as_code {
401 2     2   3 my ($self) = @_;
402            
403 2         46 my $char = ReadKey(0);
404 2         65 return unpack 'U', $char;
405             }
406            
407             sub _get_input_number {
408 1     1   1 my ($self) = @_;
409            
410 1         29 return int(ReadLine(0));
411             }
412            
413             =head1 INSTALLATION
414            
415             To install this module, run the following commands:
416            
417             perl Build.PL
418             ./Build
419             ./Build test
420             ./Build install
421            
422             =head1 AUTHOR
423            
424             Rakjin Hwang, C<< >>
425            
426             =head1 LICENSE
427            
428             This program is free software; you can redistribute it and/or modify it
429             under the same terms as Perl itself.
430            
431             =cut
432            
433             1;