The ICI Programming Lanaguage: Contents Previous chapter Next chapter
There are several levels at which the ICI interpreter can interface with C and C++ code. This chapter gives a collection of universal rules, then addresses common tasks. Each task can be considered in isolation to alleviate the reader from details beyond their current needs. Finally, both a summary and detailed description of ICI's C API is given.
The reader is expected to be a C/C++ programmer.
On most systems ICI is built as a dynamically loading library. It can be linked with statically if required. However for this description I will assume the normal case of dynamic loading.
To compile and run with ICI you will need, as a minimum:
If you are writing modules that run from ICI, you will also want an ICI top level command:
In broad terms ICI is either used as an adjunct to another application, or as the main program with specific custom modules providing special functionality. Most of what is described in this section is common to both.
ICI objects are structures that have a common 32 bit header (since version 4; in previous versions it was a 64 bit header). Pointers to objects are declared as either object_t *, which is the type of the header, or a pointer to the particular structure (for example, string_t *), depending on the circumstances of each piece of code, and depending whether the real type is known at that point. The macro objof() is often used to demote to a generic object pointer -- it is just a cast to object_t *. Most types define a similar macro to promote to their specific type, as well as a macro such as isstring() or isstruct() to test if a particular pointer points to an object of the given type. However, there is no particular requirement to use these macros. They are just casts and simple tests.
ICI objects are garbage collected. Garbage collection can occur on any attempt to allocate memory using ICI's allocation routines. This is fairly often. Garbage collection has the effect of freeing any objects that the garbage collector thinks are not being usefully referenced. Failing to obey the rules associated with garbage collection can be disastrous and hard to debug. But the rules are fairly simple.
The ICI object header includes a small reference count field. This is a count of additional references to the object that would otherwise be invisible to the garbage collector. For example, if your C code keeps a global static pointer to an object, the garbage collector would not be aware of that reference, and might free the object leaving you with an invalid pointer. So you must account for the reference by incrementing the reference count. However, references to the object from other ICI objects are visible to the garbage collector, so you do not need to account for these.
The macros ici_incref() and ici_decref() are used to increment and decrement reference counts. Note that the range of reference counts is quite small. Their frequency of use is expected to be in relationship to the number of actual instances of C variables that simultaneously refer to the same object.
In practice, many calls to ici_incref() and ici_decref() can be avoided because objects are known to be referenced by other things. For example, when coding a function called from ICI, the objects that are passed as arguments are known to be referenced from the ICI operand stack, which is referenced by the current execution context, which has a reference count as long as it is running.
When coding functions that are called by the ICI interpreter, a simple mechanism is used for all error returns. Each function will have some return value to indicate an error (commonly 0 is success, 1 is an error, or for functions that return a pointer, NULL will probably be the error indicator). In any case, the return value will only imply a boolean indicating that an error has occurred. The nature of the error must have been set before return by setting the global character pointer ici_error to a short human readable string revealing what happened.
Note, however, that only the originator of an error condition should set ici_error. If you call another function that uses the error return convention, and it returns a failure, you must simply clean up your immediate situation (such as any required ici_decref() calls) and return your failure indication in turn. For example, the ici_alloc() function obeys the convention. Thus we might see the fragment:
if ((p = ici_alloc(sizeof(mytype)) == NULL) return 1;
Now, in setting and returning an error, your code will be losing control and must be concerned about the memory that you set the ici_error variable to point to. Simple static strings are, of course, of no concern. For example:
if (that_failed) { ici_error = "failed to do that"; return 1; }
If you need to format a string (say with sprintf) you can avoid the necessity of a permanently allocated buffer by using a generic growable buffer provided by ICI. The buffer is pointed to by ici_buf and always has room for at least 120 characters. Thus me might see:
if (v > 256) { sprintf(ici_buf, "%d set but 256 is limit", v); ici_error = ici_buf; }
If the size of the generated message is not so limited, the buffer can be checked (or forced) to have a larger size with ici_chkbuf(). For example:
if (file == NULL) { if (ici_chkbuf(40 + strlen(fname)) return 1; sprintf(ici_buf, "could not open %s", fname); ici_error = ici_buf; return 1; }
Notice ici_chkbuf() could fail, and if so, we return immediately (the error will inevitably be "ran out of memory"). The 40 above is some number clearly larger than the length of the format string.
One final point, which is not specifically to do with error returns, but commonly associated with them, is how to get a short human readable description of an ICI object suitable for diagnostics. The function ici_objname() can be used to get a small (guaranteed to fit within a 30 character buffer) diagnostic description of an object. For example:
{ char n1[30]; char n2[30]; sprintf(ici_buf, "attempt to read %s keyed by %s", ici_objname(n1, o), ici_objname(n2, k)); ici_error = ici_buf; return 1; }
In general ICI uses the native malloc() and free() functions for its memory allocation. However, because ICI deals in many small objects, and because it needs to track memory usage to control when to run its mark/sweep garbage collector, ICI has a few allocation functions that you may wish to be aware of.
There are three pairs of matched alloc/free functions. Memory allocated with one must be freed by the matching one. Common to all three is the property that they (try) to track the amount of memory allocated so that they can trigger garbage collections, and they may run the garbage collector before they return.
Two of them (ici_talloc / ici_tfree and ici_nalloc / ici_nfree) are designed to handle small objects efficiently (small meaning up to 64 bytes). They allocate small objects densely (no boundary words) out of larger raw allocations from malloc() and maintain fast free lists so that most allocations and frees can avoid function calls completely. However, in order to avoid boundary words they both require that the caller tells the free routines how much memory was asked for on the initial alloc. This is fairly easy 95% of the time, but where it can't be managed, you must use the simpler ici_alloc / ici_free pair. These are completely malloc() equivalent, except they handle the garbage collection and have the usual ICI error return convention.
The tracking of memory usage is only relevant to memory the garbage collector has some control over, meaning memory associated with ICI objects (that would get freed if the object was collected). So, technically, these routines should be used for memory associated with objects, and not other allocations. But in practice the division is not strict.
This is sometimes done as part of a dynamically loaded extension module, and at other times done in a program that uses ICI as a sub-module. By simple function we mean a function that takes and returns values that are directly equivalent to simple C data types.
Transfer from the ICI execution engine to an "intrinsic" function (as they are called) is designed to have extremely low overhead. Thus, on arrival at a function, the arguments are still ICI objects on the ICI operand stack. If you are dealing with simple argument and return types, you can then use the ici_typecheck() function to marshal your arguments into C variables, and a set of similar functions to return simple values.
Intrinsic functions have a return type of int, use C calling convention, follow the usual error convention (1 is failure) and in simple cases declare no arguments. For example, a function that expects to be called with an int and a float from ICI, print them, and return the int plus one, would read:
static int f_myfunc() { long i; double f; if (ici_typecheck("if", &i, &f)) return 1; printf("Got %ld, %lf.\n", i, f); return int_ret(i + 1); }
Note that ICI ints are C longs, and ICI floats are C doubles.
The ici_typecheck() function allows marshalling of:
It also supports skipping argument and variable argument lists, but these features are typically used in functions that use a mixture of ici_typecheck() calls and explicit argument processing. See ici_typecheck() in cfunc.c.
To return simple data types, the functions ici_int_ret(), ici_float_ret(), ici_str_ret(), and ici_null_ret() can be used. These convert the value to the approriate ICI data object, and replace the arguments on the operand stack with that object, then return 0.
Take care never to simply "return 0;" from an intrinsic function. Although returning 1 on error is correct, and the non-error return value is zero, before returning the arguments must be replaced with the return value on the ICI operand stack. The functions above, and various others, do this. They should always be used on successful return from any intrinsic function.
It is possible to write a function that is passed pointers to values from ICI, and have those values updated by the intrinsic function, by using a combination of the ici_typecheck() function and the ici_retcheck() function. For example:
static int f_myotherfunc() { long i; double f; if (ici_typecheck("fI", &f, &i)) return 1; printf("Got %ld, %lf.\n", i, f); if (ici_retcheck("-i", i + 1)) return 1; return float_ret(f * 3.0); }
This function takes a float, and a pointer to an int. It returns three times the passed float value, and increments the pointed-to int. Notice the capitalisation in the ici_typecheck() call to indicate a pointer to an int is required. Also note the hyphen in the ici_retcheck() call to indicate it should ignore the first argument.
To call an ICI function from C you can use any of the functions:
ici_funcv() is the root universal method, but ici_call() and ici_method() are the most common practical methods. Each accept actual parameters described by a short character string that instructs how to translate each C argument into an ICI type. The string may also specify that the return value is required. See the ici_call() specification for details.
For example, to call the ICI nels() function on some object, we could say:
long result; if (ici_call(ICIS(nels), "i=o", &result, obj)) /* Failure. ici_error is message.*/; /* OK, result is number of elements. */
The "i=" part of the string is optional and only used if the return value is needed. For example, to call the ICI include function, and ignore the return value:
if (ici_call(ICIS(include), "s", filename)) /* Failure. */; /* OK */
In most circumstances you can use a ici_handle_t to act as a new ICI primitive type. See ici_handle_new for details. However, if you want to support indexing or calling of the type where your C code handles these operations, you will want to define a new type.
To do this you must:
One thing you must consider is the atomic form of the object. Remeber that any object can be reduced to a unique read-only atomic form with respect to its current value. The basic question is: what aspect of this object is significant in an equality test (i.e. ==) against another object of the same type? You can chose one of these answers:
Two functions you always have to supply are the mark and free functions. (Well, unless your objects are always statically allocated and not registerd with the garbage collector. In that case you wouldn't need the free function. But this is rare.)
There are two basic forms a "new" function for an object might take. Our first example is a good template for an object that is not intrinsically atomic. We assume the type has been registered and the type code is in my_ici_tcode.
my_ici_type * my_ici_type_new(...stuff...) { my_ici_type *m; if ((m = ici_talloc(my_ici_type)) == NULL) return NULL; ICI_OBJ_SET_TFNZ(m, my_ici_tcode, 0, 1, 0); m->... = ...stuff... ici_rego(m); return m; }
Our second example is a good template for an intrinsically atomic object. The can be done by simple adding the line:
m = (my_ici_type *)ici_atom(objof(m), 1);
just before the return above. This is fine if you don't expect to be often called on to allocate objects that exist as atoms. But if you want to avoid an extra object allocation and collection in the case were an object of equal value already exsits, you can use the following template that first probes the atom pool using a prototype form of the object.
my_ici_type * my_ici_type_new(...stuff...) { my_ici_type *m; my_ici_type proto; ICI_OBJ_SET_TFNZ(&proto, my_ici_tcode, 0, 1, 0); proto... = ...stuff... m = (my_ici_type *)ici_atom_probe(objof(&proto)); if (m != NULL) { ici_incref(m); return m; } if ((m = ici_talloc(my_ici_type)) == NULL) return NULL; *m = proto; ici_rego(m); m = (my_ici_type *)ici_atom(objof(m), 1); return m; }
Mark functions often look like this:
unsigned long my_type_mark(ici_obj_t *o) { o->o_flags |= O_MARK; return sizeof(my_ici_type); }
But if the type has references to other ICI objects, it might look like this:
unsigned long my_type_mark(ici_obj_t *o) { o->o_flags |= O_MARK; return sizeof(my_ici_type) + ici_mark(((my_ici_type *)o)->an_obj) + ici_mark(((my_ici_type *)o)->another); }
The loaded library must contain a function of the following name and declaration:
object_t * ici_var_library_init() { ... }
where var is the as yet undefined variable name. This is the initialisation function which is called when the library is loaded. This function should return an ICI object, or NULL on error, in which case the ICI error variable must be set. The returned object will be assigned to var as described above.
The following sample module, mbox.c, illistrates a typical form for a simple dynamically loaded ICI module (it is a Windows example, but should be clear anyway):
#include <windows.h> #include <ici.h> /* * mbox_msg => mbox.msg(string) from ICI * * Pops up a modal message box with the given string in it * and waits for the user to hit OK. Returns NULL. */ int mbox_msg() { char *msg; if (typecheck("s", &msg)) return 1; MessageBox(NULL, msg, (LPCTSTR)"ICI", MB_OK | MB_SETFOREGROUND); return ici_null_ret(); } /* * Object stubs for our intrinsic functions. */ ici_cfunc_t mbox_cfuncs[] = { {CF_OBJ, "msg", mbox_msg}, {CF_OBJ} }; /* * ici_mbox_library_init * * Initialisation routine called on load of this module into the ICI * interpreter's address space. Creates and returns a struct which will * be assigned to "mbox". This struct contains references to our * intrinsic functions. */ ici_obj_t * ici_mbox_library_init() { ici_objwsup_t *s; if (ici_interface_check(ICI_VER, ICI_BACK_COMPAT_VAR, "mbox")) return NULL; if ((s = ici_module_new(mbox_cfuncs)) == NULL) return NULL; return objof(s); }
The following simple Makefile illustrates forms suitable for compiling this module into a DLL under Windows. Note in particular the use of /export in the link line to make the function ici_mbox_library_init externally visible.
CFLAGS = OBJS = mbox.obj LIBS = ici4.lib user32.lib icimbox.dll : $(OBJS) link /dll /out:$@ $(OBJS) /export:ici_mbox_library_init $(LIBS)
Note that there is no direct supprt for the /export option in the MS Developer Studio link settings panel, but it can be entered directly in the Project Options text box.
The following Makefile achieves an equivalent result under Solaris:
CC = gcc -pipe -g CFLAGS = -fpic -I.. OBJS = mbox.o icimbox.so : $(OBJS) ld -o $@ -dc -dp $(OBJS)
References to short strings that are known at compile time is common in ICI modules for field names and such-like. But ICI strings need to be looked up in the ICI atom pool to find the unique pointer for each particular one (and created if it does not already exist). To assist external modules in obtaining the pointer to names they need (especially when there are lots), some macros are defined in ici.h to assist. The following procedure can be used:
/* * Any strings listed in this file may be referred to * with ICIS(name) for a (string_t *), and ICISO(name) * for an (object_t *). * * This file is included with varying definitions * of ICI_STR() to declare, define, and initialise * the strings. */ ICI_STR(fred, "fred") ICI_STR(jane, "jane") ICI_STR(amp, "&")
#include <icistr-setup.h>
This include file both defines variables to hold pointer to the strings (based on the names you gave in icistr.h) and defines a function called init_ici_str() which initialises those pointers. It does this by including your icistr.h file twice, but under the influence of special defines for ICI_STR().
object_t * ici_XXX_library_init(void) { if (init_ici_str()) return NULL; ...
#include "ici.h" #include "icistr.h" ... object_t *o; struct_t *s ... o = ici_fetch(s, ICIS(fred));
ICI array objects are, in general, circular buffers of object pointers in a single growable memory allocation. Because the wrap point of the circular buffer may occur at any point, it is necessary to use various functions and macros to access elements rather than simple indexing and/or pointer arithmetic. The functions and macros that work in general are:
It is also possible to progress a pointer along all the elements of an array in sequence with:
ici_array_t *a; ici_obj_t **e; ... for (e = ici_astart(a); e != ici_alimit(a); e = ici_anext(a, e)) ... /* *e is the object */
However, arrays are implemented in a manner to allow them to be used as stacks even more efficiently than through the functions listed above. They are used that way in the execution engine. Basicly, until an rpop or rpush operation is done on an array, it can be assumed that the wrap point of the circular buffer is just before the first element, and therefore all elements are contiguously addressable relative to the bottom of the array. In the particular situation that some C code has just made an array, and it has not yet been released to any ICI program or other code that is not well understood, certain simplifying assumtions can be made, and certain more efficient methods can be used. In comments, an array is said to be `still a stack' or `might be a queue'.
The C structure that is an ICI array object contains the pointers a_base and a_limit that define the limits of the allocated memory (a_limit points to the element just beyond the end of the allocation), and the pointers a_bot and a_top that define the limits of the array's current content (a_top points to the elememt just beyond the current content). When an array is still a stack, a_bot is always equal to a_base and all the elements are contiguously addressable from there.
Further, it is possible to ensure (force) the array allocation to have any desired amount of room for efficient growth with the macro:
It is also possible to request an allocation size when calling ici_array_new() to make a new array. So, for example, to create an array of ten integers:
if ((a = ici_array_new(10)) == NULL) fail... for (i = 0; i < 10; ++i) *a->a_top++ = ici_zero;
Similarly, looping over the elements of an array that is still a stack is simpler than the for-loop shown above.
Note that one must never take the atomic form of a stack, and assume the result is still a stack. Also remember that the actual memory of the array is a growable buffer. Anything that could ccause the stack to grow (any push or call to ici_stk_push_chk) may change the allocated buffer to a new location. So don't hang on to copies of the array buffer pointers in private variables.
The following table summarises function and public data that form ICI's C API. The use of some of these functions has been illustrated above. The full specification of each is given below. This summary is only intended to document the limits of the public interface, allow you to select the relevant functions, and direct you to the source with the full description.
The final column gives a hint about upgrading from ICI 3. If just a name is mentioned, that is the old name of this function (many names have acquired an ici_ prefix). Other notes indicate what constructs should be checked for possible upgrade to the given function. If it is blank, there is no change. There is an ICI program, ici3check.ici, in the ICI source directory that will grep your source for usage of changed constructs and print a note on upgrade action.
Name |
Synopsis |
Upgrade from ICI3 |
---|---|---|
ici.h |
The ICI C API include file. This is generated specifically for each platform. |
|
ici_alloc |
Allocate memory, in particular memory subject to collection (possibly indirectly). Must be freed with ici_free. See also ici_talloc and ici_nalloc which are both preferable. See alloc.c. |
|
ici_argcount |
Generate an error message indicating that this intrinsic function was given the wrong number of argument. See cfunc.c. |
|
ici_argerror |
Generate an errror message indicating that this argument of this intrinsic function is wrong. See cfunc.c. |
|
ici_array_gather |
Copy a (possibly disjoint) run of elements of an array into contiguous memory. |
Use of a_top. |
ici_array_get |
Fetch an elemet of an array indexed by a C int. See array.c. |
Use of a_top. |
ici_array_nels |
Return the number of elements in an array. See array.c. |
Use of a_top. |
ici_array_new |
Allocate a new ICI array. See array.c. |
new_array |
ici_array_pop |
Pop and return last element of an ICI array. See array.c. |
Use of a_top. |
ici_array_push |
Push an object onto an array. See array.c. |
Use of a_top. |
ici_array_rpop |
Rpop an obect from an ICI array. See array.c. |
|
ici_array_rpush |
Rpush an oject onto an ICI array. See array.c. |
|
ici_array_t |
The ICI array object type. See array.h. |
array_t |
ici_assign |
Assign to an ICI object (vectors through type). See object.h. |
assign |
ici_assign_cfuncs |
Assign of bunch of intrinsic function prototypes into the ICI namespace. See cfunco.c. |
|
ici_assign_fail |
Generic function that can be used for types that don't support assignment. See object.c. |
assign_simple |
ici_atexit |
Register a function to be called at ici_uninit. See uninit.c. |
|
ici_atom |
Return the atomic form of an object. See object.c. |
atom |
ici_buf |
A general purpose growable character buffer, typically used for error messages. See ici_chkbuf. See buf.c. |
|
ici_call |
Call an ICI function from C by name. See call.c. |
Prototype change. |
ici_callv |
Same as ici_call but takes a va_list. See call.c. |
Prototype change. |
ici_cfunc_t |
The ICI intrinsic function object types. See cfunc.h and cfunco.c (not cfunc.c). |
cfunc_t |
ici_chkbuf |
Verify or grow ici_buf to be big enough. See buf.c. |
|
ici_cmp_unique |
Generic function that can be used for types that can't be merged through the atom pool. See object.c. |
cmp_unique |
ici_copy_simple |
Generic copy function that can be used for types that are intrinsically atomic. See object.c. |
copy_simple |
ici_debug |
A pointer to the current debug functions. See idb2.c. |
|
ici_debug_enabled |
If compiled with debug, an int giving the current status of debug callbacks. Else defined to 0 by the preprocessor. See fwd.h. |
|
ici_debug_t |
A struct of function pointers for debug functions (like break and watch). See exec.h. |
debug_t |
ici_decref |
Decrement the reference count of an ICI object. See object.h. |
decref |
ici_def_cfunc |
Define C functions in the current scope. See cfunco.c. |
def_cfuncs |
ici_dont_record_line_nums |
A global int which can be set to prevent line number records (which will marginally speed execution, but errors won't reveal source location). See fwd.h. |
|
ici_enter |
Acquire the global ICI mutex, which is required for access to ICI data and functions. See thread.c. |
|
ici_error |
A global char pointer to any current error message. |
|
ici_exec |
A pointer to the current ICI execution context (NB: Don't look at or touch x_os, x_xs or x_vs fields). See exec.h. |
|
ici_fetch |
Fetch an element from an object. Vectors by object type. See object.h. |
fetch |
ici_fetch_fail |
A generic function that can be used by objects that don't support fetching. |
fetch_simple |
ici_fetch_int |
Fetch an int from an object into a C long. See mkvar.c. |
|
ici_fetch_num |
Fetch an int or float from an object into a C double. See mkvar.c. |
|
ici_file_close |
Close the low-level file associated with an ICI file object. See file.c. |
file_close |
ici_file_new |
Create a new ICI file object. See file.c. |
new_file |
ici_file_t |
The ICI file object type. See file.h. |
file_t |
ici_float_new |
Get an ICI float from a C double. See float.c. |
new_float |
ici_float_ret |
Return a C double from an intrinsic function as an ICI float. See cfunc.c. |
float_ret |
ici_float_t |
The ICI float object type. See float.h. |
float_t |
ici_free |
Free memory allocated with ici_alloc. See also ici_tfree and ici_nfree. See alloc.c. |
|
ici_func |
Call an ICI function, given you have a func_t *. See ici_call to call by name. See call.c. |
Prototype change. |
ici_func_t |
The ICI function object type. See func.h. |
func_t |
ici_funcv |
Same as ici_func except it takes a va_list. |
Prototype change. |
ici_get_last_errno |
Set ici_error (see) based on the last failed system function (i.e. errno). See syserr.c. |
syserr (and semantics change) |
ici_get_last_win32_error |
Windows only. Set ici_error based on the value of GetLastError(). See win32err.c. |
|
ici_handle_new |
Make a new handle_t object. |
|
ici_handle_t |
The type of handle objects. See handle.c. |
|
ici_hash_unique |
A generic function that can be used in a type_t struct for objects that can't be merged through the atom pool. See object.c. |
|
ici_incref |
Increment reference to an ICI object. |
incref |
ici_init |
Initialise the ICI interpreter. See init.c. |
|
ici_int_new |
Get an ICI int from a C long. See int.c. |
new_int |
ici_int_ret |
Return a C long from an intrinsic function.See cfunc.c. |
int_ret |
ici_int_t |
|
int_t |
ici_leave |
Unlock the global ICI mutex to allow thread switching. See thread.c. |
|
ici_main |
A wrapper round ici_init() that does argc, argv argument processing. See icimain.c. |
|
ici_mark |
Mark an object as part of the garbage collection mark phase. See object.h. |
mark |
ici_mem_new |
Allocate a new mem_t object. See mem.c. |
new_mem |
ici_mem_t |
|
mem_t |
ici_method |
Call an ICI method given an instance and a method name. See call.c. |
|
ici_method_new |
Allocate a new metnod_t object. See method.c. |
ici_new_method |
ici_method_t |
|
method_t |
ici_nalloc |
Allocate memory, in particular memory subject to collection (possibly indirectly). Must be freed with ici_nfree. See also ici_talloc and ici_alloc. See alloc.c. |
|
ici_need_stdin |
Return ICI file object that is the current value of stdin. |
need_stdin |
ici_need_stdout |
Return ICI file object that is the current value of stdout. |
need_stdout |
ici_nfree |
Free memory allocated with ici_nalloc. See also ici_tfree and ici_free. See alloc.c. |
|
ici_null |
A pointer to the ICI NULL object. |
objof(&o_null) |
ici_null_ret |
Return an ICI NULL from an intrinsic function.See cfunc.c. |
null_ret |
ici_null_t |
|
null_t |
ici_obj_t |
The generic ICI object type. All ICI objects have this as their first element. |
object_t |
ici_objname |
Get a short human readable representation of any object for diagnostics reports. See cfunc.c. |
objname |
ici_objwsup_t |
|
objwsup_t |
ici_one |
A global pointer to the ICI int 1. |
o_one |
ici_os |
An ICI array which is the operand stack of the current execution context. |
|
ici_parse_file |
Parse a file as a new top-level module. |
parse_file |
ici_ptr_new |
Allocate a new ICI ptr object. See ptr.c. |
new_ptr |
ici_ptr_t |
|
ptr_t |
ici_reclaim |
Run the ICI garbage collector. See object.c. |
|
ici_regexp_new |
Make a new ICI regexp object. |
new_regexp |
ici_regexp_t |
|
regexp_t |
ici_register_type |
Register a new type_t structure with the interpreter to obtain the small int type code that must be placed in the header of ICI objects. |
o_type assignment |
ici_rego |
Register a new object with the garbage collector. See object.h (a macro). |
rego |
ici_ret_no_decref |
Return an ICI object from an intrinsic C function, without an ici_decref. See cfunc.c. |
|
ici_ret_with_decref |
Return an ICI object from an intrinsic C function, but ici_decref it in the process. See cfunc.c. |
|
ici_retcheck |
Check and update values returned through pointers. |
|
ici_set_new |
Allocate a new ICI set object. See set.c. |
new_set |
ici_set_t |
|
set_t |
ici_set_unassign |
Remove an element from a set. See set.c. |
unassign_set |
ici_set_val |
Set a C int, long, double, FILE * or ICI object into the inner-most scope of any object that supports a super. See mkvar.c. |
|
ici_skt_t |
|
skt_t |
ici_stdio_ftype |
A struct holding pointers to stdio functions to facilitate making stdio based ICI files. |
stdio_ftype |
ici_stk_push_chk |
Ensures there is a certain amount of contiguous room at the end of an array for direct push operations through a_top. See array.h. |
|
ICI_STR |
Multi-purpose macro used by the icistr-setup.h mechanism. See str.h. |
|
ici_str_alloc |
Do half of the allocation of a string. Data must be added and ici_atom called before completion. See string.c. |
new_string |
ici_str_get_nul_term |
Get the ICI form of a string of nul terminated chars without an extra reference count. See string.c. |
get_cname |
ici_str_new |
Get the ICI form of a string of chars, by explicit length. See string.c. |
new_name |
ici_str_new_nul_term |
Get the ICI form of a string of nul terminated chars. See string.c. |
new_cname |
ici_str_ret |
Return a nul terminated C string from an intrinsic function as an ICI string. See cfunc.c. |
str_ret |
ici_str_t |
|
string_t |
ici_struct_new |
Allocate a new ICI struct object. See struct.c. |
new_struct |
ici_struct_t |
|
struct_t |
ici_struct_unassign |
Remove an element from an ICI struct. See struct.c. |
unassign_struct |
ici_talloc |
Allocate memory, in particular memory subject to collection (possibly indirectly) sufficient for a given type. Must be freed with ici_tfree. See also ici_nalloc and ici_alloc. See alloc.c. |
ici_alloc |
ici_tfree |
Free memory allocated with ici_tfree. See alloc.c. |
|
ici_type_t |
The type that holds information about ICI primitive object types. You must declare, initialise, and register one of these to make a new ICI primitive type. See ici_register_type. See object.h. |
type_t |
ici_typecheck |
Check and marshall ICI arguments to an intrinsic function into C variables. See cfunc.c. |
|
ici_uninit |
Shutdown and free resources associated with the ICI interpreter. See uninit.c. |
|
ici_vs |
The scope ("variable") stack of the current ICI execution context. |
|
ici_wakeup |
Wake up any ICI threads waiting on a given ICI object. See thread.c. |
|
ici_wrap_t |
Struct to support ici_atexit. See uninit.c. |
wrap_t |
ici_xs |
The execution stack of the current ICI execution engine. See exec.c. |
|
ici_XXX_library_init |
The entry point you must define for dynamically loaded modules. |
|
ici_zero |
The ICI int 0. |
o_zero |
icistr.h |
The include file you must make to get initialised ICI strings. See str.h. |
|
icistr-setup.h |
The include file you must include to initialised ICI strings. See str.h. |
|
objof |
Macro to cast an object to an (object_t *) |
|
#define ARG(n) ...
In a call from ICI to a function coded in C, this macro returns the object passed as the 'i'th actual parameter (the first parameter is ARG(0)). The type of the result is an (ici_obj_t *). There is no actual or implied incref associated with this. Parameters are known to be on the ICI operand stack, and so can be assumed to be referenced and not garbage collected.
(This macro has no ICI_ prefix for historical reasons.)
#define ARGS() ...
In a call from ICI to a function coded in C, this macro returns a pointer to the first argument to this function, with subsequent arguments being available by *decrementing* the pointer.
(This macro has no ICI_ prefix for historical reasons.)
#define CF_ARG1() ...
In a call from ICI to a function coded in C, this macro returns the cf_arg1 field of the current C function. The macro CF_ARG2() can also be used to obtain the cf_arg2 field. See the ici_cfunc_t type.
They are both (void *) (Prior to ICI 4.0, CF_ARG1() was a function pointer.)
#define ICI_BACK_COMPAT_VER ...
The oldet version number for which the binary interface for seperately compiled modules is backwards compatible. This is updated whenever the exernal interface changes in a way that could break already compiled modules. We aim to never to do that again. See ici_interface_check().
#define ICI_DIR_SEP ...
The character which seperates segments in a path on this architecture.
#define ICI_DLL_EXT ...
The string which is the extension of a dynamicly loaded library on this architecture.
#define ICI_NO_OLD_NAMES ...
This define may be made before an include of ici.h to suppress a group of old (backward compatible) names. These names have been upgraded to have ici_ prefixes since version 4.0.4. These names don't effect the binary interface of the API; they are all type or macro names. But you might want to suppress them if you get a clash with some other include file (for example, file_t has been known to clash with defines in <file.h> on some systems).
If you just was to get rid of one or two defines, you can #undef them after the include of ici.h.
The names this define supresses are:
array_t float_t object_t catch_t slot_t set_t struct_t exec_t file_t func_t cfunc_t method_t int_t mark_t null_t objwsup_t op_t pc_t ptr_t regexp_t src_t string_t type_t wrap_t ftype_t forall_t parse_t mem_t debug_t
#define ICI_OBJ_SET_TFNZ(o, tcode, flags, nrefs, leafz) ...
Set the basic fields of the object header of o. o can be any struct declared with an object header (this macro casts it). This macro is prefered to doing it by hand in case there is any future change in the structure. See comments on each field of ici_obj_t. This is normally the first thing done after allocating a new bit of memory to hold an ICI object.
#define ICI_PATH_SEP ...
The character which seperates directories in a path list on this architecture.
#define ICI_VER ...
The ICI version number composed into an 8.8.16 unsigned long for simple comparisons. The components of this are also available as ICI_VER_MAJOR, ICI_VER_MINOR, and ICI_VER_RELEASE.
#define NARGS() ...
In a call from ICI to a function coded in C, this macro returns the count of actual arguments to this C function.
(This macro has no ICI_ prefix for historical reasons.)
#define hassuper(o) ...
Test if this object supports a super type. (It may or may not have a super at any particular time).
#define ici_alimit(a) ...
A macro to assist in doing for loops over the elements of an array. Use as:
ici_array_t *a; ici_obj_t **e; for (e = ici_astart(a); e != ici_alimit(a); e = ici_anext(a, e)) ...
void * ici_alloc(size_t z)
Allocate a block of size z. This just maps to a raw malloc() but does garbage collection as necessary and attempts to track memory usage to control when the garbage collector runs. Blocks allocated with this must be freed with ici_free().
It is preferable to use ici_talloc(), or failing that, ici_nalloc(), instead of this function. But both require that you can match the allocation by calling ici_tfree() or ici_nalloc() with the original type/size you passed in the allocation call. Those functions use dense fast free lists for small objects, and track memory usage better.
See also: ICIs allocation functions, ici_free(), ici_talloc(), ici_nalloc().
#define ici_anext(a, e) ...
A macro to assist in doing for loops over the elements of an array. Use as:
ici_array_t *a; ici_obj_t **e; for (e = ici_astart(a); e != ici_alimit(a); e = ici_anext(a, e)) ...
int ici_argcount(int n)
Generate a generic error message to indicate that the wrong number of arguments have been supplied to an intrinsic function, and that it really (or most commonly) takes n. This function sets the error descriptor (ici_error) to a message like:
%d arguments given to %s, but it takes %d
and then returns 1.
This function may only be called from the implementation of an intrinsic function. It takes the number of actual argument and the function name from the current operand stack, which therefore should not have been distured (which is normal for intrincic functions). It takes the number of arguments the function should have been supplied with (or typically is) from n. This function is typically used from C coded functions that are not using ici_typecheck() to process arguments. For example, a function that just takes a single object as an argument might start:
static int myfunc() { ici_obj_t *o; if (NARGS() != 1) return ici_argcount(1); o = ARG(0); . . .
int ici_argerror(int i)
Generate a generic error message to indicate that argument i of the current intrinsic function is bad. Despite being generic, this message is generally pretty informative and useful. It has the form:
argument %d of %s incorrectly supplied as %s
The argument number is base 0. I.e. ici_argerror(0) indicates the 1st argument is bad.
The function returns 1, for use in a direct return from an intrinsic function.
This function may only be called from the implementation of an intrinsic function. It takes the function name from the current operand stack, which therefore should not have been distured (which is normal for intrincic functions). This function is typically used from C coded functions that are not using ici_typecheck() to process arguments. For example, a function that just takes a single mem object as an argument might start:
static int myfunc() { ici_obj_t *o; if (NARGS() != 1) return ici_argcount(1); if (!ismem(ARG(0))) return ici_argerror(0); . . .
void ici_array_gather(ici_obj_t **b, ici_array_t *a, ptrdiff_t start, ptrdiff_t n)
Copy n object pointers from the given array, starting at index start, to b. The span must cover existing elements of the array (that is, don't try to read from negative or excessive indexes).
This function is used to copy objects out of an array into a contiguous destination area. You can't easily just memcpy, because the span of elements you want may wrap around the end. For example, the implementaion of interval() uses this to copy the span of elements it wants into a new array.
ici_obj_t * ici_array_get(ici_array_t *a, ptrdiff_t i)
Return the element or the array a from index i, or ici_null if out of range. No incref is done on the object.
ptrdiff_t ici_array_nels(ici_array_t *a)
Return the number of elements in the array a.
ici_array_t * ici_array_new(ptrdiff_t n)
Return a new array. It will have room for at least n elements to be pushed contigously (that is, there is no need to use ici_stk_push_chk() for objects pushed immediately, up to that limit). If n is 0 an internal default will be used. The returned array has ref count 1. Returns NULL on failure, usual conventions.
ici_obj_t * ici_array_pop(ici_array_t *a)
Pop and return the top of the given array, or ici_null if it is empty. Returns NULL on error (for example, attempting to pop and atomic array). Usual error conventions.
int ici_array_push(ici_array_t *a, ici_obj_t *o)
Push the object o onto the end of the array a. This is the general case that works for any array whether it is a stack or a queue. On return, o_top[-1] is the object pushed. Returns 1 on error, else 0, usual error conventions.
ici_obj_t * ici_array_rpop(ici_array_t *a)
Pop and return the front of the given array, or ici_null if it is empty. Returns NULL on error (for example, attempting to pop and atomic array). Usual error conventions.
int ici_array_rpush(ici_array_t *a, ici_obj_t *o)
Push the object o onto the front of the array a. Return 1 on failure, else 0, usual error conventions.
#define ici_assign(o,k,v) ...
Assign the value v to key k of the object o. This macro just calls the particular object's t_assign() function.
Note that the argument o is subject to multiple expansions.
Returns non-zero on error, usual conventions.
#define ici_assign_base(o,k,v) ...
Assign the value v to key k of the object o, but only assign into the base object, even if there is a super chain. This may only be called on objects that support supers.
Note that the argument o is subject to multiple expansions.
Returns non-zero on error, usual conventions.
int ici_assign_cfuncs(ici_objwsup_t *s, ici_cfunc_t *cf)
Assign into the structure s all the intrinsic functions listed in the array of ici_cfunc_t structures pointed to by cf. The array must be terminated by an entry with a cf_name of NULL. Typically, entries in the array are formated as:
{CF_OBJ, "func", f_func},
Where CF_OBJ is a convenience macro to take care of the normal object header, "func" is the name your function will be assigned to in the given struct, and f_func is a C function obeying the rules of ICI intrinsic functions.
Returns non-zero on error, in which case error is set, else zero.
int ici_assign_fail(ici_obj_t *o, ici_obj_t *k, ici_obj_t *v)
This is a convenience function which can be used directly as the t_assign entry in a type's ici_type_t struction if the type doesn't support asignment. It sets ici_error to a message of the form:
attempt to set %s keyed by %s to %s
and returns 1. Also, it can b called from within a custom assign function in cases where the particular assignment is illegal.
#define ici_astart(a) ...
A macro to assist in doing for loops over the elements of an array. Use as:
ici_array_t *a; ici_obj_t **e; for (e = ici_astart(a); e != ici_alimit(a); e = ici_anext(a, e)) ...
void ici_atexit(void (*func)(void), ici_wrap_t *w)
Register the function func to be called at ICI interpreter shutdown (i.e. ici_uninit() call).
The caller must supply a ici_wrap_t struct, which is usually statically allocated. This structure will be linked onto an internal list and be unavailable till after ici_uninit() is called.
ici_obj_t * ici_atom(ici_obj_t *o, int lone)
Return the atomic form of the given object o. This will be an object equal to the one given, but read-only and possibly shared by others. (If the object it already the atomic form, it is just returned.)
This is achieved by looking for an object of equal value in the atom pool. The atom pool is a hash table of all atoms. The object's t_hash and t_cmp functions will be used it this lookup process (from this object's ici_type_t struct).
If an existing atomic form of the object is found in the atom pool, it is returned.
If the lone flag is 1, the object is free'd if it isn't used. ("lone" because the caller has the lone reference to it and will replace that with what atom returns anyway.) If the lone flag is zero, and the object would be used (rather than returning an equal object already in the atom pool), a copy will made and that copy stored in the atom pool and returned. Also note that if lone is 1 and the object is not used, the nrefs of the passed object will be transfered to the object being returned.
Never fails, at worst it just returns its argument (for historical reasons).
ici_obj_t * ici_atom_probe(ici_obj_t *o)
Probe the atom pool for an atomic form of o. If found, return that atomic form, else NULL. This can be use by *_new() routines of intrinsically atomic objects. These routines generally set up a dummy version of the object being made which is passed to this probe. If it finds a match, that is returned, thus avoiding the allocation of an object that may be thrown away anyway.
int ici_call(ici_str_t *func_name, char *types, ...)
Call an ICI function by name from C with simple argument types and return value. The name (func_name) is looked up in the current scope.
See ici_func() for an explanation of types. Apart from taking a name, rather than an ICI function object, this function behaves in the same manner as ici_func().
There is some historical support for @ operators, but it is deprecated and may be removed in future versions.
int ici_callv(ici_str_t *func_name, char *types, va_list va)
Varient of ici_call() (see) taking a variable argument list.
There is some historical support for @ operators, but it is deprecated and may be removed in future versions.
struct ici_cfunc { ici_obj_t o_head; char *cf_name; int (*cf_cfunc)(); void *cf_arg1; void *cf_arg2; }
The C struct which is the ICI intrinsic function type. That is, a function that is coded in C. (There are actually two types, this one, and a second for functions that are coded in ICI, that are both called func.)
ici_cfunc_t objects are often declared staticly (in an array) when setting up a group of C functions to be called from ICI. When doing this, the macro CF_OBJ can be used as the initialiser of the o_head field (the standard ICI object heade).
The type has a well-known built-in type code of TC_CFUNC.
o_head The standard ICI object header.
#define ici_chkbuf(n) ...
Ensure that ici_buf points to enough memory to hold index n (plus room for a nul char at the end). Returns 0 on success, else 1 and sets ici_error.
See also: The error return convention.
ici_objwsup_t * ici_class_new(ici_cfunc_t *cf, ici_objwsup_t *super)
Create a new class struct and assign the given cfuncs into it (as in ici_assign_cfuncs()). If super is NULL, the super of the new struct is set to the outer-most writeable struct in the current scope. Thus this is a new top-level class (not derived from anything). If super is non-NULL, it is presumably the parent class and is used directly as the super. Returns NULL on error, usual conventions. The returned struct has an incref the caller owns.
int ici_cmp_unique(ici_obj_t *o1, ici_obj_t *o2)
This is a convenience function which can be used directly as the t_cmp entry in a type's ici_type_t struction if object of this type are intrinsically unique. That is, the object is one-to-one with the memory allocated to hold it. An object type would be instrinsically unique if you didn't want to support comparison that considered the contents, and/or didn't want to support copying. If you use this function you should almost certainly also be using ici_hash_unique and ici_copy_simple.
It returns 0 if the objects are the same object, else 1.
ici_obj_t * ici_copy_simple(ici_obj_t *o)
This is a convenience function which can be used directly as the t_copy entry in a type's ici_type_t struction if object of this type are intrinsically unique (i.e. are one-to-one with the memory they occupy, and can't be merged) or intrinsically atomic (i.e. are one-to-one with their value, are are always merged). An object type would be instrinsically unique if you didn't want to support comparison that considered the contents, and/or didn't want to support copying. An intrinsically atomic object type would also use this function because, by definition, if you tried to copy the object, you'd just end up with the same one anyway.
It increfs o, and returns it.
struct ici_debug { void (*idbg_error)(char *, ici_src_t *); void (*idbg_fncall)(ici_obj_t *, ici_obj_t **, int); void (*idbg_fnresult)(ici_obj_t *); void (*idbg_src)(ici_src_t *); void (*idbg_watch)(ici_obj_t *, ici_obj_t *, ici_obj_t *); }
ICI debug interface. The interpreter has a global debug interface enable flag, ici_debug_enabled, and a global pointer, ici_debug, to one of these structs. If the flag is set, the interpreter calls these functions. See ici_debug and ici_debug_enabled.
#define ici_decref(o) ...
Decrement the object 'o's reference count. References from ordinary machine data objects (ie. variables and stuff, not other objects) are invisible to the garbage collector. These refs must be accounted for if there is a possibility of garbage collection. Note that most routines that make objects (new_*(), copy() etc...) return objects with 1 ref. The caller is expected to ici_decref() it when they attach it into wherever it is going.
int ici_def_cfuncs(ici_cfunc_t *cf)
Define the given intrinsic functions in the current static scope. See ici_assign_cfuncs() for details.
Returns non-zero on error, in which case error is set, else zero.
int ici_dont_record_line_nums;
Set this to non-zero to stop the recording of file and line number information as code is parsed. There is nothing in the interpreter core that sets this. Setting this can both save memory and increase execution speed (slightly). But diagnostics won't report line numbers and source line debugging operations won't work.
void ici_enter(ici_exec_t *x)
Enter code that uses ICI data. ICI data referes to *any* ICI objects or static variables. You must do this after having left ICI's mutex domain, by calling ici_leave(), before you again access any ICI data. This call will re-acquire the global ICI mutex that gates access to common ICI data. You must pass in the ICI execution context pointer that you remembered from the previous matching call to ici_leave().
If the thread was in an ICI level critical section when the ici_leave() call was made, then this will have no effect (mirroring the no effect that happened when the ici_leave() was done).
Note that even ICI implementations without thread support provide this function. In these implemnetations it has no effect.
char *ici_error;
The global error message pointer. The ICI error return convention dictacts that the originator of an error sets this to point to a short human readable string, in addition to returning the functions error condition. See The error return convention for more details.
ici_obj_t * ici_eval(ici_str_t *name)
Evaluate name as if it was a variable in a script in the currently prevailing scope, and return its value. If the name is undefined, this will attempt to load extension modules in an attemot to get it defined.
This is slightly different from fetching the name from the top element of the scope stack (i.e. ici_vs.a_top[-1]) because it will attempt to auto-load, and fail if the name is not defined.
The returned object has had it's reference count incremented.
Returns NULL on error, usual conventions.
#define ici_fetch(o,k) ...
Fetch the value of the key k from the object o. This macro just calls the particular object's t_fetch() function.
Note that the returned object does not have any extra reference count; however, in some circumstances it may not have any garbage collector visible references to it. That is, it may be vunerable to a garbage collection if it is not either incref()ed or hooked into a referenced object immediately. Callers are responsible for taking care.
Note that the argument o is subject to multiple expansions.
Returns NULL on failure, usual conventions.
#define ici_fetch_base(o,k) ...
Fetch the value of the key k from the object o, but only consider the base object, even if there is a super chain. See the notes on ici_fetch(), which also apply here.
ici_obj_t * ici_fetch_fail(ici_obj_t *o, ici_obj_t *k)
This is a convenience function which can be used directly as the t_fetch entry in a type's ici_type_t struction if the type doesn't support fetching. It sets ici_error to a message of the form:
attempt to read %s keyed by %
and returns 1. Also, it can b called from within a custom assign function in cases where the particular fetch is illegal.
int ici_file_close(ici_file_t *f)
Close the given ICI file f by calling the lower-level close function given in the ici_ftype_t associated with the file. A guard flag is maintained in the file object to prevent multiple calls to the lower level function (this is really so we can optionally close the file explicitly, and let the garbage collector do it to). Returns non-zero on error, usual conventions.
ici_file_t * ici_file_new(void *fp, ici_ftype_t *ftype, ici_str_t *name, ici_obj_t *ref)
Return a file object with the given ftype and a file type specific pointer fp which is often somethings like a STREAM * or a file descriptor. The name is mostly for error messages and stuff. The returned object has a ref count of 1. Returns NULL on error.
The ftype is a pointer to a struct of stdio-like function pointers that will be used to do I/O operations on the file (see ici_ftype_t). The given structure is assumed to exist as long as necessary. (It is normally a static srtucture, so this is not a problem.) The core-supplied struct ici_stdio_ftype can be used if fp is a STREAM *.
The ref argument is an object reference that the file object will keep in case the fp argument is an implicit reference into some object (for example, this is used for reading an ICI string as a file). It may be NULL if not required.
ici_float_t * ici_float_new(double v)
Return an ICI float object corresponding to the given value v. Note that floats are intrinsically atomic. The returned object will have had its reference count inceremented. Returns NULL on error, usual conventions.
int ici_float_ret(double ret)
Use return ici_float_ret(ret); to return a float (i.e. a C double) from an intrinsic fuction. The double will be converted to an ICI float.
struct ici_float { ici_obj_t o_head; double f_value; }
The C struct that is the ICI float object.
void ici_free(void *p)
Free a block allocated with ici_alloc().
See also: ICIs allocation functions, ici_alloc(), ici_tfree(), ici_nfree().
struct ici_ftype { int (*ft_getch)(); int (*ft_ungetch)(); int (*ft_putch)(); int (*ft_flush)(); int (*ft_close)(); long (*ft_seek)(); int (*ft_eof)(); int (*ft_write)(); }
A set of function pointers for simple file abstraction. ICI file objects are implemented on top of this simple file abstraction in order to allow several different types of file-like entities. Each different type of file uses one of these structures with specific functions. Each function is assumed to be compatible with the stdio function of the same name. In the case were the file is a stdio stream, these *are* the stdio functions.
See also: ici_stdio_ftype.
int ici_func(ici_obj_t *callable, char *types, ...)
Call a callable ICI object callable from C with simple argument marshalling and an optional return value. The callable object is typically a function (but not a function name, see ici_call for that case).
types is a string that indicates what C values are being supplied as arguments. It can be of the form ".=..." or "..." where the dots represent type key letters as described below. In the first case the 1st extra argument is used as a pointer to store the return value through. In the second case, the return value of the ICI function is not provided.
Type key letters are:
Returns 0 on success, else 1, in which case ici_error has been set.
See also: ici_callv(), ici_method(), ici_call(), ici_funcv().
int ici_funcv(ici_obj_t *subject, ici_obj_t *callable, char *types, va_list va)
This function is a variation on ici_func(). See that function for details on the meaning of the types argument.
va is a va_list (variable argument list) passed from an outer var-args function.
If subject is NULL, then callable is taken to be a callable object (could be a function, a method, or something else) and is called directly. If subject is non-NULL, it is taken to be an instance object and callable should be the name of one of its methods (i.e. an ici_str_t *).
int ici_get_last_errno(char *dothis, char *tothis)
Convert the current errno (that is, the standard C global error code) into an ICI error message based on the standard C strerror function. Returns 1 so it can be use directly in a return from an ICI instrinsic function or similar. If dothis and/or tothis are non-NULL, they are included in the error message. dothis should be a short name like "open". tothis is typically a file name. The messages it sets are, depending on which of dothis and tothis are NULL, the message will be one of:
strerror failed to dothis: strerror failed to dothis tothis: strerror tothis: strerror
int ici_get_last_win32_error(void)
Windows only. Convert the current Win32 error (that is, the value of GetLastError()) into an ICI error message and sets ici_error to point to it. Returns 1 so it can be use directly in a return from an ICI instrinsic function.
int ici_handle_method_check(ici_obj_t *inst, ici_str_t *name, ici_handle_t **h, void **p)
Verify that a method on a handle has been invoked correctly. In particular, that inst is not NULL and is a handle with the given name. If OK and h is non-NULL, the handle is stored through it. If p is non-NULL, the associted pointer (h_ptr) is stored through it. Return 1 on error and sets ici_error, else 0.
For example, a typical method where the instance should be a handle of type XML_Parse might look like this:
static int ici_xml_SetBase(ici_obj_t *inst) { char *s; XML_Parser p; if (ici_handle_method_check(inst, ICIS(XML_Parser), NULL, &p)) return 1; if (ici_typecheck("s", &s)) return 1; if (!XML_SetBase(p, s)) return ici_xml_error(p); return ici_null_ret(); }
ici_handle_t * ici_handle_new(void *ptr, ici_str_t *name, ici_objwsup_t *super)
Return a handle object corresponding to the given C data ptr, with the ICI type name (which may be NULL), and with the given super (which may be NULL).
The returned handle will have had its reference count inceremented.
ICI handle objects are generic wrapper/interface objects around some C data structure. They act, on the ICI side, as objects with the type name. When you are passed a handle back from ICI code, you can check this name to prevent the ICI program from giving you some other data type's handle. (You can't make handles at the script level, so you are safe from all except other native code mimicing your type name.)
Handles are intrinsicly atomic with respect to the ptr and name. So this function actually just finds the existing handle of the given data object if that handle already exists.
Handle's will, of course, be garbage collected as usual. If your C data is one-to-one with the handle, you should store a pointer to a free function for your data in the h_pre_free field of the handle. It will be called just before the gardbage collector frees the memory of the handle.
If, on the other hand, your C data structure is the master structure and it might be freed by some other aspect of your code, you must consider that its handle object may still be referenced from ICI code. You don't want to have it passed back to you and inadvertently try to access your freed data. To prevent this you can set the H_CLOSED flag in the handle's object header when you free the C data (see ici_handle_probe()). Note that you are reponsible to checking H_CLOSED when you are passed a handle. Also, once you use this mechanism, you must *clear* the H_CLOSED field after a real new handle allocation (because you might be reusing the old memory, and this function might be returning to you a zombie handle).
Handles can support assignment to fields "just like a struct" by the automatic creation of a private struct to store such values in upon first assignment. This mechanism is, by default, only enabled if you supply a non-NULL super. But you can enable it even with a NULL super by setting O_SUPER in the handle's object header at any time.
Handles can be used as instances of a class. Typically the class will have the methods that operate on the handle. In this case you will pass the class in super. Instance variables will be supported by the automatic creation of the private struct to hold them (which allows the class to be extended in ICI with additional instance data that is not part of your C code). However, note that these instance variables are not "magic". Your C code does not notice them getting fetched or assigned to (to do that you should make your own ICI data type).
ici_handle_t * ici_handle_probe(void *ptr, ici_str_t *name)
If it exists, return a pointer to the handle corresponding to the C data structure ptr with the ICI type name. If it doesn't exist, return NULL. The handle (if returned) will have been increfed.
This function can be used to probe to see if there is an ICI handle associated with your C data structure in existence, but avoids allocating it if does not exist already (as ici_handle_new() would do). This can be useful if you want to free your C data structure, and need to mark any ICI reference to the data by setting H_CLOSED in the handle's object header.
struct ici_handle { ici_objwsup_t o_head; void *h_ptr; ici_str_t *h_name; void (*h_pre_free)(ici_handle_t *h); }
The C struct which is the ICI handle object. A handle is a generic object that can be used to refer to some C data object. Handles support an (optional) super pointer. Handles are named with an ICI string to give type checking, reporting, and diagnostic support. The handle object provides most of the generic machinery of ICI objects. An optional pre-free function pointer can be supplied to handle cleanup on final collection of the handle.
See ici_handle_new().
unsigned long ici_hash_unique(ici_obj_t *o)
This is a convenience function which can be used directly as the t_hash entry in a type's ici_type_t struction if object of this type are intrinsically unique. That is, the object is one-to-one with the memory allocated to hold it. An object type would be instrinsically unique if you didn't want to support comparison that considered the contents, and/or didn't want to support copying. If you use this function you should almost certainly also be using ici_cmp_unique and ici_copy_simple.
It returns hash based on the address o.
#define ici_incref(o) ...
Increment the object 'o's reference count. References from ordinary machine data objects (ie. variables and stuff, not other objects) are invisible to the garbage collector. These refs must be accounted for if there is a possibility of garbage collection. Note that most routines that make objects (new_*(), copy() etc...) return objects with 1 ref. The caller is expected to ici_decref() it when they attach it into wherever it is going.
int ici_init(void)
Perform basic interpreter setup. Return non-zero on failure, usual conventions.
After calling this the scope stack has a struct for autos on it, and the super of that is for statics. That struct for statics is where global definitions that are likely to be visible to all code tend to get set. All the intrinsic functions for example. It forms the extern scope of any files parsed at the top level.
In systems supporting threads, on exit, the global ICI mutex has been acquired (with ici_enter()).
ici_int_t * ici_int_new(long i)
Return the int object with the value v. The returned object has had its ref count incremented. Returns NULL on error, usual convention. Note that ints are intrinsically atomic, so if the given integer already exists, it will just incref it and return it.
Note, 0 and 1 are available directly as ici_zero and ici_one.
int ici_int_ret(long ret)
Use return ici_int_ret(ret); to return an integer (i.e. a C long) from an intrinsic fuction.
struct ici_int { ici_obj_t o_head; long i_value; }
The C struct that is the ICI int object.
int ici_interface_check(unsigned long mver, unsigned long bver, char const *name)
Check that the seperately compiled module that calls this function has been compiled against a compatible versions of the ICI core that is now trying to load it. An external module should call this like:
if (ici_interface_check(ICI_VER, ICI_BACK_COMPAT_VER, "myname")) return NULL;
As soon as it can on load. ICI_VER and ICI_BACK_COMPAT_VER come from ici.h at the time that module was compiled. This functions compares the values passed from the external modules with the values the core was compiled with, and fails (usual conventions) if there is any incompatibility.
ici_exec_t * ici_leave(void)
Leave code that uses ICI data. ICI data refers to *any* ICI objects or static variables. You would want to call this because you are about to do something that uses a lot of CPU time or blocks for any real time. But you must not even sniff any of ICI's data until after you call ici_enter() again. ici_leave() releases the global ICI mutex that stops ICI threads from simultaneous access to common data. All ICI objects are "common data" because they are shared between threads.
Returns the pointer to the ICI execution context of the current thread. This must be preserved (in a local variable on the stack or some other thread safe location) and passed back to the matching call to ici_enter() you will make some time in the future.
If the current thread is in an ICI level critical section (e.g. the test or body of a watifor) this will have no effect (but should still be matched with a call to ici_enter().
This function never fails.
Note that even ICI implementations without thread support provide this function. In these implemnetations it has no effect.
int ici_main(int argc, char *argv[])
An optional main entry point to the ICI interpreter. ici_main handles a complete interpreter life-cycle based on the given arguments. A command line ICI interpreter is expected to simply pass its given argc and argv on to ici_main then return its return value.
If ici_main2 fails (that is, returns non-zero) it will also set ici_error in the usual ICI manner. However it will have already printed an error message on standard error, so no further action need be taken.
ici_main handles all calls to ici_init() and ici_uninit() within its scope. A program calling ici_main should *not* call ici_init().
argc and argv are as standard for C main functions. For details on the interpretation of the arguments, see documentation on normal command line invocation of the ICI interpreter.
ici_mem_t * ici_mem_new(void *base, size_t length, int accessz, void (*free_func)())
Return a new ICI mem object refering to the memory at address base with length length, which is measured in units of accessz bytes. accessz must be either 1, 2 or 4. If free_func is provided it will be called when the mem object is about to be freed with base as an argument.
Returns NULL on error, usual conventions.
int ici_method(ici_obj_t *inst, ici_str_t *mname, char *types, ...)
Call the method mname of the object inst with simple argument marshalling.
See ici_func() for an explanation of types. Apart from calling a method, this function behaves in the same manner as ici_func().
int ici_method_check(ici_obj_t *o, int tcode)
Return 0 if o (the subject object argument supplied to C implemented methods) is present (indicating a method call was made) and is an object with a super and, (if tcode != TC_NONE) has the given type code. Else return 1 and set error appropriately.
ici_method_t * ici_method_new(ici_obj_t *subject, ici_obj_t *callable)
Returns a new ICI method object that combines the given subject object (typically a struct) with the given callable object (typically a function). A method is also a callable object.
Returns NULL on error, usual conventions.
ici_objwsup_t * ici_module_new(ici_cfunc_t *cf)
Create a new module struct and assign the given cfuncs into it (as in ici_assign_cfuncs()). Returns NULL on error, usual conventions. The returned struct has an incref the caller owns.
void * ici_nalloc(size_t z)
Allocate an object of the given size. Return NULL on failure, usual conventions. The resulting object must be freed with ici_nfree() and only ici_nfree(). Note that ici_nfree() also requires to know the size of the object being freed.
This function is preferable to ici_alloc(). It should be used if you can know the size of the allocation when the free happens so you can call ici_nfree(). If this isn't the case you will have to use ici_alloc().
See also: ICIs allocation functions, ici_talloc(), ici_alloc(), ici_nfree().
ici_file_t * ici_need_stdin(void)
Return the ICI file object that is the current value of the stdin name in the current scope. Else NULL, usual conventions. The file has not increfed (it is referenced from the current scope, until that assumption is broken, it is known to be uncollectable).
ici_file_t * ici_need_stdout(void)
Return the file object that is the current value of the stdout name in the current scope. Else NULL, usual conventions. The file has not increfed (it is referenced from the current scope, until that assumption is broken, it is known to be uncollectable).
void ici_nfree(void *p, size_t z)
Free an object allocated with ici_nalloc(). The size passed here must be exactly the same size passed to ici_nalloc() when the allocation was made. If you don't know the size, you should have called ici_alloc() in the first place.
See also: ICIs allocation functions, ici_nalloc().
#define ici_null_ret() ...
Use return ici_null_ret(); to return a ICI NULL from an intrinsic fuction.
struct ici_obj { char o_tcode; char o_flags; char o_nrefs; char o_leafz; }
This is the universal header of all objects. Each object includes this as its first element. In the real structures associated with each object type the type specific stuff follows
The generic flags that may appear in the lower 4 bits of o_flags are:
char * ici_objname(char p[ICI_OBJNAMEZ], ici_obj_t *o)
Format a human readable version of the object o into the buffer p in less than 30 chars. Returns p. See The error return convention for some examples.
struct ici_objwsup { ici_obj_t o_head; ici_objwsup_t *o_super; }
"Object with super." This is a specialised header for all objects that support a super pointer. All such objects must have the O_SUPER flag set in o_flags and provide the t_fetch_super() and t_assign_super() functions in their type structure. The actual o_super pointer will be NULL if there is no actual super, which is different from O_SUPER being clear (which would mean there could not be a super, ever).
int ici_parse(ici_file_t *f, ici_objwsup_t *s)
Parse the given file f in the given scope s. It is common to call this function with s being ici_vs.a_top[-1], that is, the current scope.
Returns non-zero on error, usual conventions.
int ici_parse_file(char *mname, char *file, ici_ftype_t *ftype)
Parse a file as a new top-level module. This function create new auto and static scopes, and makes the current static scope the exern scope of the new module. This function takes a generic file-like stream. The specific stream is identified by file and the stdio-like access functions required to read it are contained in the structure pointed to by ftype. A name for the module, for use in error messages, is supplied in mname (typically the name of the file).
This function can be used when the source of data to be parsed is not a real file, but some other source like a resource.
The file is closed prior to a successful return, but not a failure.
Return 0 if ok, else -1, usual conventions.
ici_ptr_t * ici_ptr_new(ici_obj_t *a, ici_obj_t *k)
Return a new ICI pointer object. The pointer will point to the element keyed by k in the object a.
The returned object has had it' reference count incremented.
Returns NULL on error, usual conventions.
int ici_register_type(ici_type_t *t)
Register a new ici_type_t structure and return a new small int type code to use in the header of objects of that type. The pointer t passed to this function is retained and assumed to remain valid indefinetly (it is normally a statically initialised structure).
Returns the new type code, or zero on error in which case ici_error has been set.
#define ici_rego(o) ...
Register the object o with the garbage collector. Object that are registered with the garbage collector can get collected. This is typically done after allocaton and initialisation of basic fields when make a new object. Once an object has been registered with the garbage collector, it can *only* be freed by the garbage collector.
(Not all objects are registered with the garabage collector. The main exception is staticly defined objects. For example, the ici_cfunt_t objects that are the ICI objects representing functions coded in C are typically staticly defined and never registered. However there are problems with unregistered objects that reference other objects, so this should be used with caution.)
int ici_ret_no_decref(ici_obj_t *o)
General way out of an intrinsic function returning the object o where the given object has no extra refernce count. Returns 0 indicating no error.
This is suitable for using as a return from an intrinsic function as say:
return ici_ret_no_decref(o);
If the object you are returning has an extra reference which must be decremented as part of the return, use ici_ret_with_decref() (above).
int ici_ret_with_decref(ici_obj_t *o)
General way out of an intrinsic function returning the object o, but the given object has a reference count which must be decref'ed on the way out. Return 0 unless the given o is NULL, in which case it returns 1 with no other action.
This is suitable for using as a return from an intrinsic function as say:
return ici_ret_with_decref(objof(ici_int_new(2)));
(Although see ici_int_ret().) If the object you wish to return does not have an extra reference, use ici_ret_no_decref().
ici_set_t * ici_set_new()
Return a new ICI set object. The returned set has been increfed. Returns NULL on error, usual conventions.
ici_file_t * ici_sopen(char *data, int size, ici_obj_t *ref)
Create an ICI file object that treats the given data (of length size) as a read-only file. If ref is non-NULL it is assumed to be an object that must hang around for this data to stay valid, and the data is used in-place (this is used when reading an ICI string as a file). But if ref is NULL, it is assumed that the data must be copied into a private allocation first. The private allocation will be freed when the file is closed.
Returns NULL on error, usual conventions.
struct ici_src { ici_obj_t s_head; int s_lineno; ici_str_t *s_filename; }
The C struct which is the ICI src object. These are never seen by ICI script code. They are source line markers that are passed to debugger functions to indicate source location.
ici_str_t * ici_str_alloc(int nchars)
Allocate a new string object (single allocation) large enough to hold nchars characters, and register it with the garbage collector. Note: This string is not yet an atom, but must become so as it is *not* mutable.
WARINING: This is *not* the normal way to make a string object. See ici_str_new().
ici_str_t * ici_str_buf_new(int n)
Return a new mutable string (i.e. one with a seperate growable allocation). The initially allocated space is n, but the length is 0 until it has been set by the caller.
The returned string has a reference count of 1 (which is caller is expected to decrement, eventually).
Returns NULL on error, usual conventions.
ici_str_t * ici_str_get_nul_term(char *p)
Make a new atomic immutable string from the given characters.
The returned string has a reference count of 0, unlike ici_str_new_nul_term() which is exactly the same in other respects.
Returns NULL on error, usual conventions.
int ici_str_need_size(ici_str_t *s, int n)
Ensure that the given string has enough allocated memory to hold a string of n characters (and a guard 0 which this routine stores). Grows ths string as necessary. Returns 0 on success, 1 on error, usual conventions. Checks that the string is mutable and not atomic.
ici_str_t * ici_str_new(char *p, int nchars)
Make a new atomic immutable string from the given characters.
Note that the memory allocated to a string is always at least one byte larger than the listed size and the extra byte contains a 0. For when a C string is needed.
The returned string has a reference count of 1 (which is caller is expected to decrement, eventually).
Returns NULL on error, usual conventions.
ici_str_t * ici_str_new_nul_term(char *p)
Make a new atomic immutable string from the given characters.
The returned string has a reference count of 1 (which is caller is expected to decrement, eventually).
Returns NULL on error, usual conventions.
int ici_str_ret(char *str)
Use return ici_str_ret(str); to return a nul terminated string from an intrinsic fuction. The string will be converted into an ICI string.
ici_struct_t * ici_struct_new(void)
Return a new ICI struct object. The returned struct has been increfed. Returns NULL on error, usual conventions.
void ici_struct_unassign(ici_struct_t *s, ici_obj_t *k)
Remove the key k from the ICI struct object s, ignoring super-structs.
#define ici_talloc(t) ...
Allocate an object of the given type t. Return NULL on failure, usual conventions. The resulting object *must* be freed with ici_tfree(). Note that ici_tfree() also requires to know the type of the object being freed.
#define ici_tfree(p, t) ...
Free the object o which was allocated by a call to ici_talloc() with the type t. The object *must* have been allocated with ici_talloc().
struct ici_type { unsigned long (*t_mark)(ici_obj_t *); void (*t_free)(ici_obj_t *); unsigned long (*t_hash)(ici_obj_t *); int (*t_cmp)(ici_obj_t *, ici_obj_t *); ici_obj_t *(*t_copy)(ici_obj_t *); int (*t_assign)(ici_obj_t *, ici_obj_t *, ici_obj_t *); ici_obj_t *(*t_fetch)(ici_obj_t *, ici_obj_t *); char *t_name; void (*t_objname)(ici_obj_t *, char [ICI_OBJNAMEZ]); int (*t_call)(ici_obj_t *, ici_obj_t *); ici_str_t *t_ici_name; int (*t_assign_super)(ici_obj_t *, ici_obj_t *, ici_obj_t *, ici_struct_t *); int (*t_fetch_super)(ici_obj_t *, ici_obj_t *, ici_obj_t **, ici_struct_t *); int (*t_assign_base)(ici_obj_t *, ici_obj_t *, ici_obj_t *); ici_obj_t *(*t_fetch_base)(ici_obj_t *, ici_obj_t *); ici_obj_t *(*t_fetch_method)(ici_obj_t *, ici_obj_t *); void *t_reserved2; /* Must be zero. */ void *t_reserved3; /* Must be zero. */ void *t_reserved4; /* Must be zero. */ }
Every object has a header. In the header the o_tcode (type code) field can be used to index the ici_types[] array to discover the obejct's type structure. This is the type structure.
Implemantations of new types typically declare one of these strutures statically and initialise its members with the functions that determine the nature of the new type. (Actually, most of the time it is only initialised as far as the t_name field. The remainder is mostly for intenal ICI use and should be left zero.)
int ici_typecheck(char *types, ...)
Marshall function arguments in a call from ICI to C. This function may only be called from the implementation of an intrinsic function.
types is a character string. Each character corresponds to an actual argument in the ICI side of the call. Each is checked according to the particular letter, and possibly converted and/or assigned through a corresponing pointer to a C-side data item provided in the vargars argument list to this function.
Any detected type mismatches result in a non-zero return. If all types match, all assignments will be made and zero will be returned.
The key letters that may be used in types, and their meaning, are:
The capitalisation of any of the alphabetic key letters above changes their meaning. The acutal must be an ICI ptr type. The value this pointer points to is taken to be the value which the above descriptions concern themselves with (i.e. in place of the raw actual parameter).
There must be exactly as many actual arguments as key letters unless the last key letter is a *.
Error returns have the usual ICI error conventions.
void ici_uninit(void)
Shut down the interpreter and clean up any allocations. This function is the reverse of ici_init(). It's first action is to call any wrap-up functions registered through ici_atexit()
Calling ici_init() again after calling this hasn't been adequately tested.
This routine currently does not handle shutdown of other threads, either gracefully or ungracefully. They are all left blocked on the global ICI mutex without any help of recovery.
int ici_waitfor(ici_obj_t *o)
Wait for the given object to be signaled. This is the core primitive of the waitfor ICI language construct. However this function only does the actual waiting part. When called, it will release the ICI mutex, and wait for the object o to be signaled by an ici_wakeup call. It will the re-aquire the mutex and return. It should always be assumed that any particular object could be "woken up" for reasons that are not aparent to the waiter. In other words, always check that the condition that necessitates you waiting has really finished.
The caller of this function would use a loop such as:
while (condition-not-met) waitfor(object);
Returns non-zero on error. Usual conventions. Note that this function will always fail in implementations without thread support.
int ici_wakeup(ici_obj_t *o)
Wake up all ICI threads that are waiting for the given object (and thus allow them re-evaluate their wait expression).
void ici_yield(void)
Allow a switch away from, and back to, this ICI thread, otherwise no effect. This allows other ICI threads to run, but by the time this function returns, the ICI mutex has be re-acquired for the current thread. This is the same as as ici_enter(ici_leave()), except it is more efficient when no actual switching was required.
Note that even ICI implementations without thread support provide this function. In these implemnetations it has no effect.
Coming soon.
In order to make sure that the ICI executable loads the debug version you have built (rather than an installed version of the extension module), do this: For Program arguments in the Settings/Debug/General tab, use:
-e "rpush(path, \"Debug\");" -f test.ici
for the Debug build, and:
-e "rpush(path, \"Release\");" -f test.ici
for the Release build.
These are notes for a new chapter. Cover:
The ICI Programming Lanaguage: Contents Previous chapter Next chapter