#! perl

# $Id: /mirror/trunk/languages/APL/tools/gen_operator_defs.pl 35402 2009-01-11T01:53:12.820693Z coke  $

use strict;
use warnings;

print <<"END_OF_HEADER";

# DO NOT EDIT.
# This file generated automatically by '$0'

END_OF_HEADER

my %macros;
$macros{DOMAIN_ERROR} = <<'END_OF_PIR';
    die "DOMAIN ERROR\n"
END_OF_PIR

my %scalar = (
    '+' => [ 'Add',   '%1 = %1 + %2' ],
    '*' => [ 'Power', << 'END_PIR' ],
        # XXX This is too restrictive. Need better tests
    if %1 >= 0 goto power_ok
%% DOMAIN_ERROR %%
power_ok:
    $N1 = %1
    $N2 = %2
    $N1 = pow $N1, $N2
        %1 = $N1

END_PIR

    '\x{d7}' => [ 'Multiply', '%1 = %1 * %2' ],
    '\x{f7}' => [ 'Divide',   '%1 = %1 / %2' ],
    '\u2212' => [ 'Subtract', '%1 = %1 - %2' ],
    '\u2308' => [ 'Maximum',  <<'END_PIR' ],
    if %1 > %2 goto maximum_done
    %1 = %2
maximum_done:
END_PIR

    '\u230a' => [ 'Minimum', <<'END_PIR' ],
    if %1 < %2 goto minimum_done
    %1 = %2
minimum_done:
END_PIR
);

my $template = <<'END_OF_TEMPLATE';

.namespace []

# any registers #'d 100 or higher are used here for temporary conversions
# to other types required by the various opcodes. XXX This should go away
# Once PMI ports his lovely new perl6 code back into APL.

.sub "__load_inlinetable" :load :init
    $P0 = new 'Hash'
    set_hll_global ['APL'], "%pirtable", $P0
    .local pmc itable
    itable = new 'Hash'
    set_hll_global ['APL';'Grammar';'Actions'], '%inlinetable', itable

    # special-purpose parrot ops here
    itable['dyadic:<']         =  <<'END_PIR'
    $I1 = islt %0, %1          # dyadic:< (less than)
    %t = $I1
END_PIR

    itable['dyadic:>']         =  <<'END_PIR'
    $I1 = isgt %0, %1          # dyadic:> (greater than)
    %t = $I1
END_PIR

    itable['dyadic:=']         =  <<'END_PIR'
    $I1 = iseq %0, %1          # dyadic:= (equals)
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2227"]  =  <<'END_PIR'
    $I0 = %0                   # dyadic:\u2227 (and)
    $I1 = %1
    $I1 = and $I0, $I1
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2228"]  = <<'END_PIR'
    $I0 = %0                   # dyadic:\u2228 (or)
    $I1 = %1
    $I1 = or $I0, $I1
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2260"]  = <<'END_PIR'
    $I1 = isne %0, %1          # dyadic:\u2260 (not equal)
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2264"]  = <<'END_PIR'
    $I1 = isle %0, %1          # dyadic:\u2264 (not greater than)
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2265"]  = <<'END_PIR'
    $I1 = isge %0, %1          # dyadic:\u2265 (not less than)
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2371"]  = <<'END_PIR'
    $I0 = %0                   # dyadic:\u2371 (nor)
    $I1 = %1
    $I1 = or $I0, $I1
    $I1 = not $I1
    %t = $I1
END_PIR

    itable[unicode:"dyadic:\u2372"]  =  <<'END_PIR'
    $I0 = %0                   # dyadic:\u2372 (nand)
    $I1 = %1
    $I1 = and $I0, $I1
    $I1 = not $I1
    %t = $I1
END_PIR

    itable['monadic:+']      =  "    %r = %0"          # conjugate
    itable['monadic:|']      =  "    %t = abs %0"      # magnitude
    itable['monadic:!']      =  <<'END_PIR'
    $I1 = %0                   # monadic:! (factorial)
    $I1 = fact $I1
    %t = $I1
END_PIR

    itable['monadic:*']      =  <<'END_PIR'
    $P1 = new 'Float'
    $P1 = %0
    %t = $P1.'exp'()
END_PIR

    itable[unicode:"monadic:\x{d7}"] =  <<'END_PIR'
    $N1 = %0                   # monadic:\x{d7} (signum)
    $I1 = cmp_num $N1, 0.0
    %t = $I1
END_PIR
    itable[unicode:"monadic:\x{f7}"] =  <<'END_PIR'
    $N1 = %0                   # monadic:\x{f7} (reciprocal)
    $N1 = 1.0 / $N1
    %t = $N1
