Next: , Previous: , Up: Top   [Index]


Basic Classes and Ctalk Statements

Ctalk is designed to work with C. There are a number of classes that correspond directly to the basic C data types.

In Ctalk, you can use instances of the classes that are shown in the table below interchangeably with their C equivalents.

Ctalk Class         C Type
-----------         ------
Array               char **, int **, ...
Character           char
Float               float, double
Integer             int, long int
LongInteger         long long int
String              char *

There are a few places that you cannot use objects, like function parameter declarations or struct members. You’ll also see a lot of examples that translate data between objects and C variables. The manner in which Ctalk translates between the two is a powerful feature of the language.

Integer, Character, LongInteger, and Float Classes

Like most object oriented languages, when you declare an object, you’re actually sending a message to a constructor method. As we mentioned earlier, the methods that create objects are called constructors. And as with many object oriented languages, you create an object with the message new.

Integer new myInt;

Objects of class Integer work exactly the same as C variables of type int. You can use expressions like the following, as though you were programming in C.

Integer new myInt;

myInt = 2;
printf ("%d\n", myInt + myInt);

With several exceptions, you can use all of the operators that C integers recognize. Prefix and postfix operators, like +, -, *, &, and so on, also work as methods.

Integer class also defines some methods as the equivalent of these operators, like invert for ! and bitComp for ~.

This example shows how you would use the methods in C statements.

int i;               /* C variable.   */
Integer new myInt;   /* Ctalk Object. */

i = 2;
myInt = 2;

/* These two statements are equivalent. */
printf ("%d\n", !i);
printf ("%d\n", myInt invert);

/* These two statements are also equivalent. */
printf ("%d\n", ~i);
printf ("%d\n", myInt bitComp);

The same is also true for instances of the Character, LongInteger, and Float classes.

For instance, you can add and subtract two characters. This is sometimes useful for conversions, as in the following examples.

Character new receiver;
Character new operand;

receiver = 'A';
operand = ' ';

printf ("%c\n", receiver + operand); 

The result is the lower case ‘a’. This has the same effect as:

Character new receiver;
receiver = 'A';
printf ("%c\n", receiver toLower);

Or even:

printf ("%c\n", 'A' toLower);

To convert a character to uppercase, you can use either - or toUpper. Both are implemented by the Character class.

Character new receiver;
Character new operand;

receiver = 'a';
operand = ' ';

/* These two statements produce the same result. */
printf ("%c\n", receiver - operand);
printf ("%c\n", receiver toUpper);

For completeness, we should note that you can also achieve the same effect with the bit operators &, |, and ^, although their application can be a bit more involved. If you’re not certain how the operators would work, enter the ASCII value of a character in a programmer’s calculator and convert it to base 2. Then notice the effect of changing different bits.

For now, we’ll just show an example of changing a letter’s case - from lower to upper case, or vice versa.

We do this by XOR’ing the fifth bit of the character value. (Note that 2^5 is 32, the ASCII value of a space (‘ ’) character.)

Here is a simple example of the operation.

Character new upperCase;
Character new lowerCase;
Integer new toggleBit;  

upperCase = 'Z';
lowerCase = 'z';
toggleBit = 100000b;
  

printf ("%c\n", upperCase ^ toggleBit);
printf ("%c\n", lowerCase ^ toggleBit);

String Class

As the introduction mentioned, Ctalk overloads many operators. This is especially obvious of class String.

This code shows how String class overloads some math operators.

String new s1;
String new s2;
String new helloString;

/* C's strcpy () would also work here. */
s1 = "Hello, ";
s2 = "world!";

/* And strcat () could perform the same task here. */
helloString = s1 + s2;

printf ("The value of \"helloString\" is \"%s\".\n", helloString);

s1 = "s1";
s2 = "s2";

/* Test whether String objects are equal without strcmp (). */
if (s1 == s2) {
  printf ("The strings s1 and s2 are equal.\n");
} else {
  printf ("The strings s1 and s2 are not equal.\n");
}

Briefly, the String instance methods =, ==, and + function similarly to the C library functions strcpy(3), strcmp(3), and strcat(3), respectively.

But these operators can also be overloaded. For example, the + method behaves like C’s strcat() function when the argument is also a String object. If the argument is a Integer, then the + method increments the reference to the receiver String object. This is discussed further below. See String_pointer_math.

Like C, however, instances of String and Character are not interchangeable. If you want to use an instance of class Character as a string, you must use the method asString (class Magnitude) to return a String version of the Character object.

Here is a brief example. The two printf statements produce the same output.

Character new myChar;

myChar = 'a';

printf ("%c\n", myChar);
printf ("%s\n", myChar asString);

String class implements several convenience methods. The split method, which we described in the previous chapter, splits a string along a separator character and puts each substring token in an array.

Here is the statement from the example in the previous chapter. See ctpath.c.

nItems = path split separator, paths;

If the receiver, path, contains a string like ‘/home/users/joe’, and separator, a Character contains ‘/’, then split places the individual names in the Array, paths.

The operation looks something like this.

path    --->       "/home/users/joe"
                     /     |     \
                    /      |      \
split              /       |       \
                  v        v        v
              --------------------------------
paths   ---> |paths at 0|paths at 1|paths at 2|
             | "home"   | "users"  | "joe"    |
              --------------------------------

The expression, paths at 0, is equivalent to the paths[0] element of a C array, and so on, for the other elements of the array.

Another convenience method, subString, returns the part of the receiver string beginning at the first argument. The second argument is the length of the substring. Here is an example.

myString = "This is a string.";
mySubString = myString subString 5, 2;

