minid.interpreter

This module contains most of the public "raw" API, as well as the MiniD bytecode interpreter .

This module is way too big!

License:
Copyright (c) 2008 Jarrett Billingsley


This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.

int getTypeMT (MDThread* t, Type type);
Push the metatable for the given type. If the type has no metatable, pushes null. The type given must be one of the "normal" types -- the "internal" types are illegal and an error will be thrown.

Params:
Type type The type whose metatable is to be pushed.

Returns:
The stack index of the newly-pushed value (null if the type has no metatable, or a namespace if it does).

void setTypeMT (MDThread* t, Type type);
Sets the metatable for the given type to the namespace or null at the top of the stack. Throws an error if the type given is one of the "internal" types, or if the value at the top of the stack is neither null nor a namespace.

Params:
Type type The type whose metatable is to be set.

int importModule (MDThread* t, char[] name);
Import a module with the given name. Works just like the import statement in MiniD. Pushes the module's namespace onto the stack.

Params:
char[] name The name of the module to be imported.

Returns:
The stack index of the imported module's namespace.

int importModule (MDThread* t, int name);
Same as above, but uses a name on the stack rather than one provided as a parameter.

Params:
int name The stack index of the string holding the name of the module to be imported.

Returns:
The stack index of the imported module's namespace.

void importModuleNoNS (MDThread* t, char[] name);
Same as importModule, but doesn't leave the module namespace on the stack.

Params:
char[] name The name of the module to be imported.

void importModuleNoNS (MDThread* t, int name);
Same as above, but uses a name on the stack rather than one provided as a parameter.

Params:
int name The stack index of the string holding the name of the module to be imported.

int getRegistry (MDThread* t);
Pushes the VM's registry namespace onto the stack. The registry is sort of a hidden global namespace only accessible from native code and which native code may use for any purpose.

Returns:
The stack index of the newly-pushed namespace.

void[] allocMem (MDThread* t, uint size);
Allocates a block of memory using the given thread's VM's allocator function. This memory is not garbage-collected. You must free the memory returned by this function in order to avoid memory leaks.

The array returned by this function should not have its length set or be appended to (~=).

Params:
uint size The size, in bytes, of the block to allocate.

Returns:
A void array representing the memory block.

void resizeMem (MDThread* t, ref void[] mem, uint size);
Resize a block of memory. Only call this on memory that has been allocated using the allocMem, resizeMem or dupMem functions. If you pass this function an empty (0-length) memory block, it will allocate memory. If you resize an existing block to a length of 0, it will deallocate that memory.

If you resize a block to a smaller size, its data will be truncated. If you resize a block to a larger size, the empty space will be uninitialized.

The array returned by this function through the mem parameter should not have its length set or be appended to (~=).

Params:
void[] mem A reference to the memory block you want to reallocate. This is a reference so that the original memory block reference that you pass in is updated. This can be a 0-length array.
uint size The size, in bytes, of the new size of the memory block.

void[] dupMem (MDThread* t, void[] mem);
Duplicate a block of memory. This is safe to call on memory that was not allocated with the thread's VM's allocator. The new block will be the same size and contain the same data as the old block.

The array returned by this function should not have its length set or be appended to (~=).

Params:
void[] mem The block of memory to copy. This is not required to have been allocated by allocMem, resizeMem, or dupMem.

Returns:
The new memory block.

void freeMem (MDThread* t, ref void[] mem);
Free a block of memory. Only call this on memory that has been allocated with allocMem, resizeMem, or dupMem. It's legal to free a 0-length block.

Params:
void[] mem A reference to the memory block you want to free. This is a reference so that the original memory block reference that you pass in is updated. This can be a 0-length array.

ulong createRef (MDThread* t, int idx);
Creates a reference to a MiniD object. A reference is like the native equivalent of MiniD's nativeobj. Whereas a nativeobj allows MiniD to hold a reference to a native object, a reference allows native code to hold a reference to a MiniD object.

References are identified by unique integer values which are passed to the and functions. These are guaranteed to be probabilistically to be unique for the life of the program. What I mean by that is that if you created a million references per second, it would take you over half a million years before the reference values wrapped around. Aren't 64-bit integers great?

References prevent the referenced MiniD object from being collected, ever, so unless you want memory leaks, you must call when your code no longer needs the object. See for some reference helpers.

Params:
int idx The stack index of the object to which a reference should be created. If this refers to a value type, an exception will be thrown.

Returns:
The new reference name for the given object. You can create several references to the same object; it will not be collectible until all references to it have been removed.

int pushRef (MDThread* t, ulong r);
Pushes the object associated with the given reference onto the stack and returns the slot of the pushed object. If the given reference is invalid, an exception will be thrown.

void removeRef (MDThread* t, ulong r);
Removes the given reference. When all references to an object are removed, it will no longer be considered to be referenced by the host app and will be subject to normal GC rules. If the given reference is invalid, an exception will be thrown.

void maybeGC (MDThread* t);
Runs the garbage collector if necessary.

This will perform a garbage collection only if a sufficient amount of memory has been allocated since the last collection.

Params:
MDThread* t The thread to use to collect the garbage. Garbage collection is vm-wide but requires a thread in order to be able to call finalization methods.

Returns:
The number of bytes collected, which may be 0.

uint gc (MDThread* t);
Runs the garbage collector unconditionally.

Params:
MDThread* t The thread to use to collect the garbage. Garbage collection is vm-wide but requires a thread in order to be able to call finalization methods.

Returns:
The number of bytes collected by this collection cycle.

int dup (MDThread* t, int slot = -1);
Duplicates a value at the given stack index and pushes it onto the stack.

Params:
int slot The slot to duplicate. Defaults to -1, which means the top of the stack.

Returns:
The stack index of the newly-pushed slot.

void swap (MDThread* t, int first = -2, int second = -1);
Swaps the two values at the given indices. The first index defaults to the second-from-top value. The second index defaults to the top-of-stack. So, if you call swap with no indices, it will swap the top two values.

Params:
int first The first stack index.
int second The second stack index.

void insert (MDThread* t, int slot);
Inserts the value at the top of the stack into the given slot, shifting up the values in that slot and everything after it up by a slot. This means the stack will stay the same size. Similar to a "rotate" operation common to many stack machines.

Throws an error if 'slot' corresponds to the 'this' parameter. 'this' can never be modified.

If 'slot' corresponds to the top-of-stack (but not 'this'), this function is a no-op.

Params:
int slot The slot in which the value at the top will be inserted. If this refers to the top of the stack, this function does nothing.

void insertAndPop (MDThread* t, int slot);
Similar to insert, but combines the insertion with a pop operation that pops everything after the newly-inserted value off the stack.

Throws an error if 'slot' corresponds to the 'this' parameter. 'this' can never be modified.

If 'slot' corresponds to the top-of-stack (but not 'this'), this function is a no-op.

void rotate (MDThread* t, uint numSlots, uint dist);
A more generic version of insert. This allows you to rotate dist items within the top numSlots items on the stack. The top dist items become the bottom dist items within that range of indices. So, if the stack looks something like "1 2 3 4 5 6", and you perform a rotate with 5 slots and a distance of 3, the stack will become "1 4 5 6 2 3". If the dist parameter is 1, it behaves just like insert.

Attempting to rotate more values than there are on the stack (excluding 'this') will throw an error.

If the distance is an even multiple of the number of slots, or if you rotate 0 or 1 slots, this function is a no-op.

void rotateAll (MDThread* t, uint dist);
Rotates all stack slots (excluding 'this'). This is the same as calling rotate with a numSlots parameter of stackSize(t) - 1.

