It is often desirable to have a scripting capability within an embedded application. The Asp scripting platform was created for such a purpose. The purpose of this document is to guide embedded application developers through the process of incorporating Asp into their applications.
Embedded systems are usually resource constrained. However, many scripting environments utilize significant amounts of resources such as memory and uninterruptable processing time. To address these concerns, Asp was designed to restrict the amounts of resources that are required when running scripts. Of course, Asp can be used where these constraints are not as rigid, but its design is particularly targeted at platforms where resources are limited.
The Asp language resembles Python. However, unlike in the Python environment, Asp scripts must be compiled before being executed in the Asp script engine. The resulting executable is usually smaller in relation to its source code because it contains compact machine-like instructions, and all variable names have been converted to numbers to save space.
The engine is provided as a small library that is linked into the host application. Because many embedded systems support only C libraries, the Asp script engine is implemented entirely in C.
Asp attempts to behave in the best ways possible with respect to memory
utilization. The engine makes no use of dynamic memory allocation (i.e.,
malloc
, free
, etc.). The amount of memory
allocated for script execution is defined by the application during Asp
initialization and remains constant. The engine's implementation makes no
use of recursion, minimizing the demands on the run-time
stack*.
Asp also leaves the application in control of the processor as much as possible. Instead of giving complete control of the processor to the script engine when running a script, Asp provides a routine to advance the script by a single instruction at a time. Each instruction takes a very small amount of time to execute, allowing the application to perform other critical tasks such as servicing watchdog timers, handling asynchronous events, etc. Also, if a script needs to be paused or stopped, the application may simply stop advancing the script to pause its execution or reset the engine to abort it.
Asp scripts may call appropriately designed application functions. Sometimes, the task that a function performs may take a long time to complete. In such cases, Asp provides a re-entry model that allows application functions to be non-blocking (and re-entrant) while making the function call from the script appear to block, simplifying script logic. This allows the application to remain in control of script execution and other tasks in a single location within the code.
For details on writing Asp scripts and preparing them for execution, see the Script Writer's Guide.
* Logic that is recursive in nature is implemented using iteration and uses Asp's data memory stack (which is bound by the amount of data memory that is made available to the engine) instead of using the embedded system's run-time stack.
To incorporate the Asp script engine into an application, the application code must be linked with the Asp engine library. The library can be provided as static or shared varieties. For Linux environments, the library can be linked using -laspe on the gcc command line. If the predefined math functions are used by the application, -laspm must also be specified.
To use the Asp application programmer interface (API) in the code, include
the <asp.h>
header.
#include <asp.h>
Many of the API functions return enumerated error codes. Functions related
to loading script executable code into the engine return a value of type
AspAddCodeResult
, while functions related to script execution
return a value of type AspRunResult
. Values for these
enumerations are listed and described in the
AspAddCodeResult
and
AspRunResult
tables.
Including the <asp.h>
header defines the
ASP_VERSION
macro. This macro is provided to guard against
changes to the API in future versions of the library. The value is a
four-byte unsigned integer composed of the four components of the version
(major, minor, patch, and tweak), with the major component being the
most-significant byte. The following example shows how to handle a
difference with some of the collection-oriented functions introduced in
version 0.7.0.0 of the library.
#if ASP_VERSION < 0x00700000 AspTupleAppend(engine, tuple, value); AspUnref(value); #else AspTupleAppend(engine, tuple, value, true); #endif
While every attempt is made to keep the API as stable as possible, sometimes changes are necessary. The above technique can come in handy if the application code must support multiple versions of Asp that have differing APIs.
For scripts to be useful, they must be able to call C functions within the application to perform actions implemented in the application code. Asp uses an application specification file to describe those functions defined within the application that a script can call.
To accommodate the inclusion of these function specifications, developing
applications that use Asp requires additional steps in the development
flow. First, the script-callable functions are identified in an
application specification source file
(.asps
). This file is then processed to
produce three output files: a C header, a C source file, and a binary
application specification file (.aspec
)
which is used by the Asp compiler when compiling scripts for the
application. The next section describes the syntax of the application
specification source file, and the figure below shows where it fits into
the development flow.
In the diagram, the region enclosed in dotted outline shows the development flow. (Outside of that is the script preparation and execution flow.) As shown, the Asp generator (aspg) is used to generate the two C source files that must be included in the build of the application. The generated header should be included in any source files that define script-callable functions to ensure they are defined properly. This is especially true if the functions are implemented in C++ to ensure they are defined using the C linkage convention. The diagram also shows the Asp engine library being linked into the application
As mentioned above, script-callable functions must be described in an
application specification source file
(.asps
) and then used in the development
flow. This source file describes (a) functions that scripts may call,
and (b) variables with optionally assigned initial values. The syntax
for describing a function resembles the Asp def
statement
without the code body. Instead of a colon (:
) at the end of
the statement, an equals sign (=
) followed by the name of the
C function is given. Here is a short example defining two functions.
def sleep(s) = asp_sleep def print(*args) = asp_print
With these definitions in place, a script that calls the
sleep
or print
functions will end up calling the
application's asp_sleep
and asp_print
C
functions, respectively*.
Notice that positional and named (keyword) variable parameter lists are
supported. For example, the print
function definition includes
the *args
parameter, which represents a variable-length tuple
of arguments.
Function parameters may be assigned default values, but the values are
restricted to simple constants. These are None
,
...
(the ellipsis), the Boolean values False
and
True
, integers, floating-point values, and strings. So, for
example, an empty tuple (()
) is not a valid default value.
Here is an example of a function that converts its argument to a string.
Its only parameter has a default value of the empty string so that it may
be called with no arguments, resulting in the function presumably
returning an empty string.
def str(x = '') = asp_str
In addition to functions, variables may be defined and assigned an initial constant value. The assigned value has the same restrictions as those for function parameter default values; they must be simple constants. Variables defined in this way provide a convenient way for the application to supply constants for use within scripts.
PI = 3.1415926535
It is also possible to define a name without assigning a value. No code is generated during script initialization for such definitions, but a symbol value is reserved for the name.
xpos ypos width height
The generated header includes a macro definition for each name used in the
specification. This includes names of functions, function parameters,
and variables, with or without an assigned value. The form of this name is
shown below. These definitions allow the application to use the symbols by
name to, for example, test for the presence of an argument in the named
variable parameter lists (e.g., **kwargs
).
#define ASP_APP_app-name_SYM_var-name symbol-value
It should be noted that all the function and assigned variable definitions described in the application specification are initialized prior to each new script execution. So, a script that makes changes to the definitions during its execution will not affect the environment of the next script to run. For example, if a script deletes an application function definition or assigns a new value to an application variable, these changes remain in effect only until the end of the script's execution. The next script sees the original definitions when it starts.
Asp defines several built-in functions, but to use them, they
must be defined in the application's specification file. To facilitate
this, application specification source files for these built-in functions
are provided and may be included in the main specification using the
include
statement. Currently, there are five such files.
include sys include type include collect include iter include math
The names of the associated C functions for these built-ins all start with
, so applications should avoid naming their own
variables or functions with this prefix. The include files are available
in a common include directory. On Linux systems, this is typically
/usr/include/asps. The built-in functions are
described in the Script Writer's Guide.
AspLib_
It is also possible to build your own libraries of script-callable
functions. When writing the specification, the first line in the
specification file should be the lib
statement, which
identifies the functions as being delivered in a library instead of being
compiled directly in the application. The only time this truly matters is
when using shared libraries, especially with Windows (i.e., DLLs).
Specifying lib
triggers aspg to
decorate the function declarations with the ASP_LIB_API
macro,
which expands to the linkage specification required for the applicable
usage (i.e., import vs. export in Windows). If specified, the
lib
statement must be the first statement in the
.asps
file.
lib
It is possible that when including another specification into your own, not
all of its definitions will be needed or even desirable. In these cases,
definitions may be removed using the del
statement. In many
cases, dropping definitions will cause the C compiler to drop the
associated implementation from the final application executable.
include common del clock, sleep
In the above example, if the clock
and sleep
functions were defined in the included common definitions, but were not
needed or desired, they can be removed from the application's specification
as indicated. Notice that multiple names can be given in a single
del
statement, separated by commas.
As with Asp's syntax, comments are permitted. Also, line continuations may be used when a definition spans multiple lines.
# Function to delete a range of files by time. def delete_files_by_time \ (start_time, end_time, \ timeout = None) = asp_delete_files_by_time
The above definition uses a number sign (#
) to introduce a
comment, and a backslash (\
) to continue the definition onto
another line.
* The examples use C function names starting withas a matter of convention. However, note that there are no restrictions on the name of the C function (other than being a valid C identifier). It is advisable to avoid names starting withasp_
, however, since this prefix is used in the library implementation.)Asp_
A script engine instance is defined by the AspEngine
type (a
typedef
of a structure definition). Most functions in the
engine's API take an AspEngine
pointer as their first
argument. Note that because everything about a script engine is defined in
this structure, it is possible to have multiple script engine instances
present simultaneously in a single application. It is left to the
imagination of the developer as to how this might be applied.
The following sections describe the API functions that control the script engine or query it for information. In most cases, these functions are not designed to be called from within a script-callable application function; they should only be used outside the context of an executing script.
The basic outline of script execution logic is as follows:
AspEngine
structure instance.
AspDataEntrySize
function to determine
the number of bytes to allocate for the engine's data area.
AspInitialize
to initialize the engine instance and
data area.
AspSetCodePaging
to enable script code
paging.
AspAddCode
one or more times to load script
executable code into the code area, and then, when all the code has
been added, call AspSeal
to seal off the code and
perform final checks prior to execution.
AspSealCode
to identify the
location and size of an external block of script executable code.
AspSetCodePaging
, make a single call to
AspPageCode
to identify the location of an external
resource where pages of script executable code can be read.
AspSetArguments
or
AspSetArgumentsString
, depending on how the arguments are
defined in the application.
AspStep
repeatedly and checking
its return value. Any value other than AspRunResult_OK
indicates that the script has ended.
AspRestart
, and then go back to step 8.
AspReset
, and then go back to step
6.
Details of these and other engine control functions are given in the sections below.
The following functions prepare the engine to execute a script:
AspInitialize
, AspSetCodePaging
,
AspAddCode
, AspSeal
, AspSealCode
,
AspPageCode
, AspSetArguments
(and the related
AspSetArgumentsString
), AspRestart
, and
AspReset
.
AspInitialize
AspRunResult AspInitialize (AspEngine *, void *code, size_t codeSize, void *data, size_t dataSize, const AspAppSpec *, void *context); AspRunResult AspInitializeEx (AspEngine *, void *code, size_t codeSize, void *data, size_t dataSize, const AspAppSpec *, void *context, AspFloatConverter);
To initialize the engine, use the AspInitialize
function and
pass it the address of an AspEngine
instance and the other
listed parameters. The application is responsible for allocating memory
for the AspEngine
instance (very small), the code and data
blocks, and, if used, the context area (more on script contexts in a
subsequent section). If the context is not being used, pass a null pointer.
The required AspAppSpec
pointer should point to the global
instance defined in the generated application specification C source file
and declared in the generated C header. Its name is
AspAppSpec_app
, where app
is the
base name of the application specification file name. The
AspAppSpec
instance is treated as read-only and can therefore
be shared between multiple engine instances if desired.
As discussed in the next section, it is possible that the code area will
not be needed (e.g., when all scripts that will be run already reside in
the application's memory). In these special cases, the code
and codeSize
parameters may be set to a null pointer and zero,
respectively.
In the rare case that the target platform does not support the
IEEE 754 binary64 (i.e., double-precision) floating-point format, a
second version of the initialization function, AspInitializeEx
is provided which accepts an additional parameter of type
AspFloatConverter
, a function pointer type as defined here.
typedef double (*AspFloatConverter)(uint8_t ieee754_binary64[8]);
Conversion is required because floating-point values in script binaries and
application specification files are encoded using the standard format.
If used, the supplied function must convert the 8-byte IEEE 754
binary64 value into the native floating-point format. The input value is
supplied as an 8-byte array and will already be in the native byte-order.
The return value is a double
in the native format, regardless
of whether its size is 8 bytes or not.
AspInitialize
needs to be called only once per engine
instance. Note also that there is no matching
function; when the script engine is no longer needed, the space occupied
by the close
AspEngine
instance may be reclaimed, along with the
code, data, and context areas.
The size of the code and data areas is specified in bytes. It should be
noted that the data area consists of a set of fixed-sized slots known as
data entries. The size of a single entry can be ascertained by using the
AspDataEntrySize
function. Knowing this size can help ensure
there are no wasted data bytes by using a multiple of the entry size for
the data area byte size.
AspAddCode
, AspSeal
, and
AspSealCode
AspAddCodeResult AspAddCode (AspEngine *, const void *code, size_t codeSize); AspAddCodeResult AspSeal(AspEngine *); AspAddCodeResult AspSealCode (AspEngine *, const void *code, size_t codeSize);
Once the engine is initialized, script binary code can be loaded. The code
can be loaded in blocks, if more convenient for the application, by making
repeated calls to AspAddCode
. Each call appends the specified
block to the end of any code already in the engine. Once all the code is
loaded, the AspSeal
function must be called to complete the
loading process.
If all the code of a script executable is already in a contiguous area of
memory, instead of copying it into the engine's code area, it may be used
in place by identifying its location with AspSealCode
. In this
special case, the identified code (i.e., the code pointed to by the
code
parameter and with a size given by codeSize
)
includes the entire script executable's content, including the header
fields. This is useful in cases where predefined scripts are included in
the code of the application. If an application runs only predefined
scripts, the engine's code area may be omitted altogether by specifying a
null pointer for it in the call to AspInitialize
and giving it
a size of zero.
These functions return an AspAddCodeResult
enumerated value.
Several things can go wrong during the loading process; details of the
error codes are given below. If all the calls to AspAddCode
and AspSeal
(or the single call to AspSealCode
)
return AspAddCodeResult_OK
, then the script is ready to run.
See the Execution Control section for functions that control script
execution. Additional functions applicable prior to execution are discussed
below.
AspAddCodeResult error code suffix |
Description |
---|---|
OK |
No error has occurred. |
InvalidFormat |
Script binary executable files must start with the four ASCII
characters AspE. If this is not the case, this error code is returned. |
InvalidVersion |
The major and minor components of the script compiler are written to the script binary executable and are compared to the corresponding version components of the engine running the code. This error code is returned if the version components do not match exactly, indicating an incompatibility issue. |
InvalidCheckValue |
The application specification defines a check value (a four-byte
cyclic redundancy check value, or CRC) based on the names of the
application functions, the names and any default values of function
parameters, and the names and assigned values of application
variables. The compiler writes this value into the script binary
executable. It is compared to the check value in the
AspAppSpec structure provided during engine
initialization and if different, this error is returned.
|
OutOfCodeMemory |
The amount of code space is insufficient to load the amount of code
contained in the script binary file. This condition cannot happen when
using AspSealCode , as the space where the script's
executable code resides is outside the engine's code area.
|
InvalidState |
AspAddCode , AspSeal , or
AspSealCode has been called when the engine is in a state
where calls to these functions are not expected, for example, when a
script is running.
|
AspSetCodePaging
and AspPageCode
AspRunResult AspSetCodePaging (AspEngine *, uint8_t pageCount, size_t pageSize, AspCodePageReader); AspAddCodeResult AspPageCode(AspEngine *, void *id);
In addition to the two methods of preparing for script execution described
above, a third method can be used that defers the loading of code until it
is needed during execution. This method, termed code paging
, is
useful when the size of a script could exceed the size of the defined code
area. Instead of storing the entire script in the code area, the code area
is split into one or more fixed-sized pages for caching blocks of code
during execution.
To enable code paging, call the AspSetCodePaging
function,
specifying the number of pages, the size of each page, and a routine that
will be called during execution to read in pages of code. The page reading
routine must be supplied by the application and must be compatible with the
the AspCodePageReader
function pointer declaration as given
below. The AspSetCodePaging
function may also be used to
disable code paging by specifying zero for either the
pageCount
or pageSize
, or both. Once set, the
paging state (enabled or disabled) remains in effect; there is no need to
call AspSetCodePaging
before running each script. Also note
that the pageSize
must be at least 12 bytes (for
implementation reasons), although it is expected it will normally be much
larger than this.
typedef AspRunResult (*AspCodePageReader) (void *id, uint32_t offset, size_t *size, void *codePage);
Assuming code paging is enabled, instead of loading the script (e.g., with
AspAddCode
, etc.), the location of the resource containing the
code (i.e., the executable .aspe
file) is
identified by calling AspPageCode
. It is up to the application
to determine how script code resources are identified. For example, if the
target system supports files, the identifier could be a C FILE
pointer or a POSIX file handle. Whatever is supplied in the call to
AspPageCode
is simply passed to calls of the supplied read
function, so the read function will typically cast the void pointer
id
argument to the applicable type.
The read routine supplied by the application will be called by the engine
when a block of code is needed during execution. The read function is
called with the id
argument identifying the resource to read
from (e.g., file), the byte offset
into the resource, the
requested page size
, passed by reference, and the destination
codePage
where the content should be stored. If the read
returns fewer bytes than requested, the size
should be updated
to reflect the actual number of bytes read. (The engine uses this update to
detect the end of the code.) This is also applicable if the requested
offset
is past the end of the content, in which case the
size
should be updated to 0
. In all these cases
(full page read, partial page read, and no data), the function should
return AspRunResult_OK
. Only if there is a problem with the
code resource identifier or the resource itself (e.g., invalid file
descriptor, file access error, etc.) should the function return something
else (e.g., AspRunResult_Application
). Returning something
other than AspRunResult_OK
will result in the script execution
ending in error.
The application developer is encouraged to choose page count and size values that are appropriate for the system at hand. During execution, after all the cache pages have been used, the engine will replace the least recently used cache page when reading a new page in. Depending on the speed at which pages can be read, using a sufficient number of pages can improve performance, especially in scripts that use a lot of nested function calls, or in other cases that may cause the program counter to jump around a lot.
A small implementation detail should be noted. To manage the cached code pages, a small amount of space is stolen from the end of the data area. The amount of space taken is equal to the specified page count times the size of each cached page record, which is currently 2 bytes. This reallocation of data space should not have any material effect on script execution if sufficient margin is included in the size of the data area. For example, if the maximum number of 255 pages is specified, the data area will be reduced by a total of 510 bytes, which is equivalent to 32 data entries.
AspSetArguments
and
AspSetArgumentsString
AspRunResult AspSetArguments(AspEngine *, const char * const *args); AspRunResult AspSetArgumentsString(AspEngine *, const char *s);
Asp supports the notion of passing external arguments into a script. This
must be done after the code has been loaded and prior to execution (i.e.,
after either AspSeal
or AspRestart
has been
called and before calling AspStep
for the first time).
External arguments may be specified in one of two ways: as an array of
character strings, or as a single string. The first method resembles the
way external arguments are passed to the C/C++ main routine (i.e.,
argc
and argv
parameters) and is invoked using
AspSetArguments
. The final item in the args array must be a
null pointer. The second method, using AspSetArgumentString
,
utilizes an argument parser to extract individual arguments from the
single string. The application may use either method, depending on which
is more convenient.
If the second method is used, the parsing rules are as follows. Normally,
arguments are separated by one or more whitespace characters (space, tab,
etc.). Single or double quotes marks ('
or "
)
may be used to surround an argument that contains whitespace. A single
quote may appear within a double-quoted string, and vice versa. The
backslash character (\
) is used outside quoted sequences or
within double-quoted strings to escape the meaning of these special
characters, including the backslash character itself.
See the Script Writer's Guide for how to access external arguments from within a script.
AspSetCycleDetectionLimit
and
AspGetCycleDetectionLimit
AspRunResult AspSetCycleDetectionLimit(AspEngine *, uint32_t limit); uint32_t AspGetCycleDetectionLimit(const AspEngine *);
Theoretically, it would be possible to create data structures that would
cause AspStep
to execute endlessly. An example is a list that
contains a reference to itself. In this theoretical situation, the engine
could get hung up cycling through the same parts of the structure. In some
cases, because of the way Asp handles recursion, the engine can run out of
memory, in which case the script's execution ends with an error. However,
without intervention, it would possible in other circumstances for the
engine never to return from AspStep
.
To deal with this situation, the engine enforces a limit on the number of
items it will visit within a data structure. Initially, this limit is set
(in AspInitialize
) to half the number of elements in the data
area, which approximates the size of the largest possible data structure.
If this value is too large or too small for the application at hand, it may
be adjusted using AspSetCycleDetectionLimit
. The current value
may be queried with AspGetCycleDetectionLimit
.
AspRestart
and AspReset
AspRunResult AspRestart(AspEngine *); AspRunResult AspReset(AspEngine *);
At any point after the script is ready to run, has started running, or is
finished execution, it may be restarted from the beginning by calling
AspRestart
. This reinitializes the data area, including the
clearing of any previously defined external arguments, but leaves the
script code intact. This can be useful if a script is to be run multiple
times, as it circumvents the need to reload the code.
To run a different script, the AspReset
function must be
called prior to loading the code of the new script. This clears not only
the data area, but also the code area, restoring the engine back to the
state it was in immediately following the call to
AspInitialize
.
In both cases, any functions and variables defined by the application are
restored to their original state, ensuring there is no cross
contamination
from the actions of one script to another.
Scripts are executed using the AspStep
function. Other
functions are available to query various aspects of the engine related to
execution: AspIsReady
, AspIsRunning
,
AspIsRunnable
, AspProgramCounter
, and
AspLowFreeCount
.
AspStep
AspRunResult AspStep(AspEngine *);
Scripts are executed by repeated calls to the AspStep
function. Each call executes a single instruction of the script. Most
lines of code from the script's source file are translated by the compiler
into several machine-like instructions, each of which is executed by a
call to AspStep
, so it may take several calls to
AspStep
to execute a single source line of script code.
The AspStep
function returns an AspRunResult
enumerated value, the values of which are documented below. Two values are
important enough to mention here. A value of AspRunResult_OK
indicates that the next instruction executed successfully and that the
script is still considered to be in the running state. A value of
AspRunResult_Complete
indicates that the script has completed
execution successfully. All other values indicate that something has gone
wrong during execution. After getting any return value other than
AspRunResult_OK
, the script's execution is considered to be
ended.
AspRunResult error code suffix |
Description |
---|---|
OK |
No error has occurred. |
Complete |
The script has successfully finished executing. Note that while most
error codes can be returned by script-callable application functions,
this one cannot be. An attempt to do so will cause AspStep
to return AspRunResult_InvalidAppFunction instead.
|
InitializationError |
A non-specific error occurred during the call to
AspInitialize . This can be caused by passing invalid
parameters to this function.
|
InvalidState |
An API function has been called when the engine is in a state where it is not expected. Calling engine control functions from within a script-callable application function call will cause this error. |
InvalidInstruction |
An invalid instruction has been encountered in the script executable code. This would normally indicate some sort of memory corruption in the code area, or a bug in the Asp compiler. |
InvalidEnd |
The script has indicated that it is finished, but the engine's internal stack (maintained in the data area) is not empty. This may indicate a bug in the Asp compiler. |
BeyondEndOfCode |
In reading the next instruction or its operand(s), the program counter would advance beyond the end of the code. This may indicate memory corruption in the code area, or a bug in the Asp compiler. |
StackUnderflow |
The engine is attempting to pop a value off its internal stack (maintained in the data area), but the stack is empty. This may indicate a bug in the Asp compiler. |
CycleDetected |
In one of several situations where the engine handles recursion by using iteration, the number of iterations has exceeded the set limit, indicating that it is likely operating on a self-referencing data structure (e.g., a list containing a references to itself). |
InvalidContext |
The engine is attempting to execute an instruction associated with an
Asp global or local statement outside the
context of a function. This may indicate a bug in the Asp compiler.
|
Redundant |
A redundant global or local statement is
being made when the variable in question already has the indicated
override.
|
UnexpectedType |
An operand is not of an expected type. Application functions can also return this code to end execution when a function argument is of an unexpected type. For example, the index value of an indexing expression is expected to be an integer. |
SequenceMismatch |
A mismatch between the sequences on the left and right sides of an assignment has been encountered. |
NameNotFound |
An attempt to load the value of a variable or otherwise use its value (e.g., calling a function by name) finds that the variable is not defined (i.e., has not been assigned a value). |
KeyNotFound |
An attempt to look up a key in a set or dictionary has been made and the key cannot be found. |
ValueOutOfRange |
A value is not in the expected range. For example, the index value used in an indexing expression is out of range for the given sequence. Another example is that the right operand of a shift operation is less than zero. |
IteratorAtEnd |
An attempt to use the value referred to by an iterator finds that the
iterator has advanced to the end of the iterable. This should not
happen when using the iterators provided by the for
statement.
|
MalformedFunctionCall |
One of several constraints was violated while processing a function call. Typically, this has to do with how arguments are specified, for example, placing keyword arguments before positional ones in the argument list, or using an unknown parameter name. |
InvalidAppFunction |
A script-callable application function returned
AspRunResult_Complete , presumably in an attempt to end
script execution successfully. This is not permitted. However, a
script may call the exit system function for such a
purpose.
|
UndefinedAppFunction |
An attempt has been made to call an application function that is not defined by the application. This may indicate a bug in the Asp compiler. |
DivideByZero |
The script has attempted to use zero as the divisor of a division or modulus operation. |
ArithmeticOverflow |
The script has attempted to perform an operation that would result in arithmetic overflow (or underflow) such as adding two integer values whose sum cannot be represented as a 32-bit signed integer. Unlike Python, Asp does not support arbitrary precision integers. |
OutOfDataMemory |
The script has attempted to allocate a data entry when no more are available in the data area. |
Again |
This code may be returned by script-callable application functions to
request re-entry in order to continue the operation. It is never
returned by AspStep or any other API function.
|
Abort |
A script has executed an assert statement with a condition
that evaluates to False .
|
Call |
The special value returned by the AspCall function to
cause subsequent invocation of the target function. Script-callable
functions should not use this return value explicitly, but instead use
the value returned by AspCall .
|
InternalError |
An unexpected condition has occurred in the engine, likely caused by memory corruption or a bug in the engine's implementation. |
NotImplemented |
The sought-after functionality has not yet been implemented in the engine. Check subsequent versions. |
Application |
Script-callable application functions may return codes unique to the application by using this code as a base for codes equal to or greater than this code. |
Normally, a script ends when either it reaches its end or an error occurs.
However, a script can also be ended prematurely by calling the
exit
function, which takes an optional exit code. The code is
translated into an appropriate AspRunResult
value according to
the following rules. If the code is an integer, zero is translated to
AspRunResult_OK
, positive values are translated to
AspRunResult_Application + code
(limiting the
value to INT32_MAX
if necessary), and negative values are
translated to INT32_MAX
. A code of None
(the
default) is translated to AspRunResult_OK
. All other types of
values (even the Boolean and floating-point types) are simply translated to
AspRunResult_Application
.
bool AspIsReady(const AspEngine *); bool AspIsRunning(const AspEngine *); bool AspIsRunnable(const AspEngine *); size_t AspProgramCounter(const AspEngine *);
After a script is loaded, the engine enters a state where it is ready to
run the script. The state is changed to running after the first call to
AspStep
. The AspIsReady
,
AspIsRunning
, and AspIsRunnable
return Boolean
values indicating whether the engine is ready to run a script, currently
running a script, or either one, respectively. The script's program
counter may be queried using the AspProgramCounter
function.
The value returned is a byte offset into the code.
AspLowFreeCount
size_t AspLowFreeCount(const AspEngine *);
The AspLowFreeCount
function returns the lowest number of
available data entries during or after script execution. This is a useful
metric to determine whether, for any planned script, the engine has
sufficient data storage to run the script. Care must be taken to add a
margin of safety, as data memory utilization can vary from one run to
another depending on the various paths through the script's logic that may
take place. Note that to get meaningful data, the function must be called
prior to restarting/resetting the engine for the next run.
If needed, the value returned by this routine can be converted to bytes by
multiplying it by the value returned by the AspDataEntrySize
function.
AspCodePageReadCount
size_t AspCodePageReadCount(AspEngine *, bool reset);
When code paging is enabled, the AspCodePageReadCount
function
returns the number of times the read function (specified in the call to
AspSetCodePaging
) has been called since the start of
execution, or since the previous call to AspCodePageReadCount
with reset
set to true
. In the unlikely event
that the read count reaches its maximum value before being reset, it is no
longer incremented until a reset occurs.
The read count can be used to assess the performance of various choices of
page size and count when code paging is enabled. If code paging is
disabled, AspCodePageReadCount
will always return zero.
Several additional functions are provided in the engine control API and are documented below.
AspDataEntrySize
size_t AspDataEntrySize(void);
As mentioned above, AspDataEntrySize
returns the number of
bytes of a single data entry within the engine's data area. The actual
value should be 16, but the function has been provided to guard against
any change in future versions of the library.
AspEngineVersion
and AspCodeVersion
void AspEngineVersion(uint8_t version[4]); void AspCodeVersion(const AspEngine *, uint8_t version[4]);
Asp uses four-component version numbers in several places. The components
are referred to as major, minor, patch, and tweak (after CMake
conventions). Asp keeps tight control over compatibility by employing
version checking. For example, the major and minor components of the
version of the code generated by the compiler must match those of the
engine running the code. A mismatch is one of the reasons that
AspAddCode
(or AspSealCode
) can fail.
Version information can be ascertained about both the engine and the code
loaded into the engine by using the AspEngineVersion
and
AspCodeVersion
functions, respectively. In both cases, the
version information is stored at the address provided by the version
argument, which is assumed to be a four-element byte array. The first
element in the array is the major component and the others follow the
expected order. Note that if code has not been completely loaded into the
engine, AspCodeVersion
will return unpredictable values.
AspMaxCodeSize
and AspMaxDataSize
size_t AspMaxCodeSize(const AspEngine *); size_t AspMaxDataSize(const AspEngine *);
It is up to the application to decide on the amount of memory to allocate
for the code and data areas. However, if the application needs to know
these values in an area of code that does not have access to the values
used during initialization, they may be obtained using
AspMaxCodeSize
and AspMaxDataSize
. Both are
maximum sizes (as specified to AspInitialize
) and are
reported in bytes.
AspTraceFile
and AspDump
void AspTraceFile(AspEngine *, FILE *); void AspDump(const AspEngine *, FILE *);
If the Asp library has been built with the ASP_DEBUG
macro
defined (typically enabled via the ENABLE_ENGINE_DEBUG
CMake
option), the AspTraceFile
and AspDump
functions
are available. The AspTraceFile
function may be used to direct
instruction trace output to the specified C file handle, the default being
stdout
. The AspDump
function dumps out the
contents of the data memory in a human-readable form to the given C file
handle. Normally, these functions are not defined. Application developers
should only use a debug version of the library in cases where debugging
their script-callable functions is required.
As mentioned previously, script-callable application functions have a specific type of definition which is defined by the Asp generator, aspg. Prototypes of the application's script-callable functions can be found in the generated C header, and look something like this:
AspRunResult function_name (AspEngine *, AspDataEntry *argument1, AspDataEntry *argument2, … AspDataEntry **returnValue);
It is the responsibility of the application developer to provide the implementation for these functions. This involves extracting values from the arguments, interacting with other parts of the application, and returning an appropriate return value. In the case of mutable arguments like lists and dictionaries, the function may also manipulate the contents of such collections if desirable.
The following sections discuss the API that script-callable functions use
to perform these tasks. All Asp objects are represented as
AspDataEntry
instances, all of which reside in the engine's
data area. All API functions that work with script objects use pointers to
AspDataEntry
. The API includes functions for: type checking,
value extraction, conversion to string, collection information, object
creation, collection manipulation, and a few sundry other tasks.
To return a value to the script, the returnValue
argument is
used. If left untouched, the script will receive a None
object as the return value. To return anything else, the implementation
should assign a data entry pointer to *returnValue
.
Script-callable application functions must return a valid
AspRunResult
value, normally AspRunResult_OK
to
indicate success. Any other value (with the exception of
AspRunResult_Again
; see below) indicates an error which will
cause the script to end. Application-specific error codes can be returned
and are formed by adding a non-negative value to
AspRunResult_Application
. See the
AspRunResult
table for a full
list of available error codes.
bool AspIsType(const AspDataEntry *); bool AspIsIntegral(const AspDataEntry *); bool AspIsNumber(const AspDataEntry *); bool AspIsNumeric(const AspDataEntry *); bool AspIsSequence(const AspDataEntry *); bool AspIsIterator(const AspDataEntry *); bool AspIsAppObject(const AspDataEntry *);
In the syntax citation above, Type
stands for any one
of the types supported by Asp. The actual function names are:
AspIsNone
, AspIsEllipsis
,
AspIsBoolean
, AspIsInteger
,
AspIsFloat
, AspIsSymbol
, AspIsRange
,
AspIsString
, AspIsTuple
, AspIsList
,
AspIsSet
, AspIsDictionary
,
AspIsForwardIterator
, AspIsReverseIterator
,
AspIsFunction
, AspIsModule
,
AspIsAppIntegerOject
, AspIsAppPointerOject
, and
AspIsType
, all corresponding to the applicable type in the
name. Each one returns true
if the argument is of the
applicable type.
In addition to these functions are a few that check for meaningful groups
of types. AspIsIntegral
returns true
if the
argument is either Boolean or integer. AspIsNumber
returns
true
for integer and floating-point values.
AspIsNumeric
returns true
for Booleans,
integers, and floating-point values. AspIsSequence
returns
true
for tuples and lists, but not for strings, even though in
some respects, strings can be treated like sequences. The
AspIsIterator
function returns true
if the
argument is either a forward or reverse iterator. And finally,
AspIsAppObject
returns true
if the object is
either of the two types of application object: integer or pointer.
bool AspIsTrue(AspEngine *, const AspDataEntry *) bool AspIntegerValue(const AspDataEntry *, int32_t *); bool AspFloatValue(const AspDataEntry *, double *); bool AspSymbolValue(const AspDataEntry *, int32_t *); bool AspRangeValues (AspEngine *, const AspDataEntry *, int32_t *start, int32_t *end, int32_t *step, bool *bounded); bool AspStringValue (AspEngine *, const AspDataEntry *, size_t *size, char *buffer, size_t index, size_t bufferSize); bool AspAppObjectTypeValue (AspEngine *, const AspDataEntry *, int16_t *); bool AspAppIntegerObjectValues (AspEngine *, const AspDataEntry *, int16_t *appType, int32_t *value); bool AspAppPointerObjectValues (AspEngine *, const AspDataEntry *, int16_t *appType, void **value);
All Asp objects can be converted to a Boolean value, which is useful for
use in conditional statements. The AspIsTrue
function returns
true
if the given Asp object would evaluate to
True
in a script conditional expression (e.g., an
if
or while
expression).
To extract a value from an Asp object, the AspIntegerValue
,
AspFloatValue
, AspSymbolValue
,
AspRangeValues
, and AspStringValue
functions are
provided. All these functions return true
to indicate success.
In the case of the first three, the sought value is deposited at the given
address. Since ranges consist of multiple values, three integer addresses,
start
, end
, and step
, are required
to receive the components of the range, and an additional Boolean address,
bounded
is required to indicate whether the end
value is used. (Note that the bounded
parameter was added in
Asp version 1.1.)
Note that it is possible to extract an integer or floating-point value from
objects of Boolean, integer, or floating-point type. For example, using
AspFloatValue
on a Boolean object will result in a value of
either 0.0
or 1.0
, depending on whether the
object was False
or True
, respectively. Calling
AspIntegerValue
on a floating-point value involves rounding
truncation.
The AspStringValue
function is a bit more complex. The
size
parameter can be a null pointer, but if an address is
given, the full size of the Asp string is written to the given address.
Since Asp strings are not null terminated like C strings are, the value at
size
refers only to the characters of the Asp string. The
buffer
parameter may also be a null pointer, in which case no
data is extracted. If buffer
is specified,
bufferSize
must indicate the number of bytes in the buffer.
The index
parameter indicates what byte within the Asp string
to start copying from.
So, a typical use of AspStringValue
could be as follows.
First, the function is called to ascertain the size of the Asp string by
passing an address for size
and a null pointer for
buffer
. The application can then use whatever means necessary
to allocate enough space for the string data, plus an additional byte for
a null terminator. It can then call AspStringValue
a second
time, this time passing a null pointer for size
(since it is
already known), a pointer to the allocated memory for buffer
,
0
for index
(since we are copying the entire
content), and the size of the buffer (including the null terminator) for
bufferSize
. Since the buffer contains sufficient room,
AspStringValue
will also write a terminating null character
at the end of the buffer. Note that in cases where only a portion of the
string is being extracted (i.e., bufferSize
is less than or
equal to *size - index
), a null terminator is not
written, as that would write past the end of the buffer. In this case, the
buffer cannot be treated as a C string, so care must be taken.
Another approach, if applicable, is to deal with the string content in a
piecemeal fashion. For example, if the application is searching for a
substring within the string or performing some other operation that does
not require the entire string to be in memory all at once, it may be
possible to extract parts of the string and process them in a loop. To
treat the pieces as C strings, the application should use a buffer one
byte larger than the size given in bufferSize
and ensure that
the final byte of this buffer is set to the null character, as
AspStringValue
will not write to it.
The type and value of application-specific objects may be obtained by using
AspAppIntegerObjectValues
or
AspAppPointerObjectValues
. If only the type is needed,
AspAppObjectTypeValue
may be used. More information on
application objects may be found in the section on
object creation).
AspDataEntry *AspToString(AspEngine *, AspDataEntry *value); AspDataEntry *AspToRepr(AspEngine *, const AspDataEntry *value);
Sometimes it is convenient to convert an Asp object to a string. An
example would be to print its value to a log. For this purpose, the
AspToString
function creates a new Asp string object with a
textual representation of the given Asp object. A pointer to the new
object is returned. A null pointer return value indicates that
insufficient data memory exists to fulfill the request, so in this case,
the calling function should return
AspRunResult_OutOfDataMemory
. In the normal case of a
non-null return value, it should be noted that the application is
responsible for ensuring the created object is either destroyed (via
AspUnref
) or its ownership is transferred to another Asp
object that will persist after the function returns. See the section on
reference counting for more information.
The AspToRepr
function is also provided. It replicates the
functionality of Python's repr
function. This can come in
handy, for example, when displaying strings containing special characters,
which will be converted to printable escape sequences.
The astute reader will notice that the AspToString
's value
argument is not declared as const
. This is because the object
being passed could in fact already be a string. In this case, instead of
creating a new string object, the reference count of the existing string
object is incremented, thus modifying the given object entry.
(AspToString
internally calls AspRef
in this
case.) The application need not treat this any differently than converting
other types of objects. It is still responsible for decrementing the use
count or otherwise dealing with the ownership of the object whose address
is returned.
AspRunResult AspCount(AspEngine *, const AspDataEntry *, int32_t *count); AspDataEntry *AspElement (AspEngine *, const AspDataEntry *sequence, int32_t index); int32_t AspRangeElement(AspEngine *, const AspDataEntry *range, int32_t index); char AspStringElement(AspEngine *, const AspDataEntry *str, int32_t index); AspDataEntry *AspFind (AspEngine *, const AspDataEntry *tree, const AspDataEntry *key); AspDataEntry *AspNext(AspEngine *, AspDataEntry *iterator);
Asp supports a few types of collections: tuples, lists, sets, and dictionaries. Strings can also be treated as collections (of characters) in some cases. Ranges represent sequences of integer values, and can therefore sometimes be treated like collections. The application function API includes a few functions for getting information from these types of objects.
AspCount
yields the number of items inside the given object.
For a collection, this is the number of items in the collection. Strings
are treated like collections in this regard, returning the number of
characters in the string. For ranges, the number of elements that would be
generated if the range was expanded into a list is computed. For all other
object types, the function returns 1
.
It should be noted that prior to version 1.1, the AspCount
function's signature was different, as indicated below. If application
code must support multiple versions of Asp, the ASP_VERSION
macro can be used to conditionally compile code for both versions.
unsigned AspCount(const AspDataEntry *); /* old signature */
The reason for the change was to accommodate the fact that attempting to return the number of elements in an unbounded range cannot be accomplished. In this case, an error is indicated in the return value.
To access the element of a sequence (i.e., a tuple or list, but not a
string or range), the AspElement
function is used, which
accepts an index value and returns a pointer to the data entry at the
indicated position within the sequence. It does not increment the use count
of the returned entry. Normally, indices are zero-based, ranging from
0
to one less than the number of elements. However, as in the
Asp language, negative indices are allowed in the range -1
to
the number of elements expressed as a negative integer. An index value of
-1
refers to the last element. If the given index value is
out of range, a null pointer is returned.
Unlike tuples and lists, strings and ranges do not contain objects. Strings
consist of characters and ranges represent sequences of integers. Because
of this, the AspElement
function does not handle either of
them. Instead, the AspStringElement
function is provided for
accessing the characters of a string, and the AspRangeElement
function for accessing the integer members of a range. Instead of returning
a data entry pointer, they return the character or integer at the indicated
position, respectively. The index argument works in the same way as with
AspElement
, permitting the use of negative index values. An
out-of-range index value causes AspStringElement
to return the
null character ('\0'
), and AspRangeElement
to
return zero.
To locate an item in a set or dictionary, use the AspFind
function. Like the AspElement
function, it does not increment
the use count of the returned data entry. As expected, if a matching item
is not found in the collection, a null pointer is returned. Otherwise, for
sets, the matching key entry is returned; for dictionaries, the entry
referring to the value associated with the matching key is returned.
The AspNext
function works with an iterator to yield the next
value in an iterable (i.e., a collection-like object). Successive calls to
AspNext
return subsequent values in the iterable. Unlike the
functions described above, the caller is responsible for unreferencing the
returned object or otherwise dealing with its ownership. This is because in
some cases (like with iterating over a range or a string), a new object is
created. In the cases where an existing object is returned,
AspNext
increments the use count so that treatment of the
returned object is consistent. If the iterator is advanced to the end of
the iterable, AspNext
returns a null pointer.
AspDataEntry *AspNewType(AspEngine *); AspDataEntry *AspNewBoolean(AspEngine *, bool value); AspDataEntry *AspNewInteger(AspEngine *, int32_t value); AspDataEntry *AspNewFloat(AspEngine *, double value); AspDataEntry *AspNewSymbol(AspEngine *, int32_t value); AspDataEntry *AspNewRange (AspEngine *, int32_t start, int32_t end, int32_t step); AspDataEntry *AspNewUnboundedRange (AspEngine *, int32_t start, int32_t step); AspDataEntry *AspNewString(AspEngine *, const char *buffer, size_t size); AspDataEntry *AspNewIterator (AspEngine *, AspDataEntry *iterable, bool reserved); AspDataEntry *AspNewAppIntegerObject (AspEngine *, int16_t appType, int32_t value, void (*destructor)(AspEngine *, int16_t appType, int32_t value)); AspDataEntry *AspNewAppPointerObject (AspEngine *, int16_t appType, void *value, void (*destructor)(AspEngine *, int16_t appType, void *value)); AspDataEntry *AspNewType(AspEngine *, const AspDataEntry *object);
In the syntax citation above, Type
stands for one of
the types supported by Asp that does not require any more information to
create the object. The actual function names are: AspNewNone
,
AspNewEllipsis
, AspNewTuple
,
AspNewList
, AspNewSet
, and
AspNewDictionary
. Each one returns a pointer to the newly
created data entry. In the case of the collection-oriented functions,
empty collections are created. Additional functions are provided that
accept additional parameter(s) to provide a specific value for the created
object.
Two functions support the creation of range objects:
AspNewRange
and AspNewUnboundedRange
. The
AspNewRange
accepts start
, end
, and
step
parameters. The AspNewUnbounededRange
function creates a range object with no specified end, so only
start
and step
parameters are required.
For AspNewString
, a character buffer and size must be
provided to initialize the value of the new string object. To create an
empty string, specify a size of 0. In this case, buffer
is
not used, so a null pointer can be passed.
For AspNewIterator
, the iterable object is specified, along
with a Boolean indicating whether the iterator should be a normal (forward)
iterator or a reverse iterator. The iterable object may be any of the
collection types, a string, or a range. Use AspNext
with the
new iterator to iterate through each value. Iterators can also be tested to
determine whether they are at their end by converting them to a Boolean
value using AspIsTrue
. A value of false
indicates
the iterator has reached its end.
In addition to specifying an iterable object, an existing iterator may be
passed as the AspNewIterator
function's iterable
parameter. In this case, a copy of the existing iterator is returned which
retains its current position. When the reversed
parameter is
set to true
, the type of iterator returned depends on the type
of iterator passed, as the direction is simply changed to be the opposite
of the original iterator.
Asp supports two application-specific object types. One uses an integer to
identify an object within the application, and the other uses a pointer.
Integer-type application objects are created using
AspNewAppIntegerObject
, while pointer-type ones are created
with AspNewAppPointerObject
. An application may use one or the
other, or both, depending on the nature of the objects being referenced.
In addition to the integer or pointer value, a 16‑bit type value is
used to identify the object's type in the application. The contents of such
objects are inaccessible to script code; scripts can only pass them around,
group them into collections, etc., but the main purpose of these objects is
to carry information sufficient for the application to locate and
manipulate data structures that only the application knows about.
A couple of examples should suffice to explain how application objects are
used, and how to decide which type is preferable in a given situation.
First, we will consider an open file descriptor, which is an integer value
in POSIX systems (e.g., the return value of the POSIX open
function). Asp scripts have no notion of files, but an application can
provide an interface for interacting with files by creating, returning, and
accepting as parameters, application objects representing open files. In
this example, an integer-type application object would be appropriate to
hold the file descriptor.
Pointer-type application objects can be used anywhere an application uses
a pointer to refer to an object or structure instance. For example,
instead of using POSIX calls for a file interface, an application could
use the C FILE *
pointer value returned by
fopen
to represent an open file.
For structure (or C++ class) instances that the application manages, the type of application object can depend on how the application manages the objects. An integer can be used, for example, to index an array of objects. A pointer can be used to reference a dynamically allocated structure. In any case, the integer or pointer must uniquely identify an object in the application.
For both integer-type and pointer-type application objects, a
destructor
function must be specified when they are created.
This destructor handles objects whose use count drops to zero during script
execution. But note that other objects may remain when a script finishes
execution, and must be handled by the application. Further discussion of
automatic application object destruction and the responsibilities of the
application can be found in the section on
destruction of application objects.
In the case of the AspNewType
function, the type of the
passed object is used to determine the value of the new type object.
It is the responsibility of the application to manage the ownership of the
newly created object. Initially, its use count is set to 1
.
If nothing else is done to the object, calling AspUnref
on it
will cause it to be destroyed. See the section on
reference counting for more information.
bool AspTupleAppend (AspEngine *, AspDataEntry *tuple, AspDataEntry *value, bool take); bool AspListAppend (AspEngine *, AspDataEntry *list, AspDataEntry *value, bool take); bool AspListInsert (AspEngine *, AspDataEntry *list, int32_t index, AspDataEntry *value, bool take); bool AspListErase (AspEngine *, AspDataEntry *list, int32_t index);
Items can be inserted into sequences or erased from sequences using one of
the above functions, each of which returns true
to indicate
success.
AspTupleAppend
is used to build a tuple. Note that in Asp,
tuples are immutable, but in C, a mechanism is required to define the
elements of a newly created tuple. An application function must not abuse
this routine to modify the contents of a pre-existing tuple. Another
function, AspListAppend
, is used to add an item to the end of
a list. Items can also be inserted at a position other than the end using
AspListInsert
. This last function accepts an index value
which may be negative and works the same way as in element access (see the
section on collection information).
For negative index values, the item is inserted after the indicated
position so that the newly inserted item is at the indicated position when
the insertion is completed.
An item can be removed from a list using AspListErase
. The
index parameter follows the same rules as for element access.
All the append/insert functions accept a Boolean argument named
take
. This indicates whether the sequence should take
ownership of the passed value entry. If a take value of true
is given, the function will call AspUnref
to decrement the
reference count of the object, obviating the need for the application to
perform this operation. This is the normal case and prevents the inserted
object from causing a memory leak into the engine's data area. Only if the
inserted object is used elsewhere (e.g., inserted into another collection,
or returned in *returnValue
) should the value of
take
be set to false
.
bool AspStringAppend (AspEngine *, AspDataEntry *str, const char *buffer, size_t bufferSize);
Like tuples, strings are immutable in Asp, but the application needs a way
to build them. So, the AspStringAppend
function is provided
for that purpose. As with tuples, an application must not abuse the
function by modifying a pre-existing string object. Using
AspStringAppend
, an application can build an Asp string from
several smaller parts if desired. The data to be appended is specified in
buffer
and its length in bufferSize
.
bool AspSetInsert (AspEngine *, AspDataEntry *set, AspDataEntry *key, bool take); bool AspSetErase (AspEngine *, AspDataEntry *set, const AspDataEntry *key); bool AspDictionaryInsert (AspEngine *, AspDataEntry *dictionary, AspDataEntry *key, AspDataEntry *value, bool take); bool AspDictionaryErase (AspEngine *, AspDataEntry *dictionary, const AspDataEntry *key);
Inserting objects into sets and dictionaries is accomplished using the
AspSetInsert
and AspDictionaryInsert
functions,
respectively. In the case of AspSetInsert
, only a key is
required, whereas AspDictionaryInsert
requires both a key and
an associated value. Items can be removed from sets and dictionaries by
using AspSetErase
and AspDictionaryErase
,
respecitively. The return value is true
if the specified key
was found and erased from the collection.
As with the sequence manipulation functions, the insert functions also
take a take
argument to indicate whether the collection will
assume exclusive ownership of the object(s) being inserted. For
AspDictionaryInsert
, this applies to both the key and the
value, so if the application needs to treat these two objects differently
(e.g., the value is used elsewhere but the key is not), then it can take
one of two courses of action. It can still specify true
for
the take
argument, and then call AspRef
on the
object that will be shared. Or the take
argument can be set
to false
, and then AspUnref
can be called on the
object not being used elsewhere. Either method achieves the same goal of
managing use counts to avoid leaks into the engine's data area.
void AspRef(AspEngine *, AspDataEntry *object); void AspUnref(AspEngine *, AspDataEntry *object);
Several sections above have mentioned reference counting. This section explains the topic in a bit more detail.
When an object is created, whether by the script's code or through the use
of one of the AspNew…
functions, it is represented in
the engine's data area as a data entry with an initial use count of 1. The
AspRef
and AspUnref
functions directly
manipulate this use count, increasing or decreasing it by 1, respectively.
When AspUnref
reduces the use count of an object's entry to
zero, the entry is immediately freed (i.e., marked as a free entry and
added to the free list where it can subsequently be allocated to house
another object).
Normally, the engine's logic manages the reference counts to ensure no
objects become orphaned and thus tie up data area entries (a condition
called a leak
). When an application's C code manipulates objects,
it sometimes becomes necessary for it to manually control the use counts.
The use count of application function arguments is automatically handled
by the engine. Likewise, the returnValue
parameter is managed
by the engine. This requires a bit of a closer look. Upon entry, the value
at *returnValue
is actually a C null pointer (i.e., it does
not point to a None
object as one might think). This means
that when an application function assigns the pointer of a newly created
object to *returnValue
, there is no need to first unreference
it. In fact, doing so would be an error since there is no object there.
When the function returns, the engine checks the value at
*returnValue
, and if it is a C null pointer, the engine
creates a None
object and supplies it as the default return
value to the calling script*.
It should be noted that if an application causes a data area leak, the condition is not permanent. When the script ends and another one is started, the entire data area is reset, and any entries that contained leaked objects are freed for use when the new script runs. Still, care should be taken not to introduce data entry leaks, as it could lead to an out of data memory condition, causing the script to terminate unnecessarily.
See also the section on the destruction of application objects, which require special consideration.
* Strictly speaking, this is not an accurate description because the engine's data area contains only one sharedNone
object. So, when aNone
object iscreated, the use count of the singletonNone
is incremented. Unreferencing this sharedNone
object should never cause it to be destroyed, as its use count should never reach zero.
When the use count of an application object is reduced to zero, a
destructor
function, specified when the object was created,
is called. (See the object creation
section for details.) This function can be used to perform any resource
management tasks required to clean up the object. For example, if the
object represents an open file, the destructor function would be used to
close the file.
Although the destructor function is called for any application object whose use count drops to zero, it should be noted this will not happen for all application objects when a scripts ends, either successfully or in error. For this reason, applications must organize such objects in a way that, upon script completion, any objects that remain can be cleaned up. Failing to deal with application objects remaining after a script finishes execution can result in unintentional memory and/or resource leaks. Of course, an application can decide to allow objects to persist from one script invocation to the next, but ulimately, the application is responsible for resource management in this regard.
Like other application functions, the destructor function should perform
its task quickly and return control back to the engine in a timely fashion.
Unlike regular application functions, there is no way to return
AspRunResult_Again
to cause a re-entrant call, so if the
destruction of an application object could take a considerable amount of
time to complete, then instead of causing a delay in the engine, the
destructor function should note the condition in the context area, and then
perform the waiting outside of script execution, i.e., between calls to
AspStep
.
AspDataEntry *AspArguments(AspEngine *);
As mentioned in the section on setting
arguments, an application may provide arguments to a script. These are
accessible to the script by accessing the args
variable in
the sys
namespace. If it is necessary for the application to
access the arguments, it can obtain access to the argument tuple by
calling AspArguments
.
If no external arguments have been supplied, and even if the application
does not provide support for external arguments (i.e., it does not call
the AspSetArguments
or AspSetArgumentsString
functions), the object returned by AspArguments
will still
exist; it will be a tuple containing a single empty string. (The first
element always refers to a concatenated string of all the arguments, and
the first argument, if present, appears as the second element, index
1
.)
If arguments have been supplied, all the elements in the tuple will be strings. It is the job of the application (or the script) to parse the arguments into meaningful values.
AspContext
and AspAgain
void *AspContext(const AspEngine *); bool AspAgain(const AspEngine *);
When an application calls AspInitialize
, one of the arguments
that is passed is a pointer to a context area. The purpose of this is to
provide an area associated with the script engine instance that contains
application-specific data that script-callable application functions can
use. If specified, the application should define and allocate a suitable
structure to store whatever information will be required. While
application functions always have access to global variables, the context
area is associated with a specific script engine instance. If multiple
script engine instances with the same application specification are being
used, per-instance data can be stored in the context area, keeping it
separate from that of other instances.
To gain access to the area from within a script-callable function, use the
AspContext
function and cast the returned void pointer to the
application-defined type for the area. The context area can be used to
solve many design issues. It should be noted that the context area is not
cleared between script runs, as the engine has no idea of how the area is
structured. So, the application must ensure the area is sufficiently reset
prior to running a new script. Depending on the application, some data may
need to persist between script runs, while other data may need to be reset
between runs.
The next few paragraphs describe a particular issue that is solved by using both the context area and another feature the Asp library supports.
As mentioned in the sections covering engine
control and the AspStep
function,
the Asp engine executes script instructions one at a time, each
instruction taking very little time to execute, thus leaving the
application in control of the processor at a high frequency. One of those
instructions is the CALL
instruction which calls either a
script-defined function or an application function. Because script-defined
functions are made up of individual instructions, they too behave in such
a way as to leave the application in control of the processor on a regular
basis. However, in the case of script-callable application functions, the
C code of the function could potentially hold on to control of the
processor, making it possible that a call to AspStep
that
executes a CALL
instruction could take an indeterminant
amount time to complete. For example, an application function that runs a
motor through a motion profile and waits for its completion could take an
unacceptably long period of time to complete, starving the main control
loop of the application of processing cycles.
To solve this problem, the Asp engine provides two design elements that work together to allow application functions to relinquish processor control (i.e., return) while appearing to block in the script, waiting for completion of their task. The first design element is the context area (discussed above), and the second is a mechanism by which an application function can arrange for re-entry and then determine whether it is being called for the first time or is being re-entered in the context of a previous call.
The context area is used to store state information to support re-entrant
application functions. To make an application function re-entrant, and yet
appear as blocking to the calling script, two things must happen. First,
the function must return a special value, AspRunResult_Again
,
on all but the final invocation. This indicates to the engine that the
CALL
instruction is not yet finished. On the next call to
AspStep
, the same CALL
instruction is executed
again, causing the application function to be re-entered. The second thing
that must be done is for the application function to check whether it is
being called for the first time or as a subsequent re-entry. This is
accomplished by calling the AspAgain
function, which returns
true
for a re-entrant call.
On the initial call into the function (when AspAgain
returns
false
), state information in the context area should be
initialized. On re-entrant calls, the state can be queried and/or updated
to progress through to the end goal of the function. When the function
determines that its task is complete, it should return
AspRunResult_OK
(or an error value if appropriate) to mark
the execution of the CALL
instruction complete and allow the
engine to move on to the next instruction.
Note that there is nothing forcing the application to make calls to
AspStep
if it can otherwise determine that a script-callable
function is in an unfinished state and no progress can be made, making it
a waste of time to call it. In this situation, the application would have
to determine (from the main loop) when a condition sufficient to advance
the logic of the script-callable function has occurred, and then resume
calling AspStep
to allow that logic to advance, updating its
state, and possibly finishing its task.
There are other ways an application can retain control of the processor without using the forgoing technique (e.g., multitasking). However, in designs that depend on a main loop running at a fixed frequency, this mechanism makes running Asp scripts possible without compromising the integrity of the system.
bool AspAddPositionalArgument (AspEngine *, AspDataEntry *value, bool take); bool AspAddNamedArgument (AspEngine *, int32_t symbol, AspDataEntry *value, bool take); bool AspAddIterableGroupArgument (AspEngine *, AspDataEntry *value, bool take); bool AspAddDictionaryGroupArgument (AspEngine *, AspDataEntry *value, bool take); void AspClearFunctionArguments(AspEngine *); AspRunResult AspCall(AspEngine *, AspDataEntry *function); AspRunResult AspReturnValue(AspEngine *, AspDataEntry **returnValue); int32_t AspNextSymbol(AspEngine *); AspDataEntry *AspLoadLocal(AspEngine *, int32_t symbol); AspRunResult AspStoreLocal (AspEngine *, int32_t symbol, AspDataEntry *value, bool take);
Asp provides the ability for script-callable application functions to call other functions, including those that are defined in the currently running script. Typically, the target function is passed as an argument from the script into the application function. An example would be an Asp-callable sort function implemented in the application that calls a passed script function to perform element comparisons.
Application functions that use this facility must be written in a special way that avoids overusing the application's stack in order to guard against the possibility of recursion, whether intentional or not. The basic steps to call a function are as follows:
AspAddTypeArgument
functions. This step is not
required if there are no arguments being passed to the target function.
The AspClearFunctionArguments
function may be used if there
is a need to clear the argument list and start over. As with other
functions that take a take
argument, be careful to manage
object use counts by indicating whether or not the argument list should
take ownership of the argument object. Normally, this will be
true
for newly created argument objects, and
false
otherwise. The AspAddTypeArgument
functions increment the object's use count when take
is
false
so that the object is not destoyed when the argument
list is consumed (and destroyed) during the call.
AspCall
to prepare for the function to be invoked. Note
that AspCall
does not directly call the target function.
Instead, it returns a special return value that the application function
must return. When the engine regains control, the special return value
causes the engine to invoke the target function.
AspRunResult_Again
(see the section on the
again feature*). At this point, the application function must extract and take ownership of the called function's return value. The return value object is extracted by calling
AspReturnValue
. The application is responsible for
the return value object. For example, if it is no longer needed after its
underlying value has been extracted, it should be freed using
AspUnref
.
The above steps ignore saving and restoring state between reentries into
the application function. In the section on the again
feature
, the context area was mentioned as a location where state
information could be saved. But because of the potential for recursion when
calling functions from within application functions, using the context area
is not recommended here. The context area can be thought of as a global
space for the engine. What is needed to handle recursion is a local space
to save and restore state. This is where the AspNextSymbol
,
AspLoadLocal
, and AspStoreLocal
functions come
in.
Therefore, the general approach to designing an application function that calls other (i.e., script) functions is as follows:
AspNextSymbol
. These symbols are
guaranteed to be unique with respect to the symbols used for the
application function's own parameters.
AspAgain
to determine whether this is the first or a
subsequent invocation. On the first invocation (i.e., when
AspAgain
returns false
):
AspLoadLocal
is used to access the stored variable from
the local scope, returning a pointer to an AspDataEntry
.
Then, an appropriate API function is used to extract the value from the
data entry (e.g., AspIntegerValue
for an integer
variable). Using AspLoadLocal
does not increment the
object's use count, so there is no need to call AspUnref
on the returned data entry.
AspCall
, perform the next
step before exiting the function with the value that AspCall
returns.
AspNewInteger
for an integer, etc.). Make
sure to check the return values to guard against an out-of-memory
condition. Then use AspStoreLocal
to save the values into
the function's local scope using the applicable symbol for each variable,
specifying true
for the take
parameter.
AspCall
was used, return its return value. Otherwise,
return AspRunResult_OK
or AspRunResult_Again
as
appropriate. When returning AspRunResult_OK
, the local scope
is automatically cleaned up, so there is no need to explicitly destroy
the objects that were stored for saving and restoring state between
invocations.
It should be noted that there are no functions for saving/restoring variables in the global scope. As mentioned above, the context area can be used as a storage location for state that is global with respect to the engine. Beyond that, simple C global variables can be used to store state that would apply to all engines running in the application.
* TheAspCall
function actually returnsAspRunResult_Call
, but the application should not use this value explicitly, but simply use the value returned byAspCall
as the return value of the application function.
AspRunResult AspAssert(AspEngine *, bool);
The implementation of the Asp engine uses the AspAssert
function for ensuring certain conditions hold true, causing an error to
occur if not. This function is also available for application functions to
use. It is like the C assert
macro, except that it does not
cause the entire application to abort, and it always performs the test,
even in non-debug builds. Instead, if the passed argument is
false
(indicating a failed assertion), it prevents the engine
from executing any more instructions, and returns
AspRunResult_InternalError
. Even if the function does not
return the error code (which it should), execution of the script is still
eventually halted, and subsequent calls to AspStep
return the
same error value. If, on the other hand, the argument is
true
, AspAssert
returns
AspRunResult_OK
and all is well.
Note that the Asp language includes an assert
statement, but
its behaviour is slightly different than that of the
AspAssert
function. On a failed assertion, the
assert
statement causes an ABORT
instruction to
execute, which causes AspStep
to return
AspRunResult_Abort
(instead of
AspRunResult_InternalError
). An application function can
replicate this behaviour by testing the condition of interest in an
if
statement and returning AspRunResult_Abort
.
In addition to the Asp engine library, which is linked into an embedded
application, the Asp platform also provides a debug library for use on an
external machine that can receive run-time information from the embedded
application. For Linux environments, the debug library can be linked using
-laspd on the gcc command
line. Applicable code should include the <asp-info.h>
header.
#include <asp-info.h>
The routines in this library make use of the debug information file
(.aspd
) generated by the compiler. Their
primary purposes are (a) translating error codes into human-readable
strings, and (b) translating a program counter into the associated
script source location, both useful for determining the nature of a script
run error. The following sections describe the available routines.
Note also that the functionality of this library is provided in the aspinfo utility. See the Script Writer's Guide for a description.
const char *AspAddCodeResultToString(int result); const char *AspRunResultToString(int result);
Functions in the Asp engine library return AspAddCodeResult
and AspRunResult
error codes. The debug library routines
AspAddCodeResultToString
and
AspRunResultToString
translate these codes into
human-readable strings so that an operator running a script can get a
description of errors that occur when a script fails to complete
successfully.
Note that these routines accept integers rather than the specific error
type used in the engine library. The reason for this is that the computer
that is deciphering debug information is not normally the same machine
running the embedded application, and therefore may not have access to the
<asp.h>
header where these types are defined. As
mentioned above, it is necessary for the embedded application to somehow
communicate the error codes to the external system prior to translating
the codes. How this is done is specific to each application.
AspSourceInfo *AspLoadSourceInfoFromFile(const char *fileName); AspSourceInfo *AspLoadSourceInfo(const char *data, size_t size); void AspUnloadSourceInfo(AspSourceInfo *); AspSourceLocation AspGetSourceLocation(const AspSourceInfo *, size_t pc); const char *AspGetSourceFileName(const AspSourceInfo *, unsigned index); const char *AspGetSymbolName(const AspSourceInfo *, uint32_t symbol);
When a script ends abnormally, the engine library provides a means of obtaining the program counter which identifies the location of the instruction that caused the error. Once the value of the program counter is communicated to the external system, and assuming the debug information file that matches the script is available, the debug library can be used to translate the program counter value into a source location consisting of file name, line number, and column.
The AspLoadSourceInfoFromFile
routine is used to load the
debug information file (.aspd
). It creates
an AspSourceInfo
structure instance which is used in
subsequent calls to other functions. It accepts a single argument
specifying the name of the debug information file. If the contents of the
file are already in memory, the AspLoadSourceInfo
function
may be used to load the content instead. When the source information
structure is no longer needed, it should be deallocated using
AspUnloadSourceInfo
, which takes a pointer to the structure
as its only argument. It should be noted that, unlike the engine library,
these debug library routines make use of dynamic memory allocation (i.e.,
malloc
and free
) to allocate enough memory for
the content. Also, when using AspLoadSourceInfo
, the source
data is shared and must remain in memory until after the call to
AspUnloadSourceInfo
.
Once an AspSourceInfo
structure instance as been created as
described above, a program counter value may be translated using the
AspGetSourceLocation
function. This function returns an
AspSourceLocation
structure instance, which is defined as
follows.
typedef struct AspSourceLocation { const char *fileName; unsigned line, column; } AspSourceLocation;
In the event an invalid program counter is passed, the returned structure
will have its fileName
member set to the null pointer.
Otherwise, the name of the source file and the applicable line and column
of script source code will be populated.
The AspGetSourceLocation
function may also be used during the
execution of a script, provided the embedded application can transmit the
current program counter frequently enough. This technique may be used to
trace the execution of a script, providing real-time source location
information to an operator. In this case, it may be useful to obtain a
list of source file names prior to execution so that they may be preloaded
(e.g., if they are going to be displayed, with the currently executing
line highlighted). The names may be obtained by calling the
AppGetSourceFileName
function with a zero-based index.
Repeatedly calling this routine with increasing values of the index
parameter until it returns a null pointer will return the names of all the
source files comprising the script.
Symbol numbers may also be translated into their corresponding name via the
AspGetSymbolName
function. Since symbols are printed as
numeric values, this can help identify a variable or other named object
of interest. Note that older versions of the compiler did not write symbol
name information into the debug information file. In this case,
AspGetSymbolName
returns a null pointer. If the symbol
information is present, but the symbol number is not found, an empty string
is returned. In this way, the two error conditions can be distinguished.
Copyright (c) 2024 Canadensys Aerospace Corporation