#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)
"-->";
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())
"<System>";
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 03/07/2024 from adv3Lite version 2.1