--          This file is part of SmartEiffel The GNU Eiffel Compiler.
--       Copyright (C) 1994-2002 LORIA - INRIA - U.H.P. Nancy 1 - FRANCE
--          Dominique COLNET and Suzanne COLLIN - SmartEiffel@loria.fr
--                       http://SmartEiffel.loria.fr
-- SmartEiffel is  free  software;  you can  redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the Free
-- Software  Foundation;  either  version  2, or (at your option)  any  later
-- version. SmartEiffel is distributed in the hope that it will be useful,but
-- WITHOUT 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  along  with  SmartEiffel;  see the file COPYING.  If not,
-- write to the  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-- Boston, MA 02111-1307, USA.
--
class PARENT
   --
   -- To store the inheritance options for one parent of a class.
   --

inherit GLOBALS

creation make

feature

   type: E_TYPE
         -- Declaration type mark of the parent.

   base_class: BASE_CLASS
	 -- An alias for `type.base_class'.

   base_class_name: STRING
	 -- An alias for `base_class.name.to_string'.

feature {PARENT}

   parent_list: PARENT_LIST
         -- The corresponding one

feature {PARENT} -- Optionnal list in syntaxical order:

   rename_list: RENAME_LIST

   export_list: EXPORT_LIST

   undefine_list: FEATURE_NAME_LIST

   redefine_list: FEATURE_NAME_LIST

   select_list: FEATURE_NAME_LIST