Ctalk has two methods that allow you to search strings: search and quickSearch.

The search method allows you to search for text patterns within strings. It recognizes a few metacharacters: ‘.’, ‘^’, ‘$’, and ‘*’. The quickSearch searches for exact text only, but it uses a much faster search algorithm.

Both methods take as their arguments the text pattern you want to search for, and an Array object, where the methods return the positions in the receiver where the first character of the pattern occurs. The first character of the receiver is offset 0. The last element of the offsets array is -1, which indicates that search found no further matches. The methods return an Integer that contains the number of matches. This example shows how a program might search a String object with quickSearch.

int main () {
  String new string;
  String new pattern;
  Array new offsets;
  Integer new nMatches;

  pattern = "He";
  string = "Hello, world! Hello, world, Hello, world!";
  
  nMatches = string quickSearch pattern, offsets;

  printf ("nMatches: %d\n", nMatches);
  offsets map {
    printf ("%d\n", self);
  }
}

When run, the program should produce output similar to this.

nMatches: 3
0
14
28
-1

Here is an example of using search to find a pattern in a String object.

int main () {
  String new string;
  String new pattern;
  Array new offsets;
  Integer new nMatches;

  pattern = "l*o";
  string = "Hello, world! Hello, world, Hello, world!";
  
  nMatches = string search pattern, offsets;

  printf ("nMatches: %d\n", nMatches);
  offsets map {
    printf ("%d\n", self);
  }
}

When run, the program should produce results like this.

nMatches: 6
2
8
16
22
30
36
-1

The search method matches as much of a pattern as possible. It’s also important to remember that a ‘*’ metacharacter matches zero or more occurrences of the character it follows. So the pattern, "l*o," matches both "llo," and, "o."

However, Ctalk’s pattern matching does not allow the text that matches a pattern to overlap. So a pattern like ‘.:’ matches the character before each pair of colons in the string ‘item1::item2::item3::item4’, while the pattern ‘:.’ matches only the first colon of each pair of colons.

Using Math Operators with Strings

As the the section above mentioned, Ctalk overloads math operators for String and its subclasses. These operators are +, -, ++, --, +=, -=, and *. For String objects (and subclasses of String if the subclass doesn’t also overload the methods) these methods behave the same as the equivalent C operators - that is, they increment (or decrement) the value of the receiver String by one or more characters.

Here is an example that demonstrates a few of the methods.


int main () {

  String new s;
  String new t;
  Integer new halfLength;
  Integer new i;

  s = "Hello, world!";

  i = 0;
  halfLength = 7;

  t = s;

  printf ("%s\n", s);

  while (i++ <= halfLength)
    printf ("%s\n", t++);

  while (t-- != NULL)
    printf ("%s\n", t);
}

When run, this program produces the following output.


Hello, world!
Hello, world!
ello, world!
llo, world!
lo, world!
o, world!
, world!
 world!
world!
world!
 world!
, world!
o, world!
lo, world!
llo, world!
ello, world!
Hello, world!
(null)

Note that String and Integer objects get iterated separately. There’s a slight semantic difference from C because Ctalk doesn’t overload subscripts - a Ctalk program would use t at i to refer to the i’th character in a String.

As you might expect, though, the expression *str returns the first element of str as a Character object.

Array Class

An Array in Ctalk is similar to arrays in every other programming language. Ctalk arrays are collections of objects, indexed sequentially from index 0 to the last element in the Array.

To add an element to an Array object, use the method atPut.

Array new myArray;
String new myString;

myString = "Hello, world!";

myArray atPut 0, myString;

The atPut method has two arguments, the index of the array element, and the object you want to store there.

To retrieve an Array element, use the method at. The at method takes one argument, the index of the array element.

Using the previous example, this statement retrieves the first element of myArray and prints it.

printf ("%s\n", myArray at 0);

You do not have to predeclare an array to a specific size. Objects of Array class can hold as many objects as necessary.

If you want to know how many elements an Array object contains, use the method size, which returns the number of elements as an Integer object.

printf ("myArray has %d elements.\n", myArray size);

If you add an object to an array at an index that already contains an object, atPut replaces the array element with the new object.

Array new myArray;

myArray atPut 0, "My";
myArray atPut 1, "name";
myArray atPut 2, "is";
myArray atPut 3, "Bill";

WriteFileStream classInit;

stdoutStream printOn "%s %s %s %s.\n", myArray at 0, 
  myArray at 1, myArray at 2, myArray at 3;

myArray atPut 3, "Joe";

stdoutStream printOn "%s %s %s %s.\n", myArray at 0, myArray at 1, 
  myArray at 2, myArray at 3;

The WriteFileStream classInit statement initializes the program’s standard output. You need to include it (also run by the WriteFileStream method new) before printing to the console with Ctalk methods. Later chapters describe file input and output in greater detail.

4.1 Repeating Operations for Each Element of a Collection

Generally, if you want to repeat an operation on all members of an array, you can use C’s for, while, or do loops.

If you want to iterate over a class using Ctalk, many classes implement the method map.

The map method can take the name of another method as its argument. map then calls the method with each element of the array as its receiver.

Here is a more complete implementation of the example above, written using map.

Array instanceMethod printArrayElement (void) {
  printf ("%s ", self);      
  return NULL;
}

int main () {
  Array new myArray;

  myArray atPut 0, "My";
  myArray atPut 1, "name";
  myArray atPut 2, "is";
  myArray atPut 3, "Bill";

  myArray map printArrayElement;
  printf ("\n");

  myArray atPut 3, "Joe";
  myArray map printArrayElement;
  printf ("\n");
}

