The ICI Programming Lanaguage: Contents Previous chapter Next chapter

CHAPTER 9 Interfacing with C and C++

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.

Universal rules and conventions

Include files and libraries

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:

ici.h
The ICI include file. Include this as necessary. This file is built specifically for each platform from ICI's internal include files.
icistr-setup.h
A utility include file to assist in defining ICI strings. See Referring to ICI strings from C below.
ici4.{a,lib}
The library file containg the linkage to the dynamically loading library (or the static library if not using dynamic loading). (Suffixes vary with OS.) Link against this when building your program.
ici4.{so,dll}
The dynamic loading library. (Suffixes vary with OS.) Should be somewhere where it will be found at run time.
ici4core.ici, ici4core{1,2,3}.ici

Core language features written in ICI. These need to be somewhere ICI will find them at run time.

If you are writing modules that run from ICI, you will also want an ICI top level command:

ici, ici.exe, or wici.exe
ICI command level executable. These just do argument parsing and invocation of the interpreter on supplied file arguments.

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.

The nature of ICI objects

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.

Garbage collection, ici_incref() and ici_decref()

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.

The error return convention

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;
}
ICI's allocation functions

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.

Common tasks

Writing a simple function that can be called from ICI

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.

Calling an ICI function or method from C

To call an ICI function from C you can use any of the functions:

ici_call()
To call by name.
ici_method()
To invoke a named method of an instance.
ici_func()
To call a callable object (e.g., function or method).
ici_callv()
To call by name with a va_list variable argument list.
ici_funcv()
To call a callable object with a va_list variable argument list, or to invoke a named method of an instance with a va_list variable argument list.

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 */
Making new ICI primitive types

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:

Nothing. If I allocate two of these objects, they can never compare as equal.

We say the objects are "intrinsically unique". You would use the standard functions ici_cmp_unique, ici_copy_simple, and ici_hash_unique for the cmp, copy, and hash implementations of this type.
These field(s) are significant, and they can change over the life of the object.

You should implement your own cmp, copy and hash functions. You must be careful to have your hash function only use aspects of the object the cmp function considers significant. Also, when any of these aspects might be modified, you must check the O_ATOM flag in the object header and reject the attempt if it is set.
These field(s) are significant, but they can't change over the life of the object.

You may chose to make the object intrinsically atomic. In this case your object creation must only return the atomic form. You can use ici_copy_simple for your copy function, but you must still write cmp and hash functions.

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);
}
Writing and compiling a dynamically loading extension module

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)
Referring to ICI strings from C code

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:

  1. Make an include file called icistr.h with your strings, and what you want to call them, formatted as in this example:
/*
 * 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, "&")
  1. Next, in one of your source files, after an include of ici.h, include the special include file icistr-setup.h. That is:
#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().

  1. Next, call init_ici_str() at startup or library load. It returns 1 on error, usual conventions. For example:
object_t *
ici_XXX_library_init(void)
{
    if (init_ici_str())
        return NULL;
    ...
  1. Include your icistr.h file in any source files that accesses the named ICI strings. Access them with either ICIS(fred) or ICISO(fred) which return string_t * and object_t* pointers respectively. For example:
  
#include "ici.h"
#include "icistr.h"
...
    object_t     *o;
    struct_t     *s
...
    o = ici_fetch(s, ICIS(fred));
Accessing ICI array objects from C

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:

ici_array_nels()
To find the number of elements.
ici_array_get()
To get an element at a given integer index.
ici_array_push()
To push an object onto the end of the array.
ici_array_rpush()
To push an object onto the start of the array.
ici_array_pop()
To pop an object from the end of the array.
ici_array_rpop()
To pop and object form the start of the array.
ici_array_gather()
To copy a possibly wrapped run of object pointers to contigous memory.

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:

ici_stk_push_chk(a, n)
Check (force) that the array a has room for n new elements at a_top.

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.

Using ICI independently from multiple threads

To be written.

Summary of ICI's C API

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 *)

 

Detailed description of ICI's C API

ARG
 #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.)

ARGS
 #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.)

CF_ARG1
 #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.)

ICI_BACK_COMPAT_VER
 #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().

ICI_DIR_SEP
 #define ICI_DIR_SEP  ...  

The character which seperates segments in a path on this architecture.

ICI_DLL_EXT
 #define ICI_DLL_EXT  ...  

The string which is the extension of a dynamicly loaded library on this architecture.

ICI_NO_OLD_NAMES
 #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 
ICI_OBJ_SET_TFNZ
 #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.

ICI_PATH_SEP
 #define ICI_PATH_SEP  ...  

The character which seperates directories in a path list on this architecture.

ICI_VER
 #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.

NARGS
 #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.)

hassuper
 #define hassuper(o)  ...  

Test if this object supports a super type. (It may or may not have a super at any particular time).

ici_alimit
 #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))
     ... 
ici_alloc
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().

ici_anext
 #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))
     ... 
ici_argcount
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);
     . . . 
ici_argerror
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);
     . . . 
ici_array_gather
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_array_get
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.

ici_array_nels
ptrdiff_t ici_array_nels(ici_array_t *a)   

Return the number of elements in the array a.

ici_array_new
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_array_pop
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.

ici_array_push
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_array_rpop
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.

ici_array_rpush
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.

ici_assign
 #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.

ici_assign_base
 #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.

ici_assign_cfuncs
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.

ici_assign_fail
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.

ici_astart
 #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))
     ... 
ici_atexit
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_atom
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_atom_probe
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.

ici_call
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.

ici_callv
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.

ici_cfunc_t
 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. 
cf_name
A name for the function. Calls to functions such as ici_assign_cfuncs will use this as the name to use when assigning it into an ICI struct. Apart from that, it is only used in error messages.
cf_func()
The implementation of the function. The formals are not mentioned here deliberately as implementaions will vary in their use of them.
cf_arg1, cf_arg2
Optional additional data items. Sometimes it is useful to write a single C function that masquerades as severl ICI functions - driven by distinguishing data from these two fields. See CF_ARG1().
ici_chkbuf
 #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_class_new
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.

ici_cmp_unique
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_copy_simple
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.

ici_debug_t
 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.

idbg_error()
Called with the current value of ici_error (redundant, for historical reasons) and a source line marker object (see ici_src_t) on an uncaught error. Actually, this is not so useful, because it is currently called after the stack has been unwound. So a user would not be able to see their stack traceback and local context. This behaviour may change in future.
idbg_fncall()
Called with the object being called, the pointer to the first actual argument (see ARGS() and the number of actual arguments just before control is transfered to a callable object (function, method or anything else).
idbg_fnresult()
Called with the object being returned from any call.
idbg_src()
Called each time execution passes into the region of a new source line marker. These typically occur before any of the code generated by a particular line of source.
idbg_watch()
In theory, called when assignments are made. However optimisations in the interpreter have made this difficult to support without performance penalties even when debugging is not enabled. So it is currently disabled. The function remains here pending discovery of a method of achieving it efficiently.
ici_decref
 #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.

ici_def_cfuncs
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.

ici_dont_record_line_nums
 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.

ici_enter
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.

ici_error
 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_eval
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.

ici_fetch
 #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.

ici_fetch_base
 #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_fetch_fail
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.

ici_file_close
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_new
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_new
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.

ici_float_ret
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.

  
ici_float_t
 struct ici_float
 {
     ici_obj_t   o_head;
     double      f_value;
 }  

The C struct that is the ICI float object.

ici_free
void ici_free(void *p)   

Free a block allocated with ici_alloc().

See also: ICIs allocation functions, ici_alloc(), ici_tfree(), ici_nfree().

ici_ftype_t
 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.

ici_func
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:

i
The corresponding argument should be a C long (a pointer to a long in the case of a return value). It will be converted to an ICI int and passed to the function.
f
The corresponding argument should be a C double. (a pointer to a double in the case of a return value). It will be converted to an ICI float and passed to the function.
s
The corresponding argument should be a nul terminated string (a pointer to a char * in the case of a return value). It will be converted to an ICI string and passed to the function.
When a string is returned it is a pointer to the character data of an internal ICI string object. It will only remain valid until the next call to any ICI function.
o
The corresponding argument should be a pointer to an ICI object (a pointer to an object in the case of a return value). It will be passed directly to the ICI function.
When an object is returned it has been ici_incref()ed (that is, it is held against garbage collection).

Returns 0 on success, else 1, in which case ici_error has been set.

See also: ici_callv(), ici_method(), ici_call(), ici_funcv().

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 *).

ici_get_last_errno
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 
ici_get_last_win32_error
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.

ici_handle_method_check
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_new
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_probe
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.

ici_handle_t
 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().

ici_hash_unique
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.

ici_incref
 #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.

ici_init
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_new
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.

ici_int_ret
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.

ici_int_t
 struct ici_int
 {
     ici_obj_t   o_head;
     long        i_value;
 }  

The C struct that is the ICI int object.

ici_interface_check
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_leave
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.

ici_main
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_new
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.

ici_method
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().

ici_method_check
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_new
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_module_new
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.

ici_nalloc
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_need_stdin
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_need_stdout
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).

ici_nfree
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().

ici_null
 #define ici_null  ...  

This ICI NULL object. It is of type (ici_obj_t *).

ici_null_ret
 #define ici_null_ret()  ...  

Use return ici_null_ret(); to return a ICI NULL from an intrinsic fuction.

ici_obj_t
 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

o_tcode
The small integer type code that characterises this object. Standard core types have well known codes identified by the TC_* defines. Other types are registered at run-time and are given the next available code.
This code can be used to index ici_types[] to discover a pointer to the type structure.
o_flags
Some boolean flags. Well known flags that apply to all objects occupy the lower 4 bits of this byte. The upper four bits are available for object specific use. See O_* below.
o_nrefs
A small integer count of the number of references to this object that are *not* otherwise visible to the garbage collector.
o_leafz
If (and only if) this object does not reference any other objects (i.e. its t_mark() function just sets the O_MARK flag), and its memory cost fits in this signed byte (< 127), then its size can be set here to accelerate the marking phase of the garbage collector. Else it must be zero.

The generic flags that may appear in the lower 4 bits of o_flags are:

O_MARK
The garbage collection mark flag.
O_ATOM
Indicates that this object is the read-only atomic form of all objects of the same type with the same value. Any attempt to change an object in a way that would change its value with respect to the t_cmp() function (see ici_type_t) must check for this flag and fail the attempt if it is set.
O_SUPER
This object can support a super.
ici_objname
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.

ici_objwsup_t
 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).

ici_parse
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.

ici_parse_file
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_new
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.

ici_register_type
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.

ici_rego
 #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.)

ici_ret_no_decref
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).

ici_ret_with_decref
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_new
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_sopen
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.

ici_src_t
 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.

s_filename
The name of the source file this source marker is associated with.
s_lineno
The linenumber.
ici_str_alloc
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_buf_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_get_nul_term
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.

ici_str_need_size
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_new
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_new_nul_term
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.

ici_str_ret
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_new
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.

ici_struct_unassign
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.

ici_talloc
 #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.

ici_tfree
 #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().

ici_type_t
 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.)

t_mark(o)
Must sets the O_MARK flag in o->o_flags of this object and all objects referenced by this one which don't already have O_MARK set. Returns the approximate memory cost of this and all other objects it sets the O_MARK of. Typically recurses on all referenced objects which don't already have O_MARK set (this recursion is a potential problem due to the uncontrolled stack depth it can create). This is only used in the marking phase of garbage collection.
The macro ici_mark() calls the t_mark function of the object (based on object type) if the O_MARK flag of the object is clear, else it returns 0. This is the usual interface to an object's mark function.
The mark function implemantation of objects can assume the O_MARK flag of the object they are being invoked on is clear.
t_free(o)
Must free the object o and all associated data, but not other objects which are referenced from it. This is only called from garbage collection. Care should be taken to remember that errors can occur during object creation and that the free function might be asked to free a partially allocated object.
t_cmp(o1, o2)
Must compare o1 and o2 and return 0 if they are the same, else non zero. This similarity is the basis for merging objects into single atomic objects and the implementation of the == operator.
Currently only zero versus non-zero results are significant. However in future versions the t_cmp() function may be generalised to return less than, equal to, or greater than zero depending if o1 is less than, equal to, or greater than o2. New implementations would be wise to adopt this usage now.
Some objects are by nature both unique and intrinsically atomic (for example, objects which are one-to-one with some other allocated data which they alloc when the are created and free when they die). For these objects the existing function ici_cmp_unique() can be used as their implementation of this function.
It is very important in implementing this function not to miss any fields which may otherwise distinguish two obejcts. The cmp, hash and copy operations of an object are all related. It is useful to check that they all regard the same data fields as significant in performing their operation.
t_copy(o)
Must returns a copy of the given object. This is the basis for the implementation of the copy() function. On failure, NULL is returned and error is set. The returned object has been ici_incref'ed. The returned object should cmp() as being equal, but be a distinct object for objects that are not intrinsically atomic.
Intrinsically atomic objects may use the existing function ici_copy_simple() as their implemenation of this function.
Return NULL on failure, usual conventions.
t_hash(o)
Must return an unsigned long hash which is sensitive to the value of the object. Two objects which cmp() equal should return the same hash.
The returned hash is used in a hash table shared by objects of all types. So, somewhat problematically, it is desireable to generate hashes which have good spread and seperation across all objects.
Some objects are by nature both unique and intrinsically atomic (for example, objects which are one-to-one with some other allocated data which they alloc when the are created and free when they die). For these objects the existing function ici_hash_unique() can be used as their implementation of this function.
t_assign(o, k, v)
Must assign to key k of the object o the value v. Return 1 on error, else 0.
The existing function ici_assign_fail() may be used both as the implementation of this function for object types which do not support any assignment, and as a simple method of generating an error for particular assignments which break some rule of the object.
Not that it is not necessarilly wrong for an intrinsically atomic object to support some form of assignment. Only for the modified field to be significant in a t_cmp() operation. Objects which are intrinsically unique and atomic often support assignments.
Return non-zero on failure, usual conventions.
t_fetch(o, k)
Fetch the value of key k of the object o. Return NULL on error.
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.
The existing function ici_fetch_fail() may be used both as the implementation of this function for object types which do not support any assignment, and as a simple method of generating an error for particular fetches which break some rule of the object.
Return NULL on failure, usual conventions.
t_name
The name of this type. Use for the implementation of typeof() and in error messages. But apart from that, type names have no fundamental importance in the langauge and need not even be unique.
t_objname(o, p)
Must place a short (less than 30 chars) human readable representation of the object in the given buffer. This is not intended as a basis for re-parsing or serialisation. It is just for diagnostics and debug. An implementation of t_objname() must not allocate memory or otherwise allow the garbage collector to run. It is often used to generate formatted failure messages after an error has occured, but before cleanup has completed.
t_call(o, s)
Must call the object o. If the object does not support being called, this should be NULL. If s is non-NULL this is a method call and s is the subject object of the call. Return 1 on error, else 0. The environment upon calling this function is the same as that for intrinsic functions. Functions and techniques that can be used in intrinsic function implementations can be used in the implementation of this function. The object being called can be assumed to be on top of the operand stack (i.e. ici_os.a_top[-1])
t_ici_name
A ici_str_t copy of t_name. This is just a cached version so that typeof() doesn't keep re-computing the string.
t_fetch_method
An optional alternative to the basic t_fetch() that will be called (if supplied) when doing a fetch for the purpose of forming a method. This is really only a hack to support COM under Windows. COM allows remote objects to have properties, like object.property, and methods, like object:method(). But without this special hack, we can't tell if a fetch operation is supposed to perform the COM get/set property operation, or return a callable object for a future method call. Most objects will leave this NULL.
Return NULL on failure, usual conventions.
ici_typecheck
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:

o
Any ICI object is required in the ICI actuals, the corresponding vararg must be a pointer to an (ici_obj_t *); which will be set to the actual argument.
h
An ICI handle object. The next available vararg must be an ICI string object. The corresponding ICI argument must be a handle with that name. The next (again) available vararg after that is a pointer to store the (ici_handle_t *) through.
p
An ICI ptr object is required in the actuals, then as for o.
d
An ICI struct object is required in the actuals, then as for o.
a
An ICI array object is required in the actuals, then as for o.
u
An ICI file object is required in the actuals, then as for o.
r
An ICI regexp object is required in the actuals, then as for o.
m
An ICI mem object is required in the actuals, then as for o.
i
An ICI int object is required in the actuals, the value of this int will be stored through the corresponding pointer which must be a (long *).
f
An ICI float object is required in the actuals, the value of this float will be stored through the corresponding pointer which must be a (double *).
n
An ICI float or int object is required in the actuals, the value of this float or int will be stored through the corresponding pointer which must be a (double *).
s
An ICI string object is required in the actuals, the corresponding pointer must be a (char **). A pointer to the raw characters of the string will be stored through this (this will be 0 terminated by virtue of all ICI strings having a gratuitous 0 just past their real end). These characters can be assumed to remain available until control is returned back to ICI because the string is still on the ICI operand stack and can't be collected. Once control has reurned to ICI, they could be collected at any time.
-
The acutal parameter at this position is skipped, but it must be present.
*
All remaining actual parametes are ignored (even if there aren't any).

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.

ici_typeof
 #define ici_typeof(o)  ...  

Return a pointer to the ici_type_t struct of the given object.

ici_uninit
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.

ici_waitfor
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.

ici_wakeup
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).

ici_yield
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.

Building ICI on various platforms

Windows

Coming soon.

Some tips for debugging extension modules in Visual C:

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.

UNIX-like systems

Coming soon.

How it works

These are notes for a new chapter. Cover:

The ICI Programming Lanaguage: Contents Previous chapter Next chapter