------------------------------------------------------------------------------
-- BUSH Arrays Package Parser                                               --
--                                                                          --
-- Part of BUSH                                                             --
------------------------------------------------------------------------------
--                                                                          --
--              Copyright (C) 2001-2005 Ken O. Burtch & FSF                 --
--                                                                          --
-- This is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
-- sion.  This is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with this;  see file COPYING.  If not, write --
-- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
-- MA 02111-1307, USA.                                                      --
--                                                                          --
-- This is maintained at http://www.pegasoft.ca                             --
--                                                                          --
------------------------------------------------------------------------------

with gnat.bubble_sort_a,
     gnat.heap_sort_a,
     ada.numerics.float_random,
     bush_os,
     string_util,
     world,
     scanner_arrays,
     parser,
     parser_aux;
use  bush_os,
     string_util,
     world,
     scanner_arrays,
     parser,
     parser_aux;

package body parser_arrays is


---------------------------------------------------------
-- PARSE THE FILES PACKAGE
---------------------------------------------------------

procedure ParseArraysFirst( f : out unbounded_string; kind : out identifier ) is
  -- Syntax: arrays.first( arraytypeorvar );
  -- Source: arraytypeorvar'first
  var_id   : identifier;
  array_id : arrayID;
begin
  expect( arrays_first_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     array_id := arrayID( to_numeric( identifiers( var_id ).value ) );
     kind := indexType( array_id );
     f := to_unbounded_string( long_integer'image( firstBound( array_id ) ) );
  end if;
end ParseArraysFirst;

procedure ParseArraysLast( f : out unbounded_string; kind : out identifier ) is
  -- Syntax: arrays.last( arraytypeorvar );
  -- Source: arraytypeorvar'last
  var_id   : identifier;
  array_id : arrayID;
begin
  expect( arrays_last_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     array_id := arrayID( to_numeric( identifiers( var_id ).value ) );
     kind := indexType( array_id );
     f := to_unbounded_string( long_integer'image( lastBound( array_id ) ) );
  end if;
end ParseArraysLast;

procedure ParseArraysLength( f : out unbounded_string ) is
  -- Syntax: arrays.length( arraytypeorvar );
  -- Source: arraytypeorvar'length
  var_id   : identifier;
  array_id : arrayID;
begin
  expect( arrays_length_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     array_id := arrayID( to_numeric( identifiers( var_id ).value ) );
     f := to_unbounded_string( long_integer'image( lastBound( array_id ) - firstBound( array_id ) + 1 ) );
  end if;
end ParseArraysLength;

-------------------------------------------------------------------------------
-- Array Sorts 
-------------------------------------------------------------------------------

-- Stuff for Sorting with GNAT packages
-- I'm concerned that GNAT uses integers for indexes in sorts...may not
-- work with long_integer arrays on some platforms...assuming peole would
-- have such friggin' huge arrays in the first place.

arrayIdBeingSorted     : arrayID;
offsetArrayBeingSorted : long_integer;
ZeroElement            : unbounded_string;

procedure moveElement( From, To : natural ) is
  data : unbounded_string;
begin
  if From = 0 then
     data := ZeroElement;
  else
     data := arrayElement( arrayIdBeingSorted, long_integer(From)+offsetArrayBeingSorted);
  end if;
  if To = 0 then
     ZeroElement := data;
  else
     assignElement( arrayIdBeingSorted, long_integer(To)+offsetArrayBeingSorted, data );
   end if;
end moveElement;

function Lt_string( Op1, Op2 : natural ) return boolean is
  data1, data2 : unbounded_string;
begin
  if Op1 = 0 then
     data1 := ZeroElement;
  else
     data1 := arrayElement( arrayIdBeingSorted, long_integer( Op1 )+offsetArrayBeingSorted);
  end if;
  if Op2 = 0 then
     data2 := ZeroElement;
  else
     data2 := arrayElement( arrayIdBeingSorted, long_integer( Op2 )+offsetArrayBeingSorted);
  end if;
  return data1 < data2;
end Lt_string;

function Lt_string_descending( Op1, Op2 : natural ) return boolean is
  data1, data2 : unbounded_string;
begin
  if Op1 = 0 then
     data1 := ZeroElement;
  else
     data1 := arrayElement( arrayIdBeingSorted, long_integer( Op1 )+offsetArrayBeingSorted);
  end if;
  if Op2 = 0 then
     data2 := ZeroElement;
  else
     data2 := arrayElement( arrayIdBeingSorted, long_integer( Op2 )+offsetArrayBeingSorted);
  end if;
  return data1 > data2;
end Lt_string_descending;

function Lt_numeric( Op1, Op2 : natural ) return boolean is
  data1, data2 : unbounded_string;
begin
  if Op1 = 0 then
     data1 := ZeroElement;
  else
     data1 := arrayElement( arrayIdBeingSorted, long_integer( Op1 )+offsetArrayBeingSorted);
  end if;
  if Op2 = 0 then
     data2 := ZeroElement;
  else
     data2 := arrayElement( arrayIdBeingSorted, long_integer( Op2 )+offsetArrayBeingSorted);
  end if;
  return to_numeric( data1 ) < to_numeric( data2 );
end Lt_numeric;

function Lt_numeric_descending( Op1, Op2 : natural ) return boolean is
  data1, data2 : unbounded_string;
begin
  if Op1 = 0 then
     data1 := ZeroElement;
  else
     data1 := arrayElement( arrayIdBeingSorted, long_integer( Op1 )+offsetArrayBeingSorted);
  end if;
  if Op2 = 0 then
     data2 := ZeroElement;
  else
     data2 := arrayElement( arrayIdBeingSorted, long_integer( Op2 )+offsetArrayBeingSorted);
  end if;
  return to_numeric( data1 ) > to_numeric( data2 );
end Lt_numeric_descending;

procedure ParseArraysBubbleSort is
  var_id : identifier;
  first, last : long_integer;
  kind   : identifier;
begin
  expect( arrays_bubble_sort_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     offsetArrayBeingSorted := first-1;
     kind := getUniType( identifiers( var_id ).kind );
     if kind = uni_string_t or kind = universal_t then
        GNAT.Bubble_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_string'access );
     elsif kind = uni_numeric_t or kind = root_enumerated_t then
        GNAT.Bubble_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_numeric'access );
     else
        err( "unable to sort this element type" );
     end if;
  end if;
end ParseArraysBubbleSort;

procedure ParseArraysBubbleSortDescending is
  var_id : identifier;
  first, last : long_integer;
  kind   : identifier;
begin
  expect( arrays_bubble_sort_descending_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     offsetArrayBeingSorted := first-1;
     kind := getUniType( identifiers( var_id ).kind );
     if kind = uni_string_t or kind = universal_t then
        GNAT.Bubble_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_string_descending'access );
     elsif kind = uni_numeric_t or kind = root_enumerated_t then
        GNAT.Bubble_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_numeric_descending'access );
     else
        err( "unable to sort this element type" );
     end if;
  end if;
end ParseArraysBubbleSortDescending;

procedure ParseArraysHeapSort is
  var_id : identifier;
  first, last : long_integer;
  kind   : identifier;
begin
  expect( arrays_heap_sort_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     offsetArrayBeingSorted := first-1;
     kind := getUniType( identifiers( var_id ).kind );
     if kind = uni_string_t or kind = universal_t then
        GNAT.Heap_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_string'access );
     elsif kind = uni_numeric_t or kind = root_enumerated_t then
        GNAT.Heap_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_numeric'access );
     else
        err( "unable to sort this element type" );
     end if;
  end if;
end ParseArraysHeapSort;

procedure ParseArraysHeapSortDescending is
  var_id : identifier;
  first, last : long_integer;
  kind   : identifier;
begin
  expect( arrays_heap_sort_descending_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     offsetArrayBeingSorted := first-1;
     kind := getUniType( identifiers( var_id ).kind );
     if kind = uni_string_t or kind = universal_t then
        GNAT.Heap_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_string_descending'access );
     elsif kind = uni_numeric_t or kind = root_enumerated_t then
        GNAT.Heap_Sort_A.Sort( natural( last - first ) + 1, moveElement'access, lt_numeric_descending'access );
     else
        err( "unable to sort this element type" );
     end if;
  end if;
end ParseArraysHeapSortDescending;

procedure ParseArraysShuffle is
  var_id : identifier;
  first, last : long_integer;
  newpos : natural;
  len    : long_integer;
  array_id : arrayID;
begin
  expect( arrays_shuffle_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     array_id := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( array_id );
     last  := lastBound( array_id );
     len   := last-first+1;
     for i in 1..len loop
       newpos := natural( 1.0 + long_float'truncation( long_float( len ) *
             long_float( Ada.Numerics.Float_Random.Random( random_generator
) ) ) );

         moveElement( integer(i), 0 );
         moveElement( integer(newpos), integer(i) );
         moveElement( 0, integer(newpos) );
     end loop;
  end if;
end ParseArraysShuffle;

procedure ParseArraysReverse is
  var_id : identifier;
  first, last : long_integer;
  newpos : natural;
  len    : long_integer;
  array_id : arrayID;
begin
  expect( arrays_reverse_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     array_id := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( array_id );
     last  := lastBound( array_id );
     len   := last-first+1;
     for i in 1..len loop
         newpos := natural( len - i );
         moveElement( integer(i), 0 );
         moveElement( integer(newpos), integer(i) );
         moveElement( 0, integer(newpos) );
     end loop;
  end if;
end ParseArraysReverse;

procedure ParseArraysShiftRight is
  var_id : identifier;
  first, last : long_integer;
  len    : long_integer;
begin
  expect( arrays_shift_right_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     len   := last-first+1;
     offsetArrayBeingSorted := first-1;
     for i in reverse 1..len-1 loop
         moveElement( integer(i), integer(i+1) );
     end loop;
  end if;
end ParseArraysShiftRight;

procedure ParseArraysShiftLeft is
  var_id : identifier;
  first, last : long_integer;
  len    : long_integer;
begin
  expect( arrays_shift_left_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     len   := last-first+1;
     offsetArrayBeingSorted := first-1;
     for i in 1..len-1 loop
         moveElement( integer(i+1), integer(i) );
     end loop;
  end if;
end ParseArraysShiftLeft;

procedure ParseArraysRotateRight is
  var_id : identifier;
  first, last : long_integer;
  len    : long_integer;
begin
  expect( arrays_rotate_right_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     len   := last-first+1;
     offsetArrayBeingSorted := first-1;
     moveElement( integer( len ), 0 );
     for i in reverse 1..len-1 loop
         moveElement( integer(i), integer(i+1) );
     end loop;
     moveElement( 0, 1 );
  end if;
end ParseArraysRotateRight;

procedure ParseArraysRotateLeft is
  var_id : identifier;
  first, last : long_integer;
  len    : long_integer;
begin
  expect( arrays_rotate_left_t );
  expect( symbol_t, "(" );
  ParseIdentifier( var_id );
  if identifiers( var_id ).class = typeClass or identifiers( var_id ).class = subClass then
     var_id := getBaseType( var_id );
     if not identifiers( var_id ).list then
        err( "Array or array type expected" );
     end if;
  elsif not (class_ok( var_id, otherClass ) and identifiers( var_id ).list) then
     err( "Array or array type expected" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     arrayIdBeingSorted := arrayID( to_numeric( identifiers( var_id ).value ) );
     first := firstBound( arrayIdBeingSorted );
     last  := lastBound( arrayIdBeingSorted );
     len   := last-first+1;
     offsetArrayBeingSorted := first-1;
     moveElement( 1, 0 );
     for i in 1..len-1 loop
         moveElement( integer(i+1), integer(i) );
     end loop;
     moveElement( 0, integer( len ) );
  end if;
end ParseArraysRotateLeft;

-------------------------------------------------------------------------------
-- Housekeeping
-------------------------------------------------------------------------------

procedure StartupArrays is
begin
  declareFunction( arrays_first_t, "arrays.first" );
  declareFunction( arrays_last_t, "arrays.last" );
  declareFunction( arrays_length_t, "arrays.length" );
  declareProcedure( arrays_bubble_sort_t, "arrays.bubble_sort" );
  declareProcedure( arrays_bubble_sort_descending_t, "arrays.bubble_sort_descending" );
  declareProcedure( arrays_heap_sort_t, "arrays.heap_sort" );
  declareProcedure( arrays_heap_sort_descending_t, "arrays.heap_sort_descending" );
  declareProcedure( arrays_shuffle_t, "arrays.shuffle" );
  declareProcedure( arrays_reverse_t, "arrays.reverse" );
  declareProcedure( arrays_rotate_left_t, "arrays.rotate_left" );
  declareProcedure( arrays_rotate_right_t, "arrays.rotate_right" );
  declareProcedure( arrays_shift_left_t, "arrays.shift_left" );
  declareProcedure( arrays_shift_right_t, "arrays.shift_right" );
end StartupArrays;

procedure ShutdownArrays is
begin
  null;
end ShutdownArrays;

end parser_arrays;