void pop (MDThread* t, uint n = cast(uint)1);
Pops a number of items off the stack. Throws an error if you try to pop more items than there are on the stack. 'this' is not counted; so if there is 'this' and one value, and you try to pop 2 values, an error is thrown.

Params:
uint n The number of items to pop. Defaults to 1. Must be greater than 0.

void setStackSize (MDThread* t, uint newSize);
Sets the thread's stack size to an absolute value. The new stack size must be at least 1 (which would leave 'this' on the stack and nothing else). If the new stack size is smaller than the old one, the old values are simply discarded. If the new stack size is larger than the old one, the new slots are filled with null. Throws an error if you try to set the stack size to 0.

Params:
uint newSize The new stack size. Must be greater than 0.

void transferVals (MDThread* src, MDThread* dest, uint num);
Moves values from one thread to another. The values are popped off the source thread's stack and put on the destination thread's stack in the same order that they were on the source stack.

If there are fewer values on the source thread's stack than the number of values, an error will be thrown in the source thread.

If the two threads belong to different VMs, an error will be thrown in the source thread.

If the two threads are the same thread object, or if 0 values are transferred, this function is a no-op.

Params:
MDThread* src The thread from which the values will be taken.
MDThread* dest The thread onto whose stack the values will be pushed.
uint num The number of values to transfer. There must be at least this many values on the source thread's stack.

int pushNull (MDThread* t);
int pushBool (MDThread* t, bool v);
int pushInt (MDThread* t, long v);
int pushFloat (MDThread* t, double v);
int pushChar (MDThread* t, dchar v);
int pushString (MDThread* t, char[] v);
These push a value of the given type onto the stack.

Returns:
The stack index of the newly-pushed value.

int pushFormat (MDThread* t, char[] fmt,...);
Push a formatted string onto the stack. This works exactly like tango.text.convert.Layout (and in fact calls it), except that the destination buffer is a MiniD string.

Params:
char[] fmt The Tango-style format string. ... = The arguments to be formatted.

Returns:
The stack index of the newly-pushed string.

int pushVFormat (MDThread* t, char[] fmt, TypeInfo[] arguments, void* argptr);
A version of pushFormat meant to be called from variadic functions.

Params:
char[] fmt The Tango-style format string.
TypeInfo[] arguments The array of TypeInfo for the variadic arguments.
void* argptr The platform-specific argument pointer.

Returns:
The stack index of the newly-pushed string.

int newTable (MDThread* t, uint size = cast(uint)0);
Creates a new table object and pushes it onto the stack.

Params:
uint size The number of slots to preallocate in the table, as an optimization.

Returns:
The stack index of the newly-created table.

int newArray (MDThread* t, uint len);
Creates a new array object and pushes it onto the stack.

Params:
uint len The length of the new array.

Returns:
The stack index of the newly-created array.

int newArrayFromStack (MDThread* t, uint len);
Creates a new array object using values at the top of the stack. Pops those values and pushes the new array onto the stack.

Params:
uint len How many values on the stack to be put into the array, and the length of the resulting array.

Returns:
The stack index of the newly-created array.

int newFunction (MDThread* t, uint function(MDThread*, uint) func, char[] name, uint numUpvals = cast(uint)0);
Creates a new native closure and pushes it onto the stack.

If you want to associate upvalues with the function, you should push them in order on the stack before calling newFunction and then pass how many upvalues you pushed. An example:

// 1. Push any upvalues.  Here we have two.  Note that they are pushed in order:
// upvalue 0 will be 5 and upvalue 1 will be "hi" once the closure is created.
pushInt(
t
, 5); pushString(
t
,
"hi"
);
// 2. Call newFunction.
newFunction
(
t
, &myFunc,
"myFunc"
, 2);
// 3. Store the resulting closure somewhere.
setGlobal(
t
,
"myFunc"
);
This function pops any upvalues off the stack and leaves the new closure in their place.

The function's environment is, by default, the current environment (see pushEnvironment). To use a different environment, see newFunctionWithEnv.

Params:
uint function(MDThread*, uint) func The native function to be used in the closure.
char[] name The name to be given to the function. This is just the 'debug' name that shows up in error messages. In order to make the function accessible, you have to actually put the resulting closure somewhere, like in the globals, or in a namespace.
uint numUpvals How many upvalues there are on the stack under the name to be associated with this closure. Defaults to 0.

Returns:
The stack index of the newly-created closure.

int newFunctionWithEnv (MDThread* t, uint function(MDThread*, uint) func, char[] name, uint numUpvals = cast(uint)0);
Creates a new native closure with an explicit environment and pushes it onto the stack.

Very similar to newFunction, except that it also expects the environment for the function (a namespace) to be on top of the stack. Using newFunction's example, one would push the environment namespace after step 1, and step 2 would call newFunctionWithEnv instead.

Params:
uint function(MDThread*, uint) func The native function to be used in the closure.
char[] name The name to be given to the function. This is just the 'debug' name that shows up in error messages. In order to make the function accessible, you have to actually put the resulting closure somewhere, like in the globals, or in a namespace.
uint numUpvals How many upvalues there are on the stack under the name and environment to be associated with this closure. Defaults to 0.

Returns:
The stack index of the newly-created closure.

int newClass (MDThread* t, int base, char[] name);
Creates a new class and pushes it onto the stack.

After creating the class, you can then fill it with members by using fielda.

Params:
int base The stack index of the base class. The base can be `null`, in which case Object (defined in the base library and which lives in the global namespace) will be used. Otherwise it must be a class.
char[] name The name of the class. Remember that you still have to store the class object somewhere, though, like in a global.

Returns:
The stack index of the newly-created class.

int newClass (MDThread* t, char[] name);
Same as above, except it uses the global Object as the base. The new class is left on the top of the stack.

int newInstance (MDThread* t, int base, uint numValues = cast(uint)0, uint extraBytes = cast(uint)0);
Creates an instance of a class and pushes it onto the stack. This does not call any constructors defined for the class; this simply allocates an instance.

MiniD instances can have two kinds of extra data associated with them for use by the host: extra MiniD values and arbitrary bytes. The structure of a MiniD instance is something like this:

// ---------
// |       |
// |       | The data that's part of every instance - its parent class, fields, and finalizer.
// |       |
// +-------+
// |0: "x" | Extra MiniD values which can point into the MiniD heap.
// |1: 5   |
// +-------+
// |...    | Arbitrary byte data.
// ---------
Both extra sections are optional, and no instances created from script classes will have them.

Extra MiniD values are useful for adding "members" to the instance which are not visible to the scripts but which can still hold MiniD objects. They will be scanned by the GC, so objects referenced by these members will not be collected. If you want to hold a reference to a native D object, for instance, this would be the place to put it (wrapped in a NativeObject).

The arbitrary bytes associated with an instance are not scanned by either the D or the MiniD GC, so don't store references to GC'ed objects there. These bytes are useable for just about anything, such as storing values which can't be stored in MiniD values -- structs, complex numbers, long integers, whatever.

A clarification: You can store references to heap objects in the extra bytes, but you must not store references to GC'ed objects there. That is, you can 'malloc' some data and store the pointer in the extra bytes, since that's not GC'ed memory. You must however perform your own memory management for such memory. You can set up a finalizer function for instances in which you can perform memory management for these references.

Params:
int base The class from which this instance will be created.
uint numValues How many extra MiniD values will be associated with the instance. See above.
uint extraBytes How many extra bytes to attach to the instance. See above.

int newNamespace (MDThread* t, char[] name);
Creates a new namespace object and pushes it onto the stack.

