#charset "us-ascii"

/*
*  Copyright (c) 2008 by Kevin Forchione. All rights reserved.
*
*  This file is part of the TADS 3 Absolute Source Text Order
*
*  asto.t
*
*  Version 2.0
*
*  Adds the property absSourceTextOrder to each non-class object, and sets
*  the property to an integer giving the "absolute" order of the object
*  definition in the program source.
*
*  This property is useful because it lets you reliably determine the
*  order of objects in the program source.
*
*  In addition, this module provides object statistics with
*  the AstoSequencer.display() method.
*
*  The module also includes a function, forEachAsto() that will loop
*  over all objects defining an absolute source text order. The parameters
*  are similar to the forEachInstance() function provided by _main.t.
*
*  THIS MODULE REQUIRES THAT YOU COMPILE EACH APPLICABLE MODULE
*  WITH THE
*
*      #pragma sourceTextGroup(on)
*
*   OR COMPILE WITH THE "-Gstg" OPTION.
*/

#include <tads.h>
#pragma sourceTextGroup(off)

/*
*/
AstoSequencer: PreinitObject
{
   objCount    = 0
   classCount  = 0
   modsCount   = 0
   astoCount   = 0
   noStgCount  = 0
   stgLu       = static (new LookupTable())
   baseStoLu   = static (new LookupTable())
   astoVec     = static (new Vector(10))

   display()
   {
       local sortedKeys;

       "\b@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ";
       "\b";
       "\nObject Sequence Analysis: ";
       "\b\t- Total: <<objCount>> ";
       "\b\t- Non-sequenced objects: <<noStgCount>> ";
       "\n\t\t- classes: <<classCount>> ";
       "\n\t\t- objects: <<noStgCount - classCount>> ";
       "\b\t- Sequenced definitions: <<astoCount>> ";
       "\n\t\t- internals: <<modsCount>> ";
       "\n\t\t- objects: <<astoCount - modsCount>> ";

       "\bSource Text Group Definitions ";
       sortedKeys = stgLu.keysToList().sort(nil, new function(k1, k2)
       {
           if (k1[2] < k2[2])
               return -1;
           else if (k1[2] > k2[2])
               return 1;
           else return 0;
       });
       "(<<sortedKeys.length()>> groups): ";
       foreach (local key in sortedKeys)
       {
           "\n\tGroup <<key[2]>> (<<key[1]>>):\t<<stgLu[key]>> ";
       }

       "\bObject Seequence: ";
       forEachAsto(Object, nil, new function(o)
       {
           "\n\t<<o.absSourceTextOrder>>
           \t\t<<o.sourceTextGroupName>>:
           <<o.sourceTextSymName>> ";
       });
       "\b@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ";
       "\b";
   }

   execute()
   {
       local key, value, prev, symLu, strLu, sortedKeys, symName;


       /*
        *  Buld Global Symbol and String Lookups for sourceTextSymName
        */
       symLu   = t3GetGlobalSymbols();
       strLu   = new LookupTable();
       symLu.forEachAssoc(new function(key, value)
       {
           strLu[value]    = key;
       });

       /*
        *  Loop over all Object instances and classes
        */
       for (local o = firstObj(Object, ObjAll); o != nil; o = nextObj(o, Object, ObjAll))
       {
           /*
            *  Count all objects
            */
           objCount++;

           /*
            *  If the object doesn't define sourceTExtGroup then
            *  count it and continue looping.
            */
           if (o.propDefined(&sourceTextGroup, PropDefDirectly) == nil)
           {
               noStgCount++;

               /*
                *  If the object is a class, count it and continue looping
                */
               if (o.isClass())
               {
                   classCount++;
               }

               continue;
           }

           /*
            *  If the object is a modification count it
            */
           if (o.isClass())
           {
               modsCount++;
           }

           astoVec.append(o);

           /*
            *  Count this object as an "absolute" source text order object
            */
           astoCount++;

           /*
            *  Build key for source text group and base source text order
            *  lookup tables.
            */
           key = [o.sourceTextGroup.sourceTextGroupName,
               o.sourceTextGroup.sourceTextGroupOrder];

           /*
            *  Retrieve the value from the source text group lookup
            *  for this key.
            */
           value   = stgLu[key];

           /*
            *  If we have a value, compare it with the value
            *  of the object's source text order. If the object
            *  has a greater value then store it in the table.
            */
           if (value)
           {
               if (o.sourceTextOrder > value)
                   stgLu[key] = o.sourceTextOrder;
           }
           /*
            *  Store the object's source text order in the lookup.
            */
           else stgLu[key] = o.sourceTextOrder;
       }

       /*
        *  Sort the source text group lookup keys
        */
       sortedKeys = stgLu.keysToList.sort(nil, new function(k1, k2)
       {
           if (k1[2] < k2[2])
               return -1;
           else if (k1[2] > k2[2])
               return 1;
           else return 0;
       });

       /*
        *  Build the base Source Text Order Lookup.
        */
       prev    = 0;
       foreach (local key in sortedKeys)
       {
           value           = stgLu[key];
           baseStoLu[key]  = prev;
           prev            += value;
       }

       /*
        *  Loop over each object again and set its "absolute"
        *  source text order (if appropriate).
        */
       for (local o = firstObj(Object, ObjAll); o != nil; o = nextObj(o, Object, ObjAll))
       {
           if (o.propDefined(&sourceTextGroup, PropDefDirectly))
           {
               key = [o.sourceTextGroup.sourceTextGroupName,
                   o.sourceTextGroup.sourceTextGroupOrder];
               value    = baseStoLu[key];
               value += o.sourceTextOrder;

               o.absSourceTextOrder    = value;

               /*
                *  Set the object's source text group order from
                *  its source text group object.
                */
               setSourceTextGroupOrder(o, o.sourceTextGroup.sourceTextGroupOrder);

               /*
                *  Set the object's source text group name from
                *  its source text group object.
                */
               setSourceTextGroupName(o, o.sourceTextGroup.sourceTextGroupName);

               /*
                *  Set the object's source text sym name from
                *  the symbol and string lookup table.
                */
               symName = strLu[o];
               if (symName == nil)
               {
                   local scList;

                   symName = '* ';
                   scList  = o.getSuperclassList();
                   for (local index = 1; index <= scList.length(); ++index)
                   {
                       symName += strLu[scList[index]];
                       if (index < scList.length())
                           symName += ', ';
                   }
                   symName += ' *';
               }
               else if (toInteger(symName))
               {
                   local sc, cName;

                   cName = symName;

                   __modsLoop:
                   while(toInteger(cName))
                   {
                       for (local c = firstObj(Object, ObjAll); c != nil; c = nextObj(c, Object, ObjAll))
                       {
                           if (c.getSuperclassList().car() == o)
                           {
                               cName = strLu[c];
                               break __modsLoop;
                           }
                       }
                   }

                   symName += ( ' (' + cName + ')');
               }

               setSourceTextSymName(o, symName);

               /*
                *  Clear the object's source text group.
                */
               clearSourceTextGroup(o);
           }
       }

       astoVec.sort(nil, new function(a, b)
       {
           if (a.absSourceTextOrder < b.absSourceTextOrder)
               return -1;
           else if (a.absSourceTextOrder > b.absSourceTextOrder)
               return 1;
           else return 0;
       });
   }

   setSourceTextGroupOrder(obj, value) { obj.sourceTextGroupOrder = value; }
   setSourceTextGroupName(obj, value) { obj.sourceTextGroupName = value; }
   setSourceTextSymName(obj, value) { obj.sourceTextSymName = value; }
   clearSourceTextGroup(obj) { obj.sourceTextGroup = nil; }
}