END_PIR

    itable[unicode:"monadic:\u2212"] =  "    %t = neg %0"      # negate
    itable[unicode:"monadic:\u2308"] =  <<'END_PIR'
    $N1 = %0                   # monadic:\u2308 (ceiling)
    $I1 = ceil $N1
    %t = $I1
END_PIR

    itable[unicode:"monadic:\u230a"] =  <<'END_PIR'
    $N1 = %0                   # monadic:\u230a (floor)
    $I1 = floor $N1
    %t = $I1
END_PIR

    itable[unicode:"monadic:\u235f"] =  <<'END_PIR'
    $P1 = new 'Float'
    $P1 = %0
    %t = $P1.'ln'()
END_PIR


    itable[unicode:"monadic:\u25cb"] =  "    %t = %0 * 3.14159265358979323846"
                                      # PI

    itable[unicode:"monadic:\u2373"]  =  <<'END_PIR'            # index of
    #XXX hack all the _1's need the same, generated unique number.
    %r = new 'APLVector'              # monadic:\u2373 (index of)
    $I0 = 1
    $I1 = 0
    $I2 = %0
  loop_begin_1:
    if $I0 > $I2 goto loop_done_1
    %r[$I1] = $I0
    inc $I1
    inc $I0
    goto loop_begin_1
  loop_done_1:
END_PIR

.end

.sub 'aplformat'
    .param pmc arg

    .local string result
    result = ''

    .local pmc value
    $I0 = does arg, 'array'
    if $I0 goto print_vector
    value = arg
    bsr print_value
    .return (result)

  print_vector:
    .local pmc shape, iter
    .local string value_type, old_type
    value_type = 'String'
    iter = new 'Iterator', arg
    shape = arg.'get_shape'()
    $I0 = shape
    if $I0 == 2 goto print_2D
    # XXX assume 1d otherwise.
    unless iter goto iter_end
  iter_loop:
    old_type = value_type
    value = shift iter
    bsr print_value
    unless iter goto iter_end
    value_type = typeof value
    if value_type != 'String' goto print_space
    if old_type != value_type goto print_space
    goto iter_loop
  print_space:
    result .= ' '
    goto iter_loop
  iter_end:
    .return (result)

  print_2D:
    .local int row_size, pos, newline
    row_size = shape[1]
    pos = 1
    iter = new 'Iterator', arg
    value_type = 'String'
    unless iter goto loop_end_2d
  loop_2d:
    newline = 0
    if pos != row_size goto cont_2d
    newline = 1
    pos = 0

  cont_2d:
    old_type = value_type
    value = shift iter
    bsr print_value
    unless iter goto loop_end_2d
    value_type = typeof value
    if newline goto print_newline
    if value_type != 'String' goto print_space_2d
    if old_type != value_type goto print_space_2d
    goto print_newline

  print_space_2d:  # don't print a space if we're about to end a row
    if newline goto print_newline
    result .= ' '
    goto continue_2d

  print_newline:
    if newline==0 goto continue_2d
    result .= "\n"

  continue_2d:
    inc pos
    goto loop_2d
  loop_end_2d:
   .return(result)

  print_value:
    if value >= 0.0 goto print_value_1
    result .= unicode:"\u207b"
    value = abs value
  print_value_1:
    $S0 = value
    result .= $S0
    ret
.end

.sub 'aplprint'
    .param pmc arg

    $S0 = aplformat(arg)
    say $S0
    .return (arg)
.end

.sub 'aplvector'
    .param pmc args            :slurpy
    .local pmc vector, iter
    vector = new 'APLVector'
    if null args goto iter_end
    iter = new 'Iterator', args
  iter_loop:
    unless iter goto iter_end
    $P0 = shift iter
    push vector, $P0
    goto iter_loop
  iter_end:
    .return (vector)
.end

.sub 'aplstring'
    .param string s
    .local pmc vector
    vector = new 'APLVector'
    $I1 = length s
    $I0 = 0
  loop:
    unless $I0 < $I1 goto loop_end
    $S0 = substr s, $I0, 1
    push vector, $S0
    inc $I0
    goto loop
  loop_end:
    .return (vector)
.end


# XXX - the first argument to this multi sub should be some variant of
# integer - but if you set it to Integer or int, the program dies with
# 'Method not found.' or dispatches to the wrong method.

.sub unicode:"dyadic:\u2296" :multi(pmc, APLVector) # rotate
    .param int op1
    .param pmc op2

    if op1 == 0 goto nothing
    if op1 <  0 goto neg