The parent of the new namespace will be the current function environment, exactly as in MiniD when you declare a namespace without an explicit parent.

Params:
char[] name The name of the namespace.

Returns:
The stack index of the newly-created namespace.

int newNamespace (MDThread* t, int parent, char[] name);
Creates a new namespace object with an explicit parent and pushes it onto the stack.

Params:
int parent The stack index of the parent. The parent can be null, in which case the new namespace will not have a parent. Otherwise it must be a namespace.
char[] name The name of the namespace.

Returns:
The stack index of the newly-created namespace.

int newNamespaceNoParent (MDThread* t, char[] name);
Creates a new namespace object with no parent and pushes it onto the stack.

This is very similar to newNamespace but creates a namespace without a parent. This function expects no values to be on the stack.

Params:
char[] name The name of the namespace.

Returns:
The stack index of the newly-created namespace.

int newThread (MDThread* t, int func);
Creates a new thread object (coroutine) and pushes it onto the stack.

Params:
int func The slot which contains the function to be used as the coroutine's body. If extended coroutine support is enabled, this can be a native or script function; otherwise, it must be a script function.

Returns:
The stack index of the newly-created thread.

int pushThread (MDThread* t, MDThread* o);
Pushes the given thread onto this thread's stack.

Params:
MDThread* o The thread to push.

Returns:
The stack index of the newly-pushed value.

int pushNativeObj (MDThread* t, Object o);
Pushes a reference to a native (D) object onto the stack.

Params:
Object o The object to push.

Returns:
The index of the newly-pushed value.

int pushWeakRef (MDThread* t, int idx);
Pushes a weak reference to the object at the given stack index onto the stack. For value types (null, bool, int, float, and char), weak references are unnecessary, and in these cases the value will simply be pushed. Otherwise the pushed value will be a weak reference object.

Params:
int idx The stack index of the object to get a weak reference of.

Returns:
The stack index of the newly-pushed value.

int absIndex (MDThread* t, int idx);
Given an index, returns the absolute index that corresponds to it. This is useful for converting relative (negative) indices to indices that will never change. If the index is already absolute, just returns it. Throws an error if the index is out of range.

bool isValidIndex (MDThread* t, int idx);
Sees if a given stack index (negative or positive) is valid. Valid positive stack indices range from [0 .. stackSize(t)). Valid negative stack indices range from [-stackSize(t) .. 0).

bool isNull (MDThread* t, int slot);
Sees if the value at the given slot is null.

bool isBool (MDThread* t, int slot);
Sees if the value at the given slot is a bool.

bool isInt (MDThread* t, int slot);
Sees if the value at the given slot is an int.

bool isFloat (MDThread* t, int slot);
Sees if the value at the given slot is a float.

bool isNum (MDThread* t, int slot);
Sees if the value at the given slot is an int or a float.

bool isChar (MDThread* t, int slot);
Sees if the value at the given slot is a char.

bool isString (MDThread* t, int slot);
Sees if the value at the given slot is a string.

bool isTable (MDThread* t, int slot);
Sees if the value at the given slot is a table.

bool isArray (MDThread* t, int slot);
Sees if the value at the given slot is an array.

bool isFunction (MDThread* t, int slot);
Sees if the value at the given slot is a function.

bool isClass (MDThread* t, int slot);
Sees if the value at the given slot is a class.

bool isInstance (MDThread* t, int slot);
Sees if the value at the given slot is an instance.

bool isNamespace (MDThread* t, int slot);
Sees if the value at the given slot is a namespace.

bool isThread (MDThread* t, int slot);
Sees if the value at the given slot is a thread.

bool isNativeObj (MDThread* t, int slot);
Sees if the value at the given slot is a native object.

bool isWeakRef (MDThread* t, int slot);
Sees if the value at the given slot is a weak reference.

bool isTrue (MDThread* t, int slot);
Gets the truth value of the value at the given slot. null, false, integer 0, floating point 0.0, and character '\0' are considered false; everything else is considered true. This is the same behavior as within the language.

Type type (MDThread* t, int slot);
Gets the type of the value at the given slot. Value types are given by the MDValue.Type enumeration defined in minid.types.

bool getBool (MDThread* t, int slot);
Returns the boolean value at the given slot, or throws an error if it isn't one.

long getInt (MDThread* t, int slot);
Returns the integer value at the given slot, or throws an error if it isn't one.

double getFloat (MDThread* t, int slot);
Returns the float value at the given slot, or throws an error if it isn't one.

double getNum (MDThread* t, int slot);
Returns the numerical value at the given slot. This always returns an mdfloat, and will implicitly cast int values to floats. Throws an error if the value is neither an int nor a float.

dchar getChar (MDThread* t, int slot);
Returns the character value at the given slot, or throws an error if it isn't one.

char[] getString (MDThread* t, int slot);
Returns the string value at the given slot, or throws an error if it isn't one.

The returned string points into the MiniD heap. It should NOT be modified in any way. The returned array reference should also not be stored on the D heap, as once the string object is removed from the MiniD stack, there is no guarantee that the string data will be valid (MiniD might collect it, as it has no knowledge of the reference held by D). If you need the string value for a longer period of time, you should dup it.

MDThread* getThread (MDThread* t, int slot);
Returns the thread object at the given slot, or throws an error if it isn't one.

The returned thread object points into the MiniD heap, and as such, if no reference to it is held from the MiniD heap or stack, it may be collected, so be sure not to store the reference away into a D data structure and then let the thread have its references dropped in MiniD. This is really meant for access to threads so that you can call thread functions on them.

Object getNativeObj (MDThread* t, int slot);
Returns the native D object at the given slot, or throws an error if it isn't one.

T safeCode (T)(MDThread* t, lazy T code);
An odd sort of protective function. You can use this function to wrap a call to a library function etc. which could throw an exception, but when you don't want to have to bother with catching the exception yourself. Useful for writing native MiniD libraries.

Say you had a function which opened a file:

File f = OpenFile(
"filename"
);
Say this function could throw an exception if it failed. Since the interpreter can only catch (and make meaningful stack traces about) exceptions which derive from MDException, any exceptions that this throws would just percolate up out of the interpreter stack. You could catch the exception yourself, but that's kind of tedious, especially when you call a lot of native functions.

Instead, you can wrap the call to this unsafe function with a call to safeCode ().

File f = 
safeCode
(t, OpenFile(
"filename"
));
What safeCode () does is it tries to execute the code it is passed. If it succeeds, it simply returns any value that the code returns. If it throws an exception derived from MDException, it rethrows the exception. And if it throws an exception that derives from Exception, it throws a new MDException with the original exception's message as the message.

If you want to wrap statements, you can use a delegate literal:

safeCode
(t, { stmt1; stmt2; stmt3; }());
Be sure to include those empty parens after the delegate literal, due to the way D converts the expression to a lazy parameter. If you don't put the parens there, it will never actually call the delegate.

safeCode () is templated to allow any return value.

Params:
code The code to be executed. This is a lazy parameter, so it's not actually executed until inside the call to safeCode .

Returns:
Whatever the code parameter returns.

struct foreachLoop ;
This structure is meant to be used as a helper to perform a MiniD-style foreach loop. It preserves the semantics of the MiniD foreach loop and handles the foreach/opApply protocol manipulations.

To use this, first you push the container -- what you would normally put on the right side of the semicolon in a foreach loop in MiniD. Just like in MiniD, this is one, two, or three values, and if the first value is not a function, opApply is called on it with the second value as a user parameter.

Then you can create an instance of this struct using the static opCall and iterate over it with a D foreach loop. Instead of getting values as the loop indices, you get indices of stack slots that hold those values. You can break out of the loop just as you'd expect, and you can perform any manipulations you'd like in the loop body.

Example:
// 1. Push the container.  We're just iterating through modules.customLoaders.
lookup(t,
"modules.customLoaders"
);
// 2. Perform a foreach loop on a foreachLoop instance created with the thread and the number
// of items in the container.  We only pushed one value for the container, so we pass 1.
// Note that you must specify the index types (which must all be word), or else D can't infer
// the types for them.
foreach
(word k, word v;
foreachLoop
(t, 1)) {
// 3. Do whatever you want with k and v.
pushToString(t, k); pushToString(t, v); Stdout.formatln(
"{}: {}"
, getString(t, -2), getString(t, -1));
// here we're popping the strings we pushed.  You don't have to pop k and v or anything like that.
pop(t, 2); }
Note a few things: the foreach loop will pop the container off the stack, so the above code is stack-neutral (leaves the stack in the same state it was before it was run). You don't have to pop anything inside the foreach loop. You shouldn't mess with stack values below k and v, since foreachLoop keeps internal loop data there, but stack indices that were valid before the loop started will still be accessible. If you use only one index (like foreach(word v; ...)), it will work just like in MiniD where an implicit index will be inserted before that one, and you will get the second indices in v instead of the first.

static foreachLoop opCall (MDThread* t, uint numSlots);
The struct constructor.

Params:
uint numSlots How many slots on top of the stack should be interpreted as the container. Must be 1, 2, or 3.

int opApply (T)(T dg);
The function that makes everything work. This is templated to allow any number of indices, but the downside to that is that you must specify the types of the indices in the foreach loop that iterates over this structure. All the indices must be of type 'word'.

void throwException (MDThread* t);
Throws a MiniD exception using the value at the top of the stack as the exception object. Any type can be thrown. This will throw an actual D exception of type MDException as well, which can be caught in D as normal (Important: see catchException for information on catching them).

You cannot use this function if another exception is still in flight, that is, it has not yet been caught with catchException. If you try, an Exception will be thrown -- that is, an instance of the D Exception class.

This function obviously does not return.

void throwException (MDThread* t, char[] fmt,...);
A shortcut for the very common case where you want to throw a formatted string. This is equivalent to calling pushVFormat on the arguments and then calling throwException .

int catchException (MDThread* t);
When catching MiniD exceptions (those derived from MDException) in D, MiniD doesn't know that you've actually caught one unless you tell it. If you want to rethrow an exception without seeing what's in it, you can just throw the D exception object. But if you want to actually handle the exception, or rethrow it after seeing what's in it, you must call this function. This informs MiniD that you have caught the exception that was in flight, and pushes the exception object onto the stack, where you can inspect it and possibly rethrow it using throwException.

Note that if an exception occurred and you caught it, you might not know anything about what's on the stack. It might be garbage from a half-completed operation. So you might want to store the size of the stack before a 'try' block, then restore it in the 'catch' block so that the stack will be in a consistent state.

An exception must be in flight for this function to work. If none is in flight, a MiniD exception is thrown. (For some reason, that sounds funny. "Error: there is no error!")

Returns:
The stack index of the newly-pushed exception object.

int getTraceback (MDThread* t);
After catching an exception, you can get a traceback, which is the sequence of functions that the exception was thrown through before being caught. Tracebacks work across coroutine boundaries. They also work across tailcalls, and it will be noted when this happens (in the traceback you'll see something like "<4 tailcalls>(?)" to indicate that 4 tailcalls were performed between the previous function and the next function in the traceback). Lastly tracebacks work across native function calls, in which case the name of the function will be noted but no line number will be given since that would be impossible; instead it is marked as "(native)".

When you call this function, it will push a string representing the traceback onto the given thread's stack, in this sort of form:

Traceback; 
function
.that.threw.exception(9) at
function
.that.called.it(23) at <5 tailcalls>(?) at some.native.
function
(native)
(Due to a DDoc bug, it's actually "Traceback:", not "Traceback;".)

Sometimes you'll get something like "<no location available> " in the traceback. This might happen if some top-level native API manipulations (that is, those outside the context of any executing function) cause an error.

When you call this function, the traceback information associated with this thread's VM is subsequently erased. If this function is called again, you will get an empty string.

Returns:
The stack index of the newly-pushed traceback string.

void setUpval (MDThread* t, uint idx);
Sets an upvalue in the currently-executing closure. The upvalue is set to the value on top of the stack, which is popped.

This function will fail if called at top-level (that is, outside of any executing closures).

Params:
uint idx The index of the upvalue to set.

int getUpval (MDThread* t, uint idx);
Pushes an upvalue from the currently-executing closure.

This function will fail if called at top-level (that is, outside of any executing closures).

Params:
uint idx The index of the upvalue to set.

Returns:
The stack index of the newly-pushed value.

int pushTypeString (MDThread* t, int slot);
Pushes the string representation of the type of the value at the given slot.

Returns:
The stack index of the newly-pushed string.

int pushEnvironment (MDThread* t, uint depth = cast(uint)0);
Pushes the environment of a closure on the call stack.

Note that if tailcalls have occurred, environments of certain functions will be unavailable, and attempting to get them will throw an error.

If the depth you specify if deeper than the call stack, or if there are no functions on the call stack, the global namespace will be pushed.

Params:
uint depth The depth into the call stack of the closure whose environment to get. Defaults to 0, which means the currently-executing closure. A depth of 1 would mean the closure which called this closure, 2 the closure that called that one etc.

Returns:
The stack index of the newly-pushed environment.

int pushGlobal (MDThread* t, char[] name);
Pushes a global variable with the given name. Throws an error if the global cannot be found.

This function respects typical global lookup - that is, it starts at the current function's environment and goes up the chain.

Params:
char[] name The name of the global to get.

Returns:
The index of the newly-pushed value.

int getGlobal (MDThread* t);
Same as pushGlobal, except expects the name of the global to be on top of the stack. If the value at the top of the stack is not a string, an error is thrown. Replaces the name with the value of the global if found.

Returns:
The index of the retrieved value (the stack top).

void setGlobal (MDThread* t, char[] name);
Sets a global variable with the given name to the value on top of the stack, and pops that value. Throws an error if the global cannot be found. Remember that if this is the first time you are trying to set the global, you have to use newGlobal instead, just like using a global declaration in MiniD.

This function respects typical global lookup - that is, it starts at the current function's environment and goes up the chain.

Params:
char[] name The name of the global to set.

void setGlobal (MDThread* t);
Same as above, but expects the name of the global to be on the stack just below the value to set. Pops both the name and the value.

void newGlobal (MDThread* t, char[] name);
Declares a global variable with the given name, sets it to the value on top of the stack, and pops that value. Throws an error if the global has already been declared.

This function works just like a global variable declaration in MiniD. It creates a new entry in the current environment if it succeeds.

Params:
char[] name The name of the global to set.

void newGlobal (MDThread* t);
Same as above, but expects the name of the global to be on the stack under the value to be set. Pops both the name and the value off the stack.

bool findGlobal (MDThread* t, char[] name, uint depth = cast(uint)0);
Searches for a global of the given name.

By default, this follows normal global lookup, starting with the currently-executing function's environment, but you can change where the lookup starts by using the depth parameter.

Params:
char[] name The name of the global to look for.
uint depth The depth into the call stack of the closure in whose environment lookup should begin. Defaults to 0, which means the currently-executing closure. A depth of 1 would mean the closure which called this closure, 2 the closure that called that one etc.

Returns:
true if the global was found, in which case the containing namespace is on the stack. False otherwise, in which case nothing will be on the stack.

void clearTable (MDThread* t, int tab);
Removes all items from the given table object.

Params:
int tab The stack index of the table object to clear.

void fillArray (MDThread* t, int arr);
Fills the array at the given index with the value at the top of the stack and pops that value.

Params:
int arr The stack index of the array object to fill.

int getFuncEnv (MDThread* t, int func);
Pushes the environment namespace of a function closure.

Params:
int func The stack index of the function whose environment is to be retrieved.

Returns:
The stack index of the newly-pushed environment namespace.

void setFuncEnv (MDThread* t, int func);
Sets the namespace at the top of the stack as the environment namespace of a function closure and pops that namespace off the stack.

Params:
int func The stack index of the function whose environment is to be set.

char[] funcName (MDThread* t, int func);
Gets the name of the function at the given stack index. This is the name given in the declaration of the function if it's a script function, or the name given to newFunction for native functions. Some functions, like top-level module functions and nameless function literals, have automatically- generated names which always start and end with angle brackets (< and > ).

uint funcNumParams (MDThread* t, int func);
Gets the number of parameters that the function at the given stack index takes. This is the number of non-variadic arguments, not including 'this'. For native functions, always returns 0.

bool funcIsVararg (MDThread* t, int func);
Gets whether or not the given function takes variadic arguments. For native functions, always returns true.

bool funcIsNative (MDThread* t, int func);
Gets whether or not the given function is a native function.

void setFinalizer (MDThread* t, int cls);
Sets the finalizer function for the given class. The finalizer of a class is called when an instance of that class is about to be collected by the garbage collector and is used to clean up limited resources associated with it (i.e. memory allocated on the C heap, file handles, etc.). The finalizer function should be short and to-the-point as to make finalization as quick as possible. It should also not allocate very much memory, if any, as the garbage collector is effectively disabled during execution of finalizers. The finalizer function will only ever be called once for each instance. If the finalizer function causes the instance to be "resurrected", that is the instance is reattached to the application's memory graph, it will still eventually be collected but its finalizer function will not be run again.

Instances get the finalizer from the class that they are an instance of. If you instantiate a class, and then change its finalizer, the instances that were already created will use the old finalizer.

This function expects the finalizer function to be on the top of the stack. If you want to remove the finalizer function from a class, the value at the top of the stack can be null.

Params:
int cls The class whose finalizer is to be set.

int getFinalizer (MDThread* t, int cls);
Pushes the finalizer function associated with the given class, or null if no finalizer is set for that class.

Params:
int cls The class whose finalizer is to be retrieved.

Returns:
The stack index of the newly-pushed finalizer function (or null if the class has none).

void setAllocator (MDThread* t, int cls);
Normally when you instantiate a MiniD class, by doing something like "A(5)" (or similarly, by calling it as if it were a function using the native API), the following happens: the interpreter calls newInstance on the class to allocate a new instance, then calls any constructor defined for the class on the new instance with the given parameters, and finally it returns that new instance.

You can override this behavior using class allocators. A class allocator takes any number of parameters and must return a class instance. The 'this' parameter passed to a class allocator is the class which is being instantiated. Class allocators reserve the right to call or not call any constructor defined for the class. In fact, they can do just about anything as long as they return an instance.

Here is an example class allocator which performs the default behavior.

uword allocator(MDThread* 
t
, uword numParams) {
// new instance of the class held in 'this'
newInstance(
t
, 0);
// duplicate it so it can be used as a return value
dup(
t
);
// push a null for the 'this' slot of the impending method call
pushNull(
t
);
// rotate the stack so that we have
// [inst] [inst] [null] [params...]
rotateAll(
t
, 3);
// call the constructor on the instance, ignoring any returns
methodCall(
t
, 2,
"constructor"
, 0);
// now all that's left on the stack is the instance; return it
return
1; }
Why would a class use an allocator? Simple: if it needs to allocate extra values or bytes for its instances. Most of the native objects defined in the standard libraries use allocators to do just this.

You can also imagine a case where the number of extra values or bytes is dependent upon the parameters passed to the constructor, which is why class allocators get all the parameters.

This function expects the new class allocator to be on top of the stack. It should be a function, or null if you want to remove the given class's allocator.

Params:
int cls The stack index of the class object whose allocator is to be set.

int getAllocator (MDThread* t, int cls);
Pushes the allocator associated with the given class, or null if no allocator is set for that class.

Params:
int cls The class whose allocator is to be retrieved.

Returns:
The stack index of the newly-pushed allocator function (or null if the class has none).

char[] className (MDThread* t, int cls);
Gets the name of the class at the given stack index.

uint numExtraVals (MDThread* t, int slot);
Finds out how many extra values an instance has (see newInstance for info on that). Throws an error if the value at the given slot isn't an instance.

Params:
int slot The stack index of the instance whose number of values is to be retrieved.

Returns:
The number of extra values associated with the given instance.

int getExtraVal (MDThread* t, int slot, uint idx);
Pushes the idx th extra value from the instance at the given slot. Throws an error if the value at the given slot isn't an instance, or if the index is out of bounds.

Params:
int slot The instance whose value is to be retrieved.
uint idx The index of the extra value to get.

Returns:
The stack index of the newly-pushed value.

void setExtraVal (MDThread* t, int slot, uint idx);
Pops the value off the top of the stack and places it in the idx th extra value in the instance at the given slot. Throws an error if the value at the given slot isn't an instance, or if the index is out of bounds.

Params:
int slot The instance whose value is to be set.
uint idx The index of the extra value to set.

void[] getExtraBytes (MDThread* t, int slot);
Gets a void array of the extra bytes associated with the instance at the given slot. If the instance has no extra bytes, returns null. Throws an error if the value at the given slot isn't an instance.

The returned void array points into the MiniD heap, so you should not store the returned reference anywhere.

Params:
int slot The instance whose data is to be retrieved.

Returns:
A void array of the data, or null if the instance has none.

void clearNamespace (MDThread* t, int ns);
Removes all items from the given namespace object.

Params:
int ns The stack index of the namespace object to clear.

void removeKey (MDThread* t, int obj);
Removes the key at the top of the stack from the given object. The key is popped. The object must be a namespace or table.

Params:
int obj The stack index of the object from which the key is to be removed.

char[] namespaceName (MDThread* t, int ns);
Gets the name of the namespace at the given stack index. This is just the single name component that it was created with (like "foo" for "namespace foo {}").

int namespaceFullname (MDThread* t, int ns);
Pushes the "full" name of the given namespace, which includes all the parent namespace name components, separated by dots.

Returns:
The stack index of the newly-pushed name string.

State state (MDThread* t);
Gets the current coroutine state of the thread as a member of the MDThread.State enumeration.

char[] stateString (MDThread* t);
Gets a string representation of the current coroutine state of the thread.

The string returned is not on the MiniD heap, it's just a string literal.

MDVM* getVM (MDThread* t);
Gets the VM that the thread is associated with.

uint callDepth (MDThread* t);
Find how many calls deep the currently-executing function is nested. Tailcalls are taken into account.

If called at top-level, returns 0.

uint stackSize (MDThread* t);
Returns the number of items on the stack. Valid positive stack indices range from [0 .. stackSize(t)). Valid negative stack indices range from [-stackSize(t) .. 0).

Note that 'this' (stack index 0 or -stackSize(t)) may not be overwritten or changed, although it can be used with functions that don't modify their argument.

void resetThread (MDThread* t, int slot, bool newFunction = false);
Resets a dead thread to the initial state, optionally providing a new function to act as the body of the thread.

Params:
int slot The stack index of the thread to be reset. It must be in the 'dead' state.
bool newFunction If true, a function should be on top of the stack which should serve as the new body of the coroutine. The default is false, in which case the coroutine will use the function with which it was created.

void haltThread (MDThread* t);
Halts the given thread. If the given thread is currently running, throws a halt exception immediately; otherwise, places a pending halt on the thread.

void pendingHalt (MDThread* t);
Places a pending halt on the thread. This does nothing if the thread is in the 'dead' state.

bool hasPendingHalt (MDThread* t);
Sees if the given thread has a pending halt.

int deref (MDThread* t, int idx);
Works like the deref () function in the base library. If the value at the given index is a value type, just duplicates that value. If the value at the given index is a weak reference, pushes the object it refers to or 'null' if that object has been collected. Throws an error if the value at the given index is any other type. This is meant to be an inverse to pushWeakRef, hence the behavior with regards to value types.

Params:
int idx The stack index of the object to dereference.

Returns:
The stack index of the newly-pushed value.

int pushToString (MDThread* t, int slot, bool raw = false);
Push a string representation of any MiniD value onto the stack.

Params:
int slot The stack index of the value to convert to a string.
bool raw If true, will not call toString metamethods. Defaults to false, which means toString metamethods will be called.

Returns:
The stack index of the newly-pushed string.

bool opin (MDThread* t, int item, int container);
See if item is in container. Works like the MiniD 'in' operator. Calls opIn metamethods.

Params:
int item The item to look for (the lhs of 'in').
int container The object in which to look (the rhs of 'in').

Returns:
true if item is in container, false otherwise.

long cmp (MDThread* t, int a, int b);
Compare two values at the given indices, and give the comparison value (negative for a < b, positive for a > b, and 0 if a == b). This is the exact behavior of the '<=>' operator in MiniD. Calls opCmp metamethods.

Params:
int a The index of the first object.
int b The index of the second object.

Returns:
The comparison value.

bool equals (MDThread* t, int a, int b);
Test two values at the given indices for equality. This is the exact behavior of the '==' operator in MiniD. Calls opEquals metamethods.

Params:
int a The index of the first object.
int b The index of the second object.

Returns:
true if equal, false otherwise.

bool opis (MDThread* t, int a, int b);
Test two values at the given indices for identity. This is the exact behavior of the 'is' operator in MiniD.

Params:
int a The index of the first object.
int b The index of the second object.

Returns:
true if identical, false otherwise.

int idx (MDThread* t, int container, bool raw = false);
Index the container at the given index with the value at the top of the stack. Replaces the value on the stack with the result. Calls opIndex metamethods.

// x = a[6]
auto
cont = pushGlobal(
t
,
"a"
); pushInt(
t
, 6);
idx
(
t
, cont); setGlobal(
t
,
"x"
); pop(
t
);
// The stack is how it was when we started.


Params:
int container The stack index of the container object.
bool raw If true, no opIndex metamethods will be called. Defaults to false.

Returns:
The stack index that contains the result (the top of the stack).

void idxa (MDThread* t, int container, bool raw = false);
Index-assign the container at the given index with the key at the second-from-top of the stack and the value at the top of the stack. Pops both the key and the value from the stack. Calls opIndexAssign metamethods.

// a[6] = 10
auto
cont = pushGlobal(
t
,
"a"
); pushInt(
t
, 6); pushInt(
t
, 10);
idxa
(
t
, cont); pop(
t
);
// The stack is how it was when we started.


Params:
int container The stack index of the container object.
bool raw If true, no opIndex metamethods will be called. Defaults to false.

int idxi (MDThread* t, int container, long idx, bool raw = false);
Shortcut for the common case where you need to index a container with an integer index. Pushes the indexed value.

Params:
int container The stack index of the container object.
long idx The integer index.
bool raw If true, no opIndex metamethods will be called. Defaults to false.

Returns:
The stack index of the newly-pushed indexed value.

void idxai (MDThread* t, int container, long idx, bool raw = false);
Shortcut for the common case where you need to index-assign a container with an integer index. Pops the value at the top of the stack and assigns it into the container at the given index.

Params:
int container The stack index of the container object.
long idx The integer index.
bool raw If true, no opIndexAssign metamethods will be called. Defaults to false.

int field (MDThread* t, int container, char[] name, bool raw = false);
Get a field with the given name from the container at the given index. Pushes the result onto the stack.

// x = a.y
pushGlobal(
t
,
"a"
);
field
(
t
, -1,
"y"
); setGlobal(
t
,
"x"
); pop(
t
);
// The stack is how it was when we started.


Params:
int container The stack index of the container object.
char[] name The name of the field to get.
bool raw If true, does not call opField metamethods. Defaults to false, which means it will.

Returns:
The stack index of the newly-pushed result.

int field (MDThread* t, int container, bool raw = false);
Same as above, but expects the field name to be at the top of the stack. If the value at the top of the stack is not a string, an error is thrown. The field value replaces the field name, much like with idx.

Params:
int container The stack index of the container object.
bool raw If true, does not call opField metamethods. Defaults to false, which means it will.

Returns:
The stack index of the retrieved field value.

void fielda (MDThread* t, int container, char[] name, bool raw = false);
Sets a field with the given name in the container at the given index to the value at the top of the stack. Pops that value off the stack. Calls opFieldAssign metamethods.

// a.y = x
auto
cont = pushGlobal(
t
,
"a"
); pushGlobal(
t
,
"x"
);
fielda
(
t
, cont,
"y"
); pop(
t
);
// The stack is how it was when we started.


Params:
int container The stack index of the container object.
char[] name The name of the field to set.
bool raw If true, does not call opFieldAssign metamethods. Defaults to false, which means it will.

void fielda (MDThread* t, int container, bool raw = false);
Same as above, but expects the field name to be in the second-from-top slot and the value to set at the top of the stack, similar to idxa. Throws an error if the field name is not a string. Pops both the set value and the field name off the stack, just like idxa.

Params:
int container The stack index of the container object.
bool raw If true, does not call opFieldAssign metamethods. Defaults to false, which means it will.

int pushLen (MDThread* t, int slot);
Pushes the length of the object at the given slot. Calls opLength metamethods.

Params:
int slot The slot of the object whose length is to be retrieved.

Returns:
The stack index of the newly-pushed length.

long len (MDThread* t, int slot);
Gets the integral length of the object at the given slot. Calls opLength metamethods. If the length of the object is not an integer, throws an error.

Params:
int slot The slot of the object whose length is to be retrieved.

Returns:
The length of the object.

void lena (MDThread* t, int slot);
Sets the length of the object at the given slot to the value at the top of the stack and pops that value. Calls opLengthAssign metamethods.

Params:
int slot The slot of the object whose length is to be set.

int slice (MDThread* t, int container);
Slice the object at the given slot. The low index is the second-from-top value on the stack, and the high index is the top value. Either index can be null. The indices are popped and the result of the slice operation is pushed.

Params:
int container The slot of the object to be sliced.

void slicea (MDThread* t, int container);
Slice-assign the object at the given slot. The low index is the third-from-top value; the high is the second-from-top; and the value to assign into the object is on the top. Either index can be null. Both indices and the value are popped.

Params:
int container The slot of the object to be slice-assigned.

int add (MDThread* t, int a, int b);
int sub (MDThread* t, int a, int b);
int mul (MDThread* t, int a, int b);
int div (MDThread* t, int a, int b);
int mod (MDThread* t, int a, int b);
These all perform the given mathematical operation on the two values at the given indices, and push the result of that operation onto the stack. Metamethods (including reverse versions) will be called.

Don't use these functions if you're looking to do some serious number crunching on ints and floats. Just get the values and do the computation in D.

Params:
int a The slot of the first value.
int b The slot of the second value.

Returns:
The stack index of the newly-pushed result.

int neg (MDThread* t, int o);
Negates the value at the given index and pushes the result. Calls opNeg metamethods.

Like the binary operations, don't use this unless you need the actual MiniD semantics, as it's less efficient than just getting a number and negating it.

Params:
int o The slot of the value to negate.

Returns:
The stack index of the newly-pushed result.

void addeq (MDThread* t, int o);
void subeq (MDThread* t, int o);
void muleq (MDThread* t, int o);
void diveq (MDThread* t, int o);
void modeq (MDThread* t, int o);
These all perform the given reflexive mathematical operation on the value at the given slot, using the value at the top of the stack for the rhs. The rhs is popped. These call metamethods.

Like the other mathematical methods, it's more efficient to perform the operation directly on numbers rather than to use these methods. Use these only if you need the MiniD semantics.

Params:
int o The slot of the object to perform the reflexive operation on.

int and (MDThread* t, int a, int b);
int or (MDThread* t, int a, int b);
int xor (MDThread* t, int a, int b);
int shl (MDThread* t, int a, int b);
int shr (MDThread* t, int a, int b);
int ushr (MDThread* t, int a, int b);
These all perform the given bitwise operation on the two values at the given indices, and push the result of that operation onto the stack. Metamethods (including reverse versions) will be called.

Don't use these functions if you're looking to do some serious number crunching on ints. Just get the values and do the computation in D.

Params:
int a The slot of the first value.
int b The slot of the second value.

Returns:
The stack index of the newly-pushed result.

int com (MDThread* t, int o);
Bitwise complements the value at the given index and pushes the result. Calls opCom metamethods.

Like the binary operations, don't use this unless you need the actual MiniD semantics, as it's less efficient than just getting a number and complementing it.

Params:
int o The slot of the value to complement.

Returns:
The stack index of the newly-pushed result.

void andeq (MDThread* t, int o);
void oreq (MDThread* t, int o);
void xoreq (MDThread* t, int o);
void shleq (MDThread* t, int o);
void shreq (MDThread* t, int o);
void ushreq (MDThread* t, int o);
These all perform the given reflexive bitwise operation on the value at the given slot, using the value at the top of the stack for the rhs. The rhs is popped. These call metamethods.

Like the other bitwise methods, it's more efficient to perform the operation directly on numbers rather than to use these methods. Use these only if you need the MiniD semantics.

Params:
int o The slot of the object to perform the reflexive operation on.

int cat (MDThread* t, uint num);
Concatenates the top num parameters on the stack, popping them all and pushing the result on the stack.

If num is 1, this function does nothing. If num is 0, it is an error. Otherwise, the concatenation works just like it does in MiniD.

// x = "Hi, " ~ name ~ "!"
pushString(
t
,
"Hi "
); pushGlobal(
t
,
"name"
); pushString(
t
,
"!"
);
cat
(
t
, 3); setGlobal(
t
,
"x"
);


Params:
uint num How many values to concatenate.

Returns:
The stack index of the resulting object.

void cateq (MDThread* t, int dest, uint num);
Performs concatenation-assignment. dest is the stack slot of the destination object (the object to append to). num is how many values there are on the right-hand side and is expected to be at least 1. The RHS values are on the top of the stack. Pops the RHS values off the stack.

// x ~= "Hi, " ~ name ~ "!"
auto
dest
= pushGlobal(
t
,
"x"
); pushString(
t
,
"Hi "
); pushGlobal(
t
,
"name"
); pushString(
t
,
"!"
);
cateq
(
t
,
dest
, 3);
// 3 rhs values
setGlobal(
t
,
"x"
);
// have to put the new value back (since it's a string)


Params:
uint num How many values are on the RHS to be appended.

bool as (MDThread* t, int obj, int base);
Returns whether or not obj is an 'instance' and derives from base. Throws an error if base is not a class. Works just like the as operator in MiniD.

Params:
int obj The stack index of the value to test.
int base The stack index of the base class. Must be a 'class'.

Returns:
true if obj is an 'instance' and it derives from base. False otherwise.

void inc (MDThread* t, int slot);
Increments the value at the given slot. Calls opInc metamethods.

Params:
int slot The stack index of the value to increment.

void dec (MDThread* t, int slot);
Decrements the value at the given slot. Calls opDec metamethods.

Params:
int slot The stack index of the value to decrement.

int superOf (MDThread* t, int slot);
Gets the class of instances, base class of classes, or the parent namespace of namespaces and pushes it onto the stack. Throws an error if the value at the given slot is not a class, instance, or namespace. Works just like "x.super" in MiniD. For classes and namespaces, pushes null if there is no base or parent.

Params:
int slot The stack index of the instance, class, or namespace whose class, base, or parent to get.

Returns:
The stack index of the newly-pushed value.

uint rawCall (MDThread* t, int slot, int numReturns);
Calls the object at the given slot. The parameters (including 'this') are assumed to be all the values after that slot to the top of the stack.

The 'this' parameter is, according to the language specification, null if no explicit context is given. You must still push this null value, however.

An example of calling a function:

// Let's translate `x = f(5, "hi")` into API calls.
// 1. Push the function (or any callable object -- like instances, threads).
auto
slot
= pushGlobal(
t
,
"f"
);
// 2. Push the 'this' parameter.  This is 'null' if you don'_t care.  Notice in the MiniD code, we didn'_t
// put a 'with', so 'null' will be used as the context.
pushNull(
t
);
// 3. Push any params.
pushInt(
t
, 5); pushString(
t
,
"hi"
);
// 4. Call it.
rawCall
(
t
,
slot
, 1);
// 5. Do something with the return values.  setGlobal pops the return value off the stack, so now the
// stack is back the way it was when we started.
setGlobal(
t
,
"x"
);


Params:
int slot The slot containing the object to call.
int numReturns How many return values you want. Can be -1, which means you'll get all returns.

Returns:
The number of return values given by the function. If numReturns was -1, this is exactly how many returns the function gave. If numReturns was >= 0, this is the same as numReturns (and not exactly useful since you already know it).

uint methodCall (MDThread* t, int slot, char[] name, int numReturns, bool customThis = false);
Calls a method of an object at the given slot. The parameters (including a spot for 'this') are assumed to be all the values after that slot to the top of the stack.

This function behaves identically to a method call within the language, including calling opMethod metamethods if the method is not found.

The process of calling a method is very similar to calling a normal function.

// Let's translate `o.f(3)` into API calls.
// 1. Push the object on which the method will be called.
auto
slot
= pushGlobal(
t
,
"o"
);
// 2. Make room for 'this'.  If you want to call the method with a custom 'this', push it here.
// Otherwise, we'll let MiniD figure out the 'this' and we can just push null.
pushNull(
t
);
// 3. Push any params.
pushInt(
t
, 3);
// 4. Call it with the method name.  We didn'_t push a custom 'this', so we don'_t pass '_true' for that param.
methodCall
(
t
,
slot
,
"f"
, 0);
// We didn'_t ask for any return values, so the stack is how it was before we began.


Params:
int slot The slot containing the object on which the method will be called.
char[] name The name of the method to call.
int numReturns How many return values you want. Can be -1, which means you'll get all returns.
bool customThis If true, the 'this' parameter you push after the object will be respected and passed as 'this' to the method (though the method will still be looked up in the object). The default is false, where the context will be determined automatically (i.e. it's the object on which the method is being called).

Returns:
The number of return values given by the function. If numReturns was -1, this is exactly how many returns the function gave. If numReturns was >= 0, this is the same as numReturns (and not exactly useful since you already know it).

uint methodCall (MDThread* t, int slot, int numReturns, bool customThis = false);
Same as above, but expects the name of the method to be on top of the stack (after the parameters).

The parameters and return value are the same as above.

uint superCall (MDThread* t, int slot, char[] name, int numReturns);
Performs a super call. This function will only work if the currently-executing function was called as a method of a value of type 'instance'.

This function works similarly to other kinds of calls, but it's somewhat odd. Other calls have you push the thing to call followed by 'this' or a spot for it. This call requires you to just give it two empty slots. It will fill them in (and what it puts in them is really kind of scary). Regardless, when the super method is called (if there is one), its 'this' parameter will be the currently-executing function's 'this' parameter.

The process of performing a supercall is not really that much different from other kinds of calls.

// Let's translate `super.f(3)` into API calls.
// 1. Push a null.
auto
slot
= pushNull(
t
);
// 2. Push another null.  You can'_t call a super method with a custom 'this'.
pushNull(
t
);
// 3. Push any params.
pushInt(
t
, 3);
// 4. Call it with the method name.
superCall
(
t
,
slot
,
"f"
, 0);
// We didn'_t ask for any return values, so the stack is how it was before we began.


Params:
int slot The first empty slot. There should be another one on top of it. Then come any parameters.
char[] name The name of the method to call.
int numReturns How many return values you want. Can be -1, which means you'll get all returns.

Returns:
The number of return values given by the function. If numReturns was -1, this is exactly how many returns the function gave. If numReturns was >= 0, this is the same as numReturns (and not exactly useful since you already know it).

uint superCall (MDThread* t, int slot, int numReturns);
Same as above, but expects the method name to be at the top of the stack (after the parameters).

The parameters and return value are the same as above.

int fieldsOf (MDThread* t, int obj);
Gets the fields namespace of the class or instance at the given slot. Throws an exception if the value at the given slot is not a class or instance.

Params:
int obj The stack index of the value whose fields are to be retrieved.

Returns:
The stack index of the newly-pushed fields namespace.

bool hasField (MDThread* t, int obj, char[] fieldName);
Sees if the object at the stack index `obj` has a field with the given name. Does not take opField metamethods into account. Because of that, only works for tables, classes, instances, and namespaces. If the object at the stack index `obj` is not one of those types, always returns false. If this function returns true, you are guaranteed that accessing a field of the given name on the given object will succeed.

Params:
int obj The stack index of the object to test.
char[] fieldName The name of the field to look up.

Returns:
true if the field exists in `obj`; false otherwise.

bool hasMethod (MDThread* t, int obj, char[] methodName);
Sees if a method can be called on the object at stack index `obj`. Does not take opMethod metamethods into account, but does take type metatables into account. In other words, if you look up a method in an object and this function returns true, you are guaranteed that calling a method of that name on that object will succeed.

Params:
int obj The stack index of the obejct to test.
char[] methodName The name of the method to look up.

Returns:
true if the method can be called on `obj`; false otherwise.

void setHookFunc (MDThread* t, ubyte mask, uint hookDelay);
Sets or removes the debugging hook function for the given thread.

The hook function is used to "hook" into various points in the execution of MiniD scripts. When the hook function is called, you can use the other debugging APIs to get information about the call stack, the local variables and upvalues of functions on the call stack, line information etc. This way you can write a debugger for MiniD scripts.

There are a few different kind of events that you can hook into. You can only have one hook function on a thread, and that function will respond to all the types of events that you set it to.

The kinds of events that you can hook into are listed in the MDThread.Hook enumeration, and include the following:

  • MDThread.Hook.Call - This hook occurs when a function is just about to be called. The top of the call stack will be the function that is about to be called, but the hook occurs before execution begins.


  • MDThread.Hook.Ret - This hook occurs when a function is just about to return. The hook is called just before the return actually occurs, so the top of the call stack will be the function that is about to return. If you subscribe the hook function to this event, you will also get tail return events.


  • MDThread.Hook.TailRet - This hook occurs immediately after "return" hooks if the returning function has been tailcalled. One "tail return" hook is called for each tailcall that occurred. No real useful information will be available. If you subscribe the hook function to this event, you will also get normal return events.


  • MDThread.Hook.Delay - This hook occurs after a given number of MiniD instructions have executed. You set this delay as a parameter to setHookFunc , and if the delay is set to 0, this hook is not called. This hook is also only ever called in MiniD script functions.


  • MDThread.Hook.Line - This hook occurs when execution of a script function reaches a new source line. This is called before the first instruction associated with the given line occurs. It's also called immediately after a function begins executing (before its first instruction executes) or if a jump to the beginning of a loop occurs.


This function can be used to set or unset the hook function for the given thread. In either case, it expects for there to be one value at the top of the stack, which it will pop. The value must be a function or 'null'. To unset the hook function, either have 'null' on the stack, or pass 0 for the mask parameter.

When the hook function is called, the thread that the hook is being called on is passed as the 'this' parameter, and one parameter is passed. This parameter is a string containing one of the following: "call", "ret", "tailret", "delay", or "line", according to what kind of hook this is. The hook function is not required to return any values.

Params:
ubyte mask A bitwise OR-ing of the members of the MDThread.Hook enumeration as described above. The Delay value is ignored and will instead be set or unset based on the hookDelay parameter. If you have either the Ret or TailRet values, the function will be registered for all returns. If this parameter is 0, the hook function will be removed from the thread.
uint hookDelay If this is nonzero, the Delay hook will be called every hookDelay MiniD instructions. Otherwise, if it's 0, the Delay hook will be disabled.

int getHookFunc (MDThread* t);
Pushes the hook function associated with the given thread, or null if no hook function is set for it.

ubyte getHookMask (MDThread* t);
Gets a bitwise OR-ing of all the hook types set for this thread, as declared in the MDThread.Hook enumeration. Note that the MDThread.Hook.TailRet flag will never be set, as tail return events are also covered by MDThread.Hook.Ret.

uint getHookDelay (MDThread* t);
Gets the hook function delay, which is the number of instructions between each "Delay" hook event. If the hook delay is 0, the delay hook event is disabled.

void printStack (MDThread* t);
Debug mode only. Prints out the contents of the stack to Stdout in the following format:

[xxx:yyyy]: val: type
Where xxx is the absolute stack index; yyyy is the stack index relative to the currently-executing function's stack frame (negative numbers for lower slots, 0 is the first slot of the stack frame); val is a raw string representation of the value in that slot; and type is the type of that value.

void printCallStack (MDThread* t);
Debug mode only. Prints out the call stack in reverse, starting from the currently-executing function and going back, in the following format (without quotes; I have to put them to keep DDoc happy):

"Record: name"
"Base: base"
"Saved Top: top"
"Vararg Base: vargBase"
"Returns Slot: retSlot"
"Num Returns: numRets"
Where name is the name of the function at that level; base is the absolute stack index of where this activation record's stack frame begins; top is the absolute stack index of the end of its stack frame; vargBase is the absolute stack index of where its variadic args (if any) begin; retSlot is the absolute stack index where return values (if any) will started to be copied upon that function returning; and numRets being the number of returns that the calling function expects it to return (-1 meaning "as many as possible").

This only prints out the current thread's call stack. It does not take coroutine resumes and yields into account (since that's pretty much impossible).

Page was generated with on Thu Jul 16 22:47:49 2009