Copyright ©1998 by AAA+ Software Forschungs- und Entwicklungs Ges.m.b.H.  All Rights Reserved.

7 Language Summary





Joy marries the efficiency and elegance of Objective-C with the scripting capabilities and convenience of Tcl. With Joy you can build hybrid object-oriented programs where compiled and interpreted code fit together seamlessly. You can call compiled Objective-C methods and even plain C functions from interpreted Tcl code, and you can implement methods of Objective-C objects in Tcl and call them transparently from Objective-C, exactly as if they were compiled methods.

The flexibility of Tcl as an interpreted language even allows you to associate code and data with single object instances at run time, thus eliminating the illusory distinction between subclassing and instantiation! You can also dynamically create new Objective-C classes, access both Objective-C instance variables and extern C variables, and manipulate all C data types (even pointers) from interpreted Tcl code.

All functions provided by the Joy package are available both as Tcl commands and as C API. The following lists the complete functionality of the package and mentions all of the Tcl commands (but not their detailed calling sequence or their equivalent API function - consult the reference manual for that kind of information).




Data Types

Joy requires you to specify data types in Tcl code at various occasions, such as method, function, and variable declarations, and most pointer operations. Because data types do not exist in Tcl, Joy introduces its own syntax and operations for managing them.

Joy supports all Objective-C data types. Enums and function pointers are not supported explicitly, but you can use int and void * instead (see 'Pointers' for how to call-dereference a function pointer).

Joy allows you to define new types in a manner analogous to the C typedef statement by means of the Tcl command objc:typedef. You can efficiently load a whole file containing typedefs by using objc:loadTypedefs. Joy looks for the file typedefs.$tcl_platform(os).$tcl_platform(osVersion).$tcl_platform(machine) in all the auto_path directories during initialization, and loads it automatically if found. In its default configuration this file contains at least all the typedefs from the header files ansi.h, libc.h, AppKit.h, Foundation.h, itkTcl.h, and itkObjC.h. You can use the command objc:expandType to convert any valid Joy type into a form that consists of primitive (non-typedef) types only. Also, you can get a list of all defined typedefs with the command objc:info typedefs.

The following table lists all supported Objective-C types and their Joy counterparts. You can use the predicate objc:isType on any string to check if it describes a currently existing data type in Joy syntax. Also, there are Tcl commands objc:encode (analogous to the Objective-C @encode() directive) and objc:decode to convert data types between Joy syntax and Objective-C type encodings. The command objc:sizeof returns a data type's size in bytes.


----------------------------------------------------------------------------
Objective-C Joy
----------------------------------------------------------------------------

id id
Class Class
SEL SEL
char * STR, or {char *}
void void
BOOL BOOL
char char
unsigned char uchar
short short
unsigned short ushort
int int
unsigned int uint
long long
unsigned long ulong
long long longlong
unsigned long long ulonglong
float float
double double
----------------------------------------------------------------------------

TclObj, or Tcl_Obj * TclObj
----------------------------------------------------------------------------

type * {type *}
class * {class *}
struct {@defs (class)} class
type (*type) (type1, type2, ...) {void *}
type [10] {type 10}
union tag { type1 a; type2 b; ...} {union type1 type2 ...}
struct tag { type1 a; type2 b; ...} {struct type1 type2 ...}
type : m; type : n; ... {bitfield m n ...}
enum tag { const1, const2, ...} int
----------------------------------------------------------------------------




Data Values

The nature of Joy as an interface between Tcl and Objective-C implies that it must convert data values back and forth between a string representation and a binary representation. This conversion task is less straightforward than it sounds, so this section explains its details for all data types.