pos:
    unless op1 goto done
    # shift off the beginning and push onto the end.
    $P1 = shift op2
    push op2, $P1
    dec op1
    goto pos
neg:
    unless op1 goto done
    # pop off the end and unshift onto the beginning
    $P1 = pop op2
    unshift op2, $P1
    inc op1
    goto neg

done:
nothing:
    .return(op2)
.end

.sub 'dyadic:!'           # binomial coefficient
    .param pmc op1
    .param pmc op2
    $I1 = op1
    $I2 = op2
    $I3 = $I2 - $I1

    $N1 = fact $I1
    $N2 = fact $I2
    $N3 = fact $I3

    $N2 /= $N3
    $N2 /= $N1
    .return($N2)
.end

.sub unicode:"dyadic:\u2373" :multi(APLVector, APLVector) # index of
    .param pmc op1
    .param pmc op2

    .local pmc iter_one, iter_two
    .local pmc item_one, item_two
    .local int pos_one
    .local int not_found

    not_found = op1
    inc not_found

    .local pmc result
    result = new 'APLVector'

    iter_two = new 'Iterator', op2
loop_two:
    unless iter_two goto loop_two_end
    item_two = shift iter_two
    iter_one = new 'Iterator', op1
    pos_one = 0 # parrot's 0 == APL's 1
loop_one:
    unless iter_one goto loop_one_end
    item_one = shift iter_one
    inc pos_one
    if item_one != item_two goto loop_one
    push result, pos_one
    # only need to find one, go back to outer loop.
    goto loop_two
loop_one_end:
    # if we get this far, there was no match.
    push result, not_found

    goto loop_two
loop_two_end:

    .return (result)
.end

.sub unicode:"dyadic:\u2373" :multi(APLVector, Float) # index of
    .param pmc op1
    .param num op2

    .local pmc result
    result = new 'APLVector'

    .local int pos
    pos = 0
    .local num value_at
    .local int not_there
    not_there = op1
    inc not_there
    .local pmc iter
    iter = new 'Iterator', op1
loop_begin:
    unless iter goto no_gots
    value_at = shift iter
    if value_at == op2 goto got_it
    inc pos
    goto loop_begin
got_it:
    inc pos
    push result, pos
    .return (result)
no_gots:
    push result, not_there
    .return (result)
.end

.sub unicode:"dyadic:\u25cb"          # circle
    .param num op1
    .param num op2
    $I1 = op1
    if $I1 == 0 goto zero
    if $I1 == 1 goto one
    if $I1 == 2 goto two
    if $I1 == 3 goto three
    if $I1 == 4 goto four
    if $I1 == 5 goto five
    if $I1 == 6 goto six
    if $I1 == 7 goto seven
    if $I1 == -1 goto neg_one
    if $I1 == -2 goto neg_two
    if $I1 == -3 goto neg_three
    if $I1 == -4 goto neg_four
    if $I1 == -5 goto neg_five
    if $I1 == -6 goto neg_six
    if $I1 == -7 goto neg_seven

# XXX this right?
%% DOMAIN_ERROR %%
zero:
    $N1 = op2 * op2
    $N1 = 1 - $N1
    $N1 = sqrt $N1
    .return ($N1)
one:
    $N1 = sin op2
    .return ($N1)
two:
    $N1 = cos op2
    .return ($N1)
three:
    $N1 = tan op2
    .return ($N1)
four:
    $N1 = op2 * op2
    $N1 += 1
    $N1 = sqrt $N1
    .return ($N1)
five:
    $N1 = sinh op2
    .return ($N1)
six:
    $N1 = cosh op2
    .return ($N1)
seven:
    $N1 = tanh op2
    .return ($N1)
neg_one:
    $N1 = asin op2
    .return ($N1)
neg_two:
    $N1 = acos op2
    .return ($N1)
neg_three:
    $N1 = atan op2
    .return ($N1)
neg_four:
    $N1 = op2 * op2
    $N1 = 1 - $N1
    $N1 = sqrt $N1
    .return ($N1)
# These next three are implemented in terms of the available parrot opcodes.
neg_five:  # arcsinh(x) = ln(x+sqrt(x*x+1))
    $N1 = op2 * op2
    inc $N1
    $N1 = sqrt $N1
    $N1 += op2
    $N1 = ln $N1
    .return ($N1)
neg_six:   # arccosh(x) = ln(x+sqrt(x-1)*sqrt(x+1))
    $N1 = op2 + 1
    $N1 = sqrt $N1
    $N2 = op2 - 1
    $N2 = sqrt $N2
    $N2 *= $N1
    $N2 = op2 + $N2
    $N2 = ln $N2
    .return ($N2)
