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   46580 use utf8;
  1         2  
  1         6  
3 1     1   24 use strict;
  1         1  
  1         21  
4 1     1   3 use warnings;
  1         5  
  1         17  
5 1     1   483 use Term::ReadKey;
  1         3041  
  1         71  
6 1     1   8 use Encode qw/encode/;
  1         1  
  1         96  
7              
8             =encoding utf8
9              
10             =head1 NAME
11              
12             Acme::Aheui - an aheui interpreter
13              
14             =head1 VERSION
15              
16             Version 0.04
17              
18             =cut
19              
20             our $VERSION = '0.04';
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         1503 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   4 };
  1         2  
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 17296 my $class = shift;
61 9         24 my %args = @_;
62            
63 9   100     31 my $source = $args{source} || '';
64 9         16 my $codespace = build_codespace($source);
65              
66 9         35 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         19 bless $self, $class;
76              
77 9         23 return $self;
78             }
79              
80             sub build_codespace {
81 9     9 0 11 my ($source) = @_;
82              
83 9         63 my @lines = split /\r?\n/, $source;
84 9         13 my @rows = ();
85 9         14 for my $line (@lines) {
86 25         19 my @row = ();
87 25         39 for my $char (split //, $line) {
88 112         111 my $disassembled = disassemble_hangul_char($char);
89 112         128 push @row, $disassembled;
90             }
91 25         45 push @rows, \@row;
92             }
93 9         17 return \@rows;
94             }
95              
96             sub disassemble_hangul_char {
97 112     112 0 89 my ($char) = @_;
98              
99 112 100       222 if ($char =~ /[가-힣]/) {
100 99         108 my $code = unpack 'U', $char;
101 99         73 $code -= 0xAC00;
102 99         143 my ($cho, $jung, $jong) = (int($code/28/21), ($code/28)%21, $code%28);
103 99         187 return {cho => $cho, jung => $jung, jong => $jong};
104             }
105             else {
106 13         23 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 18 my ($self) = @_;
121              
122 6 100       11 return 0 unless $self->_has_initial_command();
123 4         11 return $self->_loop_steps();
124             }
125              
126             sub _has_initial_command {
127 6     6   6 my ($self) = @_;
128              
129 6         4 for my $row (@{ $self->{_codespace} }) {
  6         12  
130 10         17 my $first_command = @$row[0];
131 10 100 100     38 if ($first_command && $$first_command{cho} != -1) {
132 4         11 return 1;
133             }
134             }
135 2         13 return 0;
136             }
137              
138             sub _loop_steps {
139 4     4   3 my ($self) = @_;
140              
141 4         3 while (1) {
142 76         60 my $codespace = $self->{_codespace};
143 76         86 my ($x, $y) = ($self->{_x}, $self->{_y});
144              
145 76 50       51 if ($x > $#{$$codespace[$y]}) {
  76         117  
146 0         0 $self->_move_cursor();
147 0         0 next;
148             }
149              
150 76         66 my $c = $$codespace[$y][$x];
151              
152 76 50 33     224 if (!$c || $c->{cho} == -1) {
153 0         0 $self->_move_cursor();
154 0         0 next;
155             }
156              
157 76         51 my $cho = $c->{cho};
158 76         56 my $jung = $c->{jung};
159 76         53 my $jong = $c->{jong};
160 76         44 my $si = $self->{_stack_index};
161              
162 76         85 my ($dx, $dy) = $self->_get_deltas_upon_jung($jung);
163 76         75 $self->{_dx} = $dx;
164 76         55 $self->{_dy} = $dy;
165              
166 76         68 my $stack = $self->{_stacks}->[$si];
167 76 100       71 my $elem_num = ($stack) ? scalar @{$stack} : 0;
  72         56  
168 76 50       88 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       247 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         10 my $m = $self->_pop($si);
180 8         11 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         9 $self->_push($si, $n-$m);
187             }
188             elsif ($cho == 4) { # ㄸ
189 6         9 my $m = $self->_pop($si);
190 6         7 my $n = $self->_pop($si);
191 6         9 $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         27 my $v = $self->_pop($si);
200 18 100       33 if ($jong == 21) { # jongㅇ
    50          
201 2         3 $self->_output_number($v);
202             }
203             elsif ($jong == 27) { # jongㅎ
204 16         28 $self->_output_code_as_character($v);
205             }
206             }
207             elsif ($cho == 7) { # ㅂ
208 24         18 my $v = 0;
209 24 100       32 if ($jong == 21) { # jongㅇ
    100          
210 1         4 $v = $self->_get_input_number();
211             }
212             elsif ($jong == 27) { # jongㅎ
213 2         4 $v = $self->_get_input_character_as_code();
214             }
215             else { # the other jongs
216 21         19 $v = JONG_STROKE_NUMS->[$jong];
217             }
218 24         66 $self->_push($si, $v);
219             }
220             elsif ($cho == 8) { # ㅃ
221 12         13 $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     5 my $ret = $self->_pop($si) || 0;
246 4         28 return $ret;
247             }
248             }
249              
250 72         731 $self->_move_cursor();
251             }
252             }
253              
254             sub _move_cursor {
255 101     101   11781 my ($self) = @_;
256 101         107 my $codespace = $self->{_codespace};
257              
258 101         97 $self->{_x} += $self->{_dx};
259 101         93 $self->{_y} += $self->{_dy};
260              
261 101         74 my $last_row_index = $#{ $codespace };
  101         114  
262 101 100       183 if ($self->{_y} < 0) {
263 2         3 $self->{_y} = $last_row_index;
264             }
265 101 100       132 if ($self->{_y} > $last_row_index) {
266 2         3 $self->{_y} = 0;
267             }
268              
269 101         70 my $last_char_index = $#{ @$codespace[$self->{_y}] };
  101         103  
270 101 100       155 if ($self->{_x} < 0) {
271 4         5 $self->{_x} = $last_char_index;
272             }
273 101 100 100     240 if ($self->{_x} > $last_char_index &&
274             $self->{_dx} != 0) {
275 3         4 $self->{_x} = 0;
276             }
277             }
278              
279             sub _get_deltas_upon_jung {
280 76     76   63 my ($self, $jung) = @_;
281              
282 76         47 my $dx = $self->{_dx};
283 76         56 my $dy = $self->{_dy};
284              
285 76 100       170 if ($jung == 0) {
    50          
    100          
    50          
    100          
    50          
    100          
    50          
    50          
    100          
    50          
286 28         54 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         14 return (0, -1); # ㅗ
299             }
300             elsif ($jung == 12) {
301 0         0 return (0, -2); # ㅛ
302             }
303             elsif ($jung == 13) {
304 16         18 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         2 return (-$dx, -$dy); # ㅢ
314             }
315             elsif ($jung == 20) {
316 3         5 return (-$dx, $dy); # ㅣ
317             }
318             else {
319 0         0 return ($dx, $dy);
320             }
321             }
322              
323             sub _push {
324 257     257   31401 my ($self, $i, $n) = @_;
325              
326 257 50       341 if ($i == 27) { # ㅎ
327 0         0 return;
328             }
329             else {
330 257         171 push @{$self->{_stacks}->[$i]}, $n;
  257         470  
331             }
332             }
333              
334             sub _pop {
335 299     299   15005 my ($self, $i) = @_;
336 299         299 my $stack = $self->{_stacks}->[$i];
337              
338 299 100       437 if ($i == 21) { # ㅇ
    50          
339 9         16 return shift @$stack;
340             }
341             elsif ($i == 27) { # ㅎ
342 0         0 return;
343             }
344             else {
345 290         353 return pop @$stack;
346             }
347             }
348              
349             sub _duplicate {
350 39     39   69 my ($self, $i) = @_;
351 39         38 my $stack = $self->{_stacks}->[$i];
352              
353 39 100       92 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         35 my $last = $$stack[-1];
362 38         54 push @$stack, $last;
363             }
364             }
365              
366             sub _swap {
367 28     28   61 my ($self, $i) = @_;
368 28         30 my $stack = $self->{_stacks}->[$i];
369              
370 28 100       73 if ($i == 21) { # ㅇ
    50          
371 1         2 my $first = $$stack[0];
372 1         3 my $second = $$stack[1];
373 1         1 $$stack[0] = $second;
374 1         2 $$stack[1] = $first;
375             }
376             elsif ($i == 27) { # ㅎ
377 0         0 return;
378             }
379             else {
380 27         26 my $last = $$stack[-1];
381 27         22 my $next = $$stack[-2];
382 27         22 $$stack[-1] = $next;
383 27         36 $$stack[-2] = $last;
384             }
385             }
386              
387             sub _output_number {
388 2     2   3 my ($self, $number) = @_;
389              
390 2         36 print $number;
391             }
392              
393             sub _output_code_as_character {
394 16     16   11 my ($self, $code) = @_;
395              
396 16         34 my $unichar = pack 'U', $code;
397 16         58 print encode('utf-8', $unichar);
398             }
399              
400             sub _get_input_character_as_code {
401 2     2   1 my ($self) = @_;
402              
403 2         44 my $char = ReadKey(0);
404 2         59 return unpack 'U', $char;
405             }
406              
407             sub _get_input_number {
408 1     1   1 my ($self) = @_;
409              
410 1         28 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;