Joy allows you to define macro constants in a manner analogous to a C preprocessor #define statement by means of the Tcl command objc:define, or you can efficiently load a whole file containing macro definitions using objc:loadMacros. Joy looks for macros.$tcl_platform(os).$tcl_platform(osVersion).$tcl_platform(machine) in all the auto_path directories during initialization, and loads that file automatically if found. In its default configuration this file contains at least all the macro constants, enum constants, and extern variables from the header files ansi.h, libc.h, AppKit.h, Foundation.h, itkTcl.h, and itkObjC.h. To use a macro constant in your code you have to enclose its name in brackets. For example, [NULL] will be replaced by char@0x0. You can get a list of all defined macro constants with the command objc:info macros. You can undefine a macro constant with objc:undef.

Joy implements a Tcl predicate, objc:isValue, that allows you to check if a given data type can accept a given Tcl string as a data value.

When data values are passed as arguments or return values to/from methods/functions, Joy will automatically apply the standard C type promotions from characters to integers or from arrays to pointers, as needed.

Special note for those familiar with the inner workings of Tcl: Since Tcl8.0, Tcl data values are not strings but rather pointers to Tcl_Obj structures, that contain both a string representation and a binary representation. Joy makes use of this fact, and registers the following object types during initialization: ItkObjCIdType, ItkObjCSelType, ItkObjCPtrType, ItkObjCLongLongType, and ItkObjCTypeType.




Objects

Joy represents Objective-C objects by the id and Class data types. The string representation of any id or Class value is always ClassName@0xHexAddr, or one of nil, Nil, 0, or 0x0 for the null object/class (ClassName@0x0 works also). The ClassName@ part can always be omitted; for Class values the @0xHexAddr is optional.

To guard your program from fatal run-time errors, Joy keeps a list of "known" object id's. A conversion (in either direction) of an object that is not "known" to Joy will fail with an error message. You can turn this check mechanism on and off at runtime by changing the global variable objc:idCheckingEnabled in the Joy main interpreter, or by calling the API function ItkObjC_SetIdCheckingEnabled; a value of 0 means disable checking and don't register any id's, 1 means enable it and register new id's when they are first seen by Joy (see below), 2 (the default) means enable checking and register new id's at the moment the object is allocated - this has a certain impact on application performance, but can be useful for debugging or exploration purposes, when you need information about all objects created by the application, not just those that pass through Joy.

All class and metaclass objects known to the Objective-C run-time environment are automatically known to Joy, too. Instance objects can become "known" through either of the following events (only when id checking is enabled):

The object is returned to Tcl from any Objective-C method or C function.
The object is passed as an argument to any Joy skill from Objective-C.
The object is the target of a pointer that is dereferenced using objc:peek -trust (includes read access to instance variables or linked C variables).
The object is passed from C code to ItkObjC_RegisterId or to one of the conversion functions with the ItkObjCRegisterId flag set.
The object is allocated by the Objective-C runtime and objc:idCheckingEnabled is 2 (see above).

An object is deleted from the list of "known" objects when its memory is freed by the Objective-C run-time system or when you call the ItkObjC_UnregisterId function on it from C code. You can get a list of all "known" objects using the Tcl command objc:info ids, a list of all class names known to the Objective-C run-time environment with objc:info classes, and lists of an object's instance variables or methods with objc:info ivars and objc:info methods.




Selectors

Objective-C message selectors are represented by the SEL data type. Their string representation is identical to the selector name (including any colons), or one of 0 or 0x0 for the null selector. There is no command equivalent for the Objective-C @selector() directive since it is not needed in Joy.




Strings

C strings are represented by the Joy types char * and STR, and sometimes also by character arrays of the form char n. Conversion of a Tcl string into a C string is done according to the following rules:

If the context and the semantics of the C language allow a pointer value (for example, when you are passing a STR, char *, or char n method argument to Objective-C, but not when you are writing characters into a char n array inside a structure), and the Tcl string is of the form char[...]@0xHexAddr (the standard Joy syntax for pointers to characters or character arrays), the conversion will result in the specified pointer value.
In all other cases the Tcl string itself will be used. If you are writing characters into a char n array, the first n characters of the Tcl string will be written; if the string is shorter than n characters, the remaining bytes in the array will be set to 0. If the conversion has to result in a pointer (for example, when you are passing a STR or char * method argument), the address of the Tcl string will be used, without any copying or memory allocation, so this pointer will only remain valid until the next trip through the Tcl interpreter, and must not be freed by the method called. If this presents a problem, you have to allocate memory for the string and copy it before the call, using Tcl code like the following:

set s [objc:poke [objc:newPtr "char [expr [string length $string] + 1]"] $string]
equivalent, but shorter: set s [cpIn $string]
...
CallObjectiveC ... $s ...

The rules for the conversion of C strings into Tcl strings are as follows:

STR data values are always converted by dereferencing the pointer and returning the contents of the string to Tcl, except in the case of null pointers, where the conversion results in the string char@0x0 (the value of the macro constant NULL).
Char * differs from STR in that the pointer is never dereferenced automatically but always converted into a Tcl string of the form char@0xHexAddr. You should use char * instead of STR when you need access to the pointer itself, e.g. when you have to free its memory later. You can still dereference the pointer by using code like:

objc:peek $ptr {char 0}

Char n arrays are converted into Tcl strings by copying all n characters and appending a 0 byte. In the special case n = 0 all characters up to and including the first 0 byte are copied.

Joy implements two Tcl commands, cpIn and cpOut, that simplify the tasks of allocating memory for a string, copying it, and getting the char *, or of dereferencing a char *, freeing it, and getting its string contents. There are also Tcl commands @ and * for easy conversion of strings into NSString objects and vice versa.




Booleans

Joy represents Boolean values with the BOOL data type. All valid Tcl Boolean values, as well as the two string constants \1 and \0 (the values of the macro constants YES and NO), are valid BOOL data values. Binary BOOL values are guaranteed to be converted into a valid Tcl Boolean value.

Because of the way the Objective-C BOOL data type is implemented, there is no way for Joy to distinguish char and BOOL arguments or return types in the method signatures it gets from the Objective-C run-time environment. So be careful to always pass [YES] or [NO] to any method requiring a BOOL argument, instead of 1 and 0 (because the ASCII code of the character 0 is non-0, it would be interpreted as a true Boolean value by the Objective-C method!). The Joy version of Tcl accepts \1 and \0 wherever Boolean values are allowed, so you can simply write:

if {[$o respondsToSelector: test]} {...




Characters

Characters are represented by the char data type. All one-character Tcl strings, as well as reasonably small integers and Tcl Boolean values are acceptable char data values. Binary char data values are converted into one-character Tcl strings.




Numbers

The uchar, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, and double data types represent numbers. They work as expected.




Pointers

Joy pointer data types look like Type *. Make sure to use braces when needed, since the type name contains a space. The string representation of any pointer value is always Type@0xHexAddr. Where you have to specify a pointer value you can omit the Type@ part. For character pointers see the Strings section above. Every id data value can also be used as a pointer (to a struct containing all of the object's instance variables, or to the class structure in the case of class and metaclass objects).

Joy implements a complete set of Tcl commands for manipulating pointers: objc:peek for read-dereferencing, objc:poke for write-dereferencing, objc:call for call-dereferencing, objc:typeOfPtr for type-dereferencing, objc:offsetPtr for pointer arithmetic and type casting, objc:newPtr for allocating memory, objc:delPtr for freeing memory, objc:iVarPtr for getting the address of an Objective-C instance variable, objc:varPtr for getting the address of an extern C variable, and objc:funcPtr for getting the address of a C function (see 'Declaring C functions and extern variables' for an easier way to use C functions and variables in Tcl code).




TclObj's

The special data type TclObj allows you to pass Tcl_Obj *'s between Objective-C and Tcl directly, without any conversion at all. Any value expressible in Tcl is a valid value for TclObj. A null Tcl_Obj * (which is only possible from Objective-C) will be converted to the special Tcl object ItkObjCNullObj, and vice versa. From Tcl you can check for the presence of this special object with the command objc:isNullObj. You can get the null object itself by writing [ItkObjCNullObj]. Note that this feature of Joy provides a way to avoid a multitude of [info exists ...] calls when all you want is a way to express a "rogue" value - simply set your Tcl variables to [ItkObjCNullObj] instead of unsetting them. Warning: Peeking at TclObj's through pointers, or using them inside compound data types, is potentially dangerous, since Joy cannot tell if the target of a Tcl_Obj * is a valid Tcl_Obj structure or not.




Arrays

Array data types in Joy look like Type n. Make sure to use braces when needed, since the type name contains a space. Arrays with n = 0 are legal, but not very useful (except for character arrays). The string representation of an array is a Tcl list of length n, containing the string representations of its elements; if an array element cannot be converted to a string, the corresponding list element will be set to [ItkObjCNullObj] (see above). Character arrays are treated specially, see the Strings section above.




Structures

Joy structures are declared with a type of struct Type1 Type2 .... Make sure to use braces when needed, since the type name can contain spaces. An empty structure is legal, but not very useful (except for declaring pointer types). The string representation of a structure is a Tcl list, containing the string representations of all structure elements (without the struct keyword); unconvertable fields will be set to [ItkObjCNullObj]. Structure returning methods and functions are supported by Joy.




Unions

Unions are declared with a type of union Type1 Type2 .... Make sure to use braces when needed, since the type name can contain spaces. The string representation of a union is either a 2-element Tcl list containing a field index n (starting with 0), followed by the string representation of the field at index n, or a Tcl list with -1 as its first element, followed by the string representations of all union fields in order, followed by the binary representation of the union. When a union is converted from binary to a string, the conversion will always result in the form which includes all fields; unsuccessfully converted fields will be set to [ItkObjCNullObj].




Bitfields

Bitfields are declared with a type of bitfield Width1 Width2 .... Make sure to use braces when needed, since the type name can contain spaces. The string representation of a bitfield is a Tcl list, containing the string representations of all the fields in order (as unsigned integers).




Messaging

With Joy you can send messages to Objective-C objects from Tcl almost exactly as you would from Objective-C. Joy will automatically convert all the arguments from Tcl strings into binary form, and the return value from binary into a Tcl string, according to the type information it has about the method. You can write Tcl code like:

set arr [[NSMutableArray alloc] init]
set obj [[NSObject alloc] init]
$arr addObject: $obj
$arr replaceObjectAtIndex: 0 withObject: $arr
$arr makeObjectsPerformSelector: removeAllObjects
$arr release

You have to make sure that the colons that are part of the selector name and the arguments that follow are separated by a space, otherwise Tcl cannot parse the command correctly. A second form of the message statement is also possible; you can specify the whole selector name (including embedded colons) after the object, and all the arguments after the selector, e.g.: $arr replaceObjectAt:with: 0 $arr. Joy supports methods with variable argument lists, but only if the variable arguments are all of the same type as the last fixed argument (or char * if there aren't any fixed arguments); the following example would work:

NSArray arrayWithObjects: [NSNumber numberWithInt: 1] [NSNumber numberWithInt: 2] nil

Messages to nil are legal, like in Objective-C; they return nil. Forwarding of messages does work from Tcl like it does from Objective-C; you can even implement methodSignatureForSelector: and forwardInvocation: in Tcl, if you like.





Teaching Classes and Objects New Skills

To let you make full use of the flexibility of an interpreted language, Joy introduces a new concept into object-oriented programming, called skill. Skills are exactly like methods, but there is one difference: Methods are always the same for a whole class of objects, but skills can be taught to single objects at run time.

Without skills, if you needed to customize the functionality of an object you had to implement a new subclass which contained the necessary methods. But this only makes sense if you need many objects with the new functionality. In the much more common situation where you need just one object to respond to one message differently than other objects of its class, you had to resort to tricks like OPENSTEP's target-action paradigm, or its delegate mechanism. But all of these are rather limited in the customization "hooks" they offer: You have only one target and one or two actions, only one delegate, and the delegate is messaged only at certain times.

These limitations came directly from the nature of Objective-C as a compiled language. Only classes could have methods and instance variables because only classes were known to exist at compile time. Now, with Joy at your disposal there is no need to stick to the old limitations: You can associate both Tcl code (skills) and data (property instance variables) with individual instances.

You teach one or more classes or instances new skills by using the objc:teach Tcl command, for example:

objc:teach NSObject {
- void test {
puts "$self test"
}
+ void test {
puts "+ $self test"
}
}

set o1 [[NSObject alloc] init]
set o2 [[NSObject alloc] init]
objc:teach "$o1 $o2" { # 2 instances
- void test {
# this will call NSObject's version
super test
puts "special test"
}
}

The skills you define are registered with the Objective-C run-time environment like any Objective-C method, and can be called from Objective-C transparently! Again, Joy takes care of the conversion from binary to Tcl strings and back. If you call a Joy skill from Tcl, the skill is called directly, like any Tcl procedure; arguments and return types will be checked for correct syntax, however, if the Tcl variable objc:tclTclTypeCheckingEnabled is set to a non-0 integer value (the default) in the Joy main interpreter (performance of debugged programs will be somewhat better when type checking is disabled). Variable argument lists are supported if the variable arguments are all of the same type as the last fixed argument (or char * if there aren't any fixed arguments), and their promoted size equals that of a void *; you declare a method with variable arguments by naming its last argument args. When a variable argument skill is called from Objective-C, Joy will stop converting and pushing arguments into the args Tcl list only after it has seen a null pointer value.

If you have more than one Tcl interpreter in your application, the Joy skill will always execute in the context of the interpreter that defined it (it will have access to the correct global variables, etc.). When a Tcl interpreter is deleted, all Joy skills defined by it are deleted also.

Joy skills are inherited and overridden exactly like methods, the only difference is that the inheritance hierarchy can have an additional bottom layer at the instance level. Both class and instance skills can be defined; it is illegal to define class skills for instance objects. You can override an Objective-C implementation of a method with a Joy skill, or one Joy implementation with another (even in a different interpreter). The old implementation is not lost and can still be called through the former or objc message target, see below.

Every Joy skill has two additional hidden parameters, self and _cmd, containing the instance receiving the message and the selector, like in Objective-C.

Inside a skill body there exist three special targets for messages, super, former and objc. The super target works exactly like the Objective-C construct of the same name; note that when you message super from a skill defined for an instance, the message dispatcher will start looking for an implementation at the level of the instance's class, not its superclass. If you message the former target, the message dispatcher will use the implementation of the selector that it would have used just before the currently executing implementation was added (that includes superclass implementations); the objc target works in the same way, but looks only for an Objective-C implementation (disregarding all Joy skills); if no super, former or objc implementation is found, the call will just return $self.

There is an additional command that is only usable inside a skill body: You can use iset instead of the Tcl set command to set or read the instance variables of the messaged object. If you iset a variable that is not an Objective-C instance variable, Joy will interpret that as a request to set or read a property instance variable. Property instance variables live in a Tcl array at global level that is named $self, so you have one independent variable name space per object instance (and interpreter). This array is unset completely (in all interpreters) when the object is deallocated. You can use the objc:deallocTrace command to execute Tcl code whenever that happens.

You can also remove Joy skills at any time, using the command objc:unteach. Previous implementations of a selector that was overridden will then reappear. Only the Tcl interpreter that defined the skill can remove it.

You can request information about the arguments and body of the most recent Joy implementation of a selector using the commands objc:info args and objc:info body.




Classes

You can get a list of classes known to the Objective-C runtime using the command objc:info classes. You can add new classes using the Joy command objc:newClass, for example:

objc:newClass MyButton NSButton {
id sound
id image
}

The added class is indistinguishable from an Objective-C class created by any other means.




Protocols

Joy includes full support for Objective-C protocols. The command objc:protocol is the analogue of the Objective-C @protocol() directive. The command objc:info protocols can be used to get a list of protocols known to the Objective-C runtime, and objc:info methods can list any protocol's methods. You can add new protocols to the Objective-C runtime's data structures with objc:newProtocol. Finally, you can specify that a class (or object!) conforms to a protocol from Joy using objc:teach -protocols. For example:

objc:newProtocol MyProtocol -proto NSCoding {
# MyProtocol adopts the NSCoding protocol,
# and requires the following additional methods:
- int method1: {int x}
+ int method2: {float y}
}
objc:teach MyButton -proto MyProtocol {
- int method1: {int x} {
expr {2 * $x}
}
+ int method2: {float y} {
expr {int($y*$y)}
}
- initWithCoder: coder {}
- void encodeWithCoder: coder {}
}




Exception Handling

Joy automatically catches all NSExceptions that occur inside of Objective-C method calls or C functions and converts them to the special Tcl return code ITKOBJC_NSEXCEPTION and a result that is the id of the NSException (exceptions with name ItkObjCTclErrorException are handled specially, see below). So you can catch NSExceptions from Tcl by using the standard catch Tcl command, and from Objective-C using NS_DURING and NS_HANDLER regardless of any intervening Tcl code on the calling stack. You can raise an exception from Tcl that Joy does not catch by using the command objc:uncaughtException; because this messes up Tcl's internal data structures you should do that only in extremis (at top level, when your program is already in the process of terminating).

When a Tcl implementation of a message that was sent from Objective-C returns an abnormal Tcl return code, Joy will automatically raise a NSException with name ItkObjCTclErrorException, a reason equivalent to the result string, and a userInfo NSDictionary containing values for the keys @"interp", @"code", @"errorInfo", and @"errorCode". NSExceptions of this kind are treated specially by the code that translates NSExceptions into Tcl return codes (see above): they are just converted back into the return code and result string that are contained in the NSException object; also the errorInfo and errorCode variables will be set correctly. So you can catch Tcl errors from Objective-C using the standard NS_DURING and NS_HANDLER mechanism, and from Tcl just as if the Objective-C layer of the calling stack didn't exist.




Declaring C Functions and Extern Variables

Joy gives you direct access to plain C functions and extern variables through the objc:declare command, for example:

objc:declare int NXArgc
objc:declare [list [list STR $NXArgc] *] NXArgv

objc:typedef {struct STR {void *} int} Tcl_Interp
objc:declare {Tcl_Interp *} Tcl_CreateInterp {}

Declaring a C extern variable in this way is equivalent to setting up variable traces that keep the Tcl variable consistent with its C equivalent (like Tcl_LinkVar, but works for all data types). Declaring a function is like using proc to define a Tcl command that calls the C function and does all the necessary value conversions for argument and return values. Variable argument lists are supported if the variable arguments are all of the same type as the last fixed argument (or char * if there aren't any); you don't have to declare the additional arguments, it suffices to append them to the call. The variable or function name parameter can also be a 2-element list where the first element is the name of the Tcl variable or function and the second element is either the C name (if different from the Tcl name) or a pointer value giving the variable or function address. C names containing a : are split into a module name (before the :) and the real symbol name. This is useful only on systems with brain-damaged dynamic loading mechanisms, like Windows/NTî; the module name part is ignored on all other platforms.




NSArrays and Tcl Lists

You can use the command objc:foreach to iterate efficiently over NSArrays or any other container object implementing the objectEnumerator message. You can convert between Tcl lists and NSArrays efficiently using the commands objc:listFromArray and objc:arrayFromList.




Joy in Safe Tcl Interpreters

If the Joy package is loaded into a safe Tcl interpreter, its functionality is severely limited: The objc:declare, objc:peek, objc:poke, objc:call, objc:newPtr, objc:delPtr, objc:iVarPtr, objc:varPtr, objc:funcPtr, objc:define, objc:undef, objc:loadMacros, objc:typedef, objc:loadTypedefs, objc:uncaughtException, and iset commands will not be available, any message that results in a call to an Objective-C method or to a different Tcl interpreter will fail, and new skills cannot override existing methods or skills.