In the printArrayElement method, self refers to each successive element of myArray (in main.) Each element of myArray is an instance of class String.

However, you should note that Array elements, like elements of any other collection, can be objects from any class, and C does not necessarily handle classes that correspond to complex data so easily.

In this case, it is relatively easy for the program to determine how to deal with a String object. Mapping over other collection types, AssociativeArrays, requires that methods examine receivers of unknown classes. See Collections.

If Array elements are instances of complex classes, or the elements represent complex C data types, then the method may need to determine the class of its receiver, which is discussed in the following chapters, and the method may need to translate objects from one class to another. See Variable Promotion and Type Conversion.

The map method can also use a block of code as its argument, as in this example.

int main () {

  List new l;
  String new sPrefix;
  String new s;

  sPrefix = "This element is ";

  l push "l1";
  l push "l2";

  l map {
    s = sPrefix + self;
    printf ("%s\n", s);
  }
  exit(0);
}

You should note that the block has a different scope than the function or method where it occurs. Objects declared in the method are also visible within the block, but not vice versa. Also, self, when it occurs inside a code block in this manner, refers to each successive element of the receiver collection (each member of List l in the example above), and super, is overloaded so that, when used as a receiver, refers to the receiver of the method that contains the block of code; i.e., the self of the method that contains the block.

While you can use a break statement to exit that map block, you cannot (at least currently), return from the method from within the block. Otherwise, control structures within a block work just as you would expect. Here is an example from Object class.

Object instanceMethod libraryPath (void) {
  Array new searchDirs;
  Application new docApp;
  String new s;
  String new libraryPathName;
  String new receiverName;
  FileStream new f;
  returnObjectClass String;

  s = docApp classSearchPath;
  s split ':', searchDirs;
  receiverName = self value;
  searchDirs map {
    libraryPathName = self + "/" + receiverName;
    if (f exists libraryPathName)
      break;
  }
  return libraryPathName;
}

Once again, self within the argument block refers to each successive member of the searchDirs array.

You can use self in argument blocks even if a block occurs in a C function. Otherwise, either Ctalk or the compiler, depending on the context, will not be able to find the receiver that self should refer to, and Ctalk or the compiler, or both, will issue warning or error messages.

If the program tries to use super within a C function, Ctalk prints a warning and uses the receiver of the entire code block itself-generally, that’s the receiver of a map method.

Many collection classes declare their own versions of map. All of them use the __ctalkInlineMethod () library function to actually perform the in-line call. Programs that implement inline calls elsewhere can (and should) use this function. Refer to the __ctalkInlineMethod () description in the Ctalk Language Reference for the gritty details.

It’s worth noting here that map with an argument block does not allow certain constructs. You can nest argument blocks, but they cannot be called recursively. In addition, if the first argument to map is a separate method, some classes, like List, allow you to provide further arguments to the target method. See MapArguments.

Occasionally, references to self within code blocks can be ambiguous. This mostly happens when self is used as an argument to a method inside the block. Consider the following example, which uses mapInstanceVariables (defined in class Object).

myObject mapInstanceVariables {
  if (self name != "value") {
    s_element = self formatInstanceVariable self asSymbol; /* Here, "self" refers to s_element. */
    s = s + s_element;
  }
}

Here, the statement

s_element = self localFormatInstanceVariable self asSymbol;

can cause trouble, because self refers to s_element, not the receiver of the block. The program can find the formatInstanceVariable method (defined in ObjectInspector class) due to the way that mapInstanceVariables defines receivers, but the ‘self asSymbol’ refers to s_element (the receiver of =), instead of referring to the receiver of the block.

To make this construct clear, the block can appear, somewhat artificially, as the following.


rcvrCopy copy self;             /* Here, "self" is the method's receiver. */
myObject mapInstanceVariables {
  if (self name != "value") {  /* Here, "self" is the block's receiver. */
    selfVarSymbol = self;       /* Assigning to a Symbol creates a reference. */
    s_element = rcvrCopy formatInstanceVariable selfVarSymbol;
    s = s + s_element;
  }
}

However, it’s also useful to use super in the same manner. So the example above gets slightly abbreviated.


myObject mapInstanceVariables {
  if (self name != "value") {  /* Here, "self" is the block's receiver. */
    selfVarSymbol = self;       /* Assigning to a Symbol creates a reference. */
    s_element = super formatInstanceVariable selfVarSymbol;
    s = s + s_element;
  }
}

Use of the mapInstanceVariable method is discussed further in the following sections. See The -> Method.

4.2 Using Math Operators with Arrays (More about Looping)

Array objects, like other subclasses of Collection, can use overloaded math operators to loop through the collection’s elements. These methods include -- and ++. Here’s a brief example.


int main (int argc, char **argv) {

  Array new a;
  Key new k;

  a atPut 0, "value0";
  a atPut 1, "value1";
  a atPut 2, "value2";
  a atPut 3, "value3";

  k = *a;

  while (++k)
    printf ("%s\n", *k);

}

When run the program produces output like this.


value0
value1
value2
value3

There’s a more complete discussion of how math operators work with Collection objects and Collection's subclasses further on. See CollectionMathOperators.

Compound Statements

The result of evaluating one message can be the receiver of another message, as in the following example.

int main () {

  ReadFileStream new infileStream;
  Array new pathDirs;
  Integer new nDirs;
  SystemErrnoException new e;

  /* Substitute the path of the file here. */
  infileStream openOn "/path/name/here";
  if (e pending)
    e handle;

  nDirs = infileStream streamPath split '/', pathDirs;

  printf ("%i\n", nDirs);
  printf ("%s\n", pathDirs at 0);
  printf ("%s\n", pathDirs at 1);
  printf ("%s\n", pathDirs at 2);
}

The receiver of the method split, in the statement

nDirs = infileStream streamPath split '/', pathDirs;

is the result of sending the message streamPath to infileStream. The result is the path name of infileStream, a String.

The result of one message must be a member of the same class as the receiver of the next message, or a member of a subclass if the message refers to a method.

Note the use of the SystemErrnoException object, e. This class is used for recording system errors from methods that interact with the operating system. The later sections describe exception handling in more detail.

The Ctalk Language Reference describes the class and instance variables of each class, and the return classes of methods.

Expressions as Receivers

If you enclose a simple expression in parentheses, then Ctalk can treat the complete expression as a receiver. This expression provides an example.

("Hello, " + "world!") length;

This expression returns the result 13, because the expression "Hello, " + "world!" returns a String object containing the result, the complete String, "Hello, world!", the receiver of the length message.

C variables and objects work equivalently in receiver expressions. So the following example,

String new s1;
String new s2;

s1 = "Hello, ";
s2 = "world!";

(s1 + s2) length;

is equivalent to the example above, as is this example:

char s1[MAXLABEL];
char s2[MAXLABEL]

strcpy (s1, "Hello, ");
strcpy (s2, "world!);

(s1 + s2) length;

Method parameters and self also work normally within simple receiver expressions. The following example does exactly what you would expect it to do.

String instanceMethod catLengthArg (String sArg) {
  printf ("%d\n", (self + sArg) length);
}

int main () {
  String new s;
  String new sArg;
  s = "Hello, ";
  sArg = "world!";
  s catLengthArg sArg;
}

You can overload common C math operators in just about any case where you have an expression as a receiver. Operator precedence in Ctalk works a little differently than C, however, because non-operator methods bind more tightly than math operators; that is, they have a higher precedence.

So an expression like the following doesn’t do what you expect.

("Hello, " + "world ") + ("again" + "!") length;

That is because the length message has a higher precedence that +, so length evaluates only the subexpression, ("again" + "!"), which then generates an invalid operand exception when the second + is evaluated, due to the fact that the length method returns an Integer, while the receiver of the second + message is a String.

To get length to evaluate the complete expression, enclose the expression in parentheses.

(("Hello, " + "world ") + ("again" + "!")) length;

Here’s another example of an expresison where it’s necessary to add parentheses.


(*itemPtr) org x = self resources integerAt "scrollWidth" +
  self resources integerAt "leftMargin";

This expression is ambiguous because the ‘+’ operator in the argument list is overloaded in both String and Integer classes. So if Ctalk is to interpret the argument list, it might consider the first integerAt token to use the rest of the expression as its single argument, in which case Ctalk would interpret the argument expression like this, and try to perform an increment of the "scrollWidth" token, which isn’t (as yet) supported, and would (probably) make no sense, because "scrollWidth" is a key in the AssociativeArray, "resources".


(*itemPtr) org x = self resources integerAt ("scrollWidth" +
  self resources integerAt "leftMargin");

Instead, what we really want is to interpret the complete argument expression to the ‘=’ operator as this.


(*itemPtr) org x = (self resources integerAt "scrollWidth") +
  (self resources integerAt "leftMargin");

This causes Ctalk to first evaluate each operand of the ‘+’ operator, which results in Integer operands, so Ctalk evaluates the entire expression as an integer addition, and it uses the Integer: + method, and returns the addition of the two Integer values that are stored in the "resources" AssociativeArray.

Variable Promotion and Type Conversion

This section could also be titled When to use become, copy, or =.

Because objects can be much more complex than C data types, variable promotion and type conversion become much more complex operations in object oriented languages than in C. This will become apparent later on.

For now, because we’re discussing basic object classes, it should be sufficient to look at the methods of class Magnitude for our examples, which is the superclass of classes like Character, String (a subclass of Character), Integer, and LongInteger.

You need to use the Magnitude methods asCharacter, asInteger, and asLongInteger to perform type conversions explicitly. The Ctalk Language Reference describes the type conversion methods for each class.

When making assignments, you can use = for receivers and arguments that a function or method declares explicitly, or with constant receivers and objects. The classes Integer, Character, and String each implement a = method, so a program would use = with objects that are instances of these classes, or with constants like 1, a, or "Hello, world!"

In other cases, if a statement needs to make an assignment and the statement contains receiver or instance variables, or the expression contains an operand that the program cannot determine until run time, or if the receiver or operand are of completely different classes, then become (class Object) can in most cases perform the type conversion and assignment. This is often the case with classes that represent complex data, like files or windows.

The become method takes into account situations where the type of a receiver or local object may change during the course of a program’s execution.

Warning: However, the method become alters or even deletes the original object. That means you should not use it with objects that are passed as Symbol values, or other expressions where an application uses an object outside of it original scope.

Here’s a partial example, from the attachTo method in X11CanvasPane. The become method mutates the instance variables, paneBuffer and paneBackingStore into X11Bitmap objects. Because the front end does not know for certain that the objects have been mutated (because there’s nothing syntactically special about a method like become that indicates its function). So when the front end encounters the initialize method (which is defined in class X11Bitmap), the language may only be able to assume that paneBuffer and paneBackingStore are still Symbol objects, which do not have an initialize method. So the front end generates a warning and informs you that the statement’s evaluation is going to wait until run time.

X11CanvasPane instanceMethod attachTo (Object parentPane) {
  X11Bitmap new xPaneBuffer;
  X11Bitmap new xPaneBackingStore;
  self super attachTo parentPane;
  self paneBuffer become xPaneBuffer;
  self paneBackingStore become xPaneBackingStore;
  if (parentPane containerMode == "full") {
    self viewWidth = parentPane size x;
    self viewHeight = parentPane size y;
    self size x = parentPane size x;
    self size y = parentPane size y;
    /*
     * These two statements generate warnings, and 
     * Ctalk waits until run time to evaluate them.
     */
    self paneBuffer initialize self size x, self size y,
      self depth;
    self paneBackingStore initialize self size x, self size y,
      self depth;
  } else {
    fprintf (stderr, 
	     "attachTo (class X11CanvasPane) : undefined containerMode.\n");
  }
  return NULL;
}

If you want better statement checking, then you can try performing whatever operations you need first (in this example, those are the statements that contain the initialize method), and then mutating the objects with become second.

X11Bitmap new xPaneBuffer;
X11Bitmap new xPaneBackingStore;
...
xPaneBuffer initialize self size x, self size y,
      self depth;
xPaneBackingStore initialize self size x, self size y,
      self depth;
...
self paneBuffer become xPaneBuffer;
self paneBackingStore become xPaneBackingStore;

Classes like X11PaneDispatcher, X11CanvasPane, and X11TextPane use a lot of object references. There are some factors that you need to watch out for when working with Symbol objects that refer to other objects.

We use X11Bitmap objects because they allow us to maintain graphics features, like colors and fonts, within the X11Bitmap object, which considerably simplifies the classes of higher level objects of the Pane subclasses. It also helps guarantee that we need to set graphics features only at the beginning of the program, or once again only when the program requests that the window display at different font or color, or some other graphics operation.

There’s more discussion about graphics further on, and the next sections talk more about object references.

When a statement needs to duplicate objects of the same class, then it should use the copy (class Object) method. Remember, though, that the receiver of the copy message is the target of the copy operation. The copy method completely replaces the receiver with the source object that is copy’s argument. That means the original receiver object is no longer available.

If a class contains data that requires a special protocol to duplicate objects, then it can implement its own versions of these methods, or use any other way it needs to assign objects.

Object References

The Symbol class gives programs the ability to use the same object anywhere in a program, and makes it much easier to maintain multiple references to objects. Symbol objects accomplish this by maintaining, instead of a unique sub-object, a reference to another, original object.

From here to the end of the tutorial, you’ll see a lot of information about object references. If the information seems to be repetitive, it’s because references are essential to writing complex programs and add a level of complexity to the language that you need to be aware of.

Passing objects by reference allows programs to modify objects just about anywhere. Then, in many cases, the application can use an object without knowing beforehand exactly what class the object is, or what data the object contains, until an expression is actually evaluated. It’s something that you need to be aware of when writing complex programs.

The = method of class Symbol behaves differently depending on its context. It can either assign an object to a Symbol's label, or it can duplicate the symbol’s reference, depending on whether the Symbol object is the receiver a method like *.

The * method always provides access to the object referred to by the receiver. In this sense, * acts very much like C’s * operator. It’s also convenient to refer to Symbols in this context as pointers, following the C terminology. The following example might make this a little clearer.


int main () {

  Symbol new sym1;
  Symbol new sym2;
  Symbol new sym3;
  Integer new i;
  
  i = 2;

  sym3 = sym1;   /* Save the original value of sym1. */

  sym1 = i;
  printf ("%d\n", sym1);

  *sym2 = i;     /* Pointer context - refer to the object using '*'. */ 
  printf ("%d\n", *sym2);

  sym1 = sym3;  /* Restore sym1 to the original object. */

  i = 4;
  
  *sym1 = i;    /* Pointer context again. */

  printf ("%d\n", *sym1);
}

The * method is also a convenient shorthand for the setValue and getValue methods, which can fit into places where a prefix operator might be confusing or just plain syntactically incorrect, and which have a higher operator precedence (remember that method labels bind more tightly than operators). This somewhat hypothetical example shows one case where the precedence of an operator can cause unpredictable behavior.


TreeNode new head;
TreeNode new tChild;
Symbol new tSib;

tSib = TreeNode basicNew "Sibling Node";
head makeSibling tSib;

....                                  /* Do some stuff */

head siblings map {
  *tSib = self;

  tChild = TreeNode basicNew "Child Node";

  ...                               /* Do some more stuff. */

  tSib getValue addChild tChild;    /* This expression
                                       evaluates correctly. */

  *tSib addChild tChild;            /* This expression doesn't 
                                       evaluate left-to-right as
                                       you might expect, because 
                                       "addChild" has a higher
                                       precedence than "*". */

  (*tSib) addChild tChild;          /* Adding parentheses causes the
                                       "*" operator to be evaluated
                                       first, so this expression
                                       evaluates left-to-right. */
}

In some cases, object references tell Ctalk to wait until the application actually runs before trying to evaluate an expression. Consider another somewhat hypothetical example.

If we declare an instance variable like this:

X11Pane instanceVariable container Object NULL;

And then, further down, the program contains an expression like this:

self container deleteAndClose;

Ctalk cannot generate the correct code, because the value of container is actually an Object, and it can’t determine beforehand from the Object class library where to find the deleteAndClose method. We might assume in this case that container is also a X11Pane, but using class membership to bind receivers and methods raises all sorts of havoc; for example, when performing math operations with instance variables. And in some cases, the application needs to take care that the method’s class–here, the X11Pane class defines the deleteAndClose method–actually has been evaluated in order to avoid an undefined forward reference.

Instead, a class might declare an instance variable like this.

X11Pane instanceVariable container Symbol NULL;

Noting that a method like getValue (defined in class Symbol) returns Any class of object, Ctalk then knows that it should wait until the program is run before trying to evaluate the expression. So expressions like this example, from the subPaneResize method above, isn’t actually evaluated until the program runs.

__subPaneRef getValue displayText;

In some cases, if you try to use an instance variable message with an object reference, Ctalk generates a warning that the instance variable message is ambiguous.

For example, the x and y messages in the printf(3) arguments are ambiguous.

int main () {
  Point new p;
  Symbol new s;

  p x = 125;
  p y = 175;

  s = p;
  /*  Ambiguous messsages, "x," and, "y." */
  printf ("%d %d\n", s getValue x value, s getValue y value);
}

In these cases, Ctalk generates a warning message, so when building this program, the output looks like this (assuming the input file is named example.c).

$ ctcc example.c -o example
/usr/local/bin/ctalk    example.c  -o example.i && /usr/bin/gcc  example.i -o example -lctalk -L/usr/local/gcc-current/lib -L/usr/local/lib -L/usr/lib -L/usr/local/lib -L/usr/lib  
example.c:10: Ambiguous operand: Warning: Message, "x," follows method, "getValue," which returns "Any" class object.
example.c:10: Ambiguous operand: Warning: Message, "y," follows method, "getValue," which returns "Any" class object.

When evaluating references, Ctalk can generally figure out that successive calls to getValue (or *) mean the same reference, but a program shouldn’t depend on this behavior, especially when there are instance variable messages or other method messages in the expression.

The following examples might help explain this.

Symbol new mySym;
Integer new myInt;

myInt = 1;

mySym = myInt;
printf ("%d", mySym getValue value);           /* Correct. */


mySym = myInt asSymbol;
printf ("%d", mySym getValue value);           /* Still correct. */

printf ("%d", mySym getValue getValue value);  /* Not necessarily correct. */

In the third example, Ctalk can still figure the expression out because the receiver, mySym is the same for both getValue messages. To repeat, programs shouldn’t depend on this being the case for complex expressions.

These characteristics also apply to the subclasses of Symbol; for example, the Key class. Key objects are mainly used as the indices of Collection objects and its subclasses. Keys help simplify the task of looping through Collection members. They also help more specialized subclasses extend the basic Collection object. There’s more discussion of how to use Keys and references to them below. See Collections.

Multiple Indirection

The previous section described how to assign object references in relatively simple expressions. However, in C it is often necessary to provide multiple levels of indirection. Declarations like ‘char **argv’ and ‘char *myvar[512]’ occur frequently in C programs.

If objects need to use multiple levels of referencing and dereferencing, We’ve already mentioned Symbol class’s getValue method, and its synonym, *. Ctalk also provides several other methods for this purpose: addressOf (defined in Object class) and deref (defined in Symbol class).

These two methods make it possible to construct expressions like those in the following example.

int main () {
  Integer new i;
  Symbol new s;

  i = 1;
  s = i addressOf;
  printf ("%d\n", i);
  printf ("%d\n", s deref);
}

When run, the output of the two printf statements is identical.

The following examples also describe shorthands for the methods. addressOf is functionally equivalent to the C ‘&’ unary operator, and deref is functionally equivalent to the unary ‘*’ operator.

If a program needs to use multiple levels of indirection, it can use the addresOf and deref messages multiple times, as in this example.


int main () {
  Point new p;
  Symbol new s;

  p x = 1;
  s = p x addressOf addressOf;
  printf ("%d\n", p x);
  printf ("%d\n", s deref deref);
}

Or even,


int main () {

  Point new p;
  Symbol new s;

  p x = 1;
  s = p x addressOf addressOf addressOf;
  printf ("%d\n", p x);
  printf ("%d\n", s deref deref deref);

}

In each case, the output of the printf statements is the same.

The main difference between the addressOf and deref expressions, compared with = and getValue, is that = only converts a symbol value once, while addressOf always returns a Symbol object that points to the receiver.

In practice, it works out that deref allows statements with a little more precision than a series of getValue statements would allow.

The -> Method and C Object Members

Ctalk also overloads the C language’s dereference (->) operator so you can obtain the value of any object’s C members by referring to the C name that Ctalk uses internally.

The member names of a C OBJECT typedef are:

__o_name
__o_classname
__o_class
__o_superclassname
__o_superclass
__o_p_obj
__o_value
instance_methods
class_methods
instancevars
classvars
scope
nrefs
next
prev

Note: The use of __o_classname and __o_superclassname are being phased out. You should use the CLASSNAME and SUPERCLASSNAME macros instead.

If the -> operator is a method, then Ctalk recognizes both the old and new names. However, if -> is used with an OBJECT * as a C operator, then it is necessary to use the CLASSNAME and SUPERCLASSNAME macros.

Ctalk uses some special semantics with the -> method. First of all, when given a C name as an argument, Ctalk returns a Symbol, String or Integer object with a reference to the original object whenever necessary.

That means, where an object contains a reference to its class object, for example, the -> method returns a reference to the actual object, so you can work with the actual object representation of the C value.

This can be a bit tricky in practice, because you need to make sure that a program knows what class of object to expect in return - a String, Integer, or Symbol.

Often Ctalk can tell by the context of a statement what class of object to expect, as in this example.


int main () {
  Integer new i;
  Integer new mbrInteger;
  String new mbrString;
  Symbol new mbrSymbol;

  mbrString = i -> __o_name;
  printf ("name: %s\n", mbrString);
  mbrString = i -> __o_classname;
  printf ("classname: %s\n", mbrString);
  mbrSymbol = i -> __o_class;
  printf ("class: %p\n", mbrSymbol);
  mbrString = i -> __o_superclassname;
  printf ("superclassname: %s\n", mbrString);
  mbrSymbol = i -> __o_superclass;
  printf ("superclass: %p\n", mbrSymbol);
  mbrSymbol = i -> __o_p_obj;
  printf ("parent: %p\n", mbrSymbol);
  mbrString = i -> __o_value;
  printf ("value: %s\n", mbrString);
  mbrInteger = i -> scope;
  printf ("scope: %d\n", mbrInteger);
  mbrInteger = i -> nrefs;
  printf ("references: %d\n", mbrInteger);
  mbrSymbol = i -> instancevars;
  printf ("instancevars: %p\n", mbrSymbol);
  mbrSymbol = i -> classvars;
  printf ("classvars: %p\n", mbrSymbol);
  mbrInteger = i -> instance_methods;
  printf ("instance methods: %p\n", mbrInteger);
  mbrInteger = i -> class_methods;
  printf ("class methods: %p\n", mbrInteger);
  mbrSymbol = i -> prev;
  printf ("prev: %p\n", mbrSymbol);
  mbrSymbol = i -> next;
  printf ("next: %p\n", mbrSymbol);
}

That’s because the = method of each receiver - mbrString, mbrInteger, and mbrSymbol - perform the necessary type conversions.

However, if you use the -> method inline, as in this statement:

stdoutStream printOn "name: %s\n", self -> __o_name;

You need to make sure that the method printOn (from WriteFileStream class in this example) knows what class the argument self -> __o_name is going to return. For arguments that are members of common classes like String and Integer objects, this normally isn’t a problem.

Symbol objects, however, can be trickier, because they are references. So to get the actual C pointer to an OBJECT type, a program needs to use a statement like the following.

stdoutStream printOn "superclass: %p\n", self -> __o_superclass asSymbol;

This can lead to some very convoluted statements. To retrieve the value instance variable of an object reference, you would need a statement or two like these examples.

__objectRef getValue -> instancevars getValue value;

Or even more confusingly,

refValue become __objectRef getValue -> instancevars getValue value;

As mentioned in the last section, assigning an object to a Symbol or using the asSymbol method have the effect of dereferencing an object, similar to referencing and dereferencing objects in C with the ‘*’, ‘&’, ‘->’, and ‘.’ operators. The effective use of these C operators takes practice, and the same is true of Ctalk.

So you can have a series of statements like the following example.

Symbol new varValue;
...
varValue = self -> instancevars;
myString printOn "value: %s (%s)\n",
  varValue getValue value -> __o_value,
  varValue getValue value -> __o_classname;

But you can also reference and then dereference an object multiple times.

Symbol new varValue;
...
varValue = self -> instancevars asSymbol;
myString printOn "value: %s (%s)\n",
  varValue getValue getValue value -> __o_value,
  varValue getValue getValue value -> __o_classname;

There’s another, more serious issue when parsing instance variables. That is, if Ctalk needs to make a copy of an instance variable, it cannot also copy the links to the object’s other instance variables. To work reliably, Ctalk would need to copy the entire set of objects. That means, normally, you would not be able to loop through a set of instance variables to examine their contents.

Instead, you would need to create statements like these:

__objectRef getValue -> instancevars -> next getValue value;
__objectRef getValue -> instancevars -> next -> next getValue value;
...

And this is probably not what you want, and Ctalk does not provide special handling for -> arguments, because ->, as much as possible, needs to work like any other method.

Instead, Ctalk provides a method, mapInstanceVariables, defined in Object class, that performs this work for you. (There is also a mapClassVariables method, described below.) It works similarly to the map methods in List and other collection classes, except that it works specifically on instance variable lists. It’s also designed to handle object references differently.

For example, this program looks rather simple.

int main () {
  Symbol new sym;
  Point new p;
  p x = 100;
  p y = 150;

  sym = p;

  sym getValue mapInstanceVariables {
    if (self -> __o_name != "value") {
      printf ("name:  %s\n", self -> __o_name);
      printf ("value: %d\n", self value -> __o_value asInteger);
    }
  }
}

Except that there is a slight pitfall - sym is a Symbol, that is a reference to a Point object, p, and Ctalk can’t determine the actual class of the block’s receiver until the program is run. (The = method in the statement sym = p; doesn’t have very many special semantics, either.)

However, mapInstanceVariables can take this factor into account, and it will check for possible receiver classes and if necessary issue a warning.

The output of the example above looks something like this.

Warning: From mapInstanceVariables (class Object):
Warning: The argument block thinks the receiver's class is, "Symbol,"
Warning: when it is actually, "Point."
name:  x
value: 100
name:  y
value: 150

If you want to avoid the warning, you can rewrite the program like this.

int main () {
  Point new p;
  p x = 100;
  p y = 150;

  p mapInstanceVariables {
    if (self -> __o_name != "value") {
      printf ("name:  %s\n", self -> __o_name);
      printf ("value: %d\n", self value -> __o_value asInteger);
    }
  }
}

You should also note that -> returns the value of an object as a String object. That means if the program needs to perform any numeric conversions, it should first morph the result into an Integer, Float, or other class, using asInteger (defined in Magnitude and String classes), and so on.

Here is an example of how you might manage some simple numeric conversions, with class variable receivers provided by the mapClassVariables method, defined in Object class. When working with objects, methods like printOn and writeFormat (defined in String and WriteFileStream classes) are better at formatting the values of objects than simply using printf(3).

int main () {
  WriteFileStream new w;
  String new s;
  String new s_element;
  Symbol new varRef;

  s = "";
  w mapClassVariables {
    varRef = self;
    s_element printOn "%s (%s)\n",
      varRef getValue -> __o_name,
      varRef getValue -> __o_classname;
    s = s + s_element;
    s_element printOn "%d (%s)\n",
      varRef getValue -> __o_value asInteger,
      varRef getValue -> __o_classname;
    s = s + s_element;
  }
  stdoutStream writeFormat "%s\n", s; 
}

Here the use of writeFormat is mainly for illustration. It is likely that writeFormat will be deprecated in future releases.

There will certainly be times when you need to work with object references in C. and there are a number of Ctalk library API functions that can help you translate between object values (which are represented internally as formatted char *’s), and C variables, and vice versa. These functions and others appear in many of the examples in this this document and are described in more detail in the Ctalk Language Reference.

void *__ctalkGenericPtrFromStr (char *);
void __ctalkObjValPtr (OBJECT *, void *);
void *__ctalkStrToPtr (char *);
char   __ctalk_to_c_char (OBJECT *);
double __ctalk_to_c_double (OBJECT *);
int    __ctalk_to_c_int (OBJECT *);
char   *__ctalk_to_c_char_ptr (OBJECT *);
void   *__ctalk_to_c_ptr (OBJECT *);
long long int __ctalk_to_c_longlong (OBJECT *);
OBJECT *obj_ref_str (char *);

Default Methods and Instance Variable Messages

We should mention explicitly one other point about methods and messages, and it is related to some of the examples in the previous sections. Ctalk objects also respond to messages that name instance variables.

All Ctalk objects contain the instance variable, value, which is inherited from class Object. Because of this, if you use an object without a message, Ctalk uses the value message by default.

In this simple example, the following two statements are equivalent.

printf ("%d\n", myInt);

printf ("%d\n", myInt value);

With more complex objects, for example like those in the Stream and Pane subclasses, statements that contain instance variable messages can quickly become complex. The later chapters and code snippets in The Ctalk Language Reference describe how to use instance and class variable messages with complex objects.

The example from the previous section is typical.

Point new p;
p x = 100;
p y = 150;
...

Here x and y are not methods, but instance variables defined in Point class. These messages often work like method messages in expressions, except that you can also assign values to them, by giving the name of the instance variable and the object it belongs to.

Assigning and Evaluating Complete Objects

The rules for using a default value method have a few exceptions. They occur mainly when Ctalk translates between objects and C. That’s partly because C normally assigns objects by value, while Ctalk works with object references. This manual discusses object references here and in more detail later on, but here we’ll mention the exceptions so that you’re aware of them.

As we’ll get to, objects are represented internally as an OBJECT * C type, and that’s how programs access them using C.

So we can assign an object to a C variable with a set of statements like the following.


Object new myObj;       /* A Ctalk object. */
OBJECT *myObj_alias;    /* A C reference to an object. */

myObj_alias = myObj;    

Then you can access the object using the myObj_alias variable.

In this case, Ctalk doesn’t try to cast the object - it’s already an OBJECT *. That is, Ctalk doesn’t consider that there’s an implicit value message following the object. (Though you can, of course, add a value message to the statement if you actually do want the value.)

The same is true of using printf () to print an object. If Ctalk sees that you want to print a pointer, and there’s no message following the object, then Ctalk does just that. These statements simply print the address of an object.


printf ("%p\n", myObject);
printf ("%#x\n", myObject);

The second printf () statement might generate a warning with some compilers, but as long as pointers are the same size as ints, the statement should work fine.

The other case where the complete object is used on its own is in the case of the self keyword. Ctalk doesn’t consider that it has a default value message following it. So the examples above are true for self also.


OBJECT *self_alias;    /* A C reference to an object. */

self_alias = self;

printf ("%p\n", self);
printf ("%#x\n", (unsigned int)self_alias);  /* The cast prevents 
                                                a warning from the
                                                compiler. */

Finally, if there’s a C statement that you don’t want Ctalk to evaluate as a value assignment, you can always use the eval keyword to so that Ctalk interprets the statement verbatim.


myObj_alias = myObj;      /* These two statements are equivalent. */
myObj_alias = eval myObj;

Class Objects and Introspection

No object oriented programming manual would be complete without some discussion of the way objects refer to themselves. The semantics of objects might seem obvious after a bit, but they cause Ctalk to use another set of rules when referring to classes themselves.

For example, if a program contains the statements,


myString = "Key";                     /* Select the Key class. */
printf ("%s\n", myString superclassName);

The program will display, ‘Character,’ which is the superclass of String class, and not Symbol, which is the superclass of Key class.

For these cases, the method classObject (in Object class), provides the actual class object when you give it a class name.

With the use of classObject, the code sample would become this.


myString = "Key";                 
myClassObj = myString classObject;
printf ("%s\n", myClassObj superclassName);

As a general rule, if you’re working with elements of an object, it’s safer to work on the actual object, and not another object that simply contains a classes’ name.

However, if you’re trying to find information that’s external to an object, for example, its file path in the class library, it’s safe to use the name of a class expressed as the value of a String object.

The difference is not always clear-cut, and it might take some checking of what kind of receiver the method expects and what its function is in order to figure out how a class argument’s semantics are going to work. Here’s a fragment from the classdoc program that provides a small illustration of this. Refer to the manual page, classdoc(1) for more information about classdoc.

Application new classDocApp;
String new classArg;
Object new classObj;

classObj = classArg classObject;  /* classArg contains the class name
                                     supplied by the user. */

printf ("%s\n", classObj superclassName);
printf ("%s\n", classArg libraryPath);
printf ("%s\n", classDocApp classDocString classObj);


Next: , Previous: , Up: Top   [Index]