#!/usr/bin/env raku

# This script reads the Array/Slice.rakumod file, and generates the
# necessary classes for inclusion in the postcircumfix:<[ ]> table
# for accessing array slices, and writes it back to the file.
#
# NOTE: the usefulness of this script is limited: once the 50x
# performance penalty on calling a private method of a consuming
# class from a role is gone, the code can be simplified to making
# a role for lazy and a role for non-lazy, and have the generated
# classes be simple consumers of those roles.  This performance
# improvement is expected to occur with the merging of the RakuAST
# work.

# always use highest version of Raku
use v6.*;

my $generator = $*PROGRAM-NAME;
my $generated = DateTime.now.gist.subst(/\.\d+/,'');
my $start     = '#- start of generated part of array slice access';
my $end       = '#- end of generated part of array slice access';

# slurp the whole file and set up writing to it
my $filename = "src/core.c/Array/Slice.rakumod";
my @lines = $filename.IO.lines;
$*OUT = $filename.IO.open(:w);

# hash of lazy code seen, to avoid duplications in !accept-lazy method sources
my %lazycode-seen;
my constant $prefix = 'same as lazy ';
my constant $offset = $prefix.chars;

# for all the lines in the source that don't need special handling
while @lines {
    my $line := @lines.shift;

    # nothing to do yet
    unless $line.starts-with($start) {
        say $line;
        next;
    }

    # found header
    say $start ~ " " ~ "-" x 79 - $start.chars;
    say "#- Generated on $generated by $generator";
    say "#- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE";

    # skip the old version of the code
    while @lines {
        last if @lines.shift.starts-with($end);
    }

    for (
      'no actionable adverbs',
      'none',
      Q:to/CODE/,
        nqp::push($!result,throw-if-Failure $!iterable.AT-POS(pos));
CODE
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,$_)
          !! ($!done = 1);
CODE

      ':kv',
      'kv',
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,$_);
        }
CODE
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,$_);
        }
        else {
            $!done = 1;
        }
CODE

      ':!kv',
      'not-kv',
      Q:to/CODE/,
        nqp::push($!result,pos);
        nqp::push($!result,throw-if-Failure $!iterable.AT-POS(pos));
CODE
      "same as lazy :kv",

      ':p',
      'p',
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        nqp::push($!result,Pair.new(pos,$_)) if $!iterable.EXISTS-POS(pos);
CODE
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,Pair.new(pos,$_))
          !! ($!done = 1);
CODE

      ':!p',
      'not-p',
      Q:to/CODE/,
        nqp::push(
          $!result,
          Pair.new(pos,throw-if-Failure $!iterable.AT-POS(pos))
        );
CODE
      "same as lazy :p",

      ':k',
      'k',
      Q:to/CODE/,
        throw-if-Failure $!iterable.AT-POS(pos);
        nqp::push($!result,pos) if $!iterable.EXISTS-POS(pos);
CODE
      Q:to/CODE/,
        throw-if-Failure $!iterable.AT-POS(pos);
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,pos)
          !! ($!done = 1);
CODE

      ':!k',
      'not-k',
      Q:to/CODE/,
        throw-if-Failure $!iterable.AT-POS(pos);
        nqp::push($!result,pos);
CODE
      "same as lazy :k",

      ':v',
      'v',
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        nqp::push($!result,$_) if $!iterable.EXISTS-POS(pos);
CODE
      Q:to/CODE/,
        $_ := throw-if-Failure $!iterable.AT-POS(pos);
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,$_)
          !! ($!done = 1);
CODE

      ':exists',
      'exists',
      Q:to/CODE/,
        nqp::push($!result,$!iterable.EXISTS-POS(pos));
CODE
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,True)
          !! ($!done = 1);
CODE

      ':exists:kv',
      'exists-kv',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,True);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,True);
        }
        else {
            $!done = 1;
        }
CODE

      ':exists:!kv',
      'exists-not-kv',
      Q:to/CODE/,
        nqp::push($!result,pos);
        nqp::push($!result,$!iterable.EXISTS-POS(pos));
CODE
      "same as lazy :exists:kv",

      ':exists:p',
      'exists-p',
      Q:to/CODE/,
        nqp::push($!result,Pair.new(pos,True))
          if $!iterable.EXISTS-POS(pos);
CODE
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,Pair.new(pos,True))
          !! ($!done = 1);
CODE

      ':exists:!p',
      'exists-not-p',
      Q:to/CODE/,
        nqp::push($!result,Pair.new(pos,$!iterable.EXISTS-POS(pos)));
CODE
      "same as lazy :exists:p",

      ':exists:delete',
      'exists-delete',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,True);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            nqp::push($!result,False);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,True);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':exists:delete:kv',
      'exists-delete-kv',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
            nqp::push($!result,True);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
            nqp::push($!result,True);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':exists:delete:!kv',
      'exists-delete-not-kv',
      Q:to/CODE/,
        nqp::push($!result,pos);
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,True);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            nqp::push($!result,False);
        }
CODE
      "same as lazy :exists:delete:kv",

      ':exists:delete:p',
      'exists-delete-p',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,True));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,True));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':exists:delete:!p',
      'exists-delete-not-p',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,True));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            nqp::push($!result,Pair.new(pos,False));
        }
CODE
      "same as lazy :exists:delete:p",

      ':!exists',
      'not-exists',
      Q:to/CODE/,
        nqp::push($!result,!$!iterable.EXISTS-POS(pos));
CODE
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,False)
          !! ($!done = 1);
CODE

      ':!exists:kv',
      'not-exists-kv',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,False);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,False);
        }
        else {
            $!done = 1;
        }
CODE

      ':!exists:!kv',
      'not-exists-not-kv',
      Q:to/CODE/,
        nqp::push($!result,pos);
        nqp::push($!result,!$!iterable.EXISTS-POS(pos));
CODE
      "same as lazy :!exists:kv",

      ':!exists:p',
      'not-exists-p',
      Q:to/CODE/,
        nqp::push($!result,Pair.new(pos,False))
          if $!iterable.EXISTS-POS(pos);
CODE
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,Pair.new(pos,False))
          !! ($!done = 1);
CODE

      ':!exists:!p',
      'not-exists-not-p',
      Q:to/CODE/,
        nqp::push($!result,Pair.new(pos,!$!iterable.EXISTS-POS(pos)));
CODE
      "same as lazy :!exists:p",

      ':!exists:delete',
      'not-exists-delete',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,False);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            nqp::push($!result,True);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,False);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':!exists:delete:kv',
      'not-exists-delete-kv',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
            nqp::push($!result,False);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
            nqp::push($!result,False);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':!exists:delete:!kv',
      'not-exists-delete-not-kv',
      Q:to/CODE/,
        throw-if-Failure $!iterable.AT-POS(pos);
        nqp::push($!result,pos);
        self!delete(pos)
          unless nqp::push($!result,!$!iterable.EXISTS-POS(pos));
CODE
      "same as lazy :!exists:delete:kv",

      ':!exists:delete:p',
      'not-exists-delete-p',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,False));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,False));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':!exists:delete:!p',
      'not-exists-delete-not-p',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,Pair.new(pos,False));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            nqp::push($!result,Pair.new(pos,True));
        }
CODE
      "same as lazy :!exists:delete:p",

      ':delete',
      'delete',
      Q:to/CODE/,
        throw-if-Failure nqp::push($!result,self!delete(pos));
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,self!delete(pos));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':delete:kv',
      'delete-kv',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,self!delete(pos));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,pos);
            nqp::push($!result,self!delete(pos));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':delete:kv',
      'delete-not-kv',
      Q:to/CODE/,
        nqp::push($!result,pos);
        nqp::push($!result,throw-if-Failure self!delete(pos));
CODE
      "same as lazy :delete:kv",

      ':delete:p',
      'delete-p',
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,Pair.new(pos,self!delete(pos)))
          !! throw-if-Failure($!iterable.AT-POS(pos));
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,Pair.new(pos,self!delete(pos)));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':delete:!p',
      'delete-not-p',
      Q:to/CODE/,
        nqp::push($!result,Pair.new(pos,throw-if-Failure self!delete(pos)));
CODE
      "same as lazy :delete:p",

      ':delete:k',
      'delete-k',
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
        }
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            self!delete(pos);
            nqp::push($!result,pos);
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

      ':delete:!k',
      'delete-not-k',
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? self!delete(pos)
          !! throw-if-Failure($!iterable.AT-POS(pos));
        nqp::push($!result,pos);
CODE
      "same as lazy :delete:k",

      ':delete:v',
      'delete-v',
      Q:to/CODE/,
        $!iterable.EXISTS-POS(pos)
          ?? nqp::push($!result,self!delete(pos))
          !! throw-if-Failure($!iterable.AT-POS(pos));
CODE
      Q:to/CODE/,
        if $!iterable.EXISTS-POS(pos) {
            nqp::push($!result,self!delete(pos));
        }
        else {
            throw-if-Failure $!iterable.AT-POS(pos);
            $!done = 1;
        }
CODE

    ) -> $comment, $class, $code, $lazycode is copy {

        # make sure we have the right lazy code
        if $lazycode.starts-with($prefix) {
            $lazycode = %lazycode-seen{$lazycode.substr($offset)};
        }
        else {
            %lazycode-seen{$comment} = $lazycode;
        }

        # set up template values
        my %mapper =
          comment  => $comment,
          class    => $class,
          code     => $code.chomp,
          lazycode => $lazycode.chomp,
          delete   => ($comment.contains('delete')
            ?? Q:to/CODE/

    # Helper method for deleting elements, making sure that the total number
    # of elements is fixed from *before* the first deletion, so that relative
    # positions such as *-1 will continue to refer to the same position,
    # even if the last element of an array was removed (which shortens the
    # array).
    method !delete(\pos) {
        $!elems := $!iterable.elems if nqp::isnull($!elems);
        $!iterable.DELETE-POS(pos)
    }
CODE
            !! ""),
        ;

        # spurt this class
        say Q:to/SOURCE/.subst(/ '#' (\w+) '#' /, -> $/ { %mapper{$0} }, :g).chomp;

# #comment#
my class Array::Slice::Access::#class# is implementation-detail {
    has $!result;
    has $!elems;
    has $!iterable;
    has int $!done;

    # Helper sub to throw any failures being produced immediately
    my sub throw-if-Failure(Mu \value) is raw {
        nqp::istype(value,Failure)
          ?? value.throw
          !! value
    }

    method !accept(\pos --> Nil) {
#code#
    }
    method !accept-lazy(\pos --> Nil) {
#lazycode#
    }

    method !SET-SELF(\iterable) {
        $!result   := nqp::create(IterationBuffer);
        $!elems    := nqp::null;
        $!iterable := iterable;
        self
    }
    method !elems() {
        nqp::ifnull($!elems,$!elems := $!iterable.elems)
    }
    method new(\iterable) { nqp::create(self)!SET-SELF(iterable) }
#delete#
    # Handle iterator in the generated positions: this will add a List
    # with the elements pointed to by the iterator to the result.  Because
    # these positions can also be non-Int, some trickery needs to be
    # done to allow this being called recursively.
    method !handle-iterator(\iterator) {

        # basically push the current result on a stack
        my int $mark = nqp::elems($!result);

        # Lazy iterators should halt as soon as a non-existing element is seen
        if iterator.is-lazy {
            nqp::until(
              $!done
                || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd),
              nqp::if(
                nqp::istype(pos,Int),
                self!accept-lazy(pos),
                self!handle-nonInt-lazy(pos)
              )
            );

            # We're only done on this level, not generally
            $!done = 0;
        }

        # Fast path for non-lazy iterators
        else {
            nqp::until(
              nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd),
              nqp::if(
                nqp::istype(pos,Int),
                self!accept(pos),
                self!handle-nonInt(pos)
              )
            );
        }

        # Take what was added and push it as a List
        if nqp::isgt_i(nqp::elems($!result),$mark) {
            my $buffer;
            if $mark {
                $buffer :=
                  nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1));
                nqp::setelems($!result,$mark);
            }
            else {
                $buffer  := $!result;
                $!result := nqp::create(IterationBuffer);
            }
            nqp::push($!result,$buffer.List);
        }
    }

    # Handle anything non-integer in the generated positions eagerly
    method !handle-nonInt(Mu \pos) {
        nqp::istype(pos,Iterable)
          ?? nqp::iscont(pos)
            ?? self!accept(pos.Int)
            !! self!handle-iterator(pos.iterator)
          !! nqp::istype(pos,Callable)
            ?? nqp::istype((my $real := (pos)(self!elems)),Int)
              ?? self!accept($real)
              !! self!handle-nonInt($real)
            !! nqp::istype(pos,Whatever)
              ?? self!handle-iterator(
                   Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1))
                 )
              !! self!accept(pos.Int)
    }

    # Handle anything non-integer in the generated positions lazily
    method !handle-nonInt-lazy(Mu \pos) {
        nqp::istype(pos,Iterable)
          ?? nqp::iscont(pos)
            ?? self!accept-lazy(pos.Int)
            !! self!handle-iterator(pos.iterator)
          !! nqp::istype(pos,Callable)
            ?? nqp::istype((my $real := (pos)(self!elems)),Int)
              ?? self!accept-lazy($real)
              !! self!handle-nonInt-lazy($real)
            !! nqp::istype(pos,Whatever)
              ?? self!handle-iterator(
                   Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1))
                 )
              !! self!accept-lazy(pos.Int)
    }

    # The actual building of the result
    method slice(\iterator) {

        if iterator.is-lazy {
            nqp::until(
              $!done
                || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd),
              nqp::if(
                nqp::istype(pos,Int),
                self!accept-lazy(pos),
                self!handle-nonInt-lazy(pos)
              )
            );
        }
        else {
            nqp::until(
              nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd),
              nqp::if(
                nqp::istype(pos,Int),
                self!accept(pos),
                self!handle-nonInt(pos)
              )
            );
        }

        $!result.List
    }
}
SOURCE
    }

    # we're done for this combination of adverbs
    say "";
    say "#- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE";
    say $end ~ " " ~ "-" x 79 - $end.chars;
}

# close the file properly
$*OUT.close;

# vim: expandtab sw=4