/* ------------------------------------------------------------------------ */
/*
*   For convenience, a simple object iterator function.  This function
*   invokes a callback function for each instance of the given class
*   having an "absolute" source text order. The order of iteration is
*  either in descending or ascending order of "absolute" source text order.
*
*   The callback is invoked with one argument, which gives the current
*   instance.  The callback can "break" out of the loop by throwing a
*   BreakLoopSignal, which can be done conveniently using the breakLoop
*   macro.
*/
forEachAsto(cls, desc, func)
{
   local vector    = new Vector(AstoSequencer.astoCount);

   try
   {
       vector = AstoSequencer.astoVec.subset(new function(obj)
       {
           return obj.ofKind(cls) || obj == cls;
       });

       vector.sort(desc, new function(a, b)
       {
           if (a.absSourceTextOrder < b.absSourceTextOrder)
               return -1;
           else
               if (a.absSourceTextOrder > b.absSourceTextOrder)
                   return 1;
               else return 0;
       });

       vector.forEach(func);
   }
   catch (BreakLoopSignal sig)
   {
       /*
        *   ignore the signal - it simply means we want to terminate the
        *   loop and return to the caller
        */
   }
}

/* ------------------------------------------------------------------------ */
/*
*   For convenience, a simple object iterator function.  This function
*   invokes a callback function for each instance of the given class
*   having a source text group and source text order property. The
*   order of iteration is determined by the sort order of both properties.
*
*   The callback is invoked with one argument, which gives the current
*   instance.  The callback can "break" out of the loop by throwing a
*   BreakLoopSignal, which can be done conveniently using the breakLoop
*   macro.
*/
forEachStgSto(cls, stgOrder, stoOrder, func)
{
   local vector, stgLu, stgo, sortedKeys, sortedList;

   try
   {
       stgLu       = new LookupTable();

       vector = AstoSequencer.astoVec.subset(new function(obj)
       {
           return obj.ofKind(cls) || obj == cls;
       });

       /* loop over all asto of the given class */
       foreach (local obj in vector.toList())
       {
           if (obj.propDefined(&absSourceTextOrder, PropDefDirectly))
           {
               stgo = obj.sourceTextGroupOrder;
               if (stgLu.isKeyPresent(obj.sourceTextGroupOrder))
                   stgLu[stgo] = (stgLu[stgo] + obj);
               else
                   stgLu[stgo] = ([] + obj);
           }


       }

       sortedKeys = stgLu.keysToList().sort(stgOrder);

       foreach (local key in sortedKeys)
       {
           sortedList = stgLu[key].sort(stoOrder, new function(a, b)
           {
               if (a.sourceTextOrder < b.sourceTextOrder)
                   return -1;
               else if (a.sourceTextOrder > b.sourceTextOrder)
                   return 1;
               else return 0;
           });

           foreach (local obj in sortedList)
               func(obj);
       }
   }
   catch (BreakLoopSignal sig)
   {
       /*
        *   ignore the signal - it simply means we want to terminate the
        *   loop and return to the caller
        */
   }
}