The term `Intensionality' is taken from formal logic. It comes from `in tense', meaning: relating to time. The intensional meaning of a term is not some static object, but rather the object as it varies in time. This notion is extended to mean `varying in some context' instead of just time. In *IL* these contexts are: 1, 2 and 3 dimensional space:

- 1-D representing time
- 2-D the domain of images
- 3-D real space

These spaces, or domains, (see section Map domains) can exist in two forms, discrete or continuous. A discrete domain is conceptually linked with a sampled data set for instance a sound file or a digitized image. On a continuous domain any (mathematical) function can be defined. Switching from one kind of domain to the other is done by interpolation or sampling. *IL* is an intensional language in the sense that any data-type can transparently take the form of a function on one or more of the above mentioned domains. In any expression involving terms of a certain type, the value of that term can be dependent on these domains. So by just defining a pixel-type and operators for it, automatically these operators work on complete computer animations, being functions on the 1-D and 2-D domains. By this mechanism unification is established of (at least) texture mapping, image processing, procedural animation and sound synthesis.

*IL* is a pragmatic, interpreted functional language. Pragmatic in the sense that it takes the consequences of implementation issues to the limit. Thus, being interpreted and functional means that functions and strings are the same thing. In other words, quoting and function calling are complementary operations.

Basic control structures are (recursive) function calls and conditional expressions.

The language implementation software is written in C and consists of a kernel and any number of user modules. These modules implement the data-types, with functions and operators working on these. The kernel and a number of modules can be supplied as a library, because adding new types can be done without recompiling the kernel, just by linking the desired modules with the kernel. Adding new types is done in a true object-oriented fashion, with possible overloading of function names and operator symbols. The basic interpreter cycle can be mixed in a generic way with any interface mechanism, such as motif's dispatch event loop, to establish graphical user interfaces and multi-media operability.

The simplest way to begin with the language is just calling the executable from the operating system. *IL* will respond with a prompt looking like:

}

The interpreter will from now on do nothing else but reading a line from the terminal, interpreting it as an expression, evaluating it and printing the result (if not coming across an error). After this it will repeat the cycle by prompting again. Only a few types will necessarily always be built in. In practice there will probably be a lot of them. Anyway, you can be sure there will be integers. So, the following dialog is a possible one:

} 3+7 10 }

More then one expression can be put on one line, delimiting them by semicolons. They will all be evaluated in order, but only the last result will be printed.

} 3+7; 5*6+7*2 44 }

It is important to note that expressions are really the only element of *IL*. So anything that happens will always leave a result. Two other types that will always be there, are floating-point reals and booleans. All the functions and operators you expect for them are (hopefully) implemented, especially the normal (C) form of conditional expression is provided.

} degree(T); sin( 30 ) < 25e-2 ? 7+8 : 3^5 243.0 }

`degree`

is a build in function to set the mode of operation for trigonometric functions. There are several of these mode-setting functions, and they all are called in the same way. They can be called without a parameter to just deliver the current setting of the mode, or they can be called with a new setting (and delivering it as a result value). Another one of these is `eolmode`

. In the default case this mode is on. The end of line is then interpreted as end of expression, i.e. a semicolon is implied. With this mode off, semicolons at the end of any expression are obligatory and expressions can be extended across the end of line. `T`

and `F`

are built-in identifiers for the two boolean values. When degree mode is switched off (by calling `degree(F)`

, the default) all trigonometric functions work in radians.

In the above example `sin`

is called with an integer value, yet it is only defined for real-valued parameters (and complex ones). This is an example of coercion and it plays an important role in the language. More about that later.

It is also clear from the example that the two alternative parts of a conditional expression need not be of the same type (but be careful when assigning the result of an expression to an identifier, as explained in the next section). The condition can, apart from boolean, also be of the integer or real type, with the usual interpretation of 0 being false and anything else being true. The empty list also functions as false in this context. The alternative part of the expression may be omitted, implementing something like a conditional statement without `else' part. This construct still is an expression though, returning an empty list in case the condition evaluates to false.

One important built in function is exit. When called it does what you expect it to do. But make sure you call it, rather then ask what it is:

} exit implicit function } exit() %

Just `exit`

means you evaluate an expression of the type `implicit function,`

of which the value can not be printed, so you only get an indication of its type. Calling this function gives the desired effect.

In the usual way it is possible to give names to results of expressions for later use. These names, or identifiers, are built from letters and digits, the first character being a letter (note that other characters, fi. `_', are not allowed in identifiers, so they are available in operator symbols). The difference between upper and lower case letters is significant and only the first 128 characters are taken into account. Identifiers are not declared. They come into existence when first assigned to and cannot change type afterwards.

Note that assignments are also expressions with as their result the value of their right hand side. This can be significant when coercion is needed to assign to an existing identifier.

} i = 7 7 } i = exp( 1 ) 2.7183 } i 2 }

Here `i`

is initialized with an integer value, so it will be an integer identifier through its existence. When a real value is assigned to it, this value will be coerced to an integer value by truncating it. The result of the assignment expression will however be the unaltered real value.

Above we noted that implicit functions are expressions by themselves, with their own types. This has as a consequence that they too can be assigned.

} quit = exit implicit function } quit() %

Apart from the built in implicit functions, the *IL* programmer has the possibility to write her own. The type for this feature is the string type. Just like booleans and integers, the string type is always implemented. Putting a row of characters between double quotes notates its values.

} "this is a string" this is a string }

Quotes within strings are written by doubling them:

} "this is a string quote: """ this is a string quote: " }

Strings can be interpreted as functions by calling them as usual. That is, by following a string valued expression by parentheses. This means that the control for the interpreter is switched from the terminal input to the function string. This mechanism is of course the same as in most job-control (shell) languages. Here it is consequently exploited in the expression evaluating interpreter paradigm, resulting in a functional kind of language.

} f = "/exp(1)" /exp(1) } f() 0.36788 }

(Note that the slash symbol `/`

functions as a unary operator meaning 1 divided by ..., in accordance with the usual interpretation of the unary minus operator)

These functions can be called with parameters. The parameter evaluation mechanism is always `call by value'. The value of a parameter is obtainable inside the function with one of the operators `%`

or `$`

. They work in exactly the same way. In the following `%`

will be used but can be replaced with `$`

. `%n`

, `n`

being an integer, will evaluate to the nth parameter.

} f = "%1 + %2" %1 + %2 } f( 2, 6 ) 8 }

`%`

is a genuine unary operator, expecting an integer valued expression, so it is not necessary to use it with a plain integer. Moreover, there is the implicit function `pars`

, supplying the current number of parameters the function is called with.

} f = "%pars()" %pars() } f( 6, 7, 8 ) 8 } f(1,2,3,4,5,sinh(2)) 3.6269 }

There are some tricks to pass arguments from inside a function to a function that is called in a nested fashion. This is best explained with a symbolic example. Suppose `f`

is called with arguments as:

f( a, b, c, d, e );

inside `f`

, `g`

is called passing the same arguments:

g( %1, %2, %3, %4, %5 );

instead of this it is possible to write:

g(];

or

g[];

The difference of these two possibilities comes to light when calling with new arguments:

g( x, y ];

is equivalent to

g( x, y, %1, %2, %3, %4, %5 );

--- parameters are added at the start of the argument list.

Whereas

g[ x, y ];

is equivalent to

g( x, y, %3, %4, %5 );

--- parameters at the start of the list are overwritten.

There is also a possibility to add parameters at the end of the argument-list:

g[ x, y );

is equivalent to

g( %1, %2, %3, %4, %5, x, y );

but this construct has the added functionality that it forces a tail-recursive call. (See section Recursion) It will not return! This is therefore not legal as term in an expression.

There is the notion of local identifiers in the language. Identifiers can be introduced in a function by assigning to them. They will not conflict with global identifiers. Global identifiers can be referred to however, when they are not used as local identifiers. This goes for local identifiers on a higher level too, when function calls occur in a nested way. When identifiers are evaluated, they will be looked for in succeeding lower levels, until the global level. In the following example the implicit function `print`

is used. It invokes the same method as the interpreter does when printing the value of an expression in an interactive session.

} degree(F); pi = 4*atan(1); 3.1416 } f = "print(pi); a = 6; g(); a" print(pi); a = 6; g(); a } g = "pi = F; print( pi ); a = exp(1); print( a )" pi = F; print( pi ); a = exp(1); print( a ) } f() 3.1416 F 2.7183 6 } pi 3.1416 }

There is the possibility to assign explicitly to a global identifier from within any function by preceding the identifier with `::`

or ```

} g = "::pi = 180" ::pi = 180 } g() 180 } pi 180.0

Another way to get this effect is by using the implicit function `assign`

. This function takes two parameters, the first being a string. This string is interpreted as a global identifier and the value of the second parameter is assigned to it.

} assign( "blob", 666 );

`define`

works the same as `assign`

, but protects the identifier (as assignment with :=).

The use of `assign`

and `define`

is primarily intended for the creation of user defined operators, because the symbol string has not to fulfil the demands required by the identifier syntax. The following example demonstrates this feature.

} fac = "n = %1; n ? n*fac(n-1) : 1" n = %1; n ? n*fac(n-1) : 1 } assign( "~", operator( fac ) ) prio 0 n = %1; n ? n*fac(n-1) : 1 } ~6 720 }

The implicit function `operator`

creates an item of the type `Operator`

. The parameter is the user-defined function associated with the operator. There is an optional second parameter denoting the priority of the operator, useful in the case of binary operators. It is possible to overload implicit operator symbols (like `+`

) in this way. In this case it is not possible to overwrite the built-in priority. The interpreter will use the user-defined operator if the implicit operator is not applicable in the type context.

In the following example a pseudo array mechanism is implemented. The printing of results is controlled by the echomode. `#`

is string-concatenation. It is overloaded to create identifiers with an index. `getid`

is the complementary function of `assign.`

Also a simple `for`

statement is implemented.

} echo(F) } assign( "#", operator( "%1 # sprintf( ""%4.4d"", %2 )" ) ) } print( "abc"#5 ) abc0005 } arrayass = "assign( %1#%2, %3 )" } arrayval = "getid( %1#%2 )" } for = "%1 > %2 ? return() : 0; }" %3(%1); for( %1+1, %2, %3 )" } rootable = "rootable" } rootass = "assign( rootable # %1, sqrt( %1 ) )" } for( 1,10, rootass ) } print( arrayval(rootable,5) ) 2.2361 }

Note the change of the prompt to `}"`

when entering multi-line strings. The `sprintf`

function is a partial copy of the C function. An alternative to the `getid`

use here, because it uses correct identifier syntax, would be:

} arrayval = "(%1#%2)()"

The use of the function `return`

should be obvious. In this case it makes the function to return the integer value 0. An alternative for this can be given as a parameter to return.

We already encountered some uses of recursion in the definition of `fac`

and `for`

. A better `for`

would be:

} for = "%1 > %2 ? pars() >= 4 ? %4 : 0 : for( %1+1, %2, %3, %3(%1) )"

or

} cfor = %1 < %2 ? cfor( %1+1, %2, %3, %3(%1) ) : pars() > 3 ? %4 : 0"

returning the value of the last iteration.

This kind of recursion is so-called tail-recursion. The interpreter knows the recursive call is the last one, so does not stack any items unnecessarily. The number of iterations will not be limited by stack-overflow. One has to be careful however when one wants local identifiers to be known to higher levels. In the case of tail-recursion local identifiers will be removed. So sometimes tail-recursion has to be deliberately prevented.

} f = "n=%1; g()" } g = "n*(n-1)" } print( f( 6 ) ) error 127: identifier unknown n } f = "n=%1; m=g(); m" } print( f( 6 ) ) 30 }

An identifier can be protected from assigning to it (making it effectively a constant) by calling `protect`

.

} pi = arg( -1 ); e = exp( 1 ); } protect( "pi", "e" ); } pi = 666; error 275: assignment to protected variable pi= }

The effect can be canceled with `unprotect`

. Protection can also be obtained with the definition symbol `:= `

.

} e := exp(1);

is equivalent to

} e = exp(1); protect( "e" );

The existence of a global identifier can be tested with `exists`

;

} echo( T ); exists( "dfi" ) F } dfi = 469; exists( "dfi" ) T }

Lines beginning with /* are considered comment (outside strings).

} /* schuitje varen, theetje drinken }

Instead of typing expressions at the terminal it must of course be possible to write programs in text files. The interpreter can be made to read such a file by calling the function `read`

with the filename as parameter.

} read( "flofbla" )

The file extension .il is added to the filename and the file is sought for, first in the current directory, then in the directory designated by environment or option. The read call will properly return and can be nested. Executing the function `exitfile`

can terminate reading a file. When starting *IL*, a number of filenames can be given as parameters. These will be read before starting an interactive session.

There is a module concept in *IL*. Such a module is a piece of *IL* code between calls of `startmodule`

and `endmodule`

. Global identifiers in such a module will only be known inside that module, except when they are explicitly exported. Likewise external identifiers can be imported. Modules are internally identified by an integer. In the default case `startmodule`

will take the next available integer to create a new module. It is however possible to give an explicit number as parameter. In this way it is possible to `reopen' an already ended module. Both functions return the number of the module.

} modnr = startmodule(); } init = "::sum=0"; } add = "::sum=sum+%1"; } export( "init", "add" ); } endmodule(); } init(); } add( 7 ); add( 8 ); } sum error 127: identifier unknown sum } startmodule( modnr ) } getsum = "sum" } export( "getsum" ) } endmodule() } print( getsum() ) 15 }

There are some things all types have in common. They have a name, an integer identification, a size and a number of methods applicable to them. `types`

prints a list of all implemented `types`

. When creating a new type in a C-module, the name is given to the kernel. Let that name be "Mine". The kernel then creates two *IL* identifiers. `Minetyp`

is an integer identifier with as value the internal identification. `Mine`

is an implicit function, which converts any parameter to the type `Mine`

if at all possible.

} Inttyp 5 } Int( pi ) 3 }

The integer type identification is very useful in combination with `typeof`

, a function that returns this integer for any parameter

} typeof( "abc" ) == Stringtyp T }

The name of a type can be obtained with `typename`

.

} typename( 6 ) Real }

One of the methods that are defined when a new type is called into existence is the one that is invoked by a call of `print`

. Another one is to copy a value of the type. This is important in the case of dynamic types. These are types that are internally referred to by a pointer, and that have a reference count. The only such type we have encountered thus far is the string-type. Simple assignments of these types copy a pointer (pointing to a value). A copy of the value itself is created with the operator `&`

. For some dynamic types there are two different kinds of copy. The so called deep-copy, with operator `&&`

, recursively copies sub elements of a value. We will see a use for this feature with lists. (See section List)

} l = [1,2,3); list } m = l list } m == l T } m = &l list } m == l F }

Conversions from one type to another are done by methods called coercions. A type together with its coercions can be regarded as a class. Which method is invoked for an implicit function or operator is determined by the type context. So all parameters have their influence on the choice of method instead of one parameter determining the class. If there is no applicable method for the current context, the kernel tries to create one by coercing the parameters. In this way inheritance is established. A simple example is adding two numbers. There is an adding method for two integers and one for two reals. When adding an integer and a real, the integer is converted to a real and the real adding method is applied. With each coercion a scheme-number is associated. Likewise each function or operator belongs to such a scheme. Only matching coercions are sought for when looking for inheritance. The default scheme is 0. In the list printed by `types`

, for each type one sees a line with:

- the type identification number
- the type name, followed by a * for dynamic types
- the size of the type (for dynamic types always 4, being the size of a pointer)
- a list of typenumbers coercible to this one, possibly extended with the scheme-number if not zero

For all types the operators `==`

and `!=`

are defined, returning a boolean value. The normal boolean operators are defined as:

`&`

- logical and
`|`

- logical or
`|^`

- logical exclusive or
`!`

- logical not (unary operator)

```
```

There is a generic list-type build in the language. The standard means to implement this type is the constant `nil`

representing the empty list and the functions `cons`

, `head`

and `tail`

with their usual interpretation. All types can be mixed as list elements.

Instead of

} l = cons( T, cons( 2, cons( 3.0, nil ) ) );

there is the following construct:

} l = [ T, 2, 3.0 );

} l'1 2 }

So `l'n`

is equivalent to applying `n`

times `tail`

followed by `head`

. The same, but without the final `head`

, is accomplished with `@`

.

} l@1 list }

The value of a list is not printed, just the type indication. One can define a function `lprint`

with the following program fragment:

startmodule(); prlelt = "print( lst'%1 )"; import( "cfor" ); lprint = "lst = %1; cfor( 0, length( lst ), prlelt ); lst"; export( "lprint" ); endmodule();

The implicit function `length`

gives the number of elements of a list. With `lput`

the head of a list can be replaced with any other item. In combination with `@`

, any element can be replaced.

} lput( l@1, 5.0 ); } lprint( l ); T 5.0 3.0 }

Lists are dynamic. After an assignment

} m = l; } lput( m, F )

both l and m are the same changed list.

Here the difference of simple copy `&`

and deep copy `&&`

is significant. A simple copy is a new list with references to the same elements as the old list (for dynamic elements, for instance lists). A deep copy gives you recursively deep-copied elements.

There is a concatenation operator `#`

.

There are complementary operators for removing and inserting sublists:

} l > n

remove a sublist of length `n`

from `l`

, starting with `l'1`

. The result of the expression is the removed sublist.

} l < lx

insert list `lx`

in list `l`

, `lx'0`

becoming `l'1`

.

These types are `Int`

, `Real`

, `Complex`

and `Quat`

. There are standard coercions in the direction:

`Int`

--> `Real`

--> `Complex`

--> `Quat`

and under scheme 1 the other way.

`Int`

and `Real`

have their standard notations for constants build in the scanner of the kernel. For `Complex`

and `Quat`

there exist functions to define new values by `summing up' the real constituents.

The usual notations for decimal, octal and hexadecimal integers are available.

} 5 5 } 0777 511 } 0x4d 77

All the relational operators are defined:

`> < >= <= `

as well as the arithmetical

`- `

(unary)

`+ - * / % `

and logical (bitwise) operators

`! & | |^ `

The function `abs`

is defined for integers.

The binary operators

`>? <? `

give the maximum and minimum of two integers.

`Real`

s are coercible to `Int`

s under scheme 1. So this coercion hardly ever occurs automatically, but it does when calling `Int`

. The same goes for complex values and quaternions.

The same relational and arithmetical operators are defined as for `Int`

. (`%`

implements the `drem' function). On top of these there are the power operator `^`

and unary `/`

. The following functions are defined for both real and complex values:

`abs, sqr, sqrt, exp, log, sin, cos, tan, asin, acos, atan,`

`sinh, cosh, tanh, asinh, acosh, atanh, sec, cosec, asec, acosec,`

`sech, cosech, asech, acosech`

.

They will return complex values for real arguments whenever necessary.

These are some more functions for reals:

`sinc, vsin, vcos, hypot, atan2, vatan2, J`

.

`sinc`

: i`sin(pi*x) / (pi*x)`

The trigonometric functions starting with a `v`

are normalized (with regard to their domain as well as to their range) to the interval [0,1], for ease of use with (2-D) texture maps. `J`

is equivalent to the C bessel-function `'jn'`

. `hypot`

works for two or three arguments.

Complex values can be defined from a pair of reals with `cmplx`

or `pcmplx`

, the latter for representation in polar form. The following functions return the obvious real constituents:

`re, im, abs, arg`

.

There is the special complex function `conj`

for the complex conjugate. A special use of complex values is their interpretation as range type for texture bump maps.

Quaternions are arithmetically fully implemented. They can be defined with the function `quat`

with either 4 `Real`

s as arguments, or 1 `Real`

and 1 `Real3`

. They are implemented with a 3-D imaginary part. There is a normalization function `norm`

to bring them on the `unit sphere' for the standard interpretation as rotations. There are coercions from and to transformation matrices. The following operators and functions are defined:

`- /`

(unary)

`+ - * / ^`

`abs, re, im, conj, sqr, exp, log`

Already a lot has been said about strings. There are some functions and operators worth mentioning:

`#`

- concatenation.
`'`

- index operator, returning a 1-character string
`@`

- returning indexed string-tail
`length`

`edit`

- invokes a standard text editor to change a string. default
`vi`

`editor`

- change the editor
`funof`

- returns the functions string of a user defined operator
`priof`

- returns the priority

```
```

The basic coercions `Int`

and `String`

are overloaded to handle ASCII-character codes.

As base classes for the geometrical types and the intensional domains the following types are defined: (with their `C like' equivalent)

`Int2`

- == int[2]
`Int3`

- == int[3]
`Real2`

- == real[2]
`Real3`

- == real[3]
`Real4`

- == real[4]
`Intl2`

- == int[2][2]
`Intl3`

- == int[2][3]
`Reall2`

- == real[2][2]
`Reall3`

- == real[2][3]

```
```

They all have basic definition functions with the same names but starting with lower case letters:

`int2, int3, real2, real3, real4, intl2, intl3, reall2, reall3`

.

They all have the index operator `'`

} x3 = real3( 1, 2, 3 ) 1.0 2.0 3.0 } x3'2 3.0 }

The single indexed types all have:

unary `-`

binary `+ -`

scalar `* /`

The rest only have unary `-`

There are the following scheme 0 coercions:

`Int2`

--> `Real2`

`Int3`

--> `Real3`

`Real2`

--> `Complex`

and all of these reversed in scheme 1.

The type `Real2`

is taken to represent 2-D points. The geometrical type of 2-D lines is `Line2`

. Their internal representation can be:

- either a starting point and a directional vector,
- or a 3-D `point', which is a normalized homogeneous `plane' equation.

There are coercions to and from `Reall2`

and `Real3`

. There is the type `Transform2`

, which is a 3*3 matrix, representing 2-D transformations.

Functions:

`transf`

- takes 9
`Real`

s or 6`Real`

s (homogeneous) or 3`Real2`

s to make a`Transform2`

`linepnt`

- takes a
`Line2`

(line) and a`Real`

to return a 2-D point on the line.

`linepnt( l, 0.0 )`

is equivalent to`l'0`

`linepnt( l, 1.0 )`

is equivalent to`l'0 + l'1`

`hypot`

`hypot( r2 )`

equiv`hypot( r2'0, r2'1 )`

`norm`

- normalizes points and lines.
`det`

- gives determinant of matrix
`translate`

- takes 2
`Real`

s or 1`Real2`

to make a translation matrix `scale`

- takes 1
`Real`

and either a point or a line to make a scaling matrix `rotate`

- takes a
`Real`

and a point to make a rotation matrix (takes degree mode into account) `skew`

- takes a point and a line to make a skewing matrix. This transformation leaves the point at its place, shifting the starting point of the line along its direction and having determinant 1.0
`mirror`

- takes a point or a line to make a mirror transformation.

```
```

These are the operators:

`*`

- dot-product of two points
`^`

- angle of two lines
`~`

- distance of two points or a point and a line
`|+`

- construct a line from two points
`||`

- construct a line through a point parallel to a line
`|_`

- construct a line through a point and perpendicular to a line
`|*`

- construct the intersection point of two lines
`*`

- takes a
`Transform2`

and a`Int2`

,`Real2`

or`Line2`

and returns the transformed item, or multiplies two matrices. `/`

- (unary) inverts matrix.

```
```

Here a plane type `Plane3`

is introduced. It is based on `Real4`

. Also the type `Transform`

is introduced (plain `Transform`

is 3-D). All the 2-D functions have their 3-D counterpart with the following notes.

`rotate`

- needs a line parameter rather then a point.
`mirror`

- there are 3 mirror possibilities (point, line, plane)
`**`

- cross-product of two points
`^`

- defined for two lines, a line and a plane or two planes
`~`

- defined for two points, a point and a line, a point and a plane or two lines.
`|+`

- construct a line through two points

a plane through a point and a line `||`

- a line through a point parallel to a line

a plane through a point parallel to a plane `|_`

- a line through a point perpendicular to a plane

a plane through a point perpendicular to a line

a line perpendicular to --, and crossing two lines `|*`

- a point as intersection of a line and a plane

a line as intersection of two planes `*`

- again for all matrix multiplications.
`^`

- takes a
`Transform`

to the power of a real. Of special interest to perform `inbetweening' on the transformation level. It is a complex algorithm that does not try to take a transformation apart in possible constituents but instead performs pure matrix algebra.

```
```

As mentioned before, special care has been taken to be able to mix matrices and quaternions by defining the appropriate coercions.

scheme 0: `Quat`

--> `Transform`

scheme 1: both ways

matrix multiplication resides under scheme 1, also because of some technicalities involved with the above mentioned power algorithm.

Besides as geometrical coordinates, Real3 can also be interpreted as representing colors. In this respect there are a couple of functions:

`rgbtohsv`

`hsvtorgb`

`nyuvtorgb`

The first two are obvious, except that they incorporate a correction to avoid the usual first order discontinuities in hue transitions. The third one interprets its argument to lie in a normalized YUV space. Normalized in the sense that each value in the interval ( [0,1], [-1,1], [-1,1] ) represents a color.

The pixel type is based on `Real4`

, interpreted as consisting of red, green, blue and `alpha'. Coercions to -- and from `Real3`

(colors) and `Real`

exist. The following operators implement the usual compositing algebra:

`pix1 +~ pix2`

`pix1 -~ pix2`

- pixel addition and subtraction
`pix1 <- pix2`

- pix2 over pix1, normal compositing
`pix1 /~ a2`

- pix1 in a2, part of pix1 inside a2, a2 probably
`pix2'3`

) `pix1 %~ a2`

- pix1 out a2, part of pix1 outside a2
`d <~ pix`

- multiply only rgb, `darken'
`d >~ pix`

- multiply alpha, `opaque'

```
```

Map is the type of intensionally defined entities. They are functions of one or more of the standard domains to any one of the static *IL* types. They are normally not evaluated in the interpreter context, but when invoked in some kind of rendering operation, like ray-tracing, image- processing or sound-synthesis. An exception to this is the function `evalmap`

, which explicitly evaluates a map after setting the domain with `setmapdom`

.

All functions and operators for a certain type are automatically applicable to maps of that type, delivering maps as result. So the syntax for creating maps is exactly the same as for normal expressions. Only when one of the arguments is a map, the result is a map, which is a compiled version of the expression instead of the evaluated result.

The type a map evaluates to is obtainable with `typeofmap`

.

There are 3 continuous map domains, one for each of the relevant dimensionalities. They are available through implicit functions:

Some possible interpretations are:

`m1()`

- global time
`m2()`

- normalized object surface for texturing
`m3()`

- 3-D space for solid textures

```
```

The following is an example that will produce a chessboard pattern when used as a texture map:

d = m2(); chs = Int( 8*d'0 ) + Int( 8*d'1 ) & 1 ? 0.0 : 1.0;

Now, `chs`

is directly usable as intensity parameter for an attribute in a solid model. In fact, using scalar multiplication of `Real2`

and because integers are converted to real when used as intensity, it is also possible to write:

d = 8*m2(); chs = Int( d'0 ) + Int( d'1 ) & 1;

As 1-dimensional domains are normally used as time-representation, there are 3 discrete versions of them, reflecting the different uses of `sample-time'. These are:

`m1da()`

- animation time, frame rate
`m1dc()`

- audio control time, the rate of sound parameters
`m1ds()`

- audio sample rate

```
```

And for the 2- and 3-dimensional cases:

`m2d()`

- images
`m3d()`

- volume data

```
```

Most of the time, when mixing discrete and continuous domains, the system does what you expect it to do, namely, converting to and from the relevant domains. To this end there are some quantities, which exists globally as well as associated with individual maps. These are (and can be set or get with):

`dbnd2`

`Int2`

: bounds of`m2d`

in relation to`m2`

`dbnd3`

`Int3`

: bounds of`m3d`

in relation to`m3`

`frqa`

`Real`

: framerate,`m1da`

in relation to`m1`

, default 25`frqc`

`Real`

: audio control rate,`m1dc`

in relation to`m1`

, default 100`frqs`

`Real`

: audio sample rate,`m1ds`

in relation to`m1`

, default 44100

```
```

So when, for instance, you multiply a (discrete) image with a (continuous) noise, the noise function is automatically sampled (evaluated) at points corresponding with the image pixels, converting the image space to the square unit interval.

Apart from this there is a complete generic way to switch domains with `trfmap`

.

trfmap( m, d )

The result is the map `m`

, but with `d`

as new domain, where `d`

can be a map itself. The following example is a function to convert a 2-D map to a 3-D solid map with a `ball-projection':

ballprj = "m = m3(); x = m'0; y = m'1; z = m'2; u = vatan2( y, x ); r = hypot( y, x ); v = 2 * vatan2( r, -z ); trfmap( %1, real2( u, v ) )";

`%1`

is supposed to be a 2-D map, dependent on `m2()`

. So, the second parameter of `trfmap`

is of type `Real2`

. This expression is the new domain. It is solely dependent on `m3`

, so will be the result map.

When maps on discrete domains are evaluated on continuous domains (perhaps as a automatic step in a conversion to another discrete domain), the values are to be interpolated. The way this is done can be set with `polmode`

.

`0`

- constant interpolation (no interpolation)
`1`

- linear interpolation

```
```

For the linear case the function `linpol`

must be implemented for the relevant types as a function that interpolates between two values of that type according to a third real argument in [0,1]:

} linpol( 4.2, 5.7, 0.4 ) 4.8 }

It is implemented for:

`Real, Real2, Real3, Pixel, Complex, Quat, Transform`

Already mentioned was the function `evalmap`

. It evaluates a map. There are global domain values to form a context for this evaluation. These can be set with `setmapdom`

. Which domain is meant, is determined by the type of the argument, on the understanding that an integer is taken to relate to `m1ds`

, setting the other 1-D discrete domains according to the relevant frequencies.

} setmapdom( real3( 5.0, 6.7, 88.9 ) ); 5.0 6.7 88.9 } evalmap( 6*m3() ); 30.0 40.2 533.4 } setmapdom( 10000 ); 5 } evalmap( m1dc() ); 22 }

Maps can be regarded as trees, with maps as nodes and constants as leafs. The number of branches at a node is the result of the function `pars`

. The nth branch of a map can be obtained with the `@`

operator. The branch can be changed with the `setmap`

function. The general cast-function Map makes a constant valued map of any value.

} m = m2()'0 + 3; map } pars( m ) 2 } pars( m@0 ) 2 } m@1 3.0 } setmap( m, 5.0, 1 ) map } m@1 5.0 } setmap( m, m2()'1, 1 ) map } m@1 map } m@1@1 1 } m = Map( 6 ); map } /* default index of setmap == 0 } setmap( m, 8 ) map } evalmap( m ) 8

To check for equivalence of the top-node of maps the function `islikemap`

is available.

} islikemap( 3*m2(), m1()*dnoise(m2()) ) T /* both are multiplication of scalar and 2-D vector

The domain dependency of a map can be obtained as a bit pattern with `idep`

. The different dependency values are built-in constants:

} dep1c 1 } dep2c 2 } dep3c 4 } dep1da 256 } dep1dc 512 } dep1ds 1024 } dep2d 2048 } dep3d 4096 } idep( m1ds() * m2() ) 1026 }

The implemented random functions are based on the `rand48' package from the standard C library. The seed can be set (or obtained) with `startrand`

in the following ways:

} startrand() 123 4567 890 } startrand( int3( 654, 765, 56766 ) ) 654 765 56766 } startrand( 654, 9966, 12549 ) 654 9966 12549 } startrand( 555555555 ) 13070 6883 8477 } /* 13070 is a default value in the last case }

The following random functions are implemented:

`random()`

- a random integer in the total range, positive or negative
`random( b )`

- an integer in the range [ 0, b )
`random( l, u )`

- an integer from the interval [ l, u )
`frandom()`

- a real from [ 0, 1 )
`frandom( x )`

- a real from [ 0, x )
`frandom( x, y )`

- a real from [ x, y )
`normrand()`

- a real with normal distribution, mean 0.0, standard deviation 1.0
`normrand( a )`

- deviation a
`normrand( s, a )`

- mean s

```
```

There are the usual process times available with `usrtime`

and `systime`

. They give their result in seconds as a real. Just `time`

is a real-time function, default starting with startup time, but a time point can be set with `settime`

.

} usrtime() 0.17186 } systime() 0.07421 } time() 149.67 } settime( 1234 ) 1234.0 } time() 1240.8

The well-known fractal landscape `midpoint displacement' algorithm is implemented to create 2-D maps. The size of the defining grid can be set with `fracsize`

. Its argument is either an integer, defining the size of a square, or an `Int2`

, defining a rectangle. In either case the size is rounded to powers of 2.

} fracsize(100 ) 128 128 } fracsize( int2( 300, 900 ) ) 256 1024 }

The fracsize becomes the `dbnd2`

of the resulting map, which is `m2d`

dependent. The maptype can be either real or complex (bumpmap!).

} rfract() map } bfract() map }

A parameter h can be given, defining the `fractal roughness' as usual. The default is 0.75. The normal range is in [0,1]. The resulting fractal dimension is 3-h. This parameter may be a map!!

The famous `Perlin noise' is implemented for 1, 2 and 3 dimensions. They really are normal functions, creating maps when called with the relevant domain. An optional second real parameter is an overall scaling factor. (For ease of use. The same effect could be obtained by multiplying the domain.) An optional third parameter is like an integer seed for the creation of independent noise functions. For each dimension there are three noises:

`noise`

- values in the range [0,1]
`bnoise`

- values in the range [-1,1]
`dnoise`

- directional vector field, with the type of the domain.

```
```

So, normal 3-D solid noise is obtained with:

} ns = noise( m3() );

and a higher frequency 2-D bump noise with:

} bns2 = cmplx( bnoise( m2(), 0.1, 0 ), bnoise( m2(), 0.1, 1 ) );

and a 3-D vector valued bump map:

} vns = dnoise( m3() );

For all dimensions it is possible to generate a cloud of (particle) coordinates according to a real valued map, interpreted as a density function. A simulated poisson process generates the coordinates. The function result is a list of values in the particular domain, which is determined by the type of the limiting interval and the dependency of the density map.

} pl = density( 100*noise( m3() ), real3(0,0,0) |+ real3(1,1,1) ) list } length( pl ) 40 } pl'0 0.06661 0.39748 0.32393 } pl = density( 100*noise( m2() ), real2(0,0) |+ real2(1,1) ) list } length( pl ) 53 } pl'0 0.06812 0.20598 } pl = density( 100*noise( m1() ), real2( 0, 1 ) ) list } length( pl ) 66 } pl'0 0.00253 }

Real valued maps can be dithered. This dithering is meant for `artistic' effects, it is not to be confused with means for exploiting limited display resources. The method is stochastic dithering, with a parameterized number of levels (default 2).

} m = dither( rfract() ); } n = dither( rfract(), 5 ); } s = dither( rfract(), 2+6*noise(m2(),0.1) );

The function `ode`

solves an ordinary differential equation with initial value. For this purpose a new primitive map is available; `v3`

is a function giving the velocity vector as a 3-D domain. With `v3()`

, `m3()`

and `m1()`

, functions can be defined for velocity and/or acceleration.

} mp = ode( ma, x0, v0 );

or

} mp = ode( ma, mv, x0, v0 );

or

} mp = ode( mv, x0 );

`ma`

the map function for acceleration, `mv`

the map function for velocity, `x0`

and `v0`

the initial values for place and velocity. Both calls can have a parameter extra for the order of the integrating algorithm (0, 1 or 2 giving first, second or fourth order). The default is 1.

} mp = ode( ma, mv, x0, v0, 2 );

The result is a `m1da`

dependent map giving the place solution of the equation. With `velocity`

the derivative can be obtained;

} m = velocity( mp );

With the function `order`

the integration order can be inspected or changed.

} order( mp ) 1 } order( mp, 0 ) 0 }

Solid models are trees with boolean operations at the nodes and primitives as leafs. Nodes as well as leafs can have lists of attributes attached to them. In *IL* they are implemented as a new type: `Solid`

.

Different primitives are distinguished by an identification integer. The integers for the implemented primitives are built-in constants:

`sbal, scyl, scon, scub, stor, spol, siso`

.

A new primitive is created with a call of `prim`

, with the identification as parameter.

} s = prim( sbal ); bal }

Some primitives, like a torus, need more information to be created. In *IL* this is arranged with attributes.

These are the above mentioned boolean operators to create a new Solid from two others:

`+`

- group
`++`

- union
`+-`

- overlay (non symmetric grouping of transparent objects)
`-`

- subtraction
`*`

- intersection

```
```

The `+`

operator can be applied as a unary operator to create a single extended node without operation.

For the selection of one of the two constituents of a constructed solid the index operator `'`

is implemented with a boolean selector.

Solids can be multiplied with transformations. This can happen on both sides, which is reflected in the order of matrix multiplication. Of course the transformations can be (time depended) maps. Transformations can be applied on each level of nodes and leafs to create a hierarchical transformed (animated) model. As solids constitute a dynamic type, the transformations are accumulated `inside', so no new assignments are necessary.

} translate( 1, 2, 3 ) * s } s * rotate( 30, point(0,0,0) |+ point(1,0,0) ) } scale( 10*mt(), point(0,1,0) ) * s

A transformation term in a solid can also be `m3`

dependent. It is evaluated in the context of the hierarchical model and the `place' of the term in the current transformation.

The transformation of a solid can be obtained with the function `trfof`

.

A single attribute is implemented as a list. The first element must be an identifying integer, the following elements are parameters with types according to that identification. An attribute to be `hooked' on a solid is a list of these lists.

Each solid has a number of `attribute hooks', again identified by integers. In addition to the general hooks, a leaf has some more to be able to give distinct sides their own attribute lists. A general solid can be given an attribute list with `putnatr`

:

} putnatr( s, i, al )

with `s`

the solid, `i`

the `hook' and `al`

the attribute list. Possible hooks are:

`light`

- to attach light sources (a list of
`ls`

... attributes) `refrind`

- for a refraction index attribute (a
`refractx`

attribute) `transcol`

- for a transparent color attribute (a
`trnsclr`

attribute) `nshad`

- for all surface property attributes (
`diffrefl`

,`specrefl`

,`reflect`

,`refract`

,`bumpmap`

,`glass`

) `nodeat`

- a receptacle for anything else. A user can put here anything she likes for her own purpose. At the moment the only attribute recognized at this place is a
`nonvisi`

attribute.

```
```

The attribute list of a solid can be obtained with `getnatr`

} al = getnatr( s, i );

In the same manner the attachment of leaf attributes is implemented with `putlatr`

and `getlatr`

. Here the `hook' is identified with the number of the side, starting with 0. A special hook is identified with `leafat`

. On this place it is possible to give the special primitive parameters that were mentioned above. For instance to give a torus a radius:

} st = prim( stor ); putlatr( st, leafat, [0.1) )

`putnatr`

and `putlatr`

return the solid. The identification of the recognizable attributes with their parameters:

surface properties (-->`nshad`

):

`diffrefl`

`Real`

: intensity

`Real3`

: color

`Real3`

: ambient light (only for this diffuse attribute)`specrefl`

`Real`

: intensity

`Real3`

: color

`Real`

: specular index`reflect`

`Real`

: intensity

`Real3`

: color`refract`

`Real`

: intensity

`Real3`

: color`glass`

`Real`

: reflection factor

`Real3`

: color`bumpmap`

`Complex or Real3`

: surface-normal modulation

```
```

special transparent solid properties (combine with refract and glass) (-->`refrind`

and `transcol`

)

`refractx`

`Real`

: refraction index`trnsclr`

`Real3`

: transparent color

```
```

light sources (-->`light`

)

Point light sources can be classified as constant, linear or quadratic. This concerns the way the distance to the light source affects the resulting intensity. They also can be shadow casting. They all have as parameters their intensity at unit distance, their color and their place.

`lspoint0`

`lspoint1`

`lspoint2 == lspoint`

`lspointsh0`

`lspointsh1`

`lspointsh2 == lspointsh`

`Real`

: intensity

`Real3`

: color

`Real3`

: place cordinates

```
```

`lsambient`

`Real`

: intensity

`Real3`

: color`lspot`

`Real`

: angle of light cone in radians

`Real`

: focus index

`Real3`

: place

`Real3`

: direction

```
```

An lspot attribute is not to be used directly as a light attribute, but rather as a parameter for a point light source instead of the place.

`nonvisi`

`Bool`

:`T`

means nonvisible.

```
```

All attribute parameters can be maps on any domain.

To be able to define polygonal primitives, a value of type `Poly`

can be attached to the `leafat`

hook of a primitive identified with `spol`

. An instance of such a type is created with `polymesh`

. It has two integer arguments, stating the number of vertices and the number of triangles. Further more there are two optional boolean arguments (default F), stating whether there are normals for the vertices to be supplied and whether 2-D (uv) coordinates will be defined for the vertices, to be able to control the definition of 2-D texture maps.

The number of vertices and triangles of a already `declared' polymesh can be obtained with `polvertexcnt`

and `poltrianglecnt`

. To define the different values there are the following functions:

} polvertex( pm, vi, r3 ) } /* pm the mesh, vi vertex index, r3 the Real3 value } poltriangle( pm, ti, i3 ) } /* define the 3 vertex indices of the triangle with index ti } polnormal( pm, vi, r3 ) } /* define the normal direction of vertex vi } poluv( pm, vi, r2 ) } /* define uv-coordinate at vertex vi } polsolid( pm, b ) } /* b boolean to make the mesh a closed solid

All the above functions can be called without the last argument to obtain the current value. To make a primimitiv of a `Poly`

, do:

} s = putlatr( prim( spol ), leafat, [pm) );

As Poly is a dynamic type, more primitives can be made of the same Poly.

A model created along the above-mentioned lines can be rendered by the built-in ray-tracer. In *IL* `raytrace`

is a function that returns a `m2d`

dependent map of the (real4) pixel-type. Apart from the model, a number of entities have to be defined, together constituting the so-called ray-trace environment. This environment is given as a second (optional) argument to the ray-tracer in the form of a list. Like attribute lists, this environment is a list of lists, each of these consisting of an integer identification and a value. These are the identifications, their types and default value:

`trcmsk`

`Int`

:`Dither | Matting | Tapedump`

This is a bit-mask that is the bit-wise or of none or more of the following:

`Tapedump`

: creates a file of the resulting image.

`Matting`

: creates a matte in the alpha-channel of the result

`Dither`

: dithers the pixel-values instead of just rounding the calculated real pixel values. (Has nothing to do with*IL*map dithering)

`Jitter`

: distributes the generated rays in their designated pixel-area as an aid in anti-aliasing.

`Dangle`

: try to eliminate ambiguities of boolean operations when different surfaces coincide.`backcol`

`Real3`

: black

background color, i.e. the color when a direct ray does not hit any solid. This also determines the matte.`neutcol`

`Real3`

: black

neutral color, i.e. the color when a reflected or refracted ray does not hit any solid`hazecol`

`Real3`

: grey (.5*white)

atmosferic color`hazedist`

`Real`

: 10000000

critical distance for atmospheric effect.`pixelratio`

`Real`

: 1.0

to define non-square pixels`rendersize`

`Int2`

: (1280,1024)

pixel size of resulting image.`subrendersize`

`Intl2`

: ((0,0)(0,0))

define a render section other then default render size. It may be bigger. Interpreted as lower left corner and size.`camtraf`

`Transform`

: unit matrix

transformation of the camera.`camzoom`

`Real`

: 1.0

camera zoom, given as cotangent of half the zoom angle.`curdepth`

`Int`

: 8

maximum number of iteration steps for continued ray tracing after reflection or refraction.`reflstep`

`Int`

: 1`refrstep`

`Int`

: 1

stepsize through`curdepth`

for reflection and refraction.`resolution`

`Int2`

(3,0)

control of anti-aliasing. First value gives order of super pixel to start the rendering proces, second value the order of depth of sub pixel rendering. So default is: start with 8*8 pixels and do not go to sub-pixel level. Starting on the pixel level, going to a 4*4 sub pixel division (if necessary) would be: (0,-2).`renderfileformat`

`Int`

: 2

(vista, only one currently implemented)`renderfilename`

`String`

: none (-->unique filename generated)

because the result is a*IL*typed value, inside one*IL*session the filename is not necessary. It is however needed to get the image in a new session.`backmap`

`Real3`

(map): none define a background map instead of`backcol`

.

```
```

The default value of each of the environment items is always obtainable with `rayenvdef`

.

Besides the call:

} mp = raytrace( s, envl )

There is:

} mp = anitrace( s, frfr, tofr, envl )

which renders an animation when `s`

is somehow time-dependent. Of course, in addition to `m2d`

, `mp`

will also be `m1da`

dependent. `frfr`

and `tofr`

are `Int`

s, indicating the first and last frame.

Ray tracing is a form of rendering. In *IL* it is possible to give a reasonable formal definition of rendering:

extensionalizing a value on a discrete domain.

I.e. calculating data that constitutes, in `tabular' form, a map on a discrete domain.

To do this in general for an image there is the function `writeimage`

:

} md = writeimage( fname, m )

`m`

is a map, probably dependent on `m2`

and/or `m2d`

of the pixel type (`Real4`

, or coercible to it. `fname`

is a string, indicating the filename for the image. With an optional third argument the number of `colors' (default 3) can be set to 4, keeping or creating the alpha channel. There is the animated version for time dependent maps:

} md = writeanimge( fname, m, frfr, tofr )

In addition to the optional fifth argument as above, there is the possibility to give a integer skip parameter n, rendering every nth frame.

To get existing images, the following are implemented:

} md = openimage( fname ) } md1 = openvalimage( fname ) } md2 = openbmpimage( fname ) } ma = openanimage( fname )

(openanimage tries to read frame 0. It is possible to give an alternative frame number as second argument).

x = openimage("portrait"); /* take existing image, set global pixelsize according dbnd2( dbnd2( x ) ); y = openvalimage("portrait"); z = openbmpimage("portrait"); /* define a color transforming function by rotating in rgb space pi2 = 2*arg(-1); collin = norm( real3(0,0,0)|+real3(1,1,1) ); colrot = "rotate( pi2*%1, collin ) * %2"; /* create a fractal defined color map fracsize(512); frcol = real3( rfract(), rfract(), rfract() ); /* define a distortion function, taking complex values as argument distm = "m2() + %1 * z"; disto = "trfmap( %1, distm(%2) )"; /* make a new picture dis = (0.5+0.5*y)*disto(1-hypot(z),0.1)*colrot( disto( y, 0.1 ), frcol ); writeimage( "distport", dis );

There is an optimized function to combine a list of linear deformed images in one image.

} md = rimage( fname, iml );

`iml`

is a list of lists, each of which is composed of a 2-D transformation and an image generating map, as above in `writeimage`

. The animated version is:

} md = ranimge( fname, iml, frfr, tofr );

A timeframe is a, possibly non-linear, function of global linear time as determined by m1. Its parameters are a Real3 and another timeframe, thus constituting a recursive hierarchy. So a timeframe is completely defined by a list of `Real3`

. The timeframe of `nil`

is `m1`

. The timeframe of `[r3)#tl`

is a function of the timeframe of tl as follows:

`r3'0`

and `r3'`

1 are interpreted as a 1-dimesional homogeneous transformation defining a linear function. `r3'2`

defines a non-linear `acceleration' or `deceleration' in the unit interval. Let a = `r3'0`

, b = `r3'1`

, p = `r3'2`

and t a time-value in the timeframe of tl.

t = ( t - b ) / a

For -1 <= p <= 1 the function defines a uniformly accelerated `movement':

p * t^2 + (1-p) * t

For p > 1 this transforms into:

t^p

and p < -1:

1 - (1-t)^-p

For direct evaluation of timeframes there are the functions: (tl a list of Real3, t a real)

} tlm1( tl, t )

and the inverse:

} itlm1( tl, t )

A timemap based on a timeframe is obtained as:

} m = tlm1( tl, m1() )

It must be noted that timeframes serve at least a double purpose. In the first place they serve to make different time-rates available. In this case the non-linear acceleration is not usable. On the other hand they define a hierarchy of intervals. This aspect is especially exploited in sound synthesis as is explained in the next section.

There is a single call to calculate a `sound image' in analogy of `raytrace`

and `writeimage`

. The result is a `Real`

valued, `m1ds`

dependent, map, generated from a number of `m1`

and `m1ds`

dependent maps. The function is `raudio`

and its arguments are a filename and a number of lists, as many as there are channels required in the resulting sound files.

} m = raudio( name, lleft, lright )

The lists are build as follows:

each element is a list. Let `e`

l be such a list. The first element of `e`

l is a timeframe list. This timeframe indicates the interval and course of time in this interval for the following elements in `el`

. For each further element of `el`

, let `ml`

be such an element, then

either:

`ml`

is a real valued map that is to be evaluated in the interval to contribute to the sound channel

or:

`ml`

is a list with parameters aimed at changing a map parameter at the time point indicated as the begin point of the interval. The first element of `ml`

is a map. Let `pm`

be this map. The second element is a value or a map. Let `np`

be this element. `np`

is a new parameter for `pm`

, like a programmed execution of `setmap`

at this point in time. The index of the parameter in `pm`

can be given as a third element of `ml`

. The default is 0.

An existing file can be opened as map with `opensound`

.

There are a couple of functions dedicated to special sorts of sound- synthesis. One example is `fsin`

, a sine generator which takes a frequency as argument. This is an aid in implementing complex fm synthesizers. The following is an example of the implementation of a fm synthesizer with one carrier and an indefinite number of modulators, each with its own frequency and modulation index.

fmgen = "fsin( rfmgen( 3 ] )"; rfmgen = "mfi = %1; ( mxi = mfi+1 ) > pars() ? return( %2 ) : 0; mf = %mfi; mx = %mxi; rfmgen[ %1+2, %2 + mx*mf*fsin( mf ) ]";

Another example is `delay`

, a distortion-free delay line with real valued argument, ideal for implementing wave-guide algorithms. There is also an implementation of a `vosim`

generator.

For the generic coupling with (graphical) user interfaces there is a mode that can be switched on with `uimode`

.

} uimode( T );

In that case the interpreter stops its normal execution cycle and passes control to some external mechanism by calling the user defined C-function `uiloop`

. The interpreter can be given control again by calling the C-function:

extern void setuimode( int );

with argument 0, turning uimode off. A callback mechanism is available from the external (user interface) environment with the following means:

extern void inituiargs();

to initialize the arguments for a callback;

extern void adduiarg( void *argp, int argtp );

to push an argument for a callback on the parameter stack;

extern void setuicom( char *name );

to execute a named *IL* function with the parameters created with `adduiargs`

. Implementations have been made with this mechanism for the following:

- 1 implementing motif widgets as a
*IL*type, creating and manipulating them directly under interpreter control with*IL*callbacks - 2 linking with
`uil`

and`vuit`

, with automatic generation of the necessary C-code to translate C callbacks to*IL*callbacks

*IL* is invoked by executing the command `il' from the operating system shell. Alternatively `ilm' can be executed for the X/Motif version.

il [file ...] [-M dir] [-L dir] [-D dir] [-P dir] [-A dir]

The file(s) are *IL* source files to be executed before entering the console interpreter. The options control, together with corresponding environment variables, the directories where the interpreter looks for files, or puts them. For each file category the process is the same:

- 1 look in the directories specified by program option
- 2 look in the directories specified by environment
- 3 if no directories are specified in either of these ways, then look in the default subdirectory of the main directory, which itself can be defined by option or environment.

When writing files the first-found (writable) directory is taken.

The file categories, with their option letter, environment variable and default subdirectory name, are:

- main, this the root directory, which holds the default subdirectories.

-M

$ILM

its default is determined at initial source configuration and so is system dependent. - source library, holding
*IL*source files

-L

$ILL

lib - data library, holding system data, like the `compiled' error messages.

-D

$ILD

dat - image library, historically called production directory

-P

$ILP

prd - audio file library

-A

$ILA

aup

A list of directories can be specified with an environment variable by separating the directories with a space or a colon `:'.

Debugging facilities become available when linking an executable with a special kernel object file. The debugger can be invoked by a direct call of the implicit function `ildb`

.

} ildb() (ildb)

Other ways to get inside the debugger are the occurrence of an error, the invoking of a stop event or the signaling of an interrupt (^ C). The ways commands can be given are very similar to `dbx', the UNIX source level debugger. The possible commands are:

`quit`

`exit`

`cont`

- these are all equivalent and return to normal interpreter control.
`where`

- print the contents of the function stack, with text pointers.
`print var`

`print ::var`

`print `var`

- print the value of the identifier, as it would be interpreted by
*IL*in the current context.`ildb`

understands```

or`::`

prefixes. Information about module, local nesting-depth and protection are given. `print %i`

`print $i`

- print value of ith argument of current function.
`pars`

- give number of arguments in current function.
`stop var`

- create an event to invoke
`ildb`

when the global identifier`var`

is assigned to. `stop in fun`

- create an event to invoke
`ildb`

when the function`fun`

is called. `trace var`

- create an event to trace assignments to
`var`

, printing the function name where this happens. `trace in fun`

- create an event to trace calls of function
`fun`

, printing from which function this call is made. `status`

- print the list of active (stop and trace) events, with identfying numbers.
`delete i`

- delete ith event from the list.
`delete all`

`delete *`

- delete all events.
`step`

`step n`

- continue operation until nth semicolon or end of line (default 1).
`stepi`

`stepi n`

- continue operation for n syntax elements (default 1).
`halt`

- resume with terminal prompt after leaving
`ildb`

. This is useful when`ildb`

is invoked with an interrupt and no further continuation is wanted.

```
```