neg_seven: # arctanh(x) = .5 * (ln (1+x) - ln (1 -x))
    $N1 = op2 + 1
    $N1 = ln $N1
    $N2 = 1 - op2
    $N2 = ln $N2
    $N1 = $N1 - $N2
    $N1 *= 0.5
    .return ($N1)
.end

.sub unicode:"dyadic:\u235f"          # logarithm
    .param num op1
    .param num op2
    $N1 = ln op1
    $N2 = ln op2
    $N3 = $N1 / $N2
    .return($N3)
.end

# This somewhat convoluted based the description from the old APL/360 manual
.sub 'dyadic:|'               # logarithm
    .param num op1
    .param num op2
    if op1 == 0 goto zero_LHS
    op1 = abs op1
    $N1 = op2 / op1
    $I1 = floor $N1
    $N1 = op1 * $I1
    $N2 = op2 - $N1
    .return($N2)
zero_LHS:
    if op2 < 0 goto neg_RHS
    .return(op2)
neg_RHS:
    %% DOMAIN_ERROR %%
.end

.sub 'monadic:~'               # not
    .param num op1
    # XXX is domain only 0,1?
    $I1 = op1
    if $I1 goto true
    .return(1)
true:
    .return(0)
.end

.sub unicode:"monadic:\u233d" :multi(APLVector) # reverse
    .param pmc op1

    .local pmc result,iter
    result = new 'APLVector'
    iter = new 'Iterator', op1

loop:
    unless iter goto done
    $P1 = shift iter
    unshift result, $P1
    goto loop
done:
    .return(result)
.end

.sub 'dyadic:~' :multi(APLVector, APLVector) # without
    .param pmc op1
    .param pmc op2

    .local pmc result
    result = new 'APLVector'

    .local pmc iter1,iter2
    iter1 = new 'Iterator', op1

outer_loop:
    unless iter1 goto outer_done
    $P1 = shift iter1

    iter2 = new 'Iterator', op2
inner_loop:
    unless iter2 goto inner_done
    $P2 = shift iter2
    if $P1 == $P2 goto outer_loop # result must be without this.
    goto inner_loop

inner_done:
    push result, $P1
    goto outer_loop

outer_done:
    .return(result)
.end

.sub unicode:"monadic:\u2191" # first
    .param pmc op1
    $P1 = shift op1
    .return ($P1)
.end

.sub unicode:"dyadic:\u2191" :multi (Float, APLVector) # take
    .param int op1
    .param pmc op2

    .local pmc result
    result = new 'APLVector'

    .local pmc iter
    iter = new 'Iterator', op2

    if op1 >= 0 goto pos_loop
    iter = 4 # ITERATE_FROM_END

neg_loop:
    if op1 == 0 goto done
    unless iter goto done

    $P1 = pop op2  # have to pop when iterating from end.
    unshift result, $P1

    inc op1
    goto neg_loop

pos_loop:
    if op1 == 0 goto done
    unless iter goto done

    $P1 = shift iter
    push result, $P1

    dec op1
    goto pos_loop

done:
    .return (result)
.end

.sub unicode:"dyadic:\u2193" :multi (Float, APLVector) # drop
    .param int op1
    .param pmc op2

    if op1 < 0 goto neg_loop

pos_loop:
    if op1 == 0 goto done
    $P1 = shift op2 # ignore p1, we're discarding it
    dec op1
    goto pos_loop

neg_loop:
    if op1 == 0 goto done
    $P1 = pop op2 # ignore p1, we're discarding it
    inc op1
    goto neg_loop

done:
    .return (op2)
.end

.sub unicode:"monadic:\u2374" :multi (Float) # shape
    .param pmc op1

    .local pmc result
    result = new 'APLVector'
    .return (result)
.end

.sub unicode:"monadic:\u2374" :multi (APLVector) # shape
    .param pmc op1
    .tailcall op1.'get_shape'()
.end

.sub unicode:"dyadic:\u2374" :multi (APLVector,APLVector) # reshape
    .param pmc op1
    .param pmc op2

    # XXX is a clone needed here?
    op2.'set_shape'(op1)
    .return (op2)
.end

.sub unicode:"dyadic:\u2374" :multi (APLVector,Float) # reshape
    .param pmc op1
    .param pmc op2

    # Convert the scalar into a vector and reshape it.
    $P1 = new 'APLVector'
    push $P1, op2
    $P1.'set_shape'(op1)
    .return ($P1)
.end


