LEOInterpreter
IntroductionUse the links in the table of contents to the left to access the documentation. GroupsLEOContext calls for call stack managementThese are used by the CALL_HANDLER_INSTR and RETURN_FROM_HANDLER_INSTR instructions. Group members:
LEOContext methodsGroup members:
Static typecasting functionsGroup members:Functions
LEOCastUInt32ToInt32inline int32_t LEOCastUInt32ToInt32( uint32_t inNum ) __attribute__((always_inline)); DiscussionReinterpret the given unsigned uint32_t as a signed int32_t. E.g. useful for an instruction's param2 field. LEOCastUInt16ToInt16inline int16_t LEOCastUInt16ToInt16( uint16_t inNum ) __attribute__((always_inline)); DiscussionReinterpret the given unsigned uint16_t as a signed int16_t. E.g. useful for an instruction's param1 field. LEOCastUInt32ToLEONumberinline LEONumber LEOCastUInt32ToLEONumber( uint32_t inNum ) __attribute__((always_inline)); DiscussionReinterpret the given unsigned uint32_t as a LEONumber floating point quantity. E.g. useful for an instruction's param2 field. LEOInitInstructionArrayvoid LEOInitInstructionArray( void ); DiscussionCall this method once before calling any other interpreter functions. This initializes the instruction look-up table with the built-in instructions. LEOAddInstructionsToInstructionArrayvoid LEOAddInstructionsToInstructionArray( struct LEOInstructionEntry *inInstructionArray, size_t inNumInstructions, size_t *outFirstNewInstruction ); DiscussionUse this method to register new instructions for your host application. The new entries will be appended to the built-in instruction array (and your array copied), and the index of your first instruction will be returned in outFirstNewInstruction. You can now add this value to your instruction indices to calculate the actual LEOInstructionID you need to generate Leonie bytecode. See the LEOInstructionEntry documentation on information on how to most easily declare your instruction array. LEOContextCreateLEOContext* LEOContextCreate( struct LEOContextGroup *inGroup, void *inUserData, LEOUserDataCleanUpFuncPtr inCleanUpFunc ); DiscussionCreate a new LEOContext. The context is added to the provided group, and will retain the context group until the context is released by its last client. So once you have added the context to the context group, you can release it if you don't intend to create any other contexts in that group directly. The context starts out with a reference count of 1. When you are done with it, release it using LEOContextRelease(). See Also LEOContextReleasevoid LEOContextRelease( LEOContext *inContext ); DiscussionRelease your reference to this context. If you were the last owner of this context, disposes of the given context's associated data structures and releases the reference to its context group that the context holds. See Also LEOContextRetainLEOContext* LEOContextRetain( LEOContext *inContext ); // returns inContext. DiscussionAcquire a reference to a context. Use this if, for example, you are passing LEOContext objects between functions, or otherwise sharing ownership of a context between several objects. Once you are done with the context, call LEOContextRelease on it to give up your reference. See Also LEORunInContextvoid LEORunInContext( LEOInstruction instructions[], LEOContext *inContext ); DiscussionShorthand for LEOPrepareContextForRunning and a loop of LEOContinueRunningContext. See Also LEOResumeContextvoid LEOResumeContext( LEOContext *inContext ); DiscussionQueues up a paused context for resumption of execution the next time LEOContextResumeIfAvailable is called. See Also LEOPauseContextvoid LEOPauseContext( LEOContext *inContext ); DiscussionAn instruction can call this to cause the given context so you can do some async work, then later come back to it with LEOResumeContext. See Also LEOContextResumeIfAvailablevoid LEOContextResumeIfAvailable( void ); See Also LEOPrepareContextForRunningvoid LEOPrepareContextForRunning( LEOInstruction instructions[], LEOContext *inContext ); DiscussionSet the currentInstruction of the given LEOContext to the given instruction array's first instruction, and initialize the Base pointer and stack end pointer and flags etc. See Also LEOContinueRunningContextbool LEOContinueRunningContext( LEOContext *inContext ); DiscussionExecute the next instruction in the context. Returns false if the code has finished executing or exited with an error. See Also LEOContextStopWithErrorvoid LEOContextStopWithError( LEOContext *inContext, size_t errLine, size_t errOffset, uint16_t fileID, const char *inErrorFmt, ... ); DiscussionStop execution in the given context with an error message. Currently sets the errMsg field of the context to the given string and set keepRunning to FALSE. inErrorFmt is a format string like you pass it to printf(). LEOContextSetLocalVariablevoid LEOContextSetLocalVariable( LEOContext *inContext, const char *varName, const char *inMessageFmt, ... ); DiscussionLook up the local variable with name varName in the current script, if present, and set its value to the given string. inMessageFmt is a format string like you pass it to printf(). LEOPushValueOnStackLEOValuePtr LEOPushValueOnStack( LEOContext *theContext, LEOValuePtr inValueToCopy ); DiscussionPush a copy of the given value onto the stack, returning a pointer to it. See Also LEOPushIntegerOnStackLEOValuePtr LEOPushIntegerOnStack( LEOContext *theContext, LEOInteger inInteger, LEOUnit inUnit ); DiscussionPush a copy of the given integer onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushNumberOnStackLEOValuePtr LEOPushNumberOnStack( LEOContext *theContext, LEONumber inNumber, LEOUnit inUnit ); DiscussionPush a copy of the given number onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushBooleanOnStackLEOValuePtr LEOPushBooleanOnStack( LEOContext *theContext, bool inBoolean ); DiscussionPush a copy of the given boolean onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushPointOnStackLEOValuePtr LEOPushPointOnStack( LEOContext *theContext, LEOInteger l, LEOInteger t ); DiscussionPush a copy of the given point onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushRectOnStackLEOValuePtr LEOPushRectOnStack( LEOContext *theContext, LEOInteger l, LEOInteger t, LEOInteger r, LEOInteger b ); DiscussionPush a copy of the given rectangle onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushArrayValueOnStackLEOValuePtr LEOPushArrayValueOnStack( LEOContext *theContext, struct LEOArrayEntry *inArray ); DiscussionPush the given array onto the stack, returning a pointer to the value. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. You may pass NULL for inArray to push an empty array. *** This takes over ownership of the given array. *** See Also LEOPushUnsetValueOnStackLEOValuePtr LEOPushUnsetValueOnStack( LEOContext *theContext ); DiscussionPushes a special empty string on the stack that a user can't generate, and which returns TRUE from LEOGetValueIsUnset(). To the user it looks just like any empty string, but if the user replaces it with any other value (even an empty string), you can detect that. Used e.g. by the message box to detect whether the code the user entered returned a result (i.e. was an expression) or not (i.e. was a command). This is a shorthand for LEOPushValueOnStack and LEOInitUnsetValue. See Also LEOPushEmptyValueOnStackLEOValuePtr LEOPushEmptyValueOnStack( LEOContext *theContext ); DiscussionPush an empty string onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEInitStringConstantValue("") call. See Also LEOPushStringValueOnStackLEOValuePtr LEOPushStringValueOnStack( LEOContext *theContext, const char *inString, size_t strLen ); DiscussionPush a copy of the given string onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOPushStringConstantValueOnStackLEOValuePtr LEOPushStringConstantValueOnStack( LEOContext *theContext, const char *inString ); DiscussionPush a copy of the given string constant onto the stack, returning a pointer to it. This is a shorthand for LEOPushValueOnStack and the corresponding LEOInitXXValue call. See Also LEOCleanUpStackToPtrvoid LEOCleanUpStackToPtr( LEOContext *theContext, union LEOValue *lastItemToDelete ); DiscussionUsed by instructions to unwind the stack (e.g. when removing their parameters) and ensure values get destructed correctly. See Also LEODebugPrintInstrvoid LEODebugPrintInstr( LEOInstruction *instruction, struct LEOScript *inScript, struct LEOHandler *inHandler, LEOContext *inContext ); ParametersDiscussionPrint the given instruction to the console for debugging purposes. See Also LEODebugPrintInstructionsvoid LEODebugPrintInstructions( LEOInstruction instructions[], size_t numInstructions, struct LEOScript *inScript, struct LEOHandler *inHandler, LEOContext *inContext ); ParametersDiscussionPrint the given array of instructions to the console for debugging purposes using LEODebugPrintInstr. See Also LEODebugPrintContextvoid LEODebugPrintContext( LEOContext *ctx ); DiscussionPrint the given context to the console for debugging purposes. This includes the stack, and is very useful for debugging new instructions. See Also LEOContextDebugPrintCallStackvoid LEOContextDebugPrintCallStack( LEOContext *inContext ); DiscussionPrint the current call stack of the given context to the console for debugging purposes. LEOContextPushHandlerScriptReturnAddressAndBasePtrvoid LEOContextPushHandlerScriptReturnAddressAndBasePtr( LEOContext *inContext, struct LEOHandler *inHandler, struct LEOScript *inScript, LEOInstruction *returnAddress, LEOValuePtr oldBP ); DiscussionPush a reference to the current script, the handler in it that is current, the return address and the previous base pointer to restore onto a special stack so the call instruction can retain the script/handler while it is running even if the owning object goes away, and so the return instruction can restore the base pointer and knows what instruction to return to. See Also LEOContextPeekCurrentHandlerstruct LEOHandler* LEOContextPeekCurrentHandler( LEOContext *inContext ); DiscussionRetrieve the object representing the handler currently in progress, which was pushed last onto a special stack. See Also LEOContextPeekCurrentScriptstruct LEOScript* LEOContextPeekCurrentScript( LEOContext *inContext ); DiscussionRetrieve the object representing the script owning the handler currently in progress, which was pushed last onto a special stack. See Also LEOContextPeekReturnAddressLEOInstruction* LEOContextPeekReturnAddress( LEOContext *inContext ); DiscussionRetrieve the address of the instruction that follows the CALL_HANDLER_INSTR instruction that brought you to this handler, and to which we are expected to return after this handler call. Note that this may be NULL to indicate you are the first handler pushed on the call stack (in C that would e.g. be main()). See Also LEOContextPeekBasePtrLEOValuePtr LEOContextPeekBasePtr( LEOContext *inContext ); DiscussionRetrieve the address of the base pointer as it was before this handler was called. Used to restore the calling handler's base pointer when this handler returns. See Also LEOContextPopHandlerScriptReturnAddressAndBasePtrvoid LEOContextPopHandlerScriptReturnAddressAndBasePtr( LEOContext *inContext ); DiscussionRemove the reference to the current handler, current script, return address and base pointer before the current handler was called from the special stack. This is called when returning from the current handler. See Also LEOFileIDForFileNameuint16_t LEOFileIDForFileName( const char *inFileName ); DiscussionGenerate a file ID for the given file name or file path (or could even be an object 'path' of some sort). This is used e.g. by the debugger to associate a source file with certain instructions. See Also LEOFileNameForFileIDconst char* LEOFileNameForFileID( uint16_t inFileID ); DiscussionReturn the file name that corresponds to the given file ID. See Also LEOSetInstructionIDToDebugPrintBeforevoid LEOSetInstructionIDToDebugPrintBefore( LEOInstructionID inID ); ParametersDiscussionTell the interpreter to log the current context (current instruction and stack) to the console before executing the specified instruction. See Also LEOSetInstructionIDToDebugPrintAftervoid LEOSetInstructionIDToDebugPrintAfter( LEOInstructionID inID ); ParametersDiscussionTell the interpreter to log the current context (current instruction and stack) to the console after executing the specified instruction. See Also LEOSetCheckForResumeProcvoid LEOSetCheckForResumeProc( void (*checkForResumeProc)() ); DiscussionA script can be paused mid-instruction and later resumed. This is handy e.g. when an instruction actually involves an asynchronous operation. Once the async operation has finished, it can call to request for the script to be resumed. Since we don't want scripts piling up in async callbacks, scripts are resumed by calling LEOContextResumeIfAvailable() from somewhere farther up the main thread (e.g. where regular event handlers would be triggered). When a resume request is made, this callback is invoked to give you the opportunity to ensure a call to LEOContextResumeIfAvailable() will be triggered by whatever mechanism you choose (queue up an event, signal a semaphore, whatever). Typedefs
LEOInstructionIDtypedef uint16_t LEOInstructionID; DiscussionIndex into our instruction function array. Index 0 contains the "unimplemented" error exit function. Note that instruction IDs are sequential and not intended to be saved to disk. If you want to save bytecode to disk, you should save a look-up-table from some persistent instruction opcode along to the file and then when loading fix up all the instruction IDs based on that name. LEOInstructionFuncPtrtypedef void ( *LEOInstructionFuncPtr)( struct LEOContext *inContext ); DiscussionAll instructions are implemented as functions with the following signature: Apart from branching instructions, every instruction has to increment the inContext->currentInstruction so it'll advance to the next instruction. The instruction functions are looked up in the gInstructions array. LEONonexistentHandlerFuncPtrtypedef void ( *LEONonexistentHandlerFuncPtr)( struct LEOContext *inContext, LEOHandlerID inHandler, TMayGoUnhandledFlag mayGoUnhandled ); DiscussionIf a handler call can't find the handler in the current script and no parent for that script exists that handles the call, this function is called to allow the host application to provide default behaviour for unhandled messages. LEOContextCompletionFuncPtrtypedef void ( *LEOContextCompletionFuncPtr)( struct LEOContext *inContext ); DiscussionThis function is called when a context finishes execution, either because of an error, or because the outermost function finished executing. You can now look at the return value or pass the message on to the next object in the chain. LEOInstructiontypedef struct LEOInstruction { LEOInstructionID instructionID; uint16_t param1; uint32_t param2; } LEOInstruction; DiscussionA LEOInstruction is how an instruction looks in bytecode. It has the instruction's ID as the first item, and then one 16-bit and one 32-bit argument that depends on the particular instruction. LEOContexttypedef struct LEOContext { size_t referenceCount; struct LEOContextGroup *group; // The group this context belongs to, containing its global state, references etc. LEOContextFlags flags; // But flags for flow control etc. char errMsg[1024]; // Error message to display when kLEOContextKeepRunning flag has been set to FALSE. size_t errLine; // Line on which the error occurred. Or SIZE_T_MAX if we don't know. size_t errOffset; // Byte offset in script at which the error occurred, or SIZE_T_MAX if we don't know. If this isn't present, errLine might still be set. uint16_t errFileID; // ID of the file in which the error occurred. char itemDelimiter; // item delimiter to use for chunk expressions in values. size_t numCallStackEntries; // Number of items in callStackEntries. LEOCallStackEntry *callStackEntries; // Array of call stack entries to allow showing a simple backtrace and picking handlers from the current script. LEOInstructionFuncPtr preInstructionProc; // For each instruction, this function gets called, to let you do idle processing, hook in a debugger etc. This should NOT be an instruction, as that would advance the PC and screw up the call of the actual instruction. LEOInstructionFuncPtr promptProc; // On certain errors, this function is called to enter into the debugger prompt. LEONonexistentHandlerFuncPtr callNonexistentHandlerProc; // When a handler is called that doesn't exist, and a script has no parent, this function is called (e.g. to display an error message or call an XCMD). size_t numSteps; // Used by LEODebugger's PreInstructionProc to implement single-stepping. LEOInstruction *currentInstruction; // PC union LEOValue *stackBasePtr; // BP union LEOValue *stackEndPtr; // SP (always points at element after last element) union LEOValue stack[LEO_STACK_SIZE]; // The stack. void *userData; LEOUserDataCleanUpFuncPtr cleanUpUserData; LEOContextCompletionFuncPtr contextCompleted; } LEOContext; Fields
DiscussionA LEOContext encapsulates all execution state needed to run bytecode. Speaking in CPU terms, it encapsulates the registers, the call stack, and a few thread-globals. Hence, each thread in which you want to run bytecode needs its own LEOContext. See Also Structs and UnionsLEOInstructionEntrystruct LEOInstructionEntry { LEOInstructionFuncPtr proc; const char *name; }; FieldsDiscussionA LEOInstructionEntry is how we look up the function associated with an instruction ID. We keep a master table of all instructions we know, and you can add your own instructions by simply adding to that list. You generally do not use this directly. Instead, you use the LEOINSTR_START, LEOINSTR, and LEOINSTR_LAST macros below to declare a global array: LEOINSTR_START(FrobnitzWebService,NUM_WEBSERVICE_INSTRUCTIONS) LEOINSTR(MyWebServiceStartInstruction) LEOINSTR_LAST(MyWebServiceStopInstruction) and pass that to LEOAddInstructionsToInstructionArray: size_t outFirstNewInstruction = 0; LEOAddInstructionsToInstructionArray( gFrobnitzWebServiceInstructions, NUM_WEBSERVICE_INSTRUCTIONS, &outFirstNewInstruction ); Assuming you've declared your instruction IDs in an enum like this: enum { MY_WEBSERVICE_START_INSTR = 0, MY_WEBSERVICE_STOP_INSTR, NUM_WEBSERVICE_INSTRUCTIONS }; The instruction IDs to generate can now be calculated like: LEOHandlerAddInstruction( theHandler, outFirstNewInstruction +MY_WEB_SERVICE_START_INSTR, 0, 0 ); or the likes. Enumerated TypesPossibleenum { kLEOContextKeepRunning = ( 1 << 0), kLEOContextPause = ( 1 << 1), kLEOContextResuming = ( 1 << 2) }; Constants
Discussionvalues for the 'flags' field in the LEOContext. Macro DefinitionsLEO_STACK_SIZE#define LEO_STACK_SIZE 1024 DiscussionHow many LEOValues can be on the stack before we run out of stack space. BACK_OF_STACK#define BACK_OF_STACK DiscussionPass this as param1 to some instructions that take a basePtr-relative address to make it pop the last value off the stack instead of reading a value from that address. Since they reinterpret the number as an int16_t, it needs to be a value in the middle of the unsigned range. Value 0x8000 (32768) |