Loadfuncpp

How to Write External Functions and Libraries
for The Icon Programming Language in C++

Carl Sturtivant, February 2010, version 0.91alpha

Contents

Summary

Since 1996 a new function for Version 9 of Icon could be written in C following a certain interface, and compiled into a shared library, where such is a shared object (.so) under Unix-like operating systems. More recently this has been implemented using dynamically linked libraries (DLLs) under cygwin. The library could then be dynamically loaded by an Icon program calling the built-in function loadfunc which is passed the location and name of the library and the name of the C function desired, and which returns an Icon function that can subsequently be called. A suite of useful examples of this technique is a part of the distribution of Icon.

Writing a significantly complex external function for use by loadfunc is potentially difficult for two reasons. First, an Icon structure (or other value, string, list, set, table, et cetera) referred to solely by variables inside external code could be garbage collected by Icon. Second, working directly with Icon data more complex than numbers, strings and files requires a thorough understanding of the implementation of Icon. The Icon runtime system is implemented in an extension of C that is automatically translated into C. The design of the Icon virtual machine is not object oriented, and contains a great deal of straight-line code. Icon structures are operated upon as combinations of complex linked blocks. Writing code to work directly with such is lengthy, error prone and time consuming.

Loadfuncpp is a tool that makes writing external functions for Icon a relatively simple matter, requiring very little understanding of the implementation of the Icon virtual machine. Loadfuncpp exploits the close compatibility of C and C++ to provide a clean abstract interface to Icon. External functions for Icon are declared with C linkage, and the Icon virtual machine requires no modification to use external functions written using loadfuncpp.

Beginning C++ programmers with programming experience in other languages should have little difficulty with using loadfuncpp. It is not necessary to use templates, exceptions, or RTTI to use loadfuncpp. Little beyond some C experience plus how to define a simple class with virtual and non-virtual member functions is needed to use loadfuncpp. So C programmers with OOP experience but without C++ experience will also find loadfuncpp not difficult to use.

Loadfuncpp makes extensive use of operator overloading and other techniques to provide in C++ essentially the same suite of operations, functions and capabilities that are available to the Icon programmer in Icon. The use of these facilities in C++ is at most an order of magnitude more difficult than the corresponding Icon, and is often much easier than that. These facilities include the ability to write external functions that suspend a sequence of results, and the ability to call an Icon procedure that returns a value, which may in turn call a function that calls Icon recursively in the same fashion.

These facilities also include the ability to create, activate and refresh coexpressions, the ability to write external functions that are new string matching or string analysis functions, and the ability to work with all kinds of Icon data as if they were built-in types. Loadfuncpp also provides garbage collection safety as a matter of course, largely transparently to the C++ programmer. Loadfuncpp also provides a simple way to add new datatypes to Icon using the new external values added to Icon version 9.5 in 2008. These are used extensively by loadfuncpp, and so loadfuncpp cannot be used with versions of Icon prior to 9.5.

Loadfuncpp consists of three shared libraries (iload.so, loadnogpx.so and iloadgpx.so) normally placed in the icon/bin directory (all are actually DLLs under cygwin despite the .so filename extension, and import library called iload.a is used to link to them under cygwin) together with a small amount of Icon in loadfuncpp.icn, compiled into loadfuncpp.u1 and loadfuncpp.u2 (using 'icont -c loadfuncpp.icn') which are normally placed in the icon/lib directory. Loadfuncpp may then be used by an Icon program by adding the line 'link loadfuncpp' which makes the function loadfuncpp available to Icon.

The function loadfuncpp is used in place of loadfunc to dynamically load external functions written to use the loadfuncpp interface. The library containing loadfuncpp is itself loaded by an implicit call to loadfunc. The first call to loadfuncpp loads iload.so (and also loads iloadgpx.so if the Icon installation supports graphics and iloadnogpx.so if not) and replaces loadfuncpp by an external function in iload.so of the same name. This sequence of events makes the C++ interface in iload.so available to all libraries subsequently loaded by Icon through calls of loadfuncpp.

