File Coverage

blib/lib/Acme/Aheui.pm
Criterion Covered Total %
statement 162 193 83.9
branch 73 98 74.4
condition 13 16 81.2
subroutine 23 23 100.0
pod 2 4 50.0
total 273 334 81.7


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