# _main.t | documentation

    #charset "us-ascii"

    /*
     *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
     *
     *   This module defines a number of low-level functions and classes that
     *   most TADS 3 programs will need, whether based on the adv3 library or
     *   not.  This module includes the main program entrypoint, the basic
     *   Exception classes, and the modular initialization framework.
     *
     *   The compiler automatically links this module into every program by
     *   default, but you can override this by specifying the "-nodef" option
     *   to t3make.  If you remove this module, you'll have to provide your own
     *   implementations for many of the functions and classes defined here.
     */

    #include "tads.h"
    #include "reflect.h"
    #include "strbuf.h"

    /* ------------------------------------------------------------------------ */
    /*
     *   Main program entrypoint.  The VM invokes this function at program
     *   startup.
     */
    _main(args)
    {
        /* call the common main entrypoint, with no startup file specified */
        _mainCommon(args, nil);
    }

    /* declare 'main' as a function, in case it's not otherwise defined */
    extern function main;

    /*
     *   Main program entrypoint for restoring a saved state.  The VM invokes
     *   this function at startup instead of _main() when the user explicitly
     *   specifies a saved state file to restore when starting the program.
     *   (On a command-line interpreter, this would involve using a special
     *   option on the T3 interpreter command line; for a GUI shell, this
     *   might simply involve double-clicking on the desktop icon for a saved
     *   state file.)
     *
     *   Note that we must export this as the 'mainRestore' symbol so that the
     *   interpreter knows how to find it.
     */
    export _mainRestore 'mainRestore';
    _mainRestore(args, restoreFile)
    {
        /* call the common main entrypoint */
        _mainCommon(args, restoreFile);
    }

    /*
     *   Common main entrypoint.  This function can be called with or without
     *   a saved state file to restore.
     */
    _mainCommon(args, restoreFile)
    {
        try
        {
            /*
             *   Keep going as long as we keep restarting.  Note that the
             *   restoreFile only counts on the first iteration, so we clear it
             *   out after each iteration; if we RESTART after that, we'll want
             *   to just start the game from the beginning.
             */
            for ( ; ; restoreFile = nil)
            {
                /* perform load-time initialization */
                initAfterLoad();

                /* if we're in preinit-only mode, we're done */
                if (t3GetVMPreinitMode())
                    return;

                /* catch any RESTART signals thrown out of the main entrypoint */
                try
                {
                    /*
                     *   If there's a saved state file to restore, call our
                     *   mainRestore() function instead of main().  If
                     *   mainRestore() isn't defined, show a message to this
                     *   effect but keep going.
                     */
                    if (restoreFile != nil
                        && dataType(mainGlobal.mainRestoreFunc) == TypeFuncPtr)
                    {
                        /*
                         *   Call the user's main startup-and-restore
                         *   entrypoint function.  Note that we call indirectly
                         *   through our function pointer so that we don't
                         *   force a function called mainRestore() to be linked
                         *   with the program.
                         */
                        (mainGlobal.mainRestoreFunc)(args, restoreFile);
                    }
                    else
                    {
                        /*
                         *   if we have a restore file but no mainRestore
                         *   routine, explain to the user that they'll have to
                         *   restore manually
                         */
                        if (restoreFile != nil)
                        {
                            "\n[This program cannot restore the saved position
                            file automatically.  Please try restoring the
                            saved position file again using a command within
                            the program.]\b";
                        }

                        /* call the selected main entrypoint */
                        flexcall(&main, args);
                    }

                    /*
                     *   we made it through the main entrypoint without a
                     *   restart, so we're done
                     */
                    break;
                }
                catch (RestartSignal rsig)
                {
                    /*
                     *   call the intrinsic restartGame function to reset all
                     *   of the static objects to their initial state
                     */
                    restartGame();

                    /*
                     *   Now that we've reset the VM, update the restart ID in
                     *   the main globals.  Note that we waited until now to do
                     *   this, because this change would have been lost in the
                     *   reset if we'd made the change before the reset.  Note
                     *   also that the 'rsig' object itself will survive the
                     *   reset because the thrower presumably allocated it
                     *   dynamically, hence it's not a static object subject to
                     *   reset.
                     */
                    mainGlobal.restartID = rsig.restartID;

                    /*
                     *   Now we can just continue on to the next iteration of
                     *   the restart loop.  This will take us back to the
                     *   initialization and enter the game as though we'd just
                     *   started the program again.
                     */
                }
            }
        }
        catch (ProgramException exc)
        {
            /*
             *   just re-throw these out to the VM, so that the VM exits to
             *   the operating system with an error indication
             */
            throw exc;
        }
        catch (Exception exc)
        {
            /* write output to the main console */
            t3SetSay(_default_display_fn);

            /* display the unhandled exception */
            "\n<<exc.displayException()>>\n";
        }
        finally
        {
            /* before exiting, call registered exit handlers */
            mainAtExit.callHandlers();
        }
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Flexible function call.  This calls the given function, passing as
     *   many arguments from the given argument list as the function actually
     *   wants.  If the list doesn't have enough arguments to satisfy the
     *   function's minimum requirements, we add 'nil' arguments to pad out the
     *   minimum.  If the list exceeds the function's maximum, we drop
     *   arguments past the maximum.
     */
    flexcall(func, [args])
    {
        /* get the function's desired argument list */
        local paramDesc = getFuncParams(func);

        /* add or remove arguments as needed to fit the limits */
        local n = args.length();
        if (n < paramDesc[1])
        {
            /* we need more to satisfy the minimum - add nil values */
            args += makeList(nil, paramDesc[1] - n);
        }
        else if (n > paramDesc[1] + paramDesc[2] && !paramDesc[3])
        {
            /*
             *   the function doesn't take infinite arguments, and we have more
             *   than it allows in the fixed plus optional parts, so drop the
             *   extra elements
             */
            args = args.sublist(1, paramDesc[1] + paramDesc[2]);
        }

        /* call the function */
        func(args...);
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Restart signal.  This can be used to restart from the main
     *   entrypoint.  The caller should create one of these objects, then use
     *   restartGame() (or an equivalent from a different function set, if
     *   appropriate) to reset static object state to the initial program load
     *   conditions, then throw the signal object.
     */
    class RestartSignal: Exception
        construct()
        {
            /*
             *   use the next restart ID, so we can tell that we're on a fresh
             *   run on this session
             */
            restartID = mainGlobal.restartID + 1;
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   General post-load initialization.  The main program entrypoint
     *   _main() calls this routine to set up the default display function,
     *   run pre-initialization if necessary, and run initialization.  This
     *   routine is also useful for the target of a restartGame() routine, to
     *   perform all of the basic load-time initialization again after a
     *   restart.
     */
    initAfterLoad()
    {
        /* establish the default display function */
        t3SetSay(_default_display_fn);

        /* if we haven't run preinit, do so now */
        if (!mainGlobal.preinited_)
        {
            /*
             *   Explicitly run garbage collection prior to preinit.
             *
             *   In most cases, this is unnecessary, but in some cases it's
             *   important.  In particular, object loops (over all objects, or
             *   over all instances of a given class) can still see otherwise
             *   unreachable objects.  It's common to do object loops in
             *   preinit to set up static data caches and tables and so on.  If
             *   there *were* any garbage objects lying around, preinit could
             *   find them via object loops, and might register them into
             *   tables or what not.
             *
             *   Even considering the preinit object loop, doing a garbage
             *   collection sweep would *still* be redundant in most cases,
             *   since preinit is normally done right after compilation, when
             *   the program wouldn't yet have had a chance to create any
             *   garbage objects to be worried about in object loops.  However,
             *   there's still one more case to consider, and that's RESTART:
             *   in a debug build, or even in some release builds, we'd have to
             *   re-run preinit after a RESTART, and there certainly could be
             *   garbage objects left around from before the RESTART.
             *
             *   To ensure that we deal gracefully with this combination of
             *   conditions - garbage objects, RESTART, and object loops in
             *   preinit - simply do an explicit garbage collection run before
             *   invoking preinit.
             */
            t3RunGC();

            /* run our internal preinit */
            _preinit();

            /* remember that we've run preinit */
            mainGlobal.preinited_ = true;
        }

        /* if we're not in preinit-only mode, run internal initialization */
        if (!t3GetVMPreinitMode())
            _init();
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Module Execution Object.  This is an abstract base class for various
     *   classes that provide modular execution hooks.  This class and its
     *   subclasses are mix-in classes - they can be multiply inherited by any
     *   object (as long as it's not already some other kind of module
     *   execution object).
     *
     *   The point of the Module Execution Object and its subclasses is to
     *   allow libraries and user code to define execution hooks, without
     *   having to worry about what other libraries and user code bits are
     *   defining the same hook.  When we need to execute a hook defined via
     *   this object, we iterate over all of the instances of the appropriate
     *   subclass and invoke its execute() method.
     *
     *   By default, the order of execution is arbitrary.  In some cases,
     *   though, dependencies will exist, so that one object cannot be invoked
     *   until another object has already been invoked.  In these cases, you
     *   must set the execBeforeMe property to contain a list of the objects
     *   whose execute() methods must be invoked before this object's
     *   execute() method is invoked.  The library will check this list before
     *   calling execute() on this object, and ensure that each object in the
     *   list has been invoked before calling this object's execute().
     */
    class ModuleExecObject: object
        /*
         *   List of objects that must be executed before me - by default, the
         *   order doesn't matter, so we'll set this to an empty list.
         *   Instances can override this if it is necessary to execute other
         *   objects before this object can be executed.
         */
        execBeforeMe = []

        /*
         *   List of objects that must be executed after me - this is
         *   analogous to execBeforeMe, but we make sure we run before these.
         */
        execAfterMe = []

        /*
         *   Subclass-specific execution method.  Each subclass should
         *   override this method to provide its execution code.
         */
        execute() { }

        /*
         *   PRIVATE METHODS AND PROPERTIES.  Subclasses and instances should
         *   not need to override or invoke these.
         */

        /* flag - true if we've been executed on this round */
        isExecuted_ = nil

        /* flag - true if we're in the process of executing */
        isDoingExec_ = nil

        /* execute - internal method: checks dependency order */
        _execute()
        {
            /*
             *   If I've already been executed, there's nothing more that I
             *   need to do.  We might be called by the arbitrarily-ordered
             *   iteration over all objects after we've already been executed,
             *   because we might be executed explicitly by an object that
             *   depends upon us if it's reached before we are.
             */
            if (isExecuted_)
                return;

            /*
             *   If we're in the process of executing any of the objects we
             *   depend upon, and a dependent calls us, we have a circular
             *   dependency.
             */
            if (isDoingExec_)
                throw new CircularExecException(self);

            /*
             *   Mark ourselves as being in the process of executing.  If
             *   there are any circular dependencies (i.e., if we depend on an
             *   object, which in turn depends on us), it's clearly an error,
             *   in that both objects can't be executed before the other.
             *   This flag allows us to detect circular dependencies by
             *   noticing if we're called by a dependent while we're in the
             *   process of calling the things we depend upon.
             */
            isDoingExec_ = true;

            /*
             *   Check each entry in my 'before' list to ensure that they've
             *   all been executed already.  Invoke execute() now for any that
             *   haven't.
             */
            for (local i = 1, local cnt = execBeforeMe.length() ;
                 i <= cnt ; ++i)
            {
                local cur;

                /* get this object */
                cur = execBeforeMe[i];

                /* if this one hasn't been executed yet, do so now */
                if (!cur.isExecuted_)
                {
                    /*
                     *   This one hasn't been executed yet - explicitly
                     *   execute it now.  Note that we do this recursively
                     *   through the internal execution method, so that 'cur'
                     *   has a chance to execute any objects that it depends
                     *   upon.
                     */
                    cur._execute();
                }
            }

            /*
             *   we've resolved all of our dependencies, so we're good to go -
             *   run the user's execution code
             */
            execute();

            /*
             *   mark ourselves as having been executed, so we don't run the
             *   user's code again should we be called again by a dependent or
             *   by the global iteration loop later in the scan
             */
            isExecuted_ = true;
            isDoingExec_ = nil;
        }

        /* flag to indicate that this is the first time running classExec */
        hasInitialized_ = nil

        /*
         *   Class execution.  Call this method on the particular class of
         *   modules to execute.  We'll iterate over all instances of that
         *   class and invoke each instance's _execute() method.
         */
        classExec()
        {
            /*
             *   If this is the first time running this classExec, turn
             *   execAfterMe dependencies into appropriate execBeforeMe
             *   dependencies.
             */
            if (!hasInitialized_)
            {
                /*
                 *   Go through all instances of this type of initializer, and
                 *   re-cast the execAfterMe lists as execBeforeMe lists.
                 */
                forEachInstance(self,
                    function(obj)
                    {
                        foreach(local dependent in obj.execAfterMe)
                            dependent.execBeforeMe += obj;
                    });

                /* remember that we're now initialized */
                hasInitialized_ = true;
            }

            /*
             *   since we're starting a new round, clear all of the 'executed'
             *   flags in all of the objects, to ensure that we execute all
             *   objects on this round (this cleans up the flag settings from
             *   any previous rounds)
             */
            forEachInstance(self,
                { obj: obj.isExecuted_ = obj.isDoingExec_ = nil });

            /* execute all objects */
            forEachInstance(self, { obj: obj._execute() });
        }
    ;

    /*
     *   Pre-Initialization object.  During pre-initialization, we'll invoke
     *   the execute() method on each instance of this class.
     */
    class PreinitObject: ModuleExecObject
        /*
         *   Each instance of this object MUST override execute() with the
         *   specific pre-initialization code that the instance wants to
         *   perform.
         *
         *   In addition, each instance can optionally set the property
         *   execBeforeMe to a list of the other PreinitObject's that must be
         *   invoked before this object is.  If this property is not set, this
         *   object's place in the preinit execution order will be arbitrary.
         */
    ;

    /*
     *   Initialization object.  During initialization, just before calling
     *   the user's main(args) function, we'll invoke the execute() method on
     *   each instance of this class.
     */
    class InitObject: ModuleExecObject
        /*
         *   Each instance of this object MUST override execute() with the
         *   specific initialization code that the instance wants to perform.
         *
         *   In addition, each instance can optionally set the property
         *   execBeforeMe to a list of the other InitObject's that must be
         *   invoked before this object is.  If this property is not set, this
         *   object's place in the initialization execution order will be
         *   arbitrary.
         */
    ;

    /*
     *   Exception: circular execution dependency in ModuleExecObject
     */
    class CircularExecException: Exception
        construct(obj) { obj_ = obj; }
        displayException()
        {
            "circular module dependency detected (refer to
            ModuleExecObject._execute() in _main.t)";
        }

        /*
         *   The object that detected the circular dependency.  We can't use
         *   this for much ourselves, but it might be useful to store this
         *   information so that it's available to the programmer from within
         *   the debugger.
         */
        obj_ = nil
    ;

    /*
     *   Library pre-initialization.  This is called immediately after
     *   compilation to pre-initialize the program.  Any changes made here to
     *   object states become part of the initial state stored in the image
     *   file, so this establishes the static initial state of the program.
     *
     *   The advantage of doing work during pre-initialization is that this
     *   work is done once, during compilation, and is thus not repeated each
     *   time a user starts the program.  Time-consuming initialization work
     *   can thus be made invisible to the user.
     *
     *   Note that the pre-initialization code should never do anything that
     *   involves the user interface, since this code runs during compilation
     *   and does not run again when users start the program.  So, anything
     *   that you want a user to see must be done during normal initialization
     *   (such as in the main() routine), not here.
     */
    _preinit()
    {
        local symtab;

        /* try getting the mainRestore() function from the global symbol table */
        if ((symtab = t3GetGlobalSymbols()) != nil)
            mainGlobal.mainRestoreFunc = symtab['mainRestore'];

        /* execute all preinit objects */
        PreinitObject.classExec();
    }

    /*
     *   Library initialization.  This is called during each program start-up
     *   to initialize the program.  Since this is run each time the user
     *   starts the program, this can display any introductory messages, set
     *   up the user interface, and so on.
     */
    _init()
    {
        /* execute all init objects */
        InitObject.classExec();
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   For convenience, a simple object iterator function.  This function
     *   invokes a callback function for each instance of the given class, in
     *   arbitrary 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.
     */
    forEachInstance(cls, func)
    {
        try
        {
            /* loop over all objects of the given class */
            for (local obj = firstObj(cls) ; obj != nil ; obj = nextObj(obj, cls))
                func(obj);
        }
        catch (BreakLoopSignal sig)
        {
            /*
             *   ignore the signal - it simply means we want to terminate the
             *   loop and return to the caller
             */
        }
    }

    /*
     *   Find an instance of the given class for which the given function
     *   returns true.  We iterate over objects of the given class in
     *   arbitrary order, and return the first instance for which the function
     *   returns true.  Retursn nil if there is no such instance.
     */
    instanceWhich(cls, func)
    {
        /* loop over all objects of the given class */
        for (local obj = firstObj(cls) ; obj != nil ; obj = nextObj(obj, cls))
        {
            /* if the callback returns true for this object, return the object */
            if (func(obj))
                return obj;
        }

        /*
         *   we didn't find any instances for which the callback returns true;
         *   indicate this by returning nil
         */
        return nil;
    }

    /*
     *   An exception object for breaking out of a callback loop, such as
     *   forEachInstance.
     */
    class BreakLoopSignal: Exception
        displayException() { "loop break signal"; }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Get the "translated" datatype of a value.  This is essentially the
     *   same as dataType(), except that anonymous function objects and dynamic
     *   function objects are treated as being "function pointer" types
     *   (TypeFuncPtr).
     */
    dataTypeXlat(val)
    {
        local t;

        /* get the base type */
        t = dataType(val);

        /* if it's an anonymous function, return TypeFuncPtr */
        if (t == TypeObject
            && (val.ofKind(AnonFuncPtr)
                || (defined(DynamicFunc) && val.ofKind(DynamicFunc))))
            return TypeFuncPtr;

        /* otherwise, just return the base type */
        return t;
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Base class for all exception objects.  We derive all exceptions from
     *   this base class so that we can write 'catch' blocks that catch all
     *   exceptions by catching 'Exception'.
     *
     *   The displayException() method displays a message describing the
     *   exception.  Subclasses should override this method.
     */
    class Exception: object
        /* construct, with an optional message describing the error */
        construct(msg?, ...)
        {
            /* if there's a message, save it, otherwise keep the default */
            if (msg != nil)
                errmsg_ = msg;
        }

        /* display the exception - should always be overridden */
        displayException()
        {
            "<<errmsg_>>";
        }

        /*
         *   Get the exception message as a string.  This captures the output
         *   of displayException() and returns it a string.  Use this instead
         *   of accessing errmsg_, since that member is private and might not
         *   reflect the actual displayed message.
         */
        getExceptionMessage()
        {
            /* capture and return the displayed exception message as a string */
            return _outputCapture({: displayException() });
        }

        /*
         *   Private member: The error message passed to the constructor, if
         *   any.  Note that this doesn't necessarily contain the actual
         *   displayed exception message, since displayException() can be
         *   overridden in subclasses to display additional parameters or other
         *   text entirely.  The definitive message is the one that
         *   displayException() generates.  If you want the displayed message
         *   as a string, use getExceptionMessage().
         */
        errmsg_ = 'Unknown exception'

        /*
         *   Display a stack trace, given a list of T3StackInfo objects.  Note
         *   that, for efficiency, we do not by default cache a stack trace
         *   when an exception occurs; individual subclasses can obtain a
         *   stack trace if desired at construction and use the information to
         *   show a stack trace for the exception.
         */
        showStackTrace(stackList)
        {
            local haveSrc;

            /* check to see if there's any source info in the stack trace */
            haveSrc = nil;
            foreach (local cur in stackList)
            {
                /* note if we have source info here */
                if (cur.srcInfo_ != nil)
                {
                    /*
                     *   we have source information - note it and stop
                     *   searching, since even one bit of source info is
                     *   enough to show the stack
                     */
                    haveSrc = true;
                    break;
                }
            }

            /*
             *   if we have any source information at all, or we have
             *   reflection services available to decode the stack trace
             *   symbolically, show the stack
             */
            if (haveSrc || mainGlobal.reflectionObj != nil)
            {
                for (local i = 1, local cnt = stackList.length() ; i <= cnt ; ++i)
                {
                    local cur = stackList[i];

                    /* show a mark next to level 1, spaces elsewhere */
                    if (i == 1)
                        "--&gt;";
                    else
                        "\ \ \ ";

                    /*
                     *   if there's a system reflection object, show symbolic
                     *   information on the current function call; otherwise,
                     *   simply show the source location
                     */
                    if (mainGlobal.reflectionObj != nil)
                    {
                        /* reflection is available - show full symbolic info */
                        "<<mainGlobal.reflectionObj.
                          formatStackFrame(cur, true)>>";
                    }
                    else
                    {
                        /* no reflection information - show source only */
                        if (cur.srcInfo_ != nil)
                            "<<cur.srcInfo_[1]>>, line <<cur.srcInfo_[2]>>";
                        else if (cur.isSystem())
                            "&lt;System&gt;";
                        else
                            "???";
                    }

                    /* end the line */
                    "\n";
                }
            }
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   RuntimeError exception class.  The VM creates and throws an instance
     *   of this class when any run-time error occurs.  The VM explicitly sets
     *   the exceptionMessage property to a string giving the VM error message
     *   for the run-time error that occurred.
     */
    class RuntimeError: Exception
        construct(errno, ...)
        {
            /* remember the VM error number */
            errno_ = errno;

            /*
             *   Store a stack trace for the current location.  Always discard
             *   the first element of the result, since this will reflect
             *   RuntimeError.construct, which is obviously not interesting.
             */
            stack_ = t3GetStackTrace().sublist(2);

            /*
             *   The next element of the stack trace is usually a native code
             *   frame, because the VM itself invokes our constructor in
             *   response to a runtime exception; this is not an interesting
             *   frame, so if it's present, remove it.
             */
            if (stack_.length() > 0 && stack_[1].isSystem())
                stack_ = stack_.sublist(2);
        }

        /* create a runtime error with a given error message */
        newRuntimeError(errno, msg)
        {
            local e = new RuntimeError(errno);
            e.exceptionMessage = msg;
            return e;
        }

        /* display the exception */
        displayException()
        {
            /* show the exception message */
            "Runtime error: <<exceptionMessage>>\n";

            /* show a stack trace if possible */
            showStackTrace(stack_);
        }

        /* check to see if it's a debugger signal of some kind */
        isDebuggerSignal()
        {
            return errno_ is in (
                2391,                        /* debugger 'abort command' signal */
                2392                               /* debugger 'restart' signal */
            );
        }

        /* the VM error number of the exception */
        errno_ = 0

        /* the exception message, provided to us by the VM after creation */
        exceptionMessage = ''

        /* the stack trace, which we store at the time we're created */
        stack_ = nil
    ;

    /*
     *   Export our RuntimeError class so that the VM knows about it and can
     *   create instances of it.  Also export our exceptionMessage property,
     *   so the VM can store its explanatory text there.
     */
    export RuntimeError;
    export exceptionMessage;

    /*
     *   Unknown character set exception - this is thrown from any routine that
     *   needs a local character set mapping when no mapping exists on the local
     *   platform.
     */
    class UnknownCharSetException: Exception
        displayException = "Unknown character set"
    ;

    /*
     *   this exception object must be exported for use by the CharacterSet
     *   intrinsic class
     */
    export UnknownCharSetException 'CharacterSet.UnknownCharSetException';

    /*
     *   A Program Exception terminates the entire program, passing an error
     *   indication to the operating system.  The VM doesn't provide a way to
     *   specify the *particular* error code to return to the OS, as there's no
     *   portable set of error codes; rather, the VM simply returns a code to
     *   the OS that means generically that an error occurred, if there's any
     *   such concept on the local operating system.  The VM will normally
     *   display this message just before it terminates the program, possibly
     *   with some additional text mentioning that a program error occurred
     *   (such as "unhandled exception: <your message>").
     */
    class ProgramException: Exception
        construct(msg) { exceptionMessage = msg.htmlify(); }
        displayException() { "<<exceptionMessage>> "; }
    ;

    /*
     *   A StorageServerError is thrown when a file operation on a remote
     *   storage server fails.  The storage server is used when the game runs
     *   on a Web game server in client/server mode.  In Web mode, files are
     *   stored on a separate storage server rather than on the Web server
     *   itself, so that the files can be transparently accessed if the game is
     *   continued from another Web server.  This exception is used when a
     *   request to the storage server fails, which could be due to an error on
     *   the storage server, a network error communicating between the game
     *   server and the storage server, or an invalid request (e.g., incorrect
     *   user credentials).
     */
    class StorageServerError: RuntimeError
        construct(errno, msg)
        {
            /*
             *   Do the base class construction.  Note that errno is the
             *   VM-level error number, which is usually just the generic
             *   "storage server error" code.  The storage server provides a
             *   separate, more specific error code of its own as the first
             *   token of the message string.
             */
            inherited(errno);

            /*
             *   storage server error messages are formatted with an error code
             *   as the first space-delimited token, and a human-readable
             *   message following
             */
            local sp = msg.find(' ');
            errCode = msg.substr(1, sp - 1);
            msg = msg.substr(sp + 1);

            /*
             *   If the error code is a negative integer, it's an error on the
             *   client side sending the HTTP request to the storage server.
             *   If it's a positive integer, it's an HTTP error code from the
             *   storage server.  Otherwise it's an error abbreviation token
             *   from the server.
             */
            local errNum = toInteger(errCode);
            if (errNum < 0)
            {
                local reqErrs = [
                    -1 -> 'out of memory',
                    -2 -> 'unable to connect',
                    -3 -> 'network error',
                    -4 -> 'invalid parameters',
                    -5 -> 'error reading temporary file',
                    -6 -> 'error writing temporary file',
                    * -> 'error code <<errNum>>'
                ];
                errMsg = reqErrs[errNum];
            }
            else if (errNum > 0)
                errMsg = 'HTTP error (status code <<errCode>>)';
            else
                errMsg = msg;
        }

        /* the storage server error code */
        errCode = nil

        /*
         *   error message - this is the message text we get back from the
         *   storage server for a request that's successful at the HTTP level
         *   but fails on the storage server, OR a message describing the HTTP
         *   error or network error that caused the request to fail
         */
        errMsg = 'no details available'

        /* display the exception */
        displayException()
        {
            /* show the exception message */
            "Storage server error: <<errMsg>>\n";

            /* show a stack trace if possible */
            showStackTrace(stack_);
        }
    ;

    /* export this for use by the interpreter networking package */
    export StorageServerError 'StorageServerError';

    /* ------------------------------------------------------------------------ */
    /*
     *   Default string display function.  Our main entrypoint code
     *   establishes this function as the default output function.
     */
    _default_display_fn(str) { _tads_io_say(str); }

    /*
     *   Raw output capture.  This bypasses any filtering and directly captures
     *   any output generated by the callback.
     */
    _outputCapture(func)
    {
        /* temporarily set the low-level string output to capture output */
        local buf = new StringBuffer();
        local oldSay = t3SetSay({str: buf.append(str)});

        /* make sure we restore the "say" function on the way out */
        try
        {
            /* call the callback in the new capture context */
            func();

            /* return the captured data */
            return toString(buf);
        }
        finally
        {
            /* restore the old "say" function */
            t3SetSay(oldSay);
        }
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   The stack information object.  The intrinsic function
     *   t3GetStackTrace() in the 't3vm' function set returns a list of these
     *   objects; each object represents a level in the stack trace.
     */
    class T3StackInfo: object
        /*
         *   Construct a stack level object.  The system invokes this
         *   constructor with information on the stack level.
         */
        construct(func, obj, prop, selfObj, argList, srcInfo,
                  locals, namedArgs, frameDesc)
        {
            /* remember the values */
            func_ = func;
            obj_ = obj;
            prop_ = prop;
            self_ = selfObj;
            argList_ = argList;
            srcInfo_ = srcInfo;
            locals_ = locals;
            namedArgs_ = namedArgs;
            frameDesc_ = frameDesc;
        }

        /*
         *   Is this a system routine?  This returns true if an intrinsic
         *   function or an intrinsic class method is running at this level.
         */
        isSystem()
        {
            /*
             *   It's a system function if:
             *
             *   - we have NEITHER a function nor a method
             *.  - the function is a built-in function pointer
             *.  - the defining object is an intrinsic class
             *
             *   The first case applies to pre-3.0.19 VMs, where no information
             *   was available for native callers.  Starting in 3.0.19, full
             *   information is available.
             */
            return ((func_ == nil && obj_ == nil)
                    || dataType(func_) == TypeBifPtr
                    || (obj_ != nil && IntrinsicClass.isIntrinsicClass(obj_)));
        }

        /*
         *   the function running at this stack level - this is nil if an
         *   object property is running instead of a function
         */
        func_ = nil

        /*
         *   The object and property running at this stack level - these are
         *   nil if a function is running instead of an object method.  The
         *   object is the object where the method is actually defined - this
         *   might not be the same as self, because the object might have
         *   inherited the method from a base class.
         */
        obj_ = nil
        prop_ = nil

        /*
         *   the 'self' object at this level - this is nil if a function is
         *   running at this level instead of an object method
         */
        self_ = nil

        /*
         *   The list of positional arguments to the function or method.  Each
         *   element is the value of an argument; the list is arranged in the
         *   same order as the arguments.
         */
        argList_ = []

        /*
         *   Local variables.  This is a LookupTable containing the local
         *   variables currently in scope at this stack level.  Each element in
         *   the table has a string key (index) giving the name of the local
         *   variable, and each corresponding value is the local's current
         *   value.  The table is only included when the stack listing was
         *   produced by a call to t3GetStackTrace() with the T3GetStackLocals
         *   flag set; otherwise it's nil.  If the locals were requested, and
         *   the stack level has no local variables, this will be an empty
         *   lookup table.
         */
        locals_ = nil

        /*
         *   Named arguments.  This is a LookupTable containing the named
         *   arguments passed in from this stack level.  Each element in the
         *   table has a string key (index) giving the name of the argument,
         *   and each corresponding value is the value of that argument.  If
         *   there are no named arguments, this value is nil.
         */
        namedArgs_ = nil

        /*
         *   The source location of the next code to be executed in the
         *   function or method in this frame.  If source-level debugging
         *   information is available for the current execution point in this
         *   frame, this will contain a list of two values:
         *
         *   srcInfo_[1] = string giving the name of the source file
         *.  srcInfo_[2] = integer giving the line number in the source file
         *
         *   If the program wasn't compiled with source-level debugging
         *   information, or the current code location in the frame doesn't
         *   have any source information, this will be set to nil.
         *
         *   Note that the location reflected here is the *return address* in
         *   this frame - that is, the code location that will be executed when
         *   control returns to the frame.  This means that the source location
         *   will frequently appear as the next executable line after the one
         *   that called the next inner frame, because this is where execution
         *   will resume when control returns to the frame.
         */
        srcInfo_ = nil

        /*
         *   A StackFrameDesc object that can be used to get information from
         *   the frame and change local variables in the frame.
         */
        frameDesc_ = nil
    ;

    /* export T3StackInfo for use by the system */
    export T3StackInfo;

    /* ------------------------------------------------------------------------ */
    /*
     *   Stream state object for String.specialsToHtml().
     */
    class SpecialsToHtmlState: object
        /*
         *   Reset the state.  This should be used when the output stream
         *   context is reset, such as when clearing the window.
         */
        resetState()
        {
            flags_ = 0;
            tag_ = '';
        }

        /*
         *   Explicitly reset to the start of a line.  This can be called after
         *   a non-output operation that resets the line position, such as
         *   reading an input line.
         */
        resetLine()
        {
            /* reset the in-line flag, space flag, qspace flag, and tab column */
            flags_ &= ~(0x0001 | 0x0040 | 0x0080 | 0x0300);
        }

        /*
         *   Internal output state flags at end of last string parsed.  This is
         *   a combination of bit flags:
         *
         *   0x0001 - last string ended within a line of text
         *.  0x0002 - caps flag '\^' pending
         *.  0x0004 - lowercase flag '\v' pending
         *.  0x0008 - last string ended within an HTML tag
         *.  0x0010 - last string ended in double-quoted HTML tag attribute text
         *.  0x0020 - last string ended in single-quoted HTML tag attribute text
         *.  0x0040 - last string ended with an ordinary space
         *.  0x0080 - last string ended with a quoted space '\ '
         *.  0x0100 - <Q> parity level: 0=double quotes, 1=single quotes
         *.  0x0300 - distance from last '\t' tab column (0..3)
         */
        flags_ = 0

        /* tag in progress at end of last string parsed */
        tag_ = ''
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   global data object for this module
     */
    mainGlobal: object
        /* flag: we've run pre-initialization */
        preinited_ = nil

        /*
         *   The global reflection object - if the program is compiled with
         *   the standard reflection module, that module will set this
         *   property to point to the reflection object.
         *
         *   We use this so that we don't require the reflection module to be
         *   included.  If the module isn't included, this will be nil, so
         *   we'll know not to use reflection.  If this is not nil, we'll know
         *   we can use reflection services.
         */
        reflectionObj = nil

        /*
         *   Restart ID.  This is an integer that indicates how the main
         *   entrypoint was last reached.  This is initially zero; each time
         *   we restart the game, this is incremented.
         *
         *   The restart ID is the only information that survives across a
         *   restart boundary.  Other than this, entering via a restart is
         *   exactly like loading the program from scratch; all other
         *   information about the program state before the restart is lost in
         *   the restart operation.
         */
        restartID = 0

        /* pointer to mainRestore function, if defined */
        mainRestoreFunc = nil
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   At-exit handlers.  This is a registry for custom handlers that are to
     *   be invoked just before the program terminates.
     */
    transient mainAtExit: object
        /*
         *   Add an at-exit handler.  User code can call this to register a
         *   handler that will be invoked just before the program exits.
         */
        addHandler(func)
        {
            handlers.append(func);
        }

        /* call our exit handlers */
        callHandlers()
        {
            /* call each handler in the list */
            foreach (local f in handlers)
            {
                try
                {
                    /* call this handler */
                    f();
                }
                catch (Exception exc)
                {
                    /* display and ignore any exceptions it throws */
                    "\nError in exit handler: <<exc.displayException()>>\n";
                }
            }
        }

        /* list of exit handlers */
        handlers = static new Vector()
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   <<one of>> index generator.  The compiler generates an anonymous
     *   instance of this class for each <<one of>> list in string, setting the
     *   property 'numItems' to the number of items in the list, and
     *   'listAttrs' property to a string giving the sequence type.  The
     *   compiler generates a call to getNextIndex() within the string to get
     *   the next index value, which is an integer from 1 to numItems.
     */
    class OneOfIndexGen: object
        /* number of list items */
        numItems = 1

        /*
         *   List attributes.  This is a string with a comma-delimited list of
         *   tokens describing the treatment on the list for each fetch.  The
         *   first call to getNextIndex() takes the first token off the list
         *   and generates an appropriate return value, possibly queuing up a
         *   list of future values.  The next call to getNextIndex() reads from
         *   the queue.  Once the queue is exhausted, the next call takes the
         *   second token off the attribute list and repeats the process.  Once
         *   the attribute list is down to one token, we don't remove the
         *   token, but simply repeat it forever.
         *
         *   For example, 'seq,rand' runs through the entire list in sequence
         *   once, then generates independently random values from then on.
         *   'shuffle,stop' runs through the list once in shuffled order, then
         *   repeats the last pick from the shuffled list forever.
         *
         *   The individual attribute values are:
         *
         *   rand - pick an item at random, independently of past selections.
         *
         *   rand-norpt - pick an item at random, but don't pick the single
         *   most recent item chosen, to avoid repeating the same thing twice
         *   in a row.
         *
         *   rand-wt - pick an item by random, weighting the items with
         *   decreasing probabilities.  The last item is given relative weight
         *   1, the second to last weight 2, the third to last weight 3, etc.
         *   In other words, the nth item from the end of the list is n times
         *   as likely to be picked as the last item.  The picks are
         *   independent.
         *
         *   seq - run through the items in sequence (1, 2, ... numItems).
         *
         *   shuffle - run through the list in a shuffled order.
         *
         *   shuffle2 - shuffle the list into a random order, but only run
         *   through half before reshuffling
         *
         *   stop - repeat the previous selection forever.  (This should only
         *   be used as the second or later attribute in the list, since it
         *   depends on a prior selection being made.)
         */
        listAttrs = ''

        /*
         *   Get the next index value.  Returns an integer from 1 to numItems.
         *   The algorithm for choosing the index depends on the list type, as
         *   defined by listAttrs.
         */
        getNextIndex()
        {
            /* if we're out of items, generate the next iteration of the list */
            if (idx_ > lst_.length())
            {
                /* pull out the first item from the attributes */
                local alst = listAttrs.split(',', 2);

                /*
                 *   If we have more than one attribute, keep only the
                 *   remaining attributes, since the first attribute is for the
                 *   first list iteration only.
                 */
                if (alst.length() > 1)
                    listAttrs = alst[2];

                /* some of the attributes are interested in the last item */
                local last = lst_.length() >= 1 ? lst_[lst_.length()] : nil;

                /* build the list for the head attribute */
                local pick;
                switch (alst[1])
                {
                case 'stop':
                    /* reuse the last item every time from now on */
                    lst_ = [last ?? 1];
                    break;

                case 'seq':
                    /* run through the list in sequence */
                    lst_ = List.generate({i: i}, numItems);
                    break;

                case 'rand':
                    /*
                     *   pick an item at random, independent of other picks -
                     *   just generate a single item list, since we'll want to
                     *   pick at random again next time
                     */
                    lst_ = [rand(numItems) + 1];
                    break;

                case 'rand-norpt':
                    /*
                     *   Pick an item at random, but don't repeat the last item
                     *   chosen.  Pick repeatedly until we select an item that
                     *   doesn't match the last selection, if there is one.
                     *   Exception: if the list has only one item, it's
                     *   impossible to choose a different item, so don't try.
                     */
                    do
                    {
                        pick = rand(numItems) + 1;
                    }
                    while (numItems > 1 && pick == last);
                    lst_ = [pick];
                    break;

                case 'rand-wt':
                    /*
                     *   Pick an item at random, independently of other trials,
                     *   weighting the list in decreasing order of probability.
                     *   The relative weights are 1 for the last item, 2 for
                     *   the second to last item, 3 for the third to last, etc.
                     *   This gives us a total weight of n(n+1)/2, so start by
                     *   picking a number in that range.  1 represents the last
                     *   item, 2..3 the second to last, 3..5 the third, etc.
                     */
                    pick = rand(numItems*(numItems+1)/2) + 1;
                    for (local i in 1..numItems ; ; pick -= i)
                    {
                        /* if pick is <= i, it's this item */
                        if (pick <= i)
                        {
                            lst_ = [numItems - i + 1];
                            break;
                        }
                    }
                    break;

                case 'shuffle':
                case 'shuffle2':
                    /*
                     *   Shuffle the list.  Populate the list with the integer
                     *   values from 1 to numItems, then shuffle the list into
                     *   a random order.
                     *
                     *   The shuffling algorithm, as a physical analogy: Start
                     *   with a deck of cards labeled 1 to numItems.  Put all
                     *   of the cards in a hat.  Pick a card at random from the
                     *   hat and set it aside - call this the "pile".  Pick
                     *   another random card from the hat and put it on the top
                     *   of the pile.  Repeat until the hat is empty.  We now
                     *   have a pile of the cards arranged in random order.
                     *
                     *   To implement this, we create a vector and fill it with
                     *   integers from 1 to numItems.  Partition the vector
                     *   with an index 'i': items from 1..i are the hat, items
                     *   from i+1..numItems are the deck.  To draw a card at
                     *   random, we pick a random number from 1..i.  To remove
                     *   it from the hat, we swap it into position 'i', because
                     *   that will effectively shrink the hat by one slot and
                     *   grow the pile by one slot.  Decrement i and repeat.
                     */
                    lst_ = Vector.generate({i: i}, numItems);
                    for (local i in numItems..2 step -1)
                    {
                        /* pick a random element from the remaining set */
                        pick = rand(i) + 1;

                        /* swap the chosen item and the last element */
                        local tmp = lst_[i];
                        lst_[i] = lst_[pick];
                        lst_[pick] = tmp;
                    }

                    /*
                     *   The whole point of shuffling is to avoid obvious
                     *   repetition, so make sure we don't repeat any previous
                     *   item from a previous run through the list as the first
                     *   item of the new list.  If the first item matches the
                     *   previous item, reshuffle it into a random spot in the
                     *   list.  (If the list only has one item, of course, this
                     *   is pointless, so don't bother in that case.)
                     */
                    if (lst_[1] == last && numItems > 1)
                    {
                        pick = rand(numItems - 1) + 2;
                        lst_[1] = lst_[pick];
                        lst_[pick] = last;
                    }

                    /*
                     *   in half-shuffle mode, only keep the first half of the
                     *   list, so that we re-shuffle halfway through
                     */
                    if (alst[1] == 'shuffle2' && numItems > 2)
                        lst_ = lst_.toList(1, numItems/2);
                    break;
                }

                /* start from the first item in the list */
                idx_ = 1;
            }

            /* return and consume the next item */
            return lst_[idx_++];
        }

        /* generated list */
        lst_ = []

        /* current position in the list */
        idx_ = 1
    ;

Adv3Lite Library Reference Manual
Generated on 02/05/2024 from adv3Lite version 2.0