feature {PARENT_LIST}

   start_position: POSITION is
      do
         Result := type.start_position
      end

   id_extra_information(tfw: TEXT_FILE_WRITE) is
      do
         tfw.put_integer(base_class.id)
      end

   name_in_parent(fn: FEATURE_NAME): FEATURE_NAME is
         -- Gives Void or the name of `fn' before renaming.
      require
         fn /= Void
      do
         if rename_list = Void then
            Result := fn
         else
            Result := rename_list.name_in_parent(fn)
         end
      end

   do_rename(fn: FEATURE_NAME): like fn is
         -- If some rename exists for `fn', return the new name.
         -- Otherwise, give back `fn'.
      require
         fn /= Void
      do
         if rename_list = Void then
            Result := fn
         else
            Result := rename_list.name_in_child(fn)
            if Result = Void then
               Result := fn
            end
         end
      ensure
         Result /= Void
      end

   has_redefine(fn: FEATURE_NAME): BOOLEAN is
      require
         fn /= Void
      do
         if redefine_list /= Void then
            Result := redefine_list.has(fn)
         end
      end

   look_up_for(rc: RUN_CLASS; fn: FEATURE_NAME): E_FEATURE is
      require
         rc /= Void
         fn /= Void
      local
         pfn: like fn
      do
         if rename_list = Void then
            Result := base_class.look_up_for(rc,fn)
            Result := apply_undefine(Result,fn)
         else
            pfn := rename_list.name_in_parent(fn)
            if pfn /= Void then
               Result := base_class.look_up_for(rc,pfn)
               Result := apply_undefine(Result,fn)
            end
         end
      end

   precursor_for(pc: PRECURSOR_CALL; wrf: RUN_FEATURE): EFFECTIVE_ROUTINE is
         -- Look for the feature for `pc' which is written inside `wrf'.
      local
         original_fn: FEATURE_NAME
         cn: CLASS_NAME
         f: E_FEATURE
	 er: EXTERNAL_ROUTINE
      do
         original_fn := wrf.base_feature.first_name
         if has_redefine(original_fn) then
            cn := pc.parent
            if cn = Void or else cn.to_string = base_class_name then
               f := look_up_for(wrf.run_class,original_fn)
               Result ?= f
               if f /= Void and then Result = Void then
                  error_handler.add_position(pc.start_position)
                  error_handler.add_position(f.start_position)
		  er ?= f
		  if er /= Void then
		     error_handler.append(
                        "Precursor construct not implemented for %
		        %external features (sorry).")
		     error_handler.print_as_fatal_error
		  end
                  error_handler.append(
                     "The Precursor is not an effective routine.")
		  error_handler.print_as_fatal_error
               end
            end
         end
      end

   multiple_check(other: like Current) is
         -- Note : is called twice in order to swap each couple of parents.
      require
         other /= Current
      local
         bc1, bc2: BASE_CLASS; i: INTEGER; fn1, fn2: FEATURE_NAME
      do
         check
            parent_list = other.parent_list
         end
         bc1 := base_class
         bc2 := other.base_class
         if bc1 = bc2 or else
            bc1.is_subclass_of(bc2) or else
            bc2.is_subclass_of(bc1)
          then
            if redefine_list /= Void then
               from
                  i := redefine_list.count
               until
                  i = 0
               loop
                  fn1 := redefine_list.item(i)
                  if other.rename_list = Void then
                  else
                     fn2 := other.rename_list.name_in_child(fn1)
                     if fn2 /= fn1 then
                        if select_list /= Void then
                           if select_list.has(fn1) then
                              if other.select_list /= Void then
                                 if other.select_list.has(fn2) then
                                    select_conflict(fn1,fn2)
                                 end
                              end
                           elseif other.select_list = Void then
                              missing_select(fn1,fn2)
                           elseif not other.select_list.has(fn2) then
                              missing_select(fn1,fn2)
                           end
                        elseif other.select_list = Void then
                           missing_select(fn1,fn2)
                        elseif not other.select_list.has(fn2) then
                           missing_select(fn1,fn2)
                        end
                     end
                  end
                  i := i - 1
               end
            end
         else
            -- Nothing because of swapped duplicate call.
         end
      end

   runnable_type(ct: E_TYPE): E_TYPE is
      require
         ct.is_run_type
      do
         if type.is_generic then
            Result := type.to_runnable(ct)
         else
            Result := type
         end
      ensure
         Result.is_run_type
      end

   is_a_vncg(t1, t2: E_TYPE): BOOLEAN is
      require
         t1.run_type = t1
         t2.run_type = t2
         t2.generic_list /= Void
         error_handler.is_empty
      local
         rank, i: INTEGER; gl, gl1, gl2: ARRAY[E_TYPE]
         tfg: TYPE_FORMAL_GENERIC; rt: E_TYPE; t2_bc: BASE_CLASS
         t2_bcn: STRING
      do
	 t2_bc := t2.base_class
         t2_bcn := t2_bc.name.to_string
         if base_class_name = t2_bcn then -- Here is a good parent:
            gl := type.generic_list
            gl2 := t2.generic_list
            if gl = Void or else gl.count /= gl2.count then
               error_handler.add_position(start_position)
               error_handler.add_position(t2.start_position)
               error_handler.append("Bad number of generic arguments.")
	       error_handler.print_as_fatal_error
            end
            if t1.is_generic then
               gl1 := t1.generic_list
               from
                  Result := true
                  i := gl2.count
               until
                  not Result or else i = 0
               loop
                  if gl.item(i).is_formal_generic then
                     tfg ?= gl.item(i)
                     rank := tfg.rank
                     Result :=  gl1.item(rank).is_a(gl2.item(i))
                  else
                     rt := gl.item(i).to_runnable(t1).run_type
                     Result := rt.is_a(gl2.item(i))
                  end
                  i := i - 1
               end
            elseif type.is_run_type then
               Result := type.is_a(t2)
            else
	       -- Don't know exactely what to do here :(
	       error_handler.add_position(start_position)
	       error_handler.append(
                  "SmartEiffel is not yet able to handle this parent type %
		  %mark (probably because of constrained genericity). You %
		  %should try to inherit something else, more accurate for %
		  %the compiler. Sorry.")
	       error_handler.print_as_fatal_error
            end
            if not Result then error_handler.cancel end
         elseif base_class.is_subclass_of(t2_bc) then
            if t1.is_generic or else not type.is_run_type then
               rt := type.to_runnable(t1).run_type
               Result := base_class.is_a_vncg(rt,t2)
            else
               Result := base_class.is_a_vncg(type,t2)
            end
            if not Result then error_handler.cancel end
         end
      ensure
         error_handler.is_empty
      end

   graph_node_vncg_update(site: POSITION; t1, t2: E_TYPE): BOOLEAN is
      require
	 t1.is_a(t2)
	 t1.run_type = t1
	 t2.run_type = t2
	 t2.generic_list /= Void 
      local
         rank, i: INTEGER; gl, gl1, gl2: ARRAY[E_TYPE]
         tfg: TYPE_FORMAL_GENERIC; rt: E_TYPE; t2_bc: BASE_CLASS
         t2_bcn: STRING
      do
	 t2_bc := t2.base_class
         t2_bcn := t2_bc.name.to_string
         if base_class_name = t2_bcn then -- Here is a good parent:
            gl := type.generic_list
            gl2 := t2.generic_list
            if t1.is_generic then
               gl1 := t1.generic_list
               from
                  Result := true
                  i := gl2.count
               until
                  not Result or else i = 0
               loop
                  if gl.item(i).is_formal_generic then
                     tfg ?= gl.item(i)
                     rank := tfg.rank
                     Result := gn_vncg_update(site,gl1.item(rank),gl2.item(i))
                  else
                     rt := gl.item(i).to_runnable(t1).run_type
                     Result := gn_vncg_update(site,rt,gl2.item(i))
                  end
                  i := i - 1
               end
            elseif type.is_run_type then
               Result := gn_vncg_update(site,type,t2)
            end
         elseif base_class.is_subclass_of(t2_bc) then
            if t1.is_generic or else not type.is_run_type then
               rt := type.to_runnable(t1).run_type
               Result := base_class.graph_node_vncg_update(site,rt,t2)
            else
               Result := base_class.graph_node_vncg_update(site,type,t2)
            end
            if not Result then error_handler.cancel end
         end
      end

   e_feature(fn: FEATURE_NAME): E_FEATURE is
      local
         fn2: FEATURE_NAME
      do
         if rename_list = Void then
            Result := base_class.e_feature(fn)
         else
            fn2 := rename_list.name_in_parent(fn)
            if fn2 /= Void then
               Result := base_class.e_feature(fn2)
            end
         end
      end

   clients_for(fn: FEATURE_NAME): CLIENT_LIST is
      require
         fn /= Void
      local
         fn2: like fn
      do
         if rename_list = Void then
            Result := consider_export(fn)
         else
            fn2 := rename_list.name_in_parent(fn)
            if fn2 /= Void then
	       if export_list /= Void then
		  Result := export_list.clients_for(fn)
	       end
	       if Result = Void then
		  Result := consider_export(fn2)
	       end
            end
         end
      end

   pretty_print is
      local
         end_needed: BOOLEAN
      do
         pretty_printer.set_indent_level(1)
         pretty_printer.indent
         type.pretty_print
         if rename_list = Void and then
            export_list = Void and then
            undefine_list = Void and then
            redefine_list = Void and then
            select_list = Void then
            pretty_printer.put_character(';')
         end
         if comment /= Void then
            pretty_printer.put_character(' ')
            comment.pretty_print
         end
         if rename_list /= Void then
            end_needed := true
            rename_list.pretty_print
         end
         if export_list /= Void then
            end_needed := true
            export_list.pretty_print
         end
         if undefine_list /= Void then
            end_needed := true
            pretty_printer.set_indent_level(2)
            pretty_printer.indent
            pretty_printer.keyword(once "undefine")
            undefine_list.pretty_print
         end
         if redefine_list /= Void then
            end_needed := true
            pretty_printer.set_indent_level(2)
            pretty_printer.indent
            pretty_printer.keyword(once "redefine")
            redefine_list.pretty_print
         end
         if select_list /= Void then
            end_needed := true
            pretty_printer.set_indent_level(2)
            pretty_printer.indent
            pretty_printer.keyword(once "select")
            select_list.pretty_print
         end
         if end_needed then
            pretty_printer.set_indent_level(2)
            pretty_printer.indent
            pretty_printer.keyword(once "end;")
         end
         pretty_printer.set_indent_level(1)
         pretty_printer.indent
      end

   get_started(pl: like parent_list) is
      require
         pl /= Void
      local
         i: INTEGER; wbc: BASE_CLASS; fn, old_fn, new_fn: FEATURE_NAME
         all_check: BOOLEAN
      do
         all_check := ace.all_check
	 parent_list := pl
         base_class := type.base_class
	 base_class_name := base_class.name.to_string
         wbc := parent_list.base_class
         if forbidden_parent_list.fast_has(type.written_mark) and then
	    wbc.name.to_string /= as_integer_32
	  then
            error_handler.add_position(start_position)
            error_handler.append("You cannot inherit %"")
            error_handler.append(type.written_mark)
            error_handler.append("%" (not yet implemented).")
	    error_handler.print_as_fatal_error
         end
         if all_check then
            if base_class.formal_generic_list /= Void then
               if type.generic_list = Void then
                  -- Nothing, because previous call triggers an error message.
               end
            elseif type.is_generic then
               error_handler.add_position(base_class.name.start_position)
               error_handler.add_position(start_position)
               error_handler.append("This class is not generic (VTUG.1).")
	       error_handler.print_as_fatal_error
            end
         end
         if all_check and then rename_list /= Void then
            rename_list.get_started(base_class)
         end
         if all_check and then undefine_list /= Void then
            from
               i := undefine_list.count
            until
               i = 0
            loop
               fn := undefine_list.item(i)
               old_fn := get_old_name(fn)
               if old_fn = Void and then not base_class.has(fn) then
                  error_handler.add_position(fn.start_position)
                  error_handler.append(
                     "Cannot undefine non-existent feature (VDUS.1).")
		  error_handler.print_as_fatal_error
               end
               i := i - 1
            end
         end
         if redefine_list /= Void then
            from
               i := redefine_list.count
            until
               i = 0
            loop
               fn := redefine_list.item(i)
               if not wbc.proper_has(fn) then
                  error_handler.add_position(fn.start_position)
                  error_handler.append("Redefinition not found.")
		  error_handler.print_as_fatal_error
               end
               if all_check then
                  old_fn := get_old_name(fn)
                  if old_fn = Void and then not base_class.has(fn) then
                     error_handler.add_position(fn.start_position)
                     error_handler.append(Vdrs1)
		     error_handler.print_as_fatal_error
                  end
                  new_fn := get_new_name(fn)
                  if new_fn /= Void then
                     error_handler.add_position(new_fn.start_position)
                     error_handler.add_position(fn.start_position)
                     error_handler.append(Vdrs1)
		     error_handler.print_as_fatal_error
                  end
               end
               i := i - 1
            end
         end
         if all_check and then select_list /= Void then
            from
               i := select_list.count
            until
               i = 0
            loop
               fn := select_list.item(i)
               old_fn := get_old_name(fn)
               if old_fn = Void and then not base_class.has(fn) then
                  error_handler.add_position(fn.start_position)
                  error_handler.append(Vmss)
		  error_handler.print_as_fatal_error
               end
               new_fn := get_new_name(fn)
               if new_fn /= Void then
                  if get_old_name(new_fn) = Void then
                     error_handler.add_position(new_fn.start_position)
                     error_handler.add_position(fn.start_position)
                     error_handler.append(Vmss)
		     error_handler.print_as_fatal_error
                  end
               end
               i := i - 1
            end
         end
      ensure
         parent_list = pl
      end

   up_to_original(bottom: BASE_CLASS; top_fn: FEATURE_NAME): FEATURE_NAME is
      local
         old_name: FEATURE_NAME
         bc: BASE_CLASS
      do
         bc := base_class
         if rename_list = Void then
            Result := bc.up_to_original(bottom,top_fn)
         else
            old_name := rename_list.name_in_parent(top_fn)
            if old_name /= Void then
               Result := bc.up_to_original(bottom,old_name)
            end
         end
      end

   original_name(top: BASE_CLASS; bottom_fn: FEATURE_NAME): FEATURE_NAME is
      require
         top /= Void
         bottom_fn /= Void
      local
         old_name: FEATURE_NAME
         bc: BASE_CLASS
      do
         bc := base_class
         if bc = top or else bc.is_subclass_of(top) then
            if rename_list = Void then
               Result := bc.original_name(top,bottom_fn)
            else
               old_name := rename_list.name_in_parent(bottom_fn)
               if old_name /= Void then
                  Result := bc.original_name(top,old_name)
               end
            end
         end
      end

   going_up(trace: FIXED_ARRAY[PARENT]; top: BASE_CLASS
            top_fn: FEATURE_NAME;): FEATURE_NAME is
      local
         bc: like base_class
      do
         bc := base_class
         if bc = top then
            Result := going_down(trace,top_fn)
         elseif base_class_name = as_general then
            Result := going_down(trace,top_fn)
         elseif bc.is_subclass_of(top) then
            trace.add_last(Current)
            Result := bc.going_up(trace,top,top_fn)
         end
      end

   has_select_for(fn: FEATURE_NAME): BOOLEAN is
      require
         fn /= Void
      do
         if select_list /= Void then
            Result := select_list.has(fn)
         end
      end

feature {EIFFEL_PARSER}

   set_comment(c: like comment) is
      do
         comment := c
      end

   add_rename(rp: RENAME_PAIR) is
      require
         rp /= Void
      do
         if rename_list = Void then
            !!rename_list.make(rp)
         else
            rename_list.add_last(rp)
         end
      end

   set_export(el: EXPORT_LIST) is
      require
         el /= Void
      do
         export_list := el
      ensure
         export_list = el
      end

   set_undefine(ul: FEATURE_NAME_LIST) is
      do
         undefine_list := ul
      ensure
         undefine_list = ul
      end

   set_redefine(rl: FEATURE_NAME_LIST) is
      do
         redefine_list := rl
      ensure
         redefine_list = rl
      end

   set_select(sl: like select_list) is
      do
         select_list := sl
      ensure
         select_list = sl
      end

feature {PARENT}

   going_down(trace: FIXED_ARRAY[PARENT]; fn: FEATURE_NAME;): FEATURE_NAME is
      require
         trace /= Void
         fn /= Void
      local
         previous: like Current
      do
         if rename_list = Void then
            Result := fn
         else
            Result := rename_list.name_in_child(fn)
         end
         if not trace.is_empty then
            previous := trace.last
            trace.remove_last
            Result := previous.going_down(trace,Result)
         end
      ensure
         Result /= Void
      end

feature {NONE}

   comment: COMMENT
         -- Associated heading comment.

   make(t: like type) is
      require
         not t.is_anchored
         not t.start_position.is_unknown
      do
         type := t
      ensure
         type = t
      end

   forbidden_parent_list: ARRAY[STRING] is
      once
         Result := << as_none, as_boolean, as_integer_8, as_integer_16,
		      as_integer_32, as_character, as_real, as_double,
		      as_bit, as_pointer, as_native_array
		   >>
      end
   
   get_old_name(fn: FEATURE_NAME): like fn is
      do
         if rename_list /= Void then
            Result := rename_list.name_in_parent(fn)
            if Result = fn then
               Result := Void
            end
         end
      end

   get_new_name(fn: FEATURE_NAME): like fn is
      do
         if rename_list /= Void then
            Result := rename_list.name_in_child(fn)
            if Result = fn then
               Result := Void
            end
         end
      end

   apply_undefine(f: E_FEATURE; fn: FEATURE_NAME): E_FEATURE is
      local
         ufn: FEATURE_NAME
         fnkey: STRING
         index: INTEGER
      do
         ufn := has_undefine(fn)
         if ufn /= Void then
            if undefine_memory1 = Void then
               !!undefine_memory1.with_capacity(undefine_list.count)
               !!undefine_memory2.with_capacity(undefine_list.count)
            end
            fnkey := ufn.to_key
            index := undefine_memory1.fast_index_of(fnkey)
            if index > undefine_memory1.upper then
               undefine_memory1.add_last(fnkey)
               Result := f.try_to_undefine(ufn,parent_list.base_class)
               undefine_memory2.add_last(Result)
            else
               Result := undefine_memory2.item(index)
            end
         else
            Result := f
         end
      end

   undefine_memory1: FIXED_ARRAY[STRING]

   undefine_memory2: FIXED_ARRAY[E_FEATURE]

   has_undefine(fn: FEATURE_NAME): FEATURE_NAME is
      do
         if undefine_list /= Void then
            Result := undefine_list.feature_name(fn.to_key)
         end
      end

   select_conflict(fn1, fn2: FEATURE_NAME) is
      do
         error_handler.add_position(fn1.start_position)
         error_handler.add_position(fn2.start_position)
         error_handler.append("Select conflict for these features.")
	 error_handler.print_as_fatal_error
      end

   missing_select(fn1, fn2: FEATURE_NAME) is
      do
         error_handler.add_position(fn1.start_position)
         error_handler.add_position(fn2.start_position)
         error_handler.append("Missing select clause for these features.")
	 error_handler.print_as_fatal_error
      end

   Vmss: STRING is "Cannot select non-existent feature (VMSS)."

   Vdrs1: STRING is "Cannot redefine non-existent feature (VDRS.1)."

   consider_export(fn: FEATURE_NAME): CLIENT_LIST is
      require
         fn /= Void
      do
         if export_list = Void then
            Result := base_class.clients_for(fn)
         else
            Result := export_list.clients_for(fn)
            if Result = Void then
               Result := base_class.clients_for(fn)
            end
         end
      end

   gn_vncg_update(site: POSITION; t1, t2: E_TYPE): BOOLEAN is
      do
	 Result :=  t1.is_a(t2)
	 if Result then
	    assignment_handler.vncg(site,t1,t2)
	 else
	    error_handler.cancel
	 end
      end

invariant

   not type.is_anchored

   not start_position.is_unknown

end -- PARENT