.sub unicode:"monadic:\u2355" #format
    .param pmc op1

    $S0 = aplformat(op1)
    .local pmc result
    result = new 'APLVector'
    $I0 = 0
    $I1 = length $S0
  loop:
    if $I0 >= $I1 goto loop_end
    $S1 = substr $S0, $I0, 1
    push result, $S1
    inc $I0
    goto loop
  loop_end:
    .return(result)
.end

.sub unicode:"monadic:\u2395\u2190" # quad output
    .param pmc op1

    'aplprint'(op1)
    .return(op1)
.end


END_OF_TEMPLATE

# Generate all variants for scalar dyadic ops.
my @type_pairs = (
    [ 'Float',     'Float' ],
    [ 'Float',     'APLVector' ],
    [ 'APLVector', 'Float' ],
    [ 'APLVector', 'APLVector' ],
);

foreach my $operator ( keys %scalar ) {
    my ( $name, $code ) = @{ $scalar{$operator} };
    foreach my $types (@type_pairs) {
        my ( $type1, $type2 ) = @$types;

        $template .= <<"END_PREAMBLE";


# $name
.sub unicode:"dyadic:$operator" :multi ( $type1, $type2 )
    .param pmc op1
    .param pmc op2
END_PREAMBLE

        if ( $type1 eq "Float" && $type2 eq "Float" ) {

            # scalar to scalar..
            $template .= interpolate( $code, 'op1', 'op2' );
        }
        elsif ( $type1 eq "APLVector" && $type2 eq "APLVector" ) {

            # vector to vector
            $template .= << 'END_PIR';
    # Verify Shapes conform.
    $I1 = op1
    $I2 = op2
    if $I1 == $I2 goto good
    %% DOMAIN_ERROR %%
  good:
    # Create a result vector
    .local pmc result
    result = new 'APLVector'
    # Loop through each vector, doing the ops.
    .local pmc iter1, iter2
    iter1 = new 'Iterator', op1
    iter2 = new 'Iterator', op2
  loop:
    unless iter1 goto loop_done
    $P1 = shift iter1
    $P2 = shift iter2
    $S1 = typeof $P1
    if $S1 == 'String' goto bad_args
    $S2 = typeof $P2
    if $S2 == 'String' goto bad_args
    goto got_args
  bad_args:
    %% DOMAIN_ERROR %%
  got_args:
END_PIR

            $template .= interpolate( $code, '$P1', '$P2' );

            $template .= << 'END_PIR';

    push result, $P1
    goto loop
loop_done:
    # return the result vector
    .return (result)
END_PIR

        }
        else {

            # Vector to Scalar
            my ( $vector, $scalar, @order );
            if ( $type1 eq 'APLVector' ) {
                $vector = "op1";
                $scalar = "op2";
                @order  = qw/ $P1 $P2 /;
            }
            else {
                $vector = "op2";
                $scalar = "op1";
                @order  = qw/ $P2 $P1 /;
            }

            $template .= << "END_PIR";
    # Create a result vector
    .local pmc result
    result = new 'APLVector'
    # Loop through each vector, doing the ops.
    .local pmc iter1
    iter1 = new 'Iterator', $vector
  loop:
    unless iter1 goto loop_done
    \$P1 = shift iter1
    \$S1 = typeof \$P1
    if \$S1 != 'String' goto got_args
    %% DOMAIN_ERROR %%
  got_args:
        \$P2 = clone $scalar
END_PIR

            $template .= interpolate( $code, @order );

            $template .= 'push result, ' . $order[0] . "\n";

            $template .= << 'END_PIR';
    goto loop
loop_done:
    # return the result vector
    .return (result)
END_PIR
        }

        $template .= <<"END_POSTAMBLE"
    .return (op1) # might be pre-empted
.end
END_POSTAMBLE
    }
}

# Substitute all macros
foreach my $macro ( keys %macros ) {
    $template =~ s/%% \s+ $macro \s+ %%/$macros{$macro}/gx;
}

print $template;

# Given a code snippet, convert it to something usable in the generated file

sub interpolate {
    my $code = shift;
    my $op1  = shift;
    my $op2  = shift;
    $code =~ s/%1/$op1/g;
    $code =~ s/%2/$op2/g;
    $code .= "\n";
    return ($code);
}

__END__

=head1 NAME

tools/gen_operator_defs.pl - Generate the definitions for all the various
APL operators in all possible configurations.

=head1 LICENSE

Copyright (C) 2005-2006, The Perl Foundation.

This is free software; you may redistribute it and/or modify
it under the same terms as Parrot.

=cut


# Local Variables:
#   mode: cperl
#   cperl-indent-level: 4
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4:
