#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 
         */
    }
}