Installation

Installation of Loadfuncpp is in three parts. First ensuring a correct Icon installation. Second placing the loadfuncpp files appropriately. And third, ensuring that environment variables are set appropriately if the default locations of loadfuncpp files are not used.

Correct Icon Installation

You will need to install Icon version 9.5 Loadfuncpp to run. To verify you are running the correct version of Icon, use `icont -V` and `iconx -V`.

Default Placement of Loadfuncpp Files

Loadfuncpp consists of the following files. Starting now (2010/2/8) loadfuncpp is available as an experimental source distribution. I intend to do no further work on it. Use make and examine the following files.

iload.so

C++ part of the loadfuncpp interface to iconx

loadfuncpp.icn

Icon part of the loadfuncpp interface to iconx

iloadgpx.so

C++ interface needed with the graphics build of Icon

iloadnogpx.so

C++ interface needed with the non-graphics build of Icon

loadfuncpp.h

C++ header for writing new external functions

The default installation of these files is as follows. (Here we assume that the directory containing your Icon installation is called icon.) I recommend that you use these locations unless there is a compelling reason not to.

iload.so

icon/bin

iload.a

icon/bin (cygwin only)

loadfuncpp.u1

icon/lib (from loadfuncpp.icn)

loadfuncpp.u2

icon/lib (from loadfuncpp.icn)

iloadgpx.so

icon/bin

iloadnogpx.so

icon/bin

loadfuncpp.h

wherever is convenient to #include in C++ source


Under cygwin only there is one additional file used when linking a dynamic library that uses loadfuncpp. This is the windows import library iload.a, and is most naturally placed in the same directory as iload.so, as it contains the information necessary to link against it.

Alternative Placement of Loadfuncpp Files

Alternatively, you can place iload.so and iloadgpx.so anywhere you please and set the environment variable FPATH to include the directories containing iload.so and iloadgpx.so. FPATH should be a space or colon separated string of locations. You can compile loadfuncpp.icn using `icont -c loadfuncpp.icn` and place the resulting files (loadfuncpp.u1 and loadfuncpp.u2) in any directory and set the environment variable IPATH to include that directory. IPATH should also be a space or colon separated string of locations.

Loadfuncpp Installation Test

Once loadfuncpp is installed, you may test your installation by creating a small new external function and load and call it from Icon. Here's how.

  • Create a new directory, place a copy of loadfuncpp.h in it and work there
  • Edit a new file called (say) hello.cpp to contain the following code

    #include "loadfuncpp.h"
    
    extern "C" int hello(value argv[]) {
        argv[0] = "Hello World";
        return SUCCEEDED;
    }

  • Compile hello.cpp into a shared object hello.so using one of these compiler options
  • Edit a new file called (say) hello.icn to contain the following code and ensure that hello.so is in the same directory

    link loadfuncpp
    
    procedure main()
        hello := loadfuncpp("./hello.so", "hello", 0)
        write( hello() )
    end

  • Compile hello.icn by typing `icont hello.icn` and run it by typing `./hello` and you should get the output Hello World appearing in the console.

Manual

This manual assumes that you have a working installation of Loadfuncpp and Icon as described above. An installation of Icon alone is not sufficient, nor can Loadfuncpp be used with any Icon version prior to 9.5, as it relies upon the presence of external values which are first implemented as a part of that version.

Writing, Loading and Calling a new External Function

A new Icon external function written in C++ takes one of the following forms.

#include "loadfuncpp.h"

extern "C" int fixed_arity(value argv[]) {
    // ... has a fixed number of arguments
    return SUCCEEDED; //or FAILED
}

extern "C" int variable_arity(int argc, value argv[]){
    // ... has a variable number of arguments
    return SUCCEEDED; //or FAILED
}

The C++ type 'value' is an Icon value (called a descriptor), representing null or an integer, real, string, cset, list, table, set, file, procedure, coexpression, record, external value or an Icon variable. When such a function is called from Icon, its arguments are passed in the array argv starting from argv[1], and argv[0] is taken to be the value returned to Icon by the function. In the function variable_arity the number of arguments is also passed in argc. So the following is a one argument external function that returns its only argument.

