File Coverage

blib/lib/Language/GolfScript.pm
Criterion Covered Total %
statement 872 1189 73.3
branch 509 834 61.0
condition 63 141 44.6
subroutine 121 138 87.6
pod 4 63 6.3
total 1569 2365 66.3


line stmt bran cond sub pod time code
1              
2             # placeholders for big integer package.
3             # Math::BigInt works well enough but
4             # I thought I might roll my own someday.
5              
6 0     0   0 sub bigi::new { Math::BigInt->new($_[0]) }
7 54     54   613 sub bigi::add { $_[0] + $_[1] }
8 29     29   93 sub bigi::mult { $_[0] * $_[1] }
9 8     8   26 sub bigi::sub { $_[0] - $_[1] }
10 124     124   470 sub bigi::mod { $_[0] % $_[1] }
11 6     6   21 sub bigi::div { int($_[0] / $_[1]) }
12 1     1   5 sub bigi::bitwise_or { $_[0] | $_[1] }
13 1     1   5 sub bigi::bitwise_and { $_[0] & $_[1] }
14 7     7   22 sub bigi::bitwise_xor { $_[0] ^ $_[1] }
15 2     2   9 sub bigi::neg { $_[0] * -1 }
16 6     6   20 sub bigi::pow { $_[0] ** $_[1] }
17 426     426   1867 sub bigi::cmp { $_[0] <=> $_[1] }
18 100     100   231 sub bigi::to_scalar { 0+"$_[0]" }
19              
20             require Math::BigInt;
21             eval {
22             Math::BigInt->import('try','GMP');
23             };
24              
25             #############################################################################
26              
27             package Language::GolfScript;
28              
29 6     6   55396 use Carp;
  6         14  
  6         559  
30 6     6   6271 use Time::HiRes;
  6         13138  
  6         37  
31 6     6   852 use warnings;
  6         25  
  6         316  
32 6     6   39 use strict;
  6         13  
  6         243  
33 6     6   37 use vars qw(@STACK @LB %DISPATCH);
  6         11  
  6         2576  
34             $| = 1;
35              
36             our $VERSION = 0.04;
37             our $DEBUG = $ENV{G_DEBUG} || 0;
38             our $INPUT = '__NOT_INITIALIZED__';
39             our $COUNT = 0;
40             our $TEST_OUTPUT;
41             our $TIMEOUT = 1800;
42             our %DISPATCH;
43              
44             sub import {
45 6 50   6   360 if ("@_" =~ /debug/i) {
46 0         0 $DEBUG = 1;
47             }
48 6 50       37 if ("@_" =~ /verbose/i) {
49 0         0 $DEBUG = 2;
50             }
51 6 50       36 if ("@_" =~ /count/i) {
52 0         0 $COUNT = 1;
53             }
54 6 50       205 if ("@_" =~ /timeout\s*=?\s*(\d+)/i) {
55 0         0 $TIMEOUT = $1;
56             }
57             }
58              
59             # command-line mode: first argument is GolfScript file.
60             # Other args are inputs to run against the GolfScript code.
61             unless (caller) {
62             my ($source) = shift @ARGV;
63             my $code;
64 6     6   7532 use File::Slurp;
  6         127856  
  6         121947  
65             if ($source =~ s/^code://) {
66             $code = $source;
67             } else {
68             $code = File::Slurp::slurp($source);
69             }
70             if (@ARGV) {
71             foreach my $input_file (@ARGV) {
72             $INPUT = File::Slurp::slurp($input_file);
73             run($code);
74             }
75             } else {
76             $INPUT = '';
77             run($code);
78             }
79             }
80              
81              
82             ##################################################################
83              
84             # GolfScript stack elements come in four types: int, block, string, and array.
85             # In ruby, classes are used and the type of an element is evident by what
86             # class it is an instance of.
87             # In perl let's do it a little differently. On the stack, we'll identify
88             # element types as follows:
89             # Array: normal perl array reference
90             # String: scalar that begins with "
91             # Block: scalar that begins with {
92             # Integer: any other scalar
93              
94             sub is {
95 54     54 1 223 my $element = shift;
96             return
97 54 100       95 is_array($element) ? 'array' :
    100          
    100          
98             is_string($element) ? 'string' :
99             is_block($element) ? 'block' : 'number';
100             }
101              
102             # GolfScript type manipulation routines:
103             # is_XXX: true if element from stack is of type XXX
104             # to_XXX: convert a raw scalar so it is suitable to put on the stack
105             # get_XXX: convert element from stack of type XXX to raw, usable scalar
106              
107 2456     2456 0 9259 sub is_array { return ref $_[0] eq 'ARRAY' }
108 1     1 0 56 sub to_array { return $_[0] }
109 0     0 0 0 sub get_array { return $_[0] }
110              
111 2112     2112 0 18281 sub is_string { return $_[0] =~ /^\"/ }
112 166     166 0 722 sub to_string { return "\"" . $_[0] }
113 179     179 0 638 sub get_string { return substr($_[0], 1) }
114              
115 1974   100 1974 0 29429 sub is_number { return ref $_[0] ne 'ARRAY' && $_[0] !~ /^["{]/ } # ... }"]/;}
116 1299     1299 0 33403 sub to_number { return $_[0] }
117 1852     1852 0 5136 sub get_number { return $_[0] }
118              
119 2072     2072 0 25047 sub is_block { return $_[0] =~ /^\{/ }
120 68     68 0 261 sub to_block { return "{" . $_[0] }
121 69     69 0 255 sub get_block { return substr($_[0], 1) }
122              
123             #############################################################################
124              
125             sub gspush {
126 1649 100 100 1649 0 16434 if (@_ == 1 && !defined $_[0]) {
127 1 50       4 if ($DEBUG) {
128 0         0 print STDERR "push [", scalar @STACK,
129             "]: called for undefined element!\n";
130             }
131 1         6 return;
132             }
133 1648         2880 foreach (@_) {
134 1730 50       3063 if ($DEBUG) {
135 0         0 print STDERR "push [", scalar @STACK, "]: ", display_element($_), "\n";
136             }
137 1730         4029 push @STACK, $_;
138             }
139 1648 50       3354 display_stack() if $DEBUG>1;
140 1648         7156 return;
141             }
142              
143             sub gspop {
144 1019     1019 0 1451 my $sz = @STACK;
145 1019         2665 for (my $i=$#LB; $i>=0; $i--) {
146 29 100       72 last if $LB[$i] < $sz;
147 13         20 $LB[$i]--;
148 13 50       43 if ($DEBUG>1) {
149 0         0 print STDERR "!\$LB[$i] reduced to $LB[$i]\n";
150             }
151             }
152              
153 1019 100       1645 if ($sz > 0) {
    50          
154 1018         1365 my $elem = pop @STACK;
155 1018 50       2715 if ($DEBUG) {
156 0         0 print STDERR "pop [", scalar @STACK, "]: ", display_element($elem),"\n";
157             }
158 1018 50       1848 display_stack() if $DEBUG > 1;
159 1018         2113 return $elem;
160             } elsif ($DEBUG) {
161 0         0 print STDERR "pop on empty stack!\n";
162 0 0       0 display_stack() if $DEBUG > 1;
163             }
164 1         3 return;
165             }
166              
167             sub gssplice {
168 132     132 0 189 my ($lb) = @_;
169 132         385 my @c = splice @STACK, $lb;
170 132 50       290 if ($DEBUG) {
171 0         0 print STDERR "splice from [$lb] ==> ", display_element(\@c), "\n";
172             }
173 132         405 return @c;
174             }
175              
176             sub gscroak {
177 1 50   1 0 22 display_stack() if $DEBUG;
178 1 50       4 if ($DEBUG) {
179 0         0 Carp::confess(@_);
180             } else {
181 1         227 croak @_;
182             }
183             }
184              
185             # estimate character count of GolfScript code snippet,
186             # after removing comments and whitespace that looks unnecessary
187             sub _code_length {
188 0     0   0 my $code = shift;
189 0         0 my $stripped = '';
190 0         0 my @tokens = tokenize($code);
191              
192 0         0 while (defined (my $token = shift @tokens)) {
193              
194 0 0       0 if ($token =~ /^\#/) {
195 0         0 while (@tokens) {
196 0         0 my $next_token = shift @tokens;
197 0 0       0 last if substr($next_token,-1) eq "\n";
198             }
199             # after a comment, strip leading whitespace on next line
200 0   0     0 while (@tokens && $tokens[0] !~ /\S/) {
201 0         0 shift @tokens;
202             }
203 0         0 next;
204             }
205              
206 0 0       0 if ($token !~ /\S/) {
207             # remove whitespace before a comment
208 0   0     0 $token .= shift @tokens while @tokens && $tokens[0] !~ /\S/;
209 0 0 0     0 next if @tokens && $tokens[0] =~ /^\#/;
210             }
211              
212 0         0 $stripped .= $token;
213             }
214 0 0       0 if (length($stripped) < length($code)) {
215              
216 0         0 print STDERR "-----------------------------\n";
217 0         0 print STDERR "stripped code:\n\n";
218 0         0 print STDERR $stripped, "\n---------------------------------\n";
219              
220             }
221 0         0 return length($stripped);
222             }
223              
224             sub ___ {
225             # for $] < 5.12 without ... operator
226 0     0   0 Carp::cluck "Unimplemented method.\n";
227             }
228              
229             # display a stack element in easily readable form.
230             # This is a debugging method and is NOT used for any transformation
231             # within the implementation or to produce the final output.
232             sub display_element {
233 586     586 0 789 my $a = shift;
234 586         769 my $output = "";
235 586 100       1169 if (is_array($a)) {
    100          
    100          
    50          
236 73         135 $output = "[ ";
237 73         239 $output .= display_element($_) . " " foreach @$a;
238 73         122 $output .= "]";
239             } elsif (is_block($a)) {
240 21         51 $output .= "{" . get_block($a) . "}";
241             } elsif (is_string($a)) {
242 79         256 $output .= "\"" . get_string($a) . "\"";
243             } elsif (is_number($a)) {
244 413         6007 $output .= get_number($a);
245             }
246 586         11776 return $output;
247             }
248              
249             sub display_stack {
250 0 0   0 0 0 print STDERR "\n" if $DEBUG > 1;
251 0         0 foreach (0..$#STACK) {
252 0         0 print STDERR "STACK[$_]: ", display_element($STACK[$_]), "\n";
253             }
254 0         0 print STDERR "\n";
255             }
256              
257             sub _init_builtins {
258             %DISPATCH =
259             (
260             '~' => \&tilde_operator,
261             '`' => \&backquote_operator,
262             '!' => \&exclamation_operator,
263             '@' => \&at_operator,
264             "\$" => \&dollar_operator,
265             '+' => \&plus_operator,
266             '-' => \&minus_operator,
267             '*' => \&star_operator,
268             '/' => \&slash_operator,
269             '%' => \&percent_operator,
270             '|' => \&pipe_operator,
271             '&' => \&ersand_operator,
272             '^' => \&caret_operator,
273             '\\' => \&backslash_operator,
274 8     8   15 ';' => sub { gspop() },
275             '<' => \&less_than_operator,
276             '>' => \&greater_than_operator,
277             '=' => \&equal_operator,
278             ',' => \&comma_operator,
279             '.' => \&dot_operator,
280             '?' => \&question_operator,
281             '(' => \&open_paren_operator,
282             ')' => \&close_paren_operator,
283             '[' => sub {
284 129     129   200 my $sz = @STACK;
285 129 50       269 if ($DEBUG) { print STDERR " Open bracket sz=$sz\n"; }
  0         0  
286 129         475 push @LB, $sz
287             },
288             ']' => sub {
289 129   100 129   438 my $sz = pop @LB || 0;
290 129         350 my @c = gssplice $sz;
291 129 50       275 if ($DEBUG) { print STDERR " Close bracket sz0=$sz [ @c ]\n"; }
  0         0  
292 129         359 gspush [ @c ]
293             },
294 6     6   19 'and' => sub { evaluate('1$if') },
295 8     8   17 'or' => sub { evaluate('1$\\if') },
296             'xor' => \&xor_function,
297             'if' => \&if_function,
298             'print' => \&print_function,
299 0     0   0 'p' => sub { evaluate('`puts') },
300 0     0   0 'n' => sub { evaluate('"\n"') },
301 0     0   0 'puts' => sub { evaluate("print n print") },
302 315     315   17509 'rand' => \&rand_function,
303             'do' => \&do_function,
304             'while' => \&while_function,
305             'until' => \&until_function,
306             'abs' => \&abs_function, # should be a way to express this in GS.
307             'zip' => \&zip_function,
308             'base' => \&base_function,
309             );
310             }
311              
312             # initialize $INPUT and @STACK
313             sub _load_input_onto_stack {
314 0 0   0   0 if ($INPUT eq '__NOT_INITIALIZED__') {
315 0 0       0 if (-t STDIN) {
316 0         0 $INPUT = '';
317             } else {
318 0         0 local $/ = undef;
319 0         0 $INPUT = ;
320             }
321             }
322 0         0 @STACK = (to_string($INPUT));
323 0 0       0 print STDERR "Stack initialized: $INPUT\n" if $DEBUG;
324             }
325              
326             sub test {
327 315     315 1 103195 my ($code, $optional_input) = @_;
328              
329 315         854 local @STACK = ();
330 315   100     1410 local $INPUT = $optional_input || '';
331 315         891 _init_builtins();
332              
333 315 50       3731 alarm $TIMEOUT if $TIMEOUT > 0;
334 315         643 evaluate($code);
335 314         1500 alarm 0;
336              
337 314         636 return $TEST_OUTPUT = join '', map { display_element($_) } @STACK;
  353         717  
338             }
339              
340             sub run {
341 0     0 1 0 my $code = shift;
342 0   0     0 my $mode = shift || 'normal';
343 0         0 _load_input_onto_stack();
344 0         0 _init_builtins();
345              
346 0 0       0 if ($COUNT) {
347 0 0       0 if ($Math::BigInt::VERSION) {
348 0         0 print STDERR "Math::BigInt::GMP library in use version $Math::BigInt::VERSION\n";
349             } else {
350 0         0 print STDERR "Math::BigInt::GMP library not loaded\n";
351             }
352             }
353 0         0 my $start_time = Time::HiRes::gettimeofday();
354              
355 0 0       0 alarm $TIMEOUT if $TIMEOUT > 0;
356 0         0 evaluate($code);
357 0         0 alarm 0;
358 0         0 my @output = @STACK;
359 0 0       0 if ($mode eq 'test') {
360 0         0 return @output;
361             }
362 0         0 gsoutput();
363              
364 0 0       0 if ($COUNT) {
365 0         0 my $elapsed_time = Time::HiRes::gettimeofday() - $start_time;
366 0         0 print "Character count: ", _code_length($code), "\n";
367 0         0 printf "Run time: %.3fs\n", $elapsed_time;
368             }
369             }
370              
371             # called at end of program. Outputs stack to STDOUT.
372             sub gsoutput {
373 0     0 0 0 my @output = @STACK;
374             {
375 0         0 local @STACK = ( to_array(\@output) );
  0         0  
376 0         0 evaluate("puts");
377             }
378             }
379              
380             # evaluate a block of GolfScript
381             sub evaluate {
382 478     478 1 742 my $input = shift;
383 478         721 my (@block_stack, $active_block) = ();
384              
385 478 50       1037 print STDERR "Evaluating: $input\n" if $DEBUG;
386              
387 478         878 my @tokens = tokenize($input);
388 478         1520 while (defined (my $token = shift @tokens)) {
389 2204 0 0     4061 if ($DEBUG && (0 || $DEBUG>2 || $token =~ /\S/)) {
      33        
390 0         0 print STDERR " Parsing: \"$token\"\n";
391             }
392 2204 100       10351 if ($token eq '#') { # comment
    100          
    100          
    100          
    100          
    100          
393 2         5 my $comment = $token;
394 2   66     5 do {
395 5         7 my $token2 = shift @tokens;
396 5         38 $comment .= $token2;
397             } while (@tokens > 0 && substr($comment,-1) ne "\n");
398 2 50 33     12 if ($DEBUG && $comment !~ /^#;/) {
399 0         0 chomp $comment;
400 0         0 print STDERR " Comment: $comment\n";
401             }
402             } elsif ($token eq '}') { # end block
403 50         74 my $finished_block = $active_block;
404 50         93 $active_block = pop @block_stack;
405 50 50       101 if (defined $active_block) {
406 0         0 $active_block .= "{" . $finished_block . "}";
407             } else {
408 50         118 gspush to_block($finished_block);
409             }
410             } elsif ($token eq '{') {
411 50 50       128 if (defined $active_block) {
412 0         0 push @block_stack, $active_block;
413             }
414 50         174 $active_block = "";
415             } elsif (defined $active_block) {
416 96         273 $active_block .= $token;
417             } elsif ($token eq ':') { # assign.
418 7         9 my $var_name = shift @tokens;
419 7         12 my $element = $STACK[-1];
420 7 50       19 if ($DEBUG) {
421 0         0 print STDERR " __ASSIGN__: {$var_name} <= sub { gspush ",
422             display_element($element), " }\n";
423             }
424 7 100       15 if (is_block($element)) {
425 2         4 my $block = get_block($element);
426 2     4   13 $DISPATCH{$var_name} = sub { evaluate($block) };
  4         20  
427             } else {
428 5     5   190 $DISPATCH{$var_name} = sub { gspush $element };
  5         9  
429             }
430             } elsif (defined $DISPATCH{$token}) {
431 876         1815 $DISPATCH{$token}->();
432             } else {
433 1123         1907 my $element = parse_token($token);
434 1122 100       3189 if (defined $element) {
435 788         1369 gspush $element;
436             }
437             }
438             }
439             }
440              
441             # interpret a raw token as a string, number, or "variable"
442             sub parse_token {
443 1123     1123 0 1812 my $token = shift;
444 1123 100 66     7308 if ($token =~ /^'/ && $token =~ /'$/) {
    100 66        
    100          
    50          
445 109         204 chop $token;
446 109         330 return to_string( unescaped_string(substr($token,1)) );
447             } elsif ($token =~ /^"/ && $token =~ /"$/) {
448 7         16 chop $token;
449 7         25 return to_string( escaped_string(substr($token,1)) );
450             } elsif ($token =~ /^-?\d+$/) {
451 673         2631 return to_number(Math::BigInt->new($token));
452             } elsif ($token eq " ") {
453 334         552 return;
454             } else {
455             # carp "unparsed token: ", ord($token), " $token\n";
456 0         0 return;
457             }
458             }
459              
460             sub unescaped_string {
461 109     109 0 250 my $input = shift;
462 109         202 $input =~ s/\\(['\\])/$1/g;
463 109         330 return $input;
464             }
465              
466             # unimplemented escapes that Perl recognizes -- not sure
467             # how/if these translate to ruby's interpolated string eval.
468             # c u x
469             # \L..\E, \U..\E, \Q..\E
470             # \N{}
471             my %escapes = ( a => "\a", b => "\b", e => "\e", f => "\f",
472             l => "\l", n => "\n", r => "\r", t => "\t",
473             E => "\e",
474             "\\" => "\\", "'" => "'", '"' => "\"", );
475             sub escaped_string {
476 7     7 0 19 my $input = shift;
477 7         12 my $output = "";
478 7         45 my @chars = split //, $input;
479 7         23 while (@chars) {
480 50         78 my $char = shift @chars;
481              
482 50 100 66     205 if ($char eq '#' && $chars[0] eq '{') {
483             # parse #{expr} expression
484             # allows single quoted strings and nested { }s
485 2         6 my @stack = ("}");
486 2         5 my $e = "";
487 2         3 shift @chars;
488 2   33     15 while (@stack && @chars) {
489 22         34 $char = shift @chars;
490 22 100 33     100 if ($char eq $stack[-1]) {
    50          
    50          
    50          
491 2         7 pop @stack;
492 2 50       9 last if @stack == 0;
493             } elsif ($char eq "\\") {
494 0         0 $char .= shift @chars;
495             } elsif ($char eq "'") {
496 0         0 push @stack, $char;
497             } elsif ($char eq "{" && $stack[-1] eq "}") {
498 0         0 push @stack, "}";
499             }
500 20         80 $e .= $char;
501             }
502 2 50       7 if ($DEBUG) {
503 0         0 print STDERR "Evaluating #{$e}\n";
504             }
505 2         256 $output .= eval $e;
506 2 100       11 if ($@) {
507 1         8 gscroak "Eval error string \"$e\": $@\n";
508             }
509 1         5 next;
510             }
511              
512 48 100       106 if ($char ne "\\") {
513 43         48 $output .= $char;
514 43         90 next;
515             }
516 5 50       15 if (@chars == 0) {
517 0         0 $output .= "\\"; # this probably can't happen.
518 0         0 next;
519             }
520 5         7 my $escaped_char = shift @chars;
521 5 100       17 if (defined $escapes{$escaped_char}) {
522 2         5 $output .= $escapes{$escaped_char};
523 2         7 next;
524             }
525              
526 3 50       11 if ($escaped_char =~ /[ceux]/) {
527             # ... not implemented
528             }
529              
530 3 50       13 if ($escaped_char !~ /[0-7]/) {
531 0         0 $output .= $escaped_char;
532 0         0 next;
533             }
534 3         5 my $octal = $escaped_char;
535 3 50 33     21 if (@chars && $chars[0] =~ /[0-7]/) {
536 3         9 $octal = 8 * $octal + (shift @chars);
537 3 50       13 if ($chars[0] =~ /[0-7]/) {
538 3         6 $octal = 8 * $octal + (shift @chars);
539             }
540             }
541 3         12 $output .= chr($octal);
542             }
543 6         26 return $output;
544             }
545              
546             # tokenize an arbitrary string of GolfScript
547             sub tokenize {
548 478     478 0 619 my $input = shift;
549 478         5014 my @tokens = grep { length }
  2694         4403  
550             $input =~ /( # capture
551             [a-zA-Z_][a-zA-Z0-9_]* | # alphanumeric-string
552             '(?:\\.|[^'])*'? | # single-quoted-string
553             "(?:\\.|[^"])*"? | # double-quoted-string
554             -?[0-9]+ | # integer
555             . | $ # any single character including newline
556             )/mgsx;
557 478         2172 return @tokens;
558             }
559              
560             ##################################################################
561              
562             sub tilde_operator {
563 5     5 0 11 my $a = gspop();
564 5 100       12 if (is_array($a)) { # DUMP
    100          
    100          
    50          
565 1         5 gspush @$a;
566             } elsif (is_string($a)) { # EVALUATE STRING
567 1         4 evaluate(get_string($a));
568             } elsif (is_block($a)) { # EVALUATE BLOCK
569 1         17 $a = get_block($a);
570 1 50       4 if ($DEBUG > 1) { print STDERR "Evaluate block: {{ $a }}\n"; }
  0         0  
571 1         3 evaluate($a);
572             } elsif (is_number($a)) { # BITWISE NOT
573 2         55 gspush to_number(_bitwise_not(get_number($a)));
574             }
575             }
576              
577             sub backquote_operator {
578 8     8 0 16 my $a = gspop();
579 8 100       20 if (is_array($a)) {
    100          
    50          
    50          
580 3         13 gspush to_string( _de_evaluate_array($a) );
581             } elsif (is_string($a)) {
582 3         9 gspush to_string( _de_evaluate_string(get_string($a)) );
583             } elsif (is_block($a)) {
584 0         0 gspush to_string( _de_evaluate_block(get_block($a)) );
585             } elsif (is_number($a)) {
586 2         63 gspush to_string( _de_evaluate_number(get_number($a)) );
587             }
588             }
589              
590             sub exclamation_operator {
591 26     26 0 187 my $a = gspop();
592 26         80 gspush to_number(is_false($a));
593             }
594              
595             sub at_operator { # ROTATE3
596 26     26 0 34 my $d = $DEBUG;
597 26         31 $DEBUG = 0;
598 26         41 my $a = gspop();
599 26         47 my $b = gspop();
600 26         42 my $c = gspop();
601 26         46 gspush $b;
602 26         63 gspush $a;
603 26         28 $DEBUG = $d;
604 26         46 gspush $c;
605             }
606              
607             sub by_golfscript_order {
608 11     11 0 22 my $d = _element_compare($a,$b);
609 11 50       186 if ($DEBUG>1) {
610 0         0 print STDERR "_element_compare ",
611             display_element($a),
612             " <=> ",
613             display_element($b),
614             " ==> $d\n";
615             }
616 11         27 return $d;
617             }
618              
619             sub dollar_operator {
620 22     22 0 44 my $aa = gspop();
621 22 100       60 if (is_array($aa)) {
    100          
    100          
    50          
622 3         30 $aa = [ sort by_golfscript_order @$aa ];
623 3         10 gspush $aa;
624             } elsif (is_string($aa)) {
625 1         4 $aa = _sort_string( get_string($aa) );
626 1         4 gspush to_string($aa);
627             } elsif (is_block($aa)) { # SORT BY FUNCTION IN BLOCK
628 2         7 $aa = get_block($aa);
629 2         6 my $bb = gspop();
630 2 100       5 if (is_array($bb)) {
    50          
631 1         6 gspush _sort_array_by_function($bb, $aa);
632             } elsif (is_string($bb)) {
633 1         12 $bb = get_string($bb);
634 1         5 gspush to_string( _sort_string_by_function($bb, $aa) );
635             } else {
636 0         0 &___;
637             }
638             } elsif (is_number($aa)) {
639 16         415 $aa = -get_number($aa) - 1;
640 16         3034 gspush _copy_element($STACK[$aa]);
641             }
642             }
643              
644             sub _copy_element {
645 152     152   464 my $element = shift;
646 152 100       265 if (is_array($element)) {
647 13         29 my @new_array = map { _copy_element($_) } @$element;
  26         40  
648 13         29 return \@new_array;
649             } else {
650 139         321 return $element;
651             }
652             }
653              
654             sub plus_operator {
655 62     62 0 120 my $a = gspop();
656 62 100       140 if (is_array($a)) {
    100          
    100          
    50          
657 5         11 my $b = gspop();
658 5 100       9 if (is_array($b)) {
    100          
    100          
    50          
659 2         6 $a = [ @$b, @$a ];
660 2         8 gspush $a;
661             } elsif (is_string($b)) {
662 1         5 my @b = _coerce_string_to_array(get_string($b));
663 1         4 $a = [ @b, @$a ];
664 1         3 $a = _coerce_array_to_string($a);
665 1         7 gspush to_string($a);
666             } elsif (is_block($b)) { # BLOCK ARRAY CONCAT
667 1         3 $a = _concat_block_array(get_block($b), $a);
668 1         4 gspush to_block($a);
669             } elsif (is_number($b)) { # INTEGER ARRAY CONCAT
670 1         51 $a = [ $b, @$a ];
671 1         4 gspush $a;
672             }
673             } elsif (is_block($a)) {
674 5         10 my $b = gspop();
675 5 100       19 if (is_array($b)) { # ARRAY BLOCK CONCAT
    100          
    100          
    50          
676 2         5 $a = _concat_array_block($b, get_block($a));
677 2         6 gspush to_block($a);
678             } elsif (is_block($b)) { # BLOCK BLOCK CONCAT
679 1         3 $b = get_block($b) . " " . get_block($a);
680 1         22 gspush to_block($b);
681             } elsif (is_string($b)) { # STRING BLOCK CONCAT
682 1         3 $b = get_string($b) . " " . get_block($a);
683 1         4 gspush to_block($b);
684             } elsif (is_number($b)) { # INTEGER BLOCK CONCAT
685 1         24 $b = get_number($b) . " " . get_block($a);
686 1         4 gspush to_block($b);
687             }
688             } elsif (is_string($a)) {
689 7         14 my $b = gspop();
690 7 100       17 if (is_array($b)) { # STRING CONCAT WITH ARRAY-TO-STRING CONVERSION
    100          
    100          
    50          
691 2         7 $b = _coerce_array_to_string($b);
692 2         9 gspush to_string($b . get_string($a));
693             } elsif (is_string($b)) { # STRING STRING CONCAT
694 3         8 gspush to_string(get_string($b) . get_string($a));
695             } elsif (is_block($b)) { # BLOCK STRING CONCAT
696 1         11 gspush to_block(get_block($b) . get_string($a));
697             } elsif (is_number($b)) { # INTEGER STRING CONCAT
698 1         28 gspush to_string(get_number($b) . get_string($a));
699             }
700             } elsif (is_number($a)) {
701 45         1333 $b = gspop();
702 45 100       114 if (is_array($b)) {
    100          
    100          
    50          
703 1         3 push @$b, $a;
704 1         3 gspush $b;
705             } elsif (is_string($b)) { # CONCATENATE NUMBER TO STRING
706 1         5 gspush to_string(get_string($b) . get_number($a));
707             } elsif (is_block($b)) { # CONCATENATE NUMBER TO BLOCK
708 1         5 gspush to_block(get_block($b) . get_number($a));
709             } elsif (is_number($b)) { # ADD
710 42         740 $a = bigi::add(get_number($a),get_number($b));
711 42         3186 gspush to_number($a);
712             }
713             }
714             }
715              
716             sub minus_operator {
717 23     23 0 41 my $a = gspop();
718 23         45 my $b = gspop();
719            
720 23 100       50 if (is_array($a)) {
    100          
    100          
    50          
721 4 100       8 if (is_array($b)) {
    100          
    100          
    50          
722 1         4 gspush _array_array_difference($b,$a);
723             } elsif (is_string($b)) {
724 1         5 gspush _string_array_difference($b,$a);
725             } elsif (is_block($b)) {
726 1         4 gspush _block_array_difference($b,$a);
727             } elsif (is_number($b)) {
728 1         29 gspush _array_array_difference( [$b], $a );
729             }
730             } elsif (is_string($a)) {
731 4 100       9 if (is_array($b)) {
    100          
    100          
    50          
732 1         5 $b = _coerce_array_to_string($b);
733 1         6 gspush to_string( _string_string_difference($b, get_string($a)) );
734             } elsif (is_string($b)) {
735 1         4 gspush to_string( _string_string_difference(get_string($b), get_string($a)) );
736             } elsif (is_block($b)) {
737 1         3 gspush to_block( _block_string_difference(get_block($b), get_string($a)) );
738             } elsif (is_number($b)) {
739 1         21 gspush to_string( _string_string_difference(get_number($b), get_string($a)) );
740             }
741             } elsif (is_block($a)) {
742 4 100       8 if (is_array($b)) {
    100          
    100          
    50          
743 1         3 $b = _coerce_array_to_string($b);
744 1         6 gspush to_block( _string_string_difference($b, get_block($a)));
745             } elsif (is_string($b)) {
746 1         7 gspush to_block( _string_block_difference(get_string($b), get_block($a)) );
747             } elsif (is_block($b)) {
748 1         4 gspush to_block( _block_block_difference(get_block($b), get_block($a)) );
749             } elsif (is_number($b)) {
750 1         26 gspush to_block( _string_string_difference(get_number($b), get_block($a)) );
751             }
752             } elsif (is_number($a)) {
753 11 100       242 if (is_array($b)) {
    100          
    100          
    50          
754 1         6 gspush _array_array_difference($b, [$a]);
755             } elsif (is_block($b)) {
756 1         3 gspush to_block( _block_block_difference(get_block($b),get_number($a)) );
757             } elsif (is_string($b)) {
758 1         3 gspush to_string( _string_string_difference(get_string($b),
759             get_number($a)) );
760             } elsif (is_number($b)) { # SUBTRACT
761 8         160 $a = bigi::sub(get_number($b), get_number($a));
762 8         920 gspush to_number($a);
763             }
764             }
765             }
766              
767              
768             sub star_operator {
769 35     35 0 67 my $a = gspop();
770 35 100       73 if (is_array($a)) {
    100          
    100          
    50          
771 3         6 my $b = gspop();
772 3 100       6 if (is_array($b)) {
    100          
    50          
    50          
773 1         5 gspush _array_join($b,$a);
774             } elsif (is_string($b)) {
775 1         3 gspush to_string( _array_string_join($a,get_string($b)) );
776             } elsif (is_block($b)) {
777 0         0 &___;
778             } elsif (is_number($b)) {
779 1         20 gspush _repeat_array($a, get_number($b));
780             }
781             } elsif (is_block($a)) {
782 2         6 $a = get_block($a);
783 2         5 my $b = gspop();
784 2 100       4 if (is_array($b)) {
    50          
    0          
    0          
785 1         3 _fold_array($b,$a);
786             } elsif (is_string($b)) {
787 1         4 $b = [ _coerce_string_to_array( get_string($b) ) ];
788 1         6 _fold_array($b,$a);
789             } elsif (is_block($b)) {
790 0         0 &___;
791             } elsif (is_number($b)) { # REPEAT
792 0         0 for my $n (1 .. get_number($b)) {
793 0         0 evaluate($a);
794             }
795             }
796             } elsif (is_string($a)) { # JOIN
797 4         8 my $b = gspop();
798 4 100       7 if (is_array($b)) {
    50          
    100          
    50          
799 2         6 gspush to_string( _array_string_join($b, get_string($a)) );
800             } elsif (is_block($b)) {
801 0         0 &___;
802             } elsif (is_string($b)) {
803 1         2 my @chars = split //, get_string($b);
804 1         2 my $joined_string = join get_string($a), @chars;
805 1         3 gspush to_string( $joined_string );
806             } elsif (is_number($b)) { # STRING MULTIPLY
807 1         21 gspush to_string( get_string($a) x get_number($b) );
808             }
809             } elsif (is_number($a)) {
810 26         576 my $b = gspop();
811 26 100       48 if (is_array($b)) { # REPEAT ARRAY
    100          
    100          
    50          
812 1         4 gspush _repeat_array($b, get_number($a));
813             } elsif (is_block($b)) { # REPEAT BLOCK
814 2         22 $b = get_block($b);
815 2         6 for my $n (1 .. get_number($a)) {
816 8         151 evaluate($b);
817             }
818             } elsif (is_string($b)) { # REPEAT STRING
819 1         3 $a = to_string(get_string($b) x get_number($a));
820 1         4 gspush $a;
821             } elsif (is_number($b)) { # MULTIPLY
822 22         435 gspush to_number( bigi::mult(get_number($a),get_number($b)) );
823             }
824             }
825             }
826              
827             sub slash_operator {
828 10     10 0 23 my $a = gspop();
829 10 100       28 if (is_array($a)) { # ARRAY-SPLIT
    100          
    100          
    50          
830 1         2 my $b = gspop();
831 1 50 0     3 if (is_array($b)) {
    0 0        
832 1         6 gspush _split_array($b,$a);
833             } elsif (is_string($b) || is_block($b) || is_number($b)) {
834 0         0 &___
835             }
836             } elsif (is_string($a)) { # STRING SPLIT
837 2         7 my $b = gspop();
838 2 50       5 if (is_array($b)) { # ARRAY-TO-STRING CONVERSION + STRING SPLIT
    50          
    0          
    0          
839 0         0 &___
840             } elsif (is_string($b)) { # STRING SPLIT
841 2         5 $a = get_string($a);
842 2         37 my @c = map { to_string($_) } split $a, get_string($b);
  7         10  
843 2 100       25 push @c, to_string("") if $b =~ /.$a$/; # difference between perl/ruby split?
844 2         8 gspush [ @c ];
845             } elsif (is_block($b)) { # BLOCK-STRING SPLIT
846 0         0 &___
847             } elsif (is_number($b)) { # COERCE INT TO STRING AND SPLIT
848 0         0 $b = get_number($b);
849 0         0 my @c = map { to_string($_) } split $a, $b;
  0         0  
850 0         0 gspush [ @c ];
851             }
852             } elsif (is_block($a)) {
853 5         14 my $b = gspop();
854 5 100       15 if (is_array($b)) { # EACH
    50          
    0          
    0          
855 3         10 my $block = get_block($a);
856 3         9 foreach my $elem (@$b) {
857 11         24 gspush $elem;
858 11         24 evaluate($block);
859             }
860             } elsif (is_block($b)) { # UNFOLD
861 2         6 my $block = get_block($a);
862 2         7 my $condition = get_block($b);
863 2         29 _unfold($condition,$block);
864             } elsif (is_string($b)) { # EACH, STRING COERCED TO ARRAY, back to string
865              
866 0         0 $b = get_string($b);
867 0         0 my @b = _coerce_string_to_array($b);
868 0         0 my $block = get_block($a);
869 0         0 my @c = ();
870 0         0 foreach my $elem (@b) {
871 0         0 my $lb = scalar @STACK;
872 0         0 gspush $elem;
873 0         0 evaluate($block);
874 0         0 push @c, gssplice $lb;
875             }
876 0 0       0 if (@c) {
877 0         0 gspush to_string( _coerce_array_to_string(\@c) );
878             }
879             } elsif (is_number($b)) {
880 0         0 my $block = get_block($a);
881 0         0 gspush to_number($b);
882 0         0 evaluate($block);
883             }
884             } elsif (is_number($a)) {
885 2         42 my $b = gspop();
886 2 100       7 if (is_array($b)) { # SUBDIVIDE ARRAY
    50          
    50          
    50          
887 1         4 my @c = @$b;
888 1         2 my @d = ();
889 1         5 while (@c > 0) {
890 3         42 push @d, [ splice @c, 0, get_number($a) ];
891             }
892 1         20 gspush [ @d ];
893             } elsif (is_block($b)) {
894 0         0 &___;
895             } elsif (is_string($b)) { # ARRAY OF SUBDIVIDED STRINGS
896 0         0 $b = get_string($b);
897 0         0 $a = get_number($a);
898 0 0       0 if ($a <= 0) { gscroak "negative argument to {string number /}" }
  0         0  
899 0         0 my @d = ();
900 0         0 while (length $b > $a) {
901 0         0 push @d, to_string(substr($b,0,$a));
902 0         0 $b = substr($b,$a);
903             }
904 0 0       0 if (length $b > 0) {
905 0         0 push @d, to_string($b);
906             }
907 0         0 gspush [ @d ];
908             } elsif (is_number($b)) {
909 1         20 my $d = bigi::div(get_number($b),get_number($a));
910 1         126 gspush to_number($d);
911             }
912             }
913             }
914              
915             sub percent_operator {
916 16     16 0 36 my $a = gspop();
917 16 50       33 if (is_array($a)) { # ARRAY-SPLIT, REMOVE EMPTY RESULTS
    100          
    100          
    50          
918 0         0 &___
919             } elsif (is_string($a)) { # SPLIT, REMOVE EMPTY RESULTS
920 1         3 my $b = gspop();
921 1 50       2 if (is_array($b)) {
    50          
    50          
    0          
922 0         0 &___
923             } elsif (is_block($b)) {
924 0         0 &___
925             } elsif (is_string($b)) {
926 1         5 my @c = map { to_string($_) } grep { length }
  2         4  
  3         5  
927             split get_string($a), get_string($b);
928 1         4 gspush [ @c ];
929             } elsif (is_number($b)) { # coerce int to string and split ???
930 0         0 &___
931             }
932             } elsif (is_block($a)) { # MAP
933 1         3 my $block = get_block($a);
934 1         3 my $b = gspop();
935 1         1 my $is_string = 0;
936 1 50       11 if (is_string($b)) {
937 0         0 $b = [ _coerce_string_to_array(get_string($b)) ];
938 0         0 $is_string = 1;
939             }
940 1 50       3 if (is_array($b)) {
    0          
    0          
941 1         3 my @elems = @$b;
942 1         2 my @c = ();
943              
944 1         2 foreach my $elem (@elems) {
945 3         5 my $lb = scalar @STACK;
946 3         5 gspush $elem;
947 3         7 evaluate($block);
948 3         5 my @result = gssplice $lb;
949              
950 3 50       7 if ($DEBUG > 1) {
951 0         0 print STDERR " map(%) operation. Block {$block}, element ",
952             display_element($elem), " ==> ";
953 0         0 print STDERR " ", display_element($_) foreach @result;
954 0         0 print STDERR "\n";
955             }
956              
957 3         8 push @c, @result;
958             }
959 1         2 if (0 && $is_string) {
960             gspush to_string( _coerce_array_to_string(\@c) );
961             } else {
962 1         3 gspush [ @c ];
963             }
964             } elsif (is_block($b)) {
965 0         0 &___
966             } elsif (is_number($b)) {
967 0         0 gspush to_number($b);
968 0         0 evaluate($block);
969             }
970             } elsif (is_number($a)) {
971 14         301 $a = get_number($a);
972 14         26 my $b = gspop();
973 14 100       27 if (is_array($b)) { # SELECT
    50          
    50          
    50          
974 3 100       9 if ($a < 0) {
975 2         242 @$b = reverse @$b;
976 2         8 $a = -$a;
977             }
978 3         212 my @c = @$b[ grep { $_ % $a == 0 } 0..$#{$b} ];
  17         2511  
  3         8  
979 3         520 gspush [ @c ];
980             } elsif (is_string($b)) {
981 0         0 $b = [ _coerce_string_to_array(get_string($b)) ];
982 0 0       0 if ($a < 0) {
983 0         0 @$b = reverse @$b;
984 0         0 $a = -$a;
985             }
986 0         0 my @c = @$b[ grep { $_ % $a == 0 } 0..$#{$b} ];
  0         0  
  0         0  
987 0         0 $b = _coerce_array_to_string($b);
988 0         0 gspush to_string($b);
989             } elsif (is_block($b)) {
990 0         0 &___
991             } elsif (is_number($b)) { # MODULUS
992 11         34 my $d = bigi::mod(get_number($b),$a);
993 11         995 gspush to_number($d);
994             }
995             }
996             }
997              
998             sub pipe_operator {
999 2     2 0 6 my $a = gspop();
1000 2 100       5 if (is_array($a)) {
    50          
    50          
    50          
1001 1         3 my $b = gspop();
1002 1 50       3 if (is_array($b)) {
1003 1         6 gspush _setwise_or($b,$a);
1004             } else {
1005 0         0 &___;
1006             }
1007             } elsif (is_block($a)) { # SET UNION
1008 0         0 &___
1009             } elsif (is_string($a)) {
1010 0         0 &___
1011             } elsif (is_number($a)) {
1012 1         23 my $b = gspop();
1013 1 50       3 if (is_number($b)) {
1014 1         22 gspush to_number( bigi::bitwise_or(get_number($a),get_number($b)) );
1015             } else {
1016 0         0 &___;
1017             }
1018             }
1019             }
1020              
1021             sub ampersand_operator {
1022 3     3 0 20 my $a = gspop();
1023 3         9 my $b = gspop();
1024              
1025 3         11 __coerce($b,$a);
1026              
1027 3 100       17 if (is_number($a)) {
    50          
    0          
    0          
1028 1         23 gspush to_number(bigi::bitwise_and(get_number($a),get_number($b)));
1029             } elsif (is_array($a)) {
1030 2         7 gspush _setwise_and($b,$a);
1031             } elsif (is_string($a)) {
1032 0         0 $a = [_coerce_string_to_array(get_string($a))];
1033 0         0 $b = [_coerce_string_to_array(get_string($b))];
1034 0         0 $b = _setwise_and($b,$a);
1035 0         0 gspush to_string(_coerce_array_to_string($b));
1036             } elsif (is_block($a)) {
1037 0         0 $a = _coerce_string_to_array(get_block($a));
1038 0         0 $b = _coerce_string_to_array(get_block($b));
1039 0         0 $b = _setwise_and($b,$a);
1040 0         0 gspush to_block(_coerce_array_to_string($b));
1041             }
1042             }
1043              
1044             sub ampersand_operator_V1 {
1045 0     0 0 0 my $a = gspop();
1046 0 0       0 if (is_array($a)) {
    0          
    0          
    0          
1047 0         0 my $b = gspop();
1048 0 0       0 if (is_array($b)) {
1049 0         0 gspush _setwise_and($b,$a);
1050             } else {
1051 0         0 &___;
1052             }
1053             } elsif (is_block($a)) {
1054 0         0 &___;
1055             } elsif (is_string($a)) {
1056 0         0 &___
1057             } elsif (is_number($a)) {
1058 0         0 my $b = gspop();
1059 0 0 0     0 if (is_array($b) || is_block($b)) {
    0          
    0          
1060 0         0 &___;
1061             } elsif (is_string($b)) {
1062 0         0 &___;
1063             } elsif (is_number($b)) {
1064 0         0 gspush to_number( bigi::bitwise_and(get_number($a), # BITWISE AND
1065             get_number($b)) );
1066             }
1067             }
1068             }
1069              
1070             sub caret_operator {
1071 13     13 0 29 my $a = gspop();
1072 13 100       32 if (is_array($a)) {
    50          
    100          
    50          
1073 4         12 my $b = gspop();
1074 4 100       11 if (is_array($b)) {
    100          
    50          
1075 2         8 gspush _setwise_symmetric_difference($b,$a);
1076             } elsif (is_string($b)) {
1077 1         5 $a = _coerce_array_to_string($a);
1078 1         7 gspush to_string(_setwise_symmetric_string_difference(get_string($b),
1079             $a) );
1080             } elsif (is_block($b)) {
1081 1         6 $a = _coerce_array_to_block($a);
1082 1         11 gspush to_block( _setwise_symmetric_string_difference(get_block($b),
1083             $a) );
1084             } else {
1085 0         0 &___;
1086             }
1087             } elsif (is_block($a)) {
1088 0         0 my $b = gspop();
1089 0 0       0 if (is_array($b)) {
1090 0         0 $b = _coerce_array_to_block($b);
1091 0         0 gspush to_block(_setwise_symmetric_string_difference($b, get_block($a)));
1092             } else {
1093 0         0 &___
1094             }
1095             } elsif (is_string($a)) {
1096 2         8 my $b = gspop();
1097 2 100       9 if (is_array($b)) {
    50          
    0          
1098 1         4 $b = _coerce_array_to_string($b);
1099 1         7 gspush to_string(_setwise_symmetric_string_difference($b,get_string($a)));
1100             } elsif (is_string($b)) {
1101 1         4 gspush to_string(
1102             _setwise_symmetric_string_difference(get_string($b), get_string($a)));
1103             } elsif (is_block($b)) {
1104 0         0 &___;
1105             } else {
1106 0         0 &___;
1107             }
1108             } elsif (is_number($a)) {
1109 7         149 my $b = gspop();
1110 7 50 33     18 if (is_array($b) || is_block($b)) {
    50          
    50          
1111 0         0 &___;
1112             } elsif (is_string($b)) {
1113 0         0 &___;
1114             } elsif (is_number($b)) {
1115 7         30 my $z = bigi::bitwise_xor( get_number($a), get_number($b) );
1116 7 50       1240 if ($DEBUG > 1) {
1117 0         0 print STDERR " $a ^ $b => $z\n";
1118             }
1119 7         22 gspush to_number($z);
1120             }
1121             }
1122             }
1123              
1124             sub backslash_operator { # SWAP
1125 20     20 0 38 my $a = gspop();
1126 20         39 my $b = gspop();
1127 20         43 gspush $a;
1128 20         31 gspush $b;
1129             }
1130              
1131             sub less_than_operator {
1132 35     35 0 69 my $a = gspop();
1133 35         67 my $b = gspop();
1134              
1135 35 50 66     78 if (is_number($b) && !is_number($a)) {
1136 0         0 ($a,$b) = ($b,$a);
1137             }
1138              
1139 35 100       659 if (is_string($a)) {
1140 2         7 $a = [ _coerce_string_to_array(get_string($a)) ];
1141             }
1142 35 100 33     624 if (is_array($a)) { # COMPARE ARRAY/STRING
    50          
    100          
    50          
    100          
    50          
1143 6 100       17 if (is_string($b)) {
1144 2         5 $b = [ _coerce_string_to_array(get_string($b)) ];
1145             }
1146 6 100       23 gspush _element_compare($b,$a) < 0 ? to_number(1) : to_number(0);
1147             } elsif (is_block($a) || is_string($a)) {
1148 0         0 &___
1149             } elsif (is_array($b)) { # SELECT LT
1150 1         5 $a = get_number($a);
1151 1 50       5 $a += scalar @$b if $a < 0;
1152 1 50       205 if ($a > 0) {
1153 1         183 my @c = @$b[0 .. $a-1];
1154 1         267 gspush [ @c ];
1155             }
1156             } elsif (is_string($b)) {
1157 0         0 gspush to_string( substr( get_string($b), 0,get_number($a) ) );
1158             } elsif (is_block($b)) {
1159 1         5 gspush to_block( substr( get_block($b), 0,get_number($a)) );
1160             } elsif (is_number($b)) {
1161 27 100       564 gspush _element_compare($b,$a) < 0 ? to_number(1) : to_number(0);
1162             }
1163             }
1164              
1165             sub greater_than_operator {
1166 21     21 0 49 my $a = gspop();
1167 21         38 my $b = gspop();
1168 21 100       42 if (is_string($a)) {
1169 2         6 $a = [ _coerce_string_to_array(get_string($a)) ];
1170             }
1171 21 100       360 if (is_array($a)) { # COMPARE ARRAY/STRING
    50          
    100          
    50          
    100          
    50          
1172 6 100       14 if (is_string($b)) {
1173 2         5 $b = [ _coerce_string_to_array(get_string($b)) ];
1174             }
1175 6 100       18 gspush _element_compare($b,$a) > 0 ? to_number(1) : to_number(0);
1176             } elsif (is_block($a)) {
1177 0         0 &___
1178             } elsif (is_array($b)) { # SELECT GT
1179 1         3 $a = get_number($a);
1180 1 50       5 $a += scalar @$b if $a < 0;
1181 1         177 my @c = splice @$b, $a;
1182 1         25 gspush [ @c ];
1183             } elsif (is_string($b)) {
1184 0         0 $b = get_string($b);
1185 0         0 gspush to_string( substr($b,get_number($a)) );
1186             } elsif (is_block($b)) {
1187 1         5 $b = get_block($b);
1188 1         3 gspush to_block( substr($b,get_number($a)) );
1189             } elsif (is_number($b)) {
1190 13 100       296 gspush _element_compare($b,$a) > 0 ? to_number(1) : to_number(0);
1191             }
1192             }
1193              
1194             sub equal_operator {
1195 7     7 0 13 my $a = gspop();
1196 7         12 my $b = gspop();
1197              
1198 7 100 100     16 if (is_number($b) && !is_number($a)) { ($a,$b)=($b,$a) } # XXX "order"
  2         5  
1199              
1200 7 50       70 if (is_string($a)) {
1201 0         0 $a = [ _coerce_string_to_array( get_string($a) ) ];
1202             }
1203 7 50       174 if (is_array($a)) { # COMPARE ARRAY/STRING
    50          
    50          
1204 0 0       0 if (is_string($b)) {
1205 0         0 $b = [ _coerce_string_to_array(get_string($b)) ];
1206             }
1207 0 0       0 gspush _element_compare($a,$b) == 0 ? to_number(1) : to_number(0);
1208             } elsif (is_block($a)) {
1209 0         0 &___
1210             } elsif (is_number($a)) {
1211 7         145 $a = get_number($a);
1212 7 100 66     13 if (is_array($b)) { # SELECT GT
    100          
    50          
1213 3 100       10 $a += scalar @$b if $a < 0;
1214 3         572 gspush $b->[$a];
1215             } elsif (is_string($b) || is_block($b)) {
1216 2         5 $b = [ _coerce_string_to_array( get_string($b) ) ];
1217 2         6 gspush $b->[$a];
1218             } elsif (is_number($b)) {
1219 2 100       55 gspush _element_compare($a,$b) == 0 ? to_number(1) : to_number(0);
1220             }
1221             }
1222             }
1223              
1224             sub comma_operator {
1225 8     8 0 16 my $a = gspop();
1226 8 100       18 if (is_array($a)) { # SIZE OF ARRAY
    100          
    50          
    50          
1227 2         6 gspush to_number(scalar @$a);
1228             } elsif (is_block($a)) { # GREP/MAP
1229 1         4 $a = get_block($a);
1230 1         3 my $b = gspop();
1231 1 50       5 if (!is_array($b)) {
1232 0         0 $b = [ $b ];
1233             }
1234 1         3 my @c = ();
1235 1         4 foreach my $c (@$b) {
1236 10         24 local @STACK = ($c);
1237 10         34 evaluate($a);
1238 10         19 my $d = gspop();
1239 10 100       19 if (is_true($d)) {
1240 4         13 push @c, $c;
1241             }
1242             }
1243             #if (@c > 0) {
1244             # gspush @c;
1245             #}
1246 1         5 gspush [ @c ];
1247             } elsif (is_string($a)) { # SIZE OF STRING/ARRAY
1248 0         0 gspush to_number( length(get_string($a)) );
1249             } elsif (is_number($a)) { # n-ELEMENT INCREASING ARRAY
1250 5         115 gspush [ map { to_number($_) } 0 .. get_number($a)-1 ];
  97         1185  
1251             }
1252             }
1253              
1254             sub dot_operator {
1255 80     80 0 125 my $a = gspop();
1256 80         152 gspush $a, _copy_element($a);
1257             }
1258              
1259             sub question_operator {
1260 13     13 0 25 my $a = gspop();
1261 13         23 my $b = gspop();
1262 13         36 __order($a,$b);
1263              
1264 13 50 66     23 if (is_block($a) && is_block($b)) {
1265 0         0 ($a,$b) = ($b,$a);
1266 0         0 $b = to_string( get_block($b) );
1267             }
1268 13 100       141 if (is_string($a)) {
1269 2         7 $a = [ _coerce_string_to_array(get_string($a)) ];
1270             }
1271              
1272 13 100       139 if (is_block($a)) { # FIND ELEMENT THAT SATISFIES CONDITION
    100          
    50          
1273 2         5 my $block = get_block($a);
1274 2         5 my $is_string = 0;
1275 2 50       5 if (is_string($b)) {
1276 0         0 $b = [ _coerce_string_to_array(get_string($b)) ];
1277 0         0 $is_string = 1;
1278             }
1279 2 50       5 if (is_array($b)) {
    0          
1280 2         4 my $found = undef;
1281 2         27 for (my $i=0; $i < @$b; $i++) {
1282 10         27 local @STACK = ($b->[$i]);
1283 10         17 evaluate($block);
1284 10         22 my $d = gspop();
1285 10 100       24 if (is_true($d)) {
1286 2         4 $found = $b->[$i];
1287 2         6 last;
1288             }
1289             }
1290 2 50       8 if (defined $found) {
1291 2         6 gspush $found;
1292             } # else do nothing to stack
1293 2         17 return;
1294             } elsif (is_number($b)) {
1295 0         0 gscroak "in `question': undefined method `find' for ",
1296             get_number($b), ":Fixnum (NoMethodError)\n";
1297             }
1298             } elsif (is_array($a)) { # FIND ELEMENT IN ARRAY
1299 5 100       11 if (is_string($b)) {
    100          
    50          
1300             # XXX - array-string-? always returns -1 ?
1301 1         5 gspush to_number(-1);
1302             } elsif (is_array($b)) {
1303             # XXX - array-array-? always returns -1 ?
1304 1         5 gspush to_number(-1);
1305             } elsif (is_number($b)) {
1306 3         72 my @c = @$a;
1307 3         6 my $found = -1;
1308 3         10 for (my $i=0; $i<@c; $i++) {
1309 11 100       520 if (_element_compare($c[$i], $b) == 0) {
1310 2         109 $found = $i;
1311 2         4 last;
1312             }
1313             }
1314 3         47 gspush to_number($found);
1315             }
1316             } elsif (is_number($a)) { # EXPONENT FUNCTION
1317 6         136 gspush to_number(bigi::pow(get_number($b),
1318             get_number($a)));
1319             }
1320             }
1321              
1322             sub open_paren_operator {
1323 5     5 0 13 my $a = gspop();
1324 5 100       11 if (is_array($a)) { # UNCONS LEFT
    50          
    100          
    50          
1325 1         3 my $elem = shift @$a;
1326 1         4 gspush $a;
1327 1         3 gspush $elem; # if defined $elem;
1328             } elsif (is_block($a)) {
1329 0         0 &___;
1330             } elsif (is_string($a)) {
1331 2         5 $a = get_string($a);
1332 2         4 $b = substr($a,1);
1333 2         8 $a = substr($a,0,1);
1334 2   100     17 gspush to_string($b || '');
1335 2         6 gspush to_number(ord $a);
1336             } elsif (is_number($a)) { # DECREMENT
1337 2         51 gspush to_number( bigi::add(get_number($a),-1) );
1338             }
1339             }
1340              
1341             sub close_paren_operator {
1342 5     5 0 11 my $a = gspop();
1343 5 100       10 if (is_array($a)) { # UNCONS RIGHT
    50          
    100          
    50          
1344 2         4 my $elem = pop @$a;
1345 2         8 gspush $a;
1346 2 50       8 gspush $elem if defined $elem;
1347             } elsif (is_block($a)) {
1348 0         0 &___;
1349             } elsif (is_string($a)) {
1350 1         4 $a = get_string($a);
1351 1         3 my $b = chop($a);
1352 1         3 gspush to_string($a);
1353 1 50       6 gspush to_number(ord $b) if defined $b;
1354             # gspush to_string($b) if defined $b;
1355             } elsif (is_number($a)) {
1356 2         41 gspush to_number( bigi::add(get_number($a),1) ); # INCREMENT
1357             }
1358             }
1359              
1360             sub xor_function {
1361              
1362             # GolfScript spec says that xor is defined as
1363             #
1364             # {\!!{!}*}
1365             #
1366             # I'm not having as much luck with that description.
1367             #
1368             # {!!\!!+1=} does what I think it should do but is
1369             # sometimes inconsistent with ruby:
1370             # 0 5 xor --> 1 in Perl, 5 in ruby
1371             # 5 0 xor --> 1 in Perl, 1 in ruby
1372             #
1373             # what about {.!\if} ?
1374             # a b xor
1375             # ==> a b .!\if
1376             # ==> a b b !\if
1377             # ==> a b !b \if
1378             # ==> a !b b if
1379             # Yeah, that looks like what ruby does.
1380              
1381 8     8 0 16 evaluate( ".!\\if");
1382             }
1383              
1384             sub if_function {
1385 26     26 0 48 my $if_false_element = gspop();
1386 26         48 my $if_true_element = gspop();
1387 26         42 my $condition = gspop();
1388 26 100       60 if (is_true($condition)) {
1389 12 50       29 if ($DEBUG) {
1390 0         0 print STDERR " $condition is true. Executing $if_true_element\n";
1391             }
1392 12 100       22 if (is_block($if_true_element)) {
1393 1         4 evaluate( get_block($if_true_element) );
1394             } else {
1395 11         94 gspush $if_true_element;
1396             }
1397             } else {
1398 14 50       43 if ($DEBUG) {
1399 0         0 print STDERR " $condition is false. Executing $if_false_element\n";
1400             }
1401 14 100       26 if (is_block($if_false_element)) {
1402 1         4 evaluate( get_block($if_false_element) );
1403             } else {
1404 13         162 gspush $if_false_element;
1405             }
1406             }
1407             }
1408              
1409             sub print_function {
1410 0     0 0 0 my $a = gspop();
1411 0 0       0 if (is_array($a)) {
    0          
    0          
    0          
1412 0         0 foreach my $element (@$a) {
1413 0         0 gspush $element;
1414 0         0 print_function();
1415             }
1416             } elsif (is_block($a)) {
1417 0         0 print STDOUT "$a}";
1418             } elsif (is_string($a)) {
1419 0         0 print STDOUT get_string($a);
1420             } elsif (is_number($a)) {
1421 0         0 print STDOUT get_number($a);
1422             }
1423             }
1424              
1425             our $rng_last = -1;
1426             our $RNG;
1427             sub rand_function {
1428 100     100 0 198 my $a = gspop();
1429 100 50       224 if (!is_number($a)) {
1430 0         0 &___yada;
1431             } else {
1432              
1433 100         2650 my $n = bigi::to_scalar(get_number($a));
1434 100         2272 gspush to_number(int($n * rand()));
1435             }
1436             }
1437              
1438             sub do_function {
1439 1     1 0 4 my $block = gspop();
1440 1 50       4 return &___ if !is_block($block);
1441              
1442 1         5 $block = get_block($block);
1443 1         12 my $a;
1444 1         2 do {
1445 5         12 evaluate($block);
1446 5 50       12 display_stack() if $DEBUG;
1447 5         10 $a = gspop();
1448             } while (is_true($a));
1449             }
1450              
1451             sub _evaluate_condition {
1452 0     0   0 my $block = shift;
1453 0         0 evaluate($block);
1454 0         0 my $a = gspop();
1455 0         0 return is_true($a);
1456             }
1457              
1458             sub while_function {
1459 0     0 0 0 my $condition = gspop();
1460 0         0 my $body = gspop();
1461              
1462 0 0 0     0 unless (is_block($condition) && is_block($body)) {
1463 0         0 &___;
1464             }
1465              
1466 0         0 $condition = get_block($condition);
1467 0         0 $body = get_block($body);
1468              
1469 0         0 while (_evaluate_condition($condition)) {
1470 0         0 evaluate($body);
1471             }
1472             }
1473              
1474             sub until_function {
1475 0     0 0 0 my $condition = gspop();
1476 0         0 my $body = gspop();
1477              
1478 0 0 0     0 unless (is_block($condition) && is_block($body)) {
1479 0         0 &___;
1480             }
1481              
1482 0         0 $condition = get_block($condition);
1483 0         0 $body = get_block($body);
1484              
1485 0         0 until (_evaluate_condition($condition)) {
1486 0         0 evaluate($body);
1487             }
1488             }
1489              
1490             sub abs_function {
1491 2     2 0 5 my $a = gspop();
1492 2 50 33     7 if (is_array($a) || is_string($a) || is_block($a)) {
      33        
1493 0         0 &___;
1494             } else {
1495 2         41 $a = get_number($a);
1496             #if (bigi::cmp($a,0) < 0) {
1497 2 100       6 if ($a < 0) {
1498 1         133 gspush to_number(bigi::mult($a,-1));
1499             } else {
1500 1         140 gspush to_number($a);
1501             }
1502             }
1503             }
1504              
1505             sub zip_function {
1506 2     2 0 4 my $a = gspop();
1507 2 50       6 if (!is_array($a)) {
1508 0         0 &___
1509             }
1510              
1511 2         8 my $rows = @$a;
1512 2         8 my $b = [];
1513 2         4 my $is_string = 0;
1514 2         7 for (my $i=0; $i < @$a; $i++) {
1515 6         11 my $aa = $a->[$i];
1516 6 100       13 if (!is_array($aa)) {
1517 3 50       8 if (is_string($aa)) {
1518 3         4 $is_string = 1;
1519 3         7 $aa = [ _coerce_string_to_array(get_string($aa)) ];
1520             } else {
1521 0         0 return &___;
1522             }
1523             }
1524 6         42 for (my $j = 0; $j < @$aa; $j++) {
1525 18         54 $b->[$j][$i] = $aa->[$j];
1526             }
1527             }
1528 2 100       6 if ($is_string) {
1529 1         5 for (my $j=0; $j < @$b; $j++) {
1530 3         9 $b->[$j] = to_string( _coerce_array_to_string($b->[$j]) );
1531             }
1532             }
1533 2         5 gspush $b;
1534             }
1535              
1536             sub base_function {
1537 3     3 0 31 my $base = gspop();
1538 3 50       10 if (is_string($base)) {
    50          
1539 0         0 my $c = get_string($base);
1540 0 0       0 if (length $c > 1) {
1541 0         0 &___;
1542             }
1543 0         0 $base = ord $c;
1544 0 0       0 if ($DEBUG) { print STDERR "base_function: treating '$c' as base $base\n"; }
  0         0  
1545             } elsif (is_number($base)) {
1546 3         68 $base = get_number($base);
1547             } else {
1548 0         0 &___;
1549             }
1550 3         10 my $operand = gspop();
1551 3 100       9 if (is_array($operand)) {
    50          
    50          
    50          
1552 2         8 my $value = Math::BigInt->new(0);
1553 2         133 foreach my $elem (@$operand) {
1554 6         251 $value = bigi::mult($value,$base);
1555 6         380 $value = bigi::add($value,get_number($elem));
1556             }
1557 2         109 gspush to_number($value);
1558             } elsif (is_string($operand)) {
1559 0         0 my $value = Math::BigInt->new(0);
1560 0         0 foreach my $elem (_coerce_string_to_array(get_string($operand))) {
1561 0         0 $value = bigi::mult($value,$base);
1562 0         0 $value = bigi::add($value, get_number($elem));
1563             }
1564 0         0 gspush to_number($value);
1565             } elsif (is_block($operand)) {
1566 0         0 &___;
1567             } elsif (is_number($operand)) {
1568 1         22 $operand = get_number($operand);
1569 1         2 my @value = ();
1570 1         5 while (bigi::cmp($operand,0) > 0) {
1571 5         1221 my $mod = bigi::mod($operand,$base);
1572 5         395 unshift @value, to_number($mod);
1573 5         11 $operand = bigi::div($operand,$base);
1574             }
1575 1         123 gspush [ @value ];
1576             }
1577             }
1578              
1579             sub is_true {
1580 101     101 0 130 my $a = shift;
1581 101 100 100     173 if ( (is_array($a) && @$a == 0)
      100        
      66        
      100        
      66        
      100        
      66        
1582             || (is_string($a) && get_string($a) eq "")
1583             || (is_block($a) && get_block($a) eq "")
1584             || (is_number($a) && get_number($a) == 0) ) {
1585 43         2287 return 0;
1586             } else {
1587 58         2317 return 1;
1588             }
1589             }
1590              
1591             sub is_false {
1592 26     26 0 32 my $a = shift;
1593 26 100       54 return is_true($a) ? "0" : "1";
1594             }
1595              
1596             sub _array_compare {
1597 14     14   26 my ($a,$b) = @_;
1598 14   66     89 for (my $i=0; $i<@$a && $i<@$b; $i++) {
1599 28         70 my $d = _element_compare($a->[$i], $b->[$i]);
1600 28 100       730 if ($d != 0) {
1601 12 50       33 if ($DEBUG > 1) {
1602 0         0 print STDERR "Array compare ", display_element($a), " <=> ",
1603             display_element($b), "; compare \@ $i: $d\n";
1604             }
1605 12         78 return $d;
1606             }
1607             }
1608 2         11 return @$a <=> @$b;
1609             }
1610              
1611             sub _array_string_compare {
1612 2     2   4 my ($array, $string) = @_;
1613 2         6 my $array2 = [ _coerce_string_to_array($string) ];
1614 2 50       6 if ($DEBUG) {
1615 0         0 print STDERR "_array_string_compare: ";
1616 0         0 print STDERR display_element($array), " / ", display_element($string);
1617 0         0 print STDERR " / ";
1618 0         0 print STDERR display_element($array2), "\n";
1619             }
1620 2         6 return _array_compare($array, $array2);
1621             }
1622              
1623             # XXX - refactor candidate
1624             # compare two elements and indicate which is greater/less
1625             # in "golfscript" order
1626             # return <0 if $a is "less than" $b
1627             # 0 if $a and $b are equivalent
1628             # >0 if $a is "greater than" $b
1629             sub _element_compare {
1630 446     446   645 my ($a,$b) = @_;
1631 446 100       777 if (is_array($a)) {
    100          
    100          
    50          
1632 13 100       30 if (is_array($b)) {
    50          
    0          
1633 12         31 return _array_compare($a,$b);
1634             } elsif (is_string($b)) {
1635 1         3 return _array_string_compare($a, get_string($b));
1636             } elsif (is_block($b)) {
1637 0         0 $b = "{" . get_block($b) . "}";
1638 0         0 return _array_string_compare($a, $b);
1639             } else {
1640 0         0 gscroak "illegal compare array with number";
1641             }
1642             } elsif (is_string($a)) {
1643 12 50       25 if (is_array($b)) {
    100          
    50          
    50          
1644 0         0 return -_array_string_compare($b, get_string($a));
1645             } elsif (is_block($b)) {
1646             # compare string with block
1647             # {block ==> "{block} for comparison
1648 1         3 return get_string($a) cmp "{" . get_block($b) . "}";
1649             } elsif (is_number($b)) {
1650 0         0 gscroak "illegal compare string with number";
1651             } elsif (is_string($b)) {
1652 11         26 return get_string($a) cmp get_string($b);
1653             }
1654             } elsif (is_block($a)) {
1655 1 50       2 if (is_array($b)) {
    0          
    0          
1656 1         4 return -_array_string_compare($b, "{".get_block($a)."}");
1657             } elsif (is_string($b)) {
1658 0         0 return "{".get_block($a)."}" cmp get_string($b);
1659             } elsif (is_block($b)) {
1660 0         0 return get_block($a) cmp get_block($b);
1661             } else {
1662 0         0 gscroak "illegal compare block with number";
1663             }
1664             } elsif (is_number($a)) {
1665 420 50       5307 if (is_array($b)) {
    50          
    50          
    50          
1666 0         0 gscroak "illegal compare number with array";
1667             } elsif (is_string($b)) {
1668 0         0 gscroak "illegal compare number with string";
1669             } elsif (is_block($b)) {
1670 0         0 gscroak "illegal compare number with block";
1671             } elsif (is_number($b)) {
1672 420         5259 return bigi::cmp(get_number($a),get_number($b));
1673             }
1674             }
1675             }
1676              
1677             ##################################################################
1678              
1679             our %GS_TYPE_PRIORITY = qw(number 1 array 2 string 3 block 4);
1680              
1681             # __order: swap arguments so that first arg's "priority" is
1682             # not lower than the second arg's
1683             # Used by * / % < > = ? operators
1684             sub __order {
1685 16     16   50 my ($type0, $type1) = (is($_[0]), is($_[1]));
1686 16 100 50     281 if (($GS_TYPE_PRIORITY{$type0} || 0) < ($GS_TYPE_PRIORITY{$type1} || 0)) {
      50        
1687 1 50       3 if ($DEBUG > 1) {
1688 0         0 print STDERR "Reorder arguments\n";
1689             }
1690 1         5 ($_[0],$_[1]) = ($_[1],$_[0]);
1691             }
1692             }
1693              
1694             # __coerce: coerce the two arguments to the
1695             # Used by + - | & ^ operators
1696             sub __coerce {
1697              
1698 6     6   23 my ($type0, $type1) = (is($_[0]), is($_[1]));
1699              
1700 6 50       40 if ($DEBUG) { print STDERR "__coerce($type0,$type1) ==> "; }
  0         0  
1701              
1702 6 100       17 return if $type0 eq $type1;
1703              
1704             # GolfScript hierarchy: block <== string <== array <== int
1705              
1706 3 100 33     40 if ($type0 eq 'number') {
    100 33        
    50 0        
    50 0        
    0 0        
    0 0        
    0          
    0          
1707 1 50       6 if ($type1 eq 'array') {
    50          
    0          
1708 0 0       0 print STDERR " $type0==>$type1\n" if $DEBUG;
1709 0         0 return $_[0] = [ $_[0] ];
1710             } elsif ($type1 eq 'string') {
1711 1 50       3 print STDERR " $type0==>$type1\n" if $DEBUG;
1712            
1713 1         4 return $_[0] = to_string(get_number($_[0]));
1714             } elsif ($type1 eq 'block') {
1715 0 0       0 print STDERR " $type0==>$type1\n" if $DEBUG;
1716 0         0 return $_[0] = to_block(get_number($_[0]));
1717             }
1718             } elsif ($type1 eq 'number') {
1719 1 50       5 if ($type0 eq 'array') {
    50          
    0          
1720 0 0       0 print STDERR " $type1===>$type0\n" if $DEBUG;
1721 0         0 return $_[1] = [ $_[1] ];
1722             } elsif ($type0 eq 'string') {
1723 1 50       5 print STDERR " $type1===>$type0\n" if $DEBUG;
1724 1         3 return $_[1] = to_string(get_number($_[1]));
1725             } elsif ($type0 eq 'block') {
1726 0 0       0 print STDERR " $type1===>$type0\n" if $DEBUG;
1727 0         0 return $_[1] = to_block(get_number($_[1]));
1728             }
1729             } elsif ($type0 eq 'array' && $type1 eq 'string') {
1730 0 0       0 print STDERR " $type0==>$type1\n" if $DEBUG;
1731 0         0 return $_[0] = to_string(_coerce_array_to_string($_[0]));
1732             } elsif ($type0 eq 'string' && $type1 eq 'array') {
1733 1 50       3 print STDERR " $type1===>$type0\n" if $DEBUG;
1734 1         6 return $_[1] = to_string(_coerce_array_to_string($_[1]));
1735             } elsif ($type0 eq 'array' && $type1 eq 'block') {
1736 0 0       0 print STDERR " $type0==>$type1\n" if $DEBUG;
1737 0         0 return $_[0] = to_block(_coerce_array_to_block($_[0]));
1738             } elsif ($type0 eq 'block' && $type1 eq 'array') {
1739 0 0       0 print STDERR " $type1===>$type0\n" if $DEBUG;
1740 0         0 return $_[1] = to_block(_coerce_array_to_block($_[0]));
1741             } elsif ($type0 eq 'string' && $type1 eq 'block') {
1742 0 0       0 print STDERR " $type0==>$type1\n" if $DEBUG;
1743 0         0 return $_[0] = to_block(get_string($_[0]));
1744             } elsif ($type0 eq 'block' && $type1 eq 'string') {
1745 0 0       0 print STDERR " $type1===>$type0\n" if $DEBUG;
1746 0         0 return $_[1] = to_block(get_string($_[1]));
1747             }
1748             }
1749              
1750             # _coerce_string_to_array
1751             # input: string
1752             # output: array of integers representing character values of the string
1753             sub _coerce_string_to_array {
1754 51     51   84 my $string = shift;
1755 51         148 my @array = map { to_number(ord $_) } split //, $string;
  223         432  
1756 51 50       153 if ($DEBUG>1) { print STDERR " coercing string to array: $string => [ @array ]\n"; }
  0         0  
1757 51         164 return @array;
1758             }
1759              
1760             # _coerce_array_to_string
1761             # input: array
1762             # output: string of characters created from values of the string
1763             #
1764             # _coerce_array_to_string treats numbers as character (ASCII) values
1765             # _coerce_array_to_block treats numbers as strings
1766             #
1767             # _coerce_array_to_string([65 66 67]) => "ABC"
1768             # _coerce_array_to_block([65 66 67]) => "65 66 67"
1769             #
1770             sub _coerce_array_to_string {
1771 32     32   54 my $array = shift;
1772 32         49 my $string = "";
1773 32         92 for (my $i=0; $i<@$array; $i++) {
1774 112         3380 my $elem = $array->[$i];
1775 112 100       194 if (is_array($elem)) {
    100          
    100          
    50          
1776 2         7 $string .= _coerce_array_to_string($elem);
1777             } elsif (is_string($elem)) {
1778 1         4 $string .= get_string($elem);
1779             } elsif (is_block($elem)) {
1780 1         4 $string .= "{" . get_block($elem) . "}";
1781             } elsif (is_number($elem)) {
1782 108         740 $string .= chr(bigi::mod(get_number($elem), 256));
1783             }
1784             }
1785 32         1602 return $string;
1786             }
1787              
1788             # _coerce_array_to_block:
1789             # input: array
1790             # output: array as string
1791             # see also: _coerce_array_to_block
1792             sub _coerce_array_to_block {
1793 2     2   7 my $array = shift;
1794 2         6 my $string = "";
1795 2         9 for (my $i=0; $i<@$array; $i++) {
1796 6 100       99 if ($i > 0) { $string .= " " };
  4         6  
1797 6         23 my $elem = $array->[$i];
1798 6 50       12 if (is_array($elem)) {
    50          
    50          
    50          
1799 0         0 $string .= _coerce_array_to_string($elem);
1800             } elsif (is_block($elem)) {
1801 0         0 $string .= get_block($elem);
1802             } elsif (is_string($elem)) {
1803 0         0 $string .= get_string($elem);
1804             } elsif (is_number($elem)) {
1805 6         143 $string .= get_number($elem);
1806             }
1807             }
1808 2         46 return $string;
1809             }
1810              
1811             # _bitwise_not:
1812             # input: integer
1813             # output: -input - 1
1814             sub _bitwise_not {
1815 2     2   4 my $integer = shift;
1816 2         7 return bigi::add(bigi::neg($integer), -1);
1817             }
1818              
1819             # _de_evaluate_array:
1820             # input: array
1821             # output: string that will become that array when evaluated
1822             sub _de_evaluate_array {
1823 4     4   8 my $array = shift;
1824 4         8 my $output = "";
1825 4         14 for (my $i=0; $i<@$array; $i++) {
1826 10         109 my $elem = $array->[$i];
1827 10 100       23 if ($i > 0) {
1828 6         20 $output .= " ";
1829             }
1830 10 100       17 if (is_array($elem)) {
    100          
    100          
    50          
1831 1         6 $output .= _de_evaluate_array($elem);
1832             } elsif (is_block($elem)) {
1833 1         88 $output .= _de_evaluate_block( get_block($elem) );
1834             } elsif (is_string($elem)) {
1835 1         5 $output .= _de_evaluate_string( get_string($elem) );
1836             } elsif (is_number($elem)) {
1837 7         174 $output .= _de_evaluate_number( get_number($elem) );
1838             }
1839             }
1840 4         76 return "[$output]";
1841             }
1842              
1843             # _de_evaluate_block:
1844             # input: block
1845             # output: string that will become that block when evaluated
1846             sub _de_evaluate_block {
1847 1     1   3 my $block = shift;
1848 1         7 return "{$block}";
1849             }
1850              
1851             # _de_evaluate_string:
1852             # input: string
1853             # output: string that will become that string when evaluated
1854             sub _de_evaluate_string {
1855 4     4   6 my $string = shift;
1856 4         18 my @chars = split //, $string;
1857              
1858 4         8 $string = "";
1859 4         12 while (@chars) {
1860 16         24 my $char = shift @chars;
1861 16 100 33     106 if ($char eq "\n") { $string .= "\\n" }
  1 50       3  
    50          
    50          
1862 0         0 elsif ($char eq "\t") { $string .= "\\t" }
1863 0         0 elsif (ord($char) < 32 || ord($char) >= 127) { $string .= sprintf "\\%03o", ord($char) }
1864 0         0 elsif ($char =~ /['"\\]/) { $string .= "\\$char" } #']/){}
1865 15         39 else { $string .= $char }
1866             }
1867 4         17 return "\"$string\"";
1868             }
1869              
1870             # _de_evaluate_number:
1871             # input: integer
1872             # output: string that will become that integer when evaluated
1873             sub _de_evaluate_number {
1874 9     9   12 my $integer = shift;
1875 9         20 return "$integer";
1876             }
1877              
1878             # _array_join
1879             # input: two arrays
1880             # output: single array
1881             # function: first array joined together by the elements of the second array
1882             sub _array_join {
1883 1     1   2 my ($b,$a) = @_;
1884 1         3 my @c;
1885 1         1 push @c, shift @$b;
1886 1         3 foreach (@$b) {
1887 2         3 push @c, @$a;
1888 2         4 push @c, $_;
1889             }
1890 1         4 return [ @c ];
1891             }
1892              
1893             sub _array_string_join {
1894 3     3   5 my ($array,$string) = @_;
1895 3         5 my $output = '';
1896 3         5 my $numElem = 0;
1897              
1898 3         6 foreach my $elem (@$array) {
1899 9 100       98 $output .= $string if $numElem++;
1900 9 100       48 if (is_array($elem)) {
    50          
    0          
    0          
1901 2         8 $output .= _coerce_array_to_string($elem);
1902             } elsif (is_number($elem)) {
1903 7         147 $output .= get_number($elem);
1904             } elsif (is_string($elem)) {
1905 0         0 $output .= get_string($elem);
1906             } elsif (is_block($elem)) {
1907 0         0 $output .= "{" . get_block($elem) . "}";
1908             } else {
1909 0         0 &___;
1910             }
1911             }
1912 3         37 return $output;
1913             }
1914              
1915             # _sort_string:
1916             # input: a string
1917             # output: the string, sorted by character value
1918             sub _sort_string {
1919 1     1   4 my $string = shift;
1920 1         10 my @chars = split //, $string;
1921 1         10 return join'', sort @chars;
1922             }
1923              
1924             # _eval:
1925             # called from _sort_array_by_function to evaluate a value
1926             # with respect to a block
1927             # input: element, block
1928             # output: valuation of the element with respect to the block
1929             sub _eval {
1930 11     11   18 my ($element, $block) = @_;
1931              
1932 11         23 gspush $element;
1933 11         22 evaluate($block);
1934 11         21 return gspop();
1935             }
1936              
1937             # _sort_array_by_function:
1938             # input: array, block
1939             # output: array, sorted by valuation of each element w.r.t. the block
1940             sub _sort_array_by_function {
1941 2     2   4 my ($array, $block) = @_;
1942              
1943             # good place for a Schwartzian transform
1944 11         78 my @output = map { $_->[0] }
  17         446  
1945 11         24 sort { _element_compare($a->[1], $b->[1]) }
1946 2         17 map { [ $_ , _eval($_, $block)] } @$array;
1947              
1948 2         23 return [ @output ];
1949             }
1950              
1951              
1952             # _sort_array_by_function:
1953             # input: array, block
1954             # output: array, sorted by valuation of each element w.r.t. the block
1955             sub _sort_string_by_function {
1956 1     1   3 my ($string, $block) = @_;
1957 1         5 my @array = _coerce_string_to_array($string);
1958 1         4 my $output = _sort_array_by_function(\@array, $block);
1959 6 50       10 my $sorted_string = join '',
1960 1         2 map{ chr( is_number($_) ? get_number($_) : $_ ) } @$output;
1961 1         5 return $sorted_string;
1962             }
1963              
1964             # _concat_block_array
1965             # input: block, array
1966             # output: block
1967             sub _concat_block_array {
1968 1     1   2 my ($block, $array) = @_;
1969 1         2 my $output = $block;
1970 1         3 foreach my $elem (@$array) {
1971 1         1 $output .= " ";
1972 1 50       2 if (is_array($elem)) {
    50          
    50          
    50          
1973 0         0 my $string = _coerce_array_to_string($elem);
1974 0         0 $output .= $string;
1975             } elsif (is_string($elem)) {
1976 0         0 $output .= get_string($elem);
1977             } elsif (is_block($elem)) {
1978 0         0 $output .= "{" . get_block($elem) . "}";
1979             } elsif (is_number($elem)) {
1980 1         20 $output .= get_number($elem);
1981             }
1982             }
1983 1         18 return $output;
1984             }
1985              
1986             # _concat_array_block
1987             # input: array, block
1988             # output: block
1989             sub _concat_array_block {
1990 2     2   5 my ($array, $block) = @_;
1991 2         3 my $output = '';
1992 2         4 foreach my $elem (@$array) {
1993 6         50 $output .= " ";
1994 6 100       11 if (is_array($elem)) {
    50          
    50          
    50          
1995 1         4 my $string = _coerce_array_to_string($elem);
1996 1         4 $output .= $string;
1997             } elsif (is_string($elem)) {
1998 0         0 $output .= get_string($elem);
1999             } elsif (is_block($elem)) {
2000 0         0 $output .= "{" . get_block($elem) . "}";
2001             } elsif (is_number($elem)) {
2002 5         94 $output .= get_number($elem);
2003             }
2004             }
2005 2         35 $output .= " $block";
2006 2         4 return substr($output,1);
2007             }
2008              
2009             # _array_array_difference:
2010             # input: array,array
2011             # output: array
2012             # elements of first array that are NOT in the second array
2013             sub _array_array_difference {
2014 15     15   23 my ($array1, $array2) = @_;
2015 15         24 my %a2 = map{$_ => 1} @$array2;
  35         93  
2016 15         211 my @a1 = grep { !$a2{$_} } @$array1;
  79         275  
2017 15         112 return \@a1;
2018             }
2019              
2020             # _string_array_difference:
2021             # input: string,array
2022             # output: string
2023             # characters of the string that are NOT in the values of the 2nd arr
2024             sub _string_array_difference {
2025 1     1   2 my ($string,$array) = @_;
2026 1         4 my @array2 = _coerce_string_to_array($string);
2027 1         4 my $c = _array_array_difference(\@array2, $array);
2028 1         5 return _coerce_array_to_string($c);
2029 0         0 &___;
2030             }
2031              
2032             sub _block_array_difference {
2033 1     1   3 my ($block, $array) = @_;
2034 1         5 my $block2 = _coerce_array_to_block($array);
2035 1         4 my $string = _string_string_difference($block,$block2);
2036 1         4 return $string;
2037             }
2038              
2039             # input: array,block
2040             # output: block
2041             sub _array_block_difference {
2042 0     0   0 my ($array,$block) = @_;
2043 0         0 my $block2 = _coerce_array_to_block($array);
2044 0         0 my $string = _string_string_difference($block2,$block);
2045 0         0 return $string;
2046             }
2047              
2048             sub _block_string_difference {
2049 1     1   2 my ($block,$string) = @_;
2050 1         3 return _string_string_difference($block,$string);
2051             }
2052              
2053             sub _string_block_difference {
2054 1     1   3 my ($string,$block) = @_;
2055 1         4 return _string_string_difference($string,$block);
2056             }
2057              
2058             sub _block_block_difference {
2059 2     2   4 my ($block1,$block2) = @_;
2060 2         6 return _string_string_difference($block1,$block2);
2061             }
2062              
2063             sub _string_string_difference {
2064 11     11   17 my ($string1,$string2) = @_;
2065 11         26 my @array1 = _coerce_string_to_array($string1);
2066 11         21 my @array2 = _coerce_string_to_array($string2);
2067 11         32 my $c = _array_array_difference(\@array1, \@array2);
2068 11         24 my $string = _coerce_array_to_string($c);
2069 11         51 return $string;
2070             }
2071              
2072             sub _repeat_array {
2073 2     2   4 my ($array, $times) = @_;
2074 2         3 my @c = ();
2075 2         6 for my $n (1 .. $times) {
2076 8         119 my $copy = _copy_element($array);
2077 8         21 push @c, @$copy;
2078             }
2079 2         8 return [ @c ];
2080             }
2081              
2082             sub _fold_array {
2083 2     2   10 my ($array, $block) = @_;
2084 2         5 my @a = @$array;
2085 2         3 my $first = shift @a;
2086 2         12 gspush $first;
2087 2         85 while (@a) {
2088 5         9 my $elem = shift @a;
2089 5         9 gspush $elem;
2090 5         6 evaluate($block);
2091             }
2092             }
2093              
2094             sub __find_array_in_array {
2095 3     3   5 my ($array1, $array2) = @_;
2096 3         9 for (my $i=0; $i<@$array1; $i++) {
2097 5         7 my $found = $i;
2098 5         11 for (my $j=0; $j<@$array2; $j++) {
2099 7 100       58 if (_element_compare($array1->[$i+$j], $array2->[$j]) != 0) {
2100 3         98 $found = -1;
2101 3         4 last;
2102             }
2103             }
2104 5 100       62 if ($found >= 0) {
2105 2         4 return $found;
2106             }
2107             }
2108 1         4 return -1;
2109             }
2110              
2111             sub _split_array {
2112 1     1   2 my ($array1, $array2) = @_;
2113 1         3 my @array1 = @$array1;
2114 1         2 my @output;
2115 1         4 my $found = __find_array_in_array(\@array1, $array2);
2116 1         4 while ($found >= 0) {
2117 2 50       5 if ($found > 0) {
2118 2         5 push @output, [ splice @array1, 0, $found ];
2119             } else {
2120 0         0 push @output, [];
2121             }
2122 2         4 splice @array1, 0, scalar @$array2;
2123 2         4 $found = __find_array_in_array(\@array1, $array2);
2124             }
2125 1         2 push @output, [ @array1 ];
2126 1         5 return [ @output ];
2127             }
2128              
2129             sub __dup_and_check_condition {
2130 24     24   36 my $condition = shift;
2131 24         60 evaluate(".");
2132 24         39 evaluate($condition);
2133 24         46 my $a = gspop();
2134 24 100       269 return is_true($a) ? $a : 0;
2135             }
2136              
2137             sub _unfold {
2138 2     2   8 my ($condition, $block) = @_;
2139 2         5 my ($z, @c);
2140 2         8 while ($z = __dup_and_check_condition($condition)) {
2141 22         62 push @c, _copy_element($STACK[-1]);
2142 22         39 evaluate($block);
2143             }
2144 2         8 gspop();
2145 2         10 gspush [ @c ];
2146             }
2147              
2148             sub _setwise_or {
2149 1     1   3 my ($set1, $set2) = @_;
2150 1         1 my @c = ();
2151 1         4 ELEM: foreach my $elem (@$set1, @$set2) {
2152 9         14 foreach my $c (@c) {
2153 30 100       517 if (_element_compare($elem,$c) == 0) {
2154 2         44 next ELEM;
2155             }
2156             }
2157 7         162 push @c, $elem;
2158             }
2159 1         7 return [ @c ];
2160             }
2161              
2162             sub _setwise_and {
2163 2     2   3 my ($set1, $set2) = @_;
2164              
2165 2         3 my @c = ();
2166 2         5 SET1: foreach my $elem1 (@$set1) {
2167 10         162 foreach my $c (@c) {
2168 1 50       3 if (_element_compare($c,$elem1) == 0) {
2169 0         0 next SET1;
2170             }
2171             }
2172 10         43 foreach my $elem2 (@$set2) {
2173 39 100       689 if (_element_compare($elem1,$elem2) == 0) {
2174 2         49 push @c, $elem1;
2175 2         9 next SET1;
2176             }
2177             }
2178             }
2179 2         29 return [ @c ];
2180             }
2181              
2182             sub _setwise_symmetric_difference {
2183 6     6   15 my ($set1, $set2) = @_;
2184 6         14 my @c = ();
2185 6         16 SET1: foreach my $elem1 (@$set1) {
2186 22         38 foreach my $elem2 (@$set2) {
2187 85 100       664 if (_element_compare($elem1,$elem2) == 0) {
2188 10         183 next SET1;
2189             }
2190             }
2191 12         138 foreach my $c (@c) {
2192 8 50       46 if (_element_compare($elem1,$c) == 0) {
2193 0         0 next SET1;
2194             }
2195             }
2196 12         75 push @c, $elem1;
2197             }
2198 6         19 SET2: foreach my $elem2 (@$set2) {
2199 28         126 foreach my $elem1 (@$set1) {
2200 85 100       681 next SET2 if _element_compare($elem1,$elem2) == 0;
2201             }
2202 18         93 foreach my $c (@c) {
2203 70 100       273 next SET2 if _element_compare($c,$elem2) == 0;
2204             }
2205 12         88 push @c, $elem2;
2206             }
2207 6         89 return [ @c ];
2208             }
2209              
2210             sub _setwise_symmetric_string_difference {
2211 4     4   11 my ($string1,$string2) = @_;
2212 4         16 my $array1 = [ _coerce_string_to_array($string1) ];
2213 4         12 my $array2 = [ _coerce_string_to_array($string2) ];
2214 4         13 my $c = _setwise_symmetric_difference($array1,$array2);
2215 4         19 return _coerce_array_to_string($c);
2216             }
2217              
2218             1;
2219              
2220             __END__