#include "loadfuncpp.h"

extern "C" int ident(value argv[]) {
    argv[0] = argv[1];
    return SUCCEEDED;
}

The int returned to C++ is a signal to Icon indicating whether the call succeeded or failed. These are represented by the constants SUCCEEDED and FAILED respectively, defined in loadfuncpp.h. However there is also a simple mechanism in loadfuncpp to write external functions that suspend a sequence of values when called in Icon.

Functions compiled into a shared object are loaded into Icon by calls of loadfuncpp. Such calls indicate to Icon whether the loaded function has a variable or a fixed number of arguments, and if the latter, how many. For example the preceding functions might be loaded into Icon as follows if the body of fixed_arity was written to use two arguments.

link loadfuncpp

procedure main()
    fixed := loadfuncpp("./mylib.so", "fixed_arity", 2)
    variadic := loadfuncpp("./mylib.so", "variable_arity")
    #fixed and variadic now contain Icon functions
    #and may be treated like any other such values
end

If the number of arguments is not specified when loading a function of fixed arity then calling the result from Icon will lead to a memory violation. (Similar behavior will likely occur if a function of variable arity is loaded with a specific arity specified, or if too small an arity is specified for a fixed arity function.) Beware!

A relative or absolute path to the shared object may be used as the first argument to loadfuncpp, in which case loadfuncpp will look exactly where specified for it and nowhere else. Alternatively, just the filename of the shared object may be specified, in which case Icon will search FPATH for the file. If FPATH is not set in the environment Icon runs in, then iconx defines FPATH to consist of the current directory followed by the icon/bin directory. If FPATH is set in the environment Icon is run in, then iconx appends the icon/bin directory. In either case FPATH should be a space or colon separated series of directories, with no spaces in their paths. (This restriction will be cleaned up "soon".)

All of the C++ in this manual requires '#include "loadfuncpp.h"' and all of the Icon requires 'link loadfuncpp'. Hereafter this will be assumed implicitly.

Here is an external function of no arguments that returns null, represented in C++ by the constant nullvalue.

extern "C" int dull(value argv[]){
    argv[0] = nullvalue;
    return SUCCEEDED;
}

If this is compiled into the shared object 'dull.so' in the current directory then it might be called by Icon as follows.

dull := loadfuncpp("./dull.so", "dull", 0)
write(image( dull() ))

The value of argv[0] when an external function is called is of type procedure, and is the Icon value representing the external function being called. So failure to assign to argv[0] means that Icon loads a function that returns itself.

The C++ class value is intended to be used primarily in the interface to Icon. Icon structures in variables of this class are not safe from garbage collection. Icon does guarantee that argv[] is garbage collection safe however.

Working with Icon values

Variables of the C++ class safe are intended to hold Icon values with guaranteed garbage collection safety. The interface to Icon is largely available through the class safe. Most computation with Icon values in external functions may be implemented through use of the overloaded operators in using this class, along with its member functions that represent additional Icon operators. Loadfuncpp also provides the Icon keywords and in the namespace 'Icon' provides a C++ variant of each of the built-in functions in Icon.

Assignment and Initialization among safe and value

Assignment of a safe to a safe has the semantics of an Icon assignment. Specifically, if the left operand contains an Icon value that is an Icon variable (i.e. an Icon value used to refer to the storage containing another Icon value so that the latter can be modified) then the assignment modifies the value referred to by that Icon variable, not the C++ variable whose value is the Icon variable.

Assignment is possible among the classes safe and value, and has simple semantics: even values that are Icon variables are copied. Initialization of variables of the class safe is possible from any of safe and value, with the same simple semantics. In both cases the semantics is the same as Icon assignment, except in the case of an Icon variable, which is merely copied, so that the variable assigned or initialized now contains the same Icon variable. This lack of dereferencing is useful if an external function needs to return an Icon variable, in the same way that an Icon procedure may.

A variable of class safe may also be initialized from an array of values as follows.

extern "C" int makelist(int argc, value argv[]){
    safe arglist(argc, argv);
    argv[0] = arglist;
    return SUCCEEDED;
}

Such initialization creates an Icon list containing the values in the array starting from position 1. So the above function called from Icon returns a list of its arguments.

A variable of class safe may be initialized by or assigned a C string, which causes an Icon string that is a copy of the original to be created, so that the original can safely be modified or destroyed later. If such copying is unwanted because the C string is a literal or constant, then the two argument value constructor may be used as follows.

extern "C" int f(value argv[]){
    safe text = value(StringLiteral, "Hello");
    // ...
    return SUCCEEDED;
}

A variable of class safe may also be initialized by or assigned a C++ long or int causing the creation of an Icon integer. Similarly initialization or assignment of a double causes the creation of an Icon real.

Icon operations on variables of class safe

Here is a table of the overloaded operators and member functions implementing Icon operators for the class safe. These are listed with their Icon equivalents, and with a note of any restrictions or extensions. The unary ! operator in Icon is a generator and is supplied through loadfuncpp by other means.

functions of safe for Icon operators

 

unary

Icon equivalent

 

*x

*x

 

~x

~x

 

-x

-x

 

++x

x +:= 1

 

--x

x -:= 1

 

binary

Icon equivalent

 

=

:=

 

+= -= *=

+:= -:= *:=

 

/= %= ^=

/:= %:= ^:=

 

+

+

 

-

-

 

*

*

 

/

/

 

%

%

 

x^y

x^y

 

x | y

x ++ y

 

x & y

x ** y

 

x && y

x -- y

 

x || y

x || y

 

|=

++:=

 

&=

**:=

 

==

===

 

!=

~===

 

< > <= >=

none

The comparison used when sorting

x[y]

x[y]

 

variadic

Icon Equivalent

 

x(...)

x(...)

Icon procedure call

(a,b ...)

[a,b ...]

Variadic list construction

member function

Icon equivalent

 

x.slice(y,z) 

x[y:z]

 

x.apply(y)

x ! y

Apply Icon procedure to arguments

x.listcat(y)

x ||| y

 

x.swap(y)

x :=: y

 

x.create()

create !x

 

x.create(y)

create x ! y

 

x.activate(y)

y@x

y defaults to &null

x.refresh()

^x

 

x.random()

?x

 

x.dereference()

.x

 

Icon Built-in Functions

All of the functions built in to Icon are available in C++ in the namespace 'Icon'. The C++ counterpart of an Icon built-in function returns &null if the original function would have failed. Those functions that are generators have been made to produce a single result. Those functions that are variadic have been made C++ compatible too; with a small number of arguments this can usually safely be ignored. The table below lists each C++ variant of each Icon function that is a generator, along with a comment indicating how it has been modified for C++ compatibility.

Function

 

bal

returns the first result generated only

find

returns the first result generated only

function

returns a list of the results originally generated

key

returns a list of the results originally generated

move

cannot be resumed

tab

cannot be resumed

upto

returns the first result generated only

Here is an example of the use of such Icon built-in functions in a new external function. The following function returns the set of its arguments.

extern "C" int makeset(int argc, value argv[]){
    safe arglist(argc, argv);
    argv[0] = Icon::set(arglist);
    return SUCCEEDED;
}

Icon Keywords

All of the Icon keywords have been made available apart from &cset (to avoid a possible name collision), and &fail. The keywords are implemented through a keyword class with the unary '&' operator overloaded and are used thus in C++, as in the following example.

extern "C" int assignprog(value argv[]){
    safe newname(argv[1]);
    &progname = newname; //Icon assignment semantics
    return FAILED;
}

The preceding function assigns a new value to the keyword &progname, just as in Icon. In all cases a keyword is used with the unary '&' operator, and therefore appears just as in an Icon program. The keywords that are generators in Icon produce a list of values in C++.

Types, Conversions and Errors

A well designed external function will probably do some type checking and conversions of its arguments, and perhaps give a run-time error if they are problematic.

The member function type() in the value class returns one of the following constants indicating its Icon type: Null, Integer, BigInteger, Real, Cset, File, Procedure, Record, List, Set, Table, String, Constructor, Coexpression, External, or Variable. Constructor means a record constructor, and BigInteger is an integer with a binary representation larger than a machine word.

The member functions isNull() and notNull() in the value class each return a boolean indicating whether or not the Icon type is null. The member functions toInteger(), toReal(), toNumeric(), toString() and toCset() in the value class each endeavors to perform a conversion in place to the corresponding type following the same conventions as Icon. Each returns a boolean indicating whether the conversion succeeded. If the conversion failed, then the Icon value remains unchanged. These functions are intended for use with the arguments of an external function supplied to C++ before they are converted to the class safe and the real computation begins. (The use of these functions on the entries in argv[] is garbage-collection safe because Icon protects argv[].) For example to check that we have a string where we would need one as follows.

extern "C" int assignprog(value argv[]){
    if( !argv[1].toString() ) {
        Icon::runerr(103, argv[1]);
        return FAILED; //in case &error is set
    }
    safe newname(argv[1]);
    &progname = newname; //Icon assignment semantics
    return FAILED;
}

The function syserror(const char*) unconditionally and fatally terminates execution with an Icon style error message referring to the point of execution in Icon together with the error message supplied as a C string argument. This nicely complements Icon::runerr.

To avoid problems with C++ conversion/overloading ambiguities, the class safe has been provided with a conversion to the class value only, and no conversions to the types char*, int, long or double. On the other hand, the value class has such conversions and so an explicit conversion to value can be used in many contexts to permit an implicit conversion to a built-in type. See below for details.

The overloaded operators for the class safe defining much of Icon's repertoire in C++ have been defined outside the class safe, with the exception of those such as assignment, subscripting and call that C++ insists be non-static member functions, and almost all such as well as all other member functions have parameters of type safe only. This is so that the wide repertoire of conversions of other types to safe defined by loadfuncpp may be of maximum utility.

Conversions of char*, double, int and long to safe as well as value are defined, those from the built-in types creating copies on the Icon heap. Specifically, the conversion from char* to safe or to value assumes a null terminated C string, and produces a correspondingly copied Icon string.

Conversions of value to long and double have been defined. These behave as expected for Icon integers and reals respectively, but perform no conversions within Icon values (from integer to real or vice-versa).

There is also a conversion from value to char* defined. This does not make a C string, but rather simply produces a pointer to the start of an Icon string, which is not null terminated, and can move in the event of a garbage collection. If null termination is desired, then concatenate the loadfuncpp constant value nullchar before converting to char*, and if a copy outside of Icon is needed, then you will have to explicitly make one. Here is an example.

extern "C" int assignprog(value argv[]){
    if( !argv[1].toString() ) {
        Icon::runerr(103, argv[1]);
        return FAILED; //in case &error is set
    }
    safe newname(argv[1]);
    char* s = value(newname || nullchar); //can move
    char sbuf[100];
    sprintf(sbuf, "%s", s);
    //use the local copy sbuf
    //...
}

The non-member functions bytestointeger and integertobytes are useful to overtly convert to and from Icon integers of any size (i.e. type Integer or BigInteger behind the scenes). Both functions take a value and return a value. In this context Icon strings are considered to be representations of natural numbers. Each character is considered a base 256 digit in the obvious way, and the digits are defined to be in order from most to least significant. The empty string represents zero. bytestointeger takes such a string and produces the corresponding Icon integer. integertobytes takes an Icon integer and produces an Icon string representing its absolute value in the preceding sense. Neither function attempts type conversions, so for meaningful results they must be passed respectively a string value and an integer value.

The non-member functions base64, base64tointeger and base64tostring are useful to overtly convert strings and integers of any size to and from the commonly used base64 encoding. Each function takes a value and returns a value, and none attempts any type conversion of its arguments. base64 may be passed an Icon integer or string and produces a string containing the base64 encoding thereof. The sign of an integer is ignored, so the base64 encoding of its absolute value is produced. base64tointeger may be passed an Icon string that is a strict base64 encoding in which case it returns the corresponding Icon integer, and similarly base64tostring may be passed an Icon string that is a strict base64 encoding in which case it returns the corresponding Icon string. By strict base64 encoding is meant that the string's length is a multiple of four, that the end of the string is a sequence of between zero and two "=" characters (used to pad the file length to a multiple of four when encoding), and apart from that the remaining characters in the string are either lower or upper case letters, or digits, or the characters "/" and "+". Failure to supply a string containing a strict base64 encoding to either function will cause null to be returned.

Variadic Functions and Dynamic List Construction

Some built-in Icon functions take an arbitrary number of arguments. Unfortunately, C++ as of the present standard has no convenient way to define a function with an arbitrary number of arguments of the same type. So variadic functions included in the namespace 'Icon' such as writes are defined in two versions. The first has at most eight arguments, with defaults and glue code to account for fewer being supplied. This takes care of most uses of such functions.

The second uses a single argument of the class variadic, which is a wrapper for an Icon list of the arguments. The operator ',' (comma) has been overloaded so as to combine two locals into a variadic, and to combine a variadic and a safe so as to append the safe's value to the variadic's list. A variadic has a conversion to safe that in effect removes the wrapper, and there are other sundry conversions and overloads of comma. These enable lists to be constructed in place, providing a syntactic equivalent of things like [x,y,z] in Icon, namely (x,y,z) in C++. The second implementation of writes may then be called as writes((x,y,z)). The second pair of parentheses is necessary as comma is not regarded as an operator by C++ when it is in a parameter list. Here is an example of the use of dynamic list construction.

extern "C" int divide(value argv[]){
    safe x(argv[1]), y(argv[2]);
    argv[0] = (x / y, x % y);
    return SUCCEEDED;
}

Calling Icon from C++

The class safe has overloaded the function call operator '()' so that a safe may be called with function call syntax. If the value of the safe is an Icon procedure (or function or record constructor) the effect is to call Icon from C++. There are two kinds of restrictions on these calls.

The first restriction is because C++ requires a specific arity when overloading the function call operator, and has no convenient way to handle an arbitrary number of parameters of the same type. This restriction is the same one affecting the calling of variadic functions, and is overcome in the same way with two implementations. One with a single argument of class variadic necessitating two pairs of parentheses when the call is made, and the other with up to eight arguments and useful for most procedure calls.

The second restriction is because there are three ways Icon can pass control back to a caller: by returning a value, by failing and by suspending a value. However, there is only one way for C++ to receive control back from a call it has made: by a value (possibly void) being returned. For this reason a call of an Icon procedure from C++ will return &null if the procedure fails, and will return rather than suspend if the procedure suspends a value. In either case, the call always returns cleanly with a single value. It is possible to iterate through the values suspended by an Icon procedure in C++ through a different mechanism.

Working with Generators from C++

Generators and the flow of control in Icon have no counterpart in C++. Nevertheless, it is useful to be able to both implement generators for Icon in C++, and iterate through generator sequences produced by Icon in C++, as well as create coexpressions in C++. All these facilities are provided by loadfuncpp.

Writing External Functions that are Generators

Here is an example of a generator function written in C++. It is a C++ implementation of the built-in Icon function seq, without the restriction to machine size integers.

class sequence: public generator {
    safe current, inc;
  public:
    sequence(local start, local increment) {
        current = start - increment;
        inc = increment;
    }
    virtual bool hasNext() { 
        return true; 
    }
    virtual value giveNext() {
        return current += inc;
    }
};

extern "C" int seq2(value argv[]){
    sequence seq(argv[1], argv[2]);
    return seq.generate(argv);
}

This exemplifies all the features of loadfuncpp that enable generator functions to be written. First a C++ version of the generator is written as a class that inherits from the loadfuncpp class generator. Some data members are added to maintain state as generation occurs, and a constructor is written to initialize those data members. Finally the virtual functions hasNext() and giveNext() with exactly the above prototypes are overloaded. The sequence generated by an object of this class is defined to be that produced by repeatedly calling hasNext() to determine if there is a next member of the sequence, and if there is, calling giveNext() to get it.

Now the external function itself simply creates a generator object of the above class, presumably using values passed to it from Icon to initialize that object's state. Then the inherited member function generate is called, passing the original argument array for technical reasons, and the signal it returns is passed back to Icon. The effect of this call is to iterate through the calls of giveNext() while hasNext() returns true, suspending the results produced by each call of giveNext() to Icon. In a nutshell the call to generate suspends the sequence of results produced by the object to Icon. The reason that generate needs to be passed argv is that it needs to send its results to Icon by assigning to argv[0], in just as a single result is passed back.

Calling Icon Procedures that are Generators from C++

Here is an example of how to iterate over the results of a call of an Icon procedure. In the example the procedure to be called and its argument list are presumed to be the arguments passed to the external function, which then computes the sum of the first ten results suspended by the call, or the sum of all the results if less than ten results are computed.

class addup: public iterate {
  public:
    safe total;
    int count;

    addup(): total(0), count(0) {}
	
    virtual void takeNext(const value& x) {
        total += x;
    }
    virtual bool wantNext(const value& x) {
        return ++count <= 10;
    }
};

extern "C" int sum10(value argv[]){
    addup sum;
    sum.every(argv[1], argv[2]);
    argv[0] = sum.total;
    return SUCCEEDED;
}

This exemplifies all the features of loadfuncpp that enable the results of a call to Icon to be iterated over in C++. First a class representing the loop that will iterate over the generator sequence is written, inheriting from the loadfuncpp class iterate. The data members of that class model the variables used in the loop, and the constructor models the initialization of those loop variables. It is convenient that these be public along with everything else; the class could be declared as a struct to achieve this. The two inherited virtual member functions wantNext() and takeNext() with exactly the above prototypes are then overridden. The function wantNext() models the loop condition: it returns true if the loop will process the next result produced by the generator, and false if the loop should be terminated. The function takeNext() models the loop body: it will be passed each result produced by the generator, and may modify the loop variables accordingly.

Now the external function itself simply creates an object of this class, using the constructor to initialize the loop variables, or simply assigning to them directly. This models setup code before the loop proper starts. Then the inherited member function every is called with the generator function and its argument list as arguments to the call. The call of every models executing the loop body by calling the generator function applied to its argument list and repeatedly alternately calling wantNext() to see if the loop should continue and takeNext() to pass the loop body the next result produced by the call to Icon. The loop is terminated either by wantNext() returning false or by the sequence of results generated by the call to Icon coming to an end, whichever occurs first.

Iterating over Exploded Structures in C++

This feature of loadfuncpp enables iteration over the results that would be generated in Icon by an expression of the form !x, with one important difference: if x is a table, then the results iterated over are those that would be produced by the Icon expression key(x). The technique use to perform such an iteration is almost identical to that used to iterate over the results of a call to an Icon procedure. The only difference is that a different inherited member function (bang) is called to run the iteration. Here is an example that sums the first ten elements of a list by quite unnecessarily using this technique.

class addup: public iterate {
  public:
    safe total;
    int count;

    addup(): total(0), count(0) {}

    virtual void takeNext(const value& x) {
        total += x;
    }
    virtual bool wantNext(const value& x) {
        return ++count <= 10;
    }
};

extern "C" int sumlist(value argv[]) {
    addup sum;
    sum.bang(argv[1]);
    argv[0] = sum.total;
    return SUCCEEDED;
}

Working with Coexpressions in C++

There are a handful of member functions in the class safe that provide an essentially complete set of operations on coexpressions. These are straightforward to use and are summarized here.

safe function

Icon equivalent

 

x.create()

create !x

 

x.create(y)

create x!y

 

x.activate(y)

y@x

y defaults to &null

x.refresh()

^x

 

Working with External Values

A new kind of external value is easily defined and used via inheritance from the loadfuncpp class external, which permanently hides the low level machinery of the C specification. Here is an example of such that illustrates the use of the available features.

class Widget: public external {
    long state;
  public:
    Widget(long x): state(x) {}
  	
    virtual value name() {
        return "Widget";
    }  	
    virtual external* copy() {
        return new Widget(state);
    }
    virtual value image() {
        char sbuf[100];
        sprintf(sbuf, "Widget_%ld(%ld)", id, state);
        return value(NewString, sbuf);
    }
    virtual long compare(external* ep) {
        //negative:less, zero:equal, positive:greater
        Widget* wp = (Widget*)ep;
        return this->state - wp->state;
    }  	
};

extern "C" int widget(value argv[]) {
    if( argv[1].type() != Integer ) {
        Icon::runerr(101, argv[1]);
        return FAILED;
    }
    argv[0] = new Widget(argv[1]);
    return SUCCEEDED;
}

extern "C" int widgetint(value argv[]) {
    if( argv[1].type() != External ) {
        Icon::runerr(131, argv[1]);
        return FAILED;
    }
    if( !argv[1].isExternal("Widget") ) {
        Icon::runerr(132, argv[1]);
        return FAILED;
    }
    external* ep = argv[1]; //implied conversion
    Widget* wp = (Widget*)ep; //can move if GC occurs!
    argv[0] = ep->state;
    return SUCCEEDED;
}

The example defines an external function widget that returns an external value to Icon, and an external function widgetint that returns an integer extracted from a Widget to Icon. Of course a real library would have in addition a number of external functions to work with Widgets; these could call additional member functions in the Widget class to do the necessary work.

Overriding the inherited virtual functions name(), copy(), image() and compare() automatically redefines the behavior respectively of the built-in Icon functions type, copy and image and the Icon operators === and ~=== when applied to Widgets, as well as the order for sorting Widgets among themselves in Icon. Such overriding is optional, and the defaults defined in the C specification will apply otherwise. Specifically, the default copy is not to copy but to return the original.

There are automatic conversions to and from external* so that new widgets may be assigned to values or safes, and vice versa when appropriate. The operator new has been overloaded so that an external is allocated by Icon as a part of an Icon external block on the Icon heap. The class external has a protected data member id that contains the serial number of the external value (assigned by Icon when it allocates the external block). Using id may be convenient when overriding the image() member function, as above.

External blocks are assumed by Icon not to contain any Icon descriptors, so do not declare any data members of the classes value or safe when inheriting from external, unless you wish to invite disaster when a garbage collection occurs. Take into account that external blocks may be relocated or garbage collected by Icon. It is not possible to arrange for a destructor or anything else to be called when that occurs. If calling a destructor is essential, then place a pointer to the real object in the external object, and allocate and manage the real object yourself.

Using Icon Records as Objects

A new procedure that is a copy of another with an Icon record bound to it may be created by calling the procedure bindself. The new procedure behaves exactly as the old one, except that a call of the procedure self from within it returns the record attached to it by bindself. This enables a record to contain a procedure that behaves like a method by virtue of being bound to it, as illustrated by the following example.

link loadfuncpp

record object(val, print)

procedure print()
    obj := self() | fail
    write( obj.val )
end

procedure newObject(x)
    obj := object(x) #don't assign print method yet
    #print will be a copy bound to the record it's embedded in
    obj.print := bindself(print, obj)
    return obj 
end

procedure main()
    obj := newObject("Hello")
    obj.print()
end

Note that self fails if called from a procedure that is not bound to a record i.e. one that has not been returned by bindself. It is possible to use bindself to bind a record to a procedure that already has a record bound to it. This simply replaces the bound record, which is useful for copying records that are to be treated as objects in this way, e.g. when copying a prototype object when simulating an object based inheritance scheme.