Languages like C identify and call functions by matching a label in the source code against an identifier in an object module, and the compiler (or more properly, the linker or interpreter) inserts a call to the function in the program.
In Ctalk, like many other object oriented languages, objects respond to messages that refer to methods defined by the object’s class or the object’s superclasses.
Note: The following sections contain a lot of material, the importance of which may not be immediately clear. If necessary, you should skip ahead to the section, Assigning Values, to see how these concepts fit together. See Assigning Values.
Here is an example. If you wanted to create a new
WriteFileStream
object, you would add a statement like this to
the program.
WriteFileStream new myOutputStream;
For the moment, we should also mention that most constructor methods should first contain a ‘super new’ message like this one. (We’ll discuss the process in more detail later on.)
WriteFileStream super new fileStreamName;
When the receiver, WriteFileStream,
receives the message,
new,
the process of executing the methods that create a
WriteFileStream
object works in the following
manner.
Class Message Action ----- ------- ------ |- Object <-------------- new 2. Return new, generic object. | ^ | |- Stream \ | | \ | |- FileStream \ | | \ | |- WriteFileStream <----- new 1.Execute | "super new." | v 3. Add the instance variables that WriteFileStream objects need and initialize the class if necessary.
Here is a typical method. This is the instance method
parseQueryString
(class CGIApp.
) The CGIApp
class is in the Ctalk demos/cgi
subdirectory and is used by
the Common Gateway Interface programs in that directory.
CGIApp instanceMethod parseQueryString (void) { String new queryString; String new paramString; Array new queryParams; Array new paramArray; Integer new nParams; String new paramKey; String new paramValue; Integer new i; Integer new j; queryString = self serverEnvironment at "QUERY_STRING"; if ((queryString length == 0) || (queryString isNull)) { return NULL; } nParams = queryString split '&', queryParams; for (i = 0; i < nParams; i = i + 1) { paramString = queryParams at i; j = paramString split '=', paramArray; if (j == 1) { self queryValues atPut (paramArray at 0), "1"; } else { if (j == 2) { self queryValues atPut (paramArray at 0), (paramArray at 1); } } } return NULL; }
The parseQueryString
method parses the parameters of a CGI
query and places them in the application’s queryValues
instance
variable, an AssociativeArray.
The queryValues
instance
variable then contains the key-value pairs of each of CGI
application’s parameters.
If you call a CGI application with a URL like the following example
http://my-web-server/cgi-bin/mysearchprog?term=ctalk&case=ignore&showresults=10
Then parseQueryString
sets the application’s queryValues
AssociativeArray
to the following.
Key Value queryValues at term ctalk queryValues at case ignore queryValues at showresults 10
The parseQueryString
method uses the split
method (class
String
) several times to split the query string at the ‘&’
delimiters, and then each parameter and its value at the ‘=’
character. If a parameter is defined but does not have a value
associated with it, parseQueryString
assigns it the value of
‘1’.
You should also note that the method checks for both a zero-length
input value, and an uninitialized input value with the method
isNull
, which might occur, in this example, if the environment
variable ‘QUERY_STRING’ is not defined.
You might recall from the earlier chapters that if a program uses an operator like ‘+’ or ‘-’ with a C variable, then Ctalk treats the operator as a C math operator. However, if you use a math operator with an object as the receiver, then Ctalk uses a method to perform the operation.
Ctalk’s base classes and methods are located in Ctalk’s class
library. On UNIX systems, the class library normally resides in
/usr/local/include/ctalk
, although the location may vary
depending on the operating system.
If you write your own methods, you might want to keep them in their own directory. In that case, the ‘-I’ command line option includes the directory given as its argument in the class search path. To include the current directory in the class search path, the command line option ‘-I .’ is a convenient shorthand.
One exception is the way Ctalk implements primitive methods. Ctalk implements these methods in C, and you can call them without worrying if a program defines a class.
These methods are available to all classes, so they effectively belong
to the Object
class. You can also subclass these methods, as
is often the case with the constructor new
. The previous
chapters have described in general how to subclass methods, and this
manual describes the process in detail later on.
Here are the primitive methods that Ctalk defines.
class classname
Defines a new class. The receiver is the name of the superclass, and the argument is the name of the class that you want to create.
classMethod alias selector (args)
Declares a new class method. The next section describes the syntax of method declarations. See Method Declarations.
classVariable varname varclass initial-value
Declare a class variable. The receiver is the class of the variable. You must provide a name for the class variable. The statement also allows you to define a class for the class variable and an initial value. See Class Variables.
instanceMethod alias selector (args)
Declares a new instance method. The next section describes the syntax of method declarations. See Method Declarations.
instanceVariable varname varclass initial-value
Define an instance variable. The receiver is the class of the
variable. Unlike class variables, instanceVariable
only defines
a variable’s declaration. A constructor like new
creates the
actual instance variables when it creates an object. See Instance Variables.
new newobjectname
Create a new object. The receiver of new
is the object’s
class.
Note: You might need to use only primitive methods and library
functions if you need to define a method in a basic class like
Object
or Symbol
, where a method can’t always depend on
a class being defined. During initialization, Ctalk programs
require
a number of basic classes: Object,
Symbol,
Integer,
and ArgumentList,
and their
required classes Magnitude
and Character.
If, for
example, you define a method in class Symbol
to print objects,
you cannot use WriteFileStream
class, which needs Symbol
and its methods already defined. So if you write a method that needs
classes other than these basic classes, it is better to define a
class specifically for your program.
For completeness, we should mention that the other exception in the method API is the use of instance and class variable names as messages.
The value
message is an example of a message that refers to an
instance variable. Remember that a statement like the following
example returns only the instance variable, and not the complete
object.
return self value;
Instance variable messages are useful mainly when creating C
references to objects. Methods, on the other hand, generally work
with complete objects. An example is the at
method in class
Array
.
myElement = myArray at 0;
The message at
returns the 0th array element.
When you use objects with C, you must also check to determine that the object references are really the objects that the program expects.
The following sections describe the Ctalk interface to C, and the many of the examples in this manual describe Ctalk’s C API.
As you probably noticed from the example above, you can declare methods in much the same manner as C functions.
However, method declarations have a different meaning, and the declarations allow for multiple methods with the same name in different classes.
The following example is the basic syntax of an instance method declaration.
class instanceMethod
[alias] selector (args) {
... Method body
}
The third term, alias, is optional. It is often used to alias a method to an operator like ‘+’, ‘-’, or another operator that you want to overload in class.
The syntax of a class method declaration is similar, but it uses the classMethod
method.
class classMethod
[alias] selector (args) {
... Method body
}
Methods can be either labels or C operators. Although Ctalk’s syntax is similar to C, it allows more flexibility in the construction of statements.
As we noted earlier, you can change characters from upper case to lower case and vice versa by toggling the fifth bit of the character. Here is that example again.
Character new upperCase; Character new lowerCase; Integer new toggleBit; upperCase = 'Z'; lowerCase = 'z'; toggleBit = 32; printf ("%c\n", upperCase ^ toggleBit); printf ("%c\n", lowerCase ^ toggleBit);
The ^
operator in these statements is actually a method in
class Character
.
Ctalk also provides two methods in Character
class,
toUpper
and toLower
, that provide the same function, and
the methods also check that the receiver is a letter of the
appropriate case before changing the case.
Here is the toLower
method.
Character instanceMethod toLower (void) { Character new result; if (self isAlpha && self isUpper) { result = self asInteger ^ 32; } else { result = self; } return result; }
We could omit the asInteger
message, but using asInteger
,
which is implemented in Magnitude
class, allows us to reliably
check that the receiver of ^
is actually a valid
number, even if the receiver of toLower
is the result
of another statement that translates its value from a completely
different receiver elsewhere in the program.
The result of self asInteger
is an Integer
object, so
it uses ^
from Integer
class to perform the translation.
The asCharacter
method, like asInteger
, is defined
in Magnitude
class.
The value
message in the example above is optional. Ctalk
sends method arguments as complete objects.
Ctalk statements can appear in many of the places that a C expression can appear. Here is another simple example.
int main () { String new ten; ten = "10"; printf ("%d\n", ten asInteger); }
As a general rule, keep in mind that receiver objects and other methods that are part of complex statements work with objects.
There are many cases, however, when you need to use a message like
value
in a statement to return an instance variable - generally
when you are using objects with C statements, which the next section
describes.
value
Message and Interfacing with CWe have already described how refer to objects with C variables in
many places. This section provides a slightly more formal definition
of how and when to use value
.
If you are assigning an object to a C variable, then in most cases you
want to use the value
message. This message, you might remember
from previous chapters, returns the receiver’s value
instance
variable.
So if you want to assign an object’s value to a C variable, you might use a set of statements like the following.
OBJECT *value_object; value_object = self value;
You should not, however, simply use an object like self
on its own, because Ctalk cannot determine exactly how you plan to use
the result. The following example will provide the complete object,
which is not what you want.
OBJECT *value_object; value_object = self;
It is okay, however, to use an object name on its own when used with other objects.
Object new receiverObjectCopy; receiverObjectCopy = self;
Once you have assigned an object to a C variable, you can treat it
as a C struct
. The next section describes the
OBJECT
C typedef
.
OBJECT
TypeCtalk represents objects internally as an OBJECT
typedef, so
when you need to use an object in a C statement, you can create a C
alias for the object by declaring and initializing a C OBJECT
*
.
Here is the OBJECT
type declaration, without some of the
machine-specific definitions. It is located in the file
classes/ctalklib
, and several other places.
struct _object { int sig; char __o_name[MAXLABEL]; char __o_classname[MAXLABEL]; OBJECT *__o_class; char __o_superclassname[MAXLABEL]; OBJECT *__o_superclass; OBJECT *__o_p_obj; VARENTRY *__o_varentry; char *__o_value; METHOD *instance_methods, *class_methods; int scope; int nrefs; struct _object *classvars; struct _object *instancevars; struct _object *next; struct _object *prev; int attrs; };
So when a program uses statements like those in the following example, the program is actually creating a C reference to an object.
OBJECT *rcvr_value; rcvr_value = self value;
Here, =
is a C operator. Ctalk evaluates the value
message, and then determines that it doesn’t need to perform
further translation on the objects because the receiver simply needs
the value
instance variable as C struct
.
Generally, C programs are mainly interested in the __o_value
field of an OBJECT *
, because this is where objects store their
data as C char *
’s.
There are also API C functions, like __ctalk_to_c_char_ptr () and __ctalk_to_c_int (), that translate objects to C. The Ctalk Language Reference describes these functions.
Methods can return any type of value. Ctalk can usually figure out what how to translate a C language value into a form that it can use internally.
These are all valid return statements.
return "hello!"; return a; return a + 2; return TRUE; return self; return myObject className;
Occasionally, a method may need to return the result of a
complex expression or an expression whose result isn’t known
until run time. In that case, you can use the eval
keyword to tell Ctalk to wait until the program is run
before evaluating the expression, as in this example.
return eval <expression>;
Ctalk treats methods that return arrays a little
differently. For a method to return a C array, the array
must be explicitly declared; that is, the following
definition is translatable into an Array
object,
int i[10];
while this statement is not.
int *i;
For a method to return a C array, the method’s definition
must specify that it returns an Array
class object.
Otherwise, the object might not be directly translatable to
a basic Ctalk class, and in that case you should consider
assigning the variable to a Symbol
object and
returning a reference to the variable. The same is true of
multi-dimensional arrays, like this one.
int i[10][10];
Again, if a method needs to return a multi-dimensional
array, it should consider assigning to to a Symbol
object and returning a reference to the method.
The size of a returned Array
is the same as the
size of the array declared in C, regardless of how the
method initializes the C array members. You need to keep
that in mind when returning C arrays that may or may not
have their members initialized.
Ctalk treats arrays declared as char[size]
as
String
objects, unless the method’s definition says
it returns an Array
, in which case the method returns
an Array
with each member being a Character
object; otherwise, methods return String
objects for
char[]
declarations.
Occasionally, you may find that you need to write a method
that declares a return value’s class explicitly. In these
cases, Ctalk also provides a number of method return
statements; e.g.; methodReturnInteger
and
methodReturnTrue
. The Ctalk Language
Reference contains a complete list of them.
In a previous section, we described a few of the principles of object references. See Object References. This section briefly mentions a few aspects of working with actual references within methods.
There are many examples of methods that use object references in the Ctalk class libraries. Here, we’ll mention only a few things to keep in mind when using references in your own methods.
Each member of a collection like an AssociativeArray
object, is a Key
object. A Key
object has two
components: a name that is used as a key to look up
the object, and a value that is a reference to the
object that belongs to the collection. Key
objects
are a more specialized subclass of Symbol
, and the
Key
class implements several methods to work with
Key
objects’ labels.
The most important thing to note when working with references, however, is that they can originate anywhere in a program, and your method should not make assumptions about where that object was declared.
However, your method can use the scope of an object to
determine how it is used. For example, it can add or remove
a VAR_REF_SCOPE
or METHOD_USER_OBJECT
scope
from an object. Object scopes are described more thoroughly
in the Ctalk Language Reference.
It’s important to remember that the main point of using object references is that when you change the value of an object, that value changes for every occurrence of that object in a program.
This is often useful for complex objects that persist
throughout the life of a program, like Pane
objects,
which are described below, but it can also cause confusing
and seemingly unrelated errors elsewhere in programs if you
are not careful.
Just as often, however, you may only need to change the value of an object within a method. In that case, it is often easier and safer to work with a copy of the object. In addition to library functions like __ctalkCreateObjectInit that create objects, Ctalk provides several methods that can alter or duplicate objects. See Variable Promotion and Type Conversion.
The preceding sections contain a lot of material to digest, and that is at least partially because there can often be several different ways to write a method.
One basic method that many classes implement is an
overloaded ‘=’ operator, which Ctalk refers to
internally as set_value
, setEqual
, or another similar
identifier.
The function of the ‘=’ method is simple–it sets the value of the receiver object to its argument. However, an assignment operator needs to be able to handle any type of object it is likely to encounter, which makes the method more complicated. The following sections discuss some of these factors.
Integer
ClassOne thing objects have in common is that they all have
values. Generally, the value of an object is
contained in an instance variable named, naturally enough,
value.
All new objects have a value
instance
variable, though some classes (like List
class) may
re-implement its value object(s) differently.
The Object
class defines a method,
addInstanceVariable
, which works nicely for this
purpose. So a basic =
method could simply use
addInstanceVariable
.
Integer instanceMethod = set_value (int __intArg) { self addInstanceVariable ("value", __intArg value); return self; }
Note that the method declares its argument, __intArg,
as an int
, not Integer.
Ctalk commonly uses C
arguments in its basic classes in order to avoid circular
object references. (I.e., we couldn’t easily define an
Integer
object while the Integer
class itself
is still being defined.)
The method above works fine, assuming that the argument is
also an Integer
object. However, often you might
want to assign a Character
, or a LongInteger
, or a
Float
to an Integer
object.
Ctalk methods can handle arguments of any class. Internally, it uses the parameter definition to implement basic argument checking, but the definition is mainly a prototype that tells the method what sort of argument to expect, though the parameter definition is not a hard-and-fast rule. Generally, methods can implement their own argument checking when it’s necessary.
Ctalk provides another method, asInteger,
defined in
Magnitude
class, which you can use for the purpose of
type conversion. The asInteger
method converts other
basic scalar types into Integer
-compatible values.
If we add an asInteger
message, our =
method
might look like this.
Integer instanceMethod = set_value (int __intArg) { self addInstanceVariable ("value", __intArg asInteger value); return self; }
Now we’ll discuss one of the curves in the road. Suppose
that __intArg
is referred to somewhere else in the
program. (It doesn’t matter where else, it could be
anywhere else.)
The receiver’s value must be unique within its scope. While
it sounds nice in theory to be able to refer to a single
object everywhere, the program must still regard scoping
rules. (Though there are many classes, like the Pane
class and subclasses, where programs need to pass objects by
reference in many different places.)
If that’s the case, the method can work on a unique instance of
the argument. The method does this with the copy
(class
Object
) method and a local object.
Integer instanceMethod = set_value (int __intArg) { Integer new localInt; localInt copy __intArg asInteger; __ctalkAddInstanceVariable (self, "value", localInt value); return self; }
The copy
and become
methods (both of them are
in Object
class) allow you to change objects from one
class to another, but there is an important difference. The
copy
method creates a unique copy of its argument,
while become
converts the original object into the
receiver. (Variable promotion and type conversion are
discussed in detail elsewhere. See Variable Promotion and Type Conversion.) So in this case, the =
method
needs to use the copy
message.
Now we need to consider another factor. That is, the class of an object’s value does not need to be the same class as the object itself. This is often the case with instance variables, which belong to the parent object’s class but contain values of a different class.
Let’s return to an earlier example, where we defined a class
called PositiveInteger
, which could hold only
positive numbers. See PositiveInteger Example.
Using the ‘=’ method above, the following program from the earlier section does not work.
Integer class PositiveInteger; ... int main () { Exception new e; PositiveInteger new p; p = 1; /* Wrong - p's value becomes an Integer. */ printf ("%d\n", p + 1); printf ("%d\n", p - 2); if (e pending) e handle; }
That’s because the PositiveInteger
class also defines
a -
method. So when Ctalk sees the expression ‘p
- 2’, it looks for -
from Integer
class,
because p's
value is now an Integer
. There is
no unambiguous way to tell PositiveInteger's
value
class really isn’t correct, so the only alternative is to
redesign the =
method to cope with this issue.
A program can deal with this issue by using Ctalk’s library API and work with the object directly in C.
Integer instanceMethod = set_value (int __intArg) { OBJECT *self_alias; char buf[MAXLABEL]; self_alias = self; sprintf (buf, "%d", __intArg); __ctalkSetObjectValueVar (self_alias, buf); return self; }
Here, we use sprintf(3) and the Ctalk library
function __ctalkSetObjectValueVar
to format the new
value and replace the C object’s value directly. Since the
method only replaces the value in the object’s internal
structure, the class of the value is unchanged, and the
example program above works correctly.
However, this version of =
does not have the ability
to perform type conversions or handle objects out of scope.
As with so much in Ctalk, a more advanced version of this method
might combine the two approaches.
Ctalk’s Object
class provides yet another method
is
, that tells you if an object is a member of a
certain class. So we’ll use that in the =
method to
determine if the value of the receiver actually is an Integer.
Integer instanceMethod = set_value (int __i) { OBJECT *self_alias; char buf[MAXLABEL]; Integer new localInt; if (self value is Integer) { localInt copy __i asInteger; __ctalkAddInstanceVariable (self, "value", localInt value); } else { self_alias = self; sprintf (buf, "%d", __i); __ctalkSetObjectValueVar (self_alias, buf); } return self; }
Further versions of this method need more checking than this. For example, methods frequently need to take into account a receiver’s context, which we’ll discuss below.
In many cases, it’s either convenient or necessary (or both) to
tailor a =
method to a particular class, as the next section
discusses.
Symbol
ClassThis manual often mentions the fact that programs need to
pass objects by reference. The Symbol
class handles
these chores.
When we want to assign a reference to a Symbol
object,
we generally mean one of several things.
Symbol
, copy its value to the
receiver as a duplicate reference, after first removing a
previous reference.
Symbol
-compatible value and create a
new reference to the argument.
NULL
, or (null)
(in
either Ctalk or C), reflect that assignment in the receiver.
Symbol instanceMethod setValue (void *v) { // Set the value of the receiver to refer to the argument. // If the argument is also a Symbol object, set the // receiver's value to refer to the same object. // Otherwise, set the receiver's value to refer to // the argument object. The referenced object's // scope includes VAR_REF_OBJECT scope. OBJECT *rcvr_val, *arg; char buf[MAXLABEL]; self removeValue; rcvr_val = self value; arg = ARG(0); if (arg) { __ctalkSetObjectScope (arg, arg -> scope | 512); if (v is Symbol) { __ctalkSetObjectValue (rcvr_val, (arg->instancevars ? arg->instancevars : arg)->__o_value); } else { #if defined (__GNUC__) && defined (__x86_64) && defined (__amd64__) sprintf (buf, "%#lx", (unsigned long int)arg); #else sprintf (buf, "%#x", (unsigned int)arg); #endif __ctalkSetObjectValue (rcvr_val, buf); } } else { /* Needs to evaluate to a NULL C pointer, not a (null) object. */ __ctalkSetObjectValue (rcvr_val, "0x0"); } return self; }
In many cases, copying the value of one object to another is good enough. The number 2 and the string “Hello, world!” are the same no matter where they occur.
There are many other cases, however, where you want one object to
become another object, or you want a method to refer to the same
object using multiple labels, or you want to perform some
manipulations on Symbol
objects.
In that case, you can write methods that take into account the context
the receiver occurs in. This matters mostly with Symbol
objects,
which need to represent other objects, but you can use the receiver’s context
with other objects as well.
In the section Object References we presented an example of how manipulate references to objects. See Object References.
More briefly here, this section discusses the inner workings of the different contexts taken from that example, like these three:
Symbol new sym1; Symbol new sym2; Symbol new sym3; Integer new i; ... sym3 = sym1; ... sym1 = i; ... *sym2 = i; ...
This should look a little like C, and in fact it works similarly. The statement, ‘sym3 = sym1’ assigns the object ‘sym1’ to the identifier ‘sym3’.
Likewise, the statement ‘sym1 = i’ assigns the object ‘i’ to the identifer ‘sym1’.
However, the statement ‘*sym2 = i’ creates a reference to ‘i’.
‘sym2’ is still a Symbol
that points to an Integer
object.
To determine how we want the assignment to behave, we can use the
method hasPointerContext
(in Object
class), which returns
True if the object to the left of the =
operator has a ‘*’
operator in front of it. If it does, we create a reference to the
object. In a non-pointer context, we use the library function
__ctalkAliasObject () (or its companion,
__ctalkAliasReceiver ()), to assign the argument to the
identifier on the left-hand side of the =
operator.
The method itself is actually quite simple.
Symbol instanceMethod = setEqual (OBJECT *__newObject) { // Alias the argument to the receiver's indentifier. // If the receiver refers to an object reference (it's // preceded by a '*' operator), or if the receiver wasn't // declared as a global or local object (e.g., it's an // instance variable), then create a reference to the // argument object using Symbol : setValue . if (self hasPointerContext) { self setValue __newObject; } else { if (__ctalkAliasObject (self, __newObject) != 0) { self setValue __newObject; } } }
You might have noticed, after Ctalk executes a statement like
‘sym1 = i’, that ‘sym1’ is no longer a Symbol
object;
it’s an Integer
.
Basically Ctalk handles this by following the same process in
the =
method in Integer
class as it does for Symbol
class. That allows us to use the object’s class to find the right
=
method; we don’t need to worry nearly as much about type checking
when making assignments.
However, if the need arises, you can add type checking to a method.
You can do this, simply enough, by adding a check of an object’s class
with the is
method from Object
class.
if (arg is Float) { warning ("Assigning a Float object (or value) to an incompatible receiver."); }
Integer
Class, RevisitedYou may have noticed, from the previous examples, that the class of a
receiver can change if we assign it a new value. The way that the
language’s semantics work is that this is really only an issue when we
change from a Symbol
, which is a reference, to a which is an
actual values, like Integers,
Characters,
Floats
,
and so on.
Normally, when you assign, say, a character to an Integer
object, the compiler and libraries take care of the conversion from
one type to another. When converting from Symbols
to other
classes, however, Ctalk allows us to add this support in a method.
So, for example, if we want to assign as Symbol object to a receiver
that is an Integer
(this isn’t so far-fetched), we might want
to look at this code again.
Symbol new sym1; Symbol new sym2; Symbol new sym3; Integer new i; ... sym3 = sym1; ... sym1 = i; /* sym1 is now an Integer. */ ... sym1 = sym3; /* Now we want sym1 to be a Symbol again. */ ...
In the statement, ‘sym1 = i’, the statement would use the =
method from Symbol
class. But in the following statement,
‘sym1 = sym3’, Ctalk would use the =
method from Integer
class.
Fortunately, we can write the Integer
=
method to support
this. Parts of the method are similar to the =
method in
Symbol
class, and parts of it look like the previous
Integer
=
methods. And we can add a convenient warning
message if the argument belongs to a class for which the Ctalk
libraries don’t normally support type conversions.
Integer instanceMethod = set_value (int __i) { char buf[MAXLABEL]; if (self hasPointerContext) { __ctalkAliasObject (self, __i); } else { if (__i is Symbol) { __ctalkAliasObject (self, __i); } else { if ((__i value is Integer) || (__i value is LongInteger) || (__i value is Character) || (__i value is Float)) { sprintf (buf, "%d", __ctalkToCInteger(ARG(0), 1)); __ctalkSetObjectValueVar (__ctalk_self_internal (), buf); } else { _warning ("Integer : = : Argument, \"__i,\" class %s is not (yet) supported.\n", __i className); } } } return self; }
It’s up to the method to handle any special semantics that the class’s objects use. For example, the following statements should function equivalently.
String new myString; Integer new myIntArg; myIntarg = 123; myString = "123"; /* These two statements should produce identical results, even though one argument is a constant and the other is an expression.*/ myString = myIntArg asString;
Programs generally expect String
constants to be assigned to
unique objects, so Ctalk provides the __ctalkStringifyName ()
function to help deal with this semantic peculiarity. There may be
others, and it’s up to the method to handle them.
String instanceMethod = setEqual (OBJECT *__stringArg) { // String assignment method. Assigns the argument to the // receiver label. Also does some String-specific // semantic stuff for different sorts of String objects. // Returns the new String object. __ctalkStringifyName (self, __stringArg); if (__ctalkAliasReceiver (self, __stringArg) != 0) { self addInstanceVariable "value", __stringArg; return self; } else { return __stringArg; } }
Constructor methods are the methods that actually create objects. Ctalk provides built-in constructors for basic classes, but they can be (and often are) overridden by constructors in the class libraries
Nearly every program example in this tutorial uses at least one constructor. This section describes constructors in more detail and discusses why and how to write them.
In many object oriented languages, and Ctalk among them,
constructor methods are almost always named new.
In an application program, their use is simple.
Here is an example of a simple constructor.
Integer new myInt;
This statement creates a new object, myInt,
that is
an instance of Integer
class.
The statement performs several tasks. First, it creates the
object myInt
and makes sure it has the correct links
to Integer's
class and superclass objects, and sets
the initial value to (null)
.
Secondly, it registers the object and sets its scope
depending on where the statement appears. If the statement
appears in a method or C function, the object is local to
that function, and the name myInt
is visible only
within that method or function, or code blocks within the
method or function. However, if the statement appears
outside of a method or function, then the object’s scope is
global to the entire program. (To be absolutely correct,
though, we should mention that the constructor itself does
not set an object’s scope. That happens after the
constructor is finished creating the object.)
Thirdly, if the class and its superclasses define instance variables, the constructor creates and initializes instance variables for the new object.
Because classes inherit methods, the constructor used by
class Integer
is the constructor from Object
class. If Integer
class defined its own new
method, it would use that constructor instead. This point
is important and is worth repeating. The process is
described in the first section. See Class Hierarchy.
Here again, a diagram should help clarify this concept.
Class Defines Constructor? Statement or Action ----- -------------------- ------------------- Object Yes Create object. ^ | Magnitude No Look up method in superclass. ^ | Integer No Integer new myInt; Look up method in superclass.
The main reason that a class needs to define a constructor is to initialize whatever data the classes’s objects need.
Suppose we wanted to create Integers that were initialized
with the value ‘0’ instead of ‘(null)’. To do
this we might define a subclass of Integer.
Let’s do
that and call the class InitializedInteger.
The first thing we need to do is define the class.
Integer class InitializedInteger;
Then we need to write the constructor for instances of
InitializedInteger
.
InitializedInteger instanceMethod new (String newObjectName) { InitializedInteger super new newObjectName; newObjectName = 0; return newObjectName; }
You should take note of the first statement.
InitializedInteger super new newObjectName;
The statement does a lot of things. First, super
(which is a keyword, not a method), looks up the new
message starting with the receiver’s superclass. It first
creates a normal instance of Object
class, then
promotes it to an instance of InitializedInteger
class. After this statement, newObjectName
is no
longer simply a method parameter, but a fully fledged
object.
Now the process looks like this.
Class Has Constructor? Statement or Action ----- ---------------- ------------------- Object Yes 2. Create Object instance. -> 3. Return Object ^ instance to caller. | | Magnitude No Look up method in superclass. | ^ | | | Integer No Look up method in superclass. | ^ | | | InitializedInteger Yes InitializedInteger new myInt; | 1. Look up method in superclass. | 4. Perform initialization <--------- 5. Return object to application.
The class definition and constructor could go in their own class library, but this example is brief enough to include in the main source file. Here is the complete program.
Integer class InitializedInteger; InitializedInteger instanceMethod new (String newObjectName) { InitializedInteger super new newObjectName; newObjectName = 0; return newObjectName; } int main () { InitializedInteger new i; printf ("%d\n", i); }
Another reason you might want to write your own constructor
is to initialize a class. This is the case with
FileStream
subclasses. The program needs to initialize
the standard input, standard output, and standard error file handles.
Initializing a class variable is much like initializing an
object, except that the class variable only needs to be
initialized once. To make sure that a program doesn’t
initialize the class every time it creates an object, The
class definition needs to include a variable that tells the
constructor that class data is already initialized. Here is
an abbreviated example from ReadFileStream
class,
where the constructor new
calls the method classInit,
which initializes stdin
.
FileStream class ReadFileStream; ... ReadFileStream classVariable stdinStream; ReadFileStream classVariable classInitDone Integer 0; ... ReadFileStream classMethod classInit (void) { OBJECT *classObject, *stdinStreamVar, *classInitVar; if (self classInitDone) return NULL; classObject = __ctalkGetClass ("ReadFileStream"); stdinStreamVar = __ctalkFindClassVariable ("stdinStream", TRUE); __ctalkObjValPtr (stdinStreamVar, stdin); classInitVar = __ctalkCreateObjectInit ("classInitDone", "Integer", "Magnitude", classObject -> scope, "1"); __ctalkAddClassVariable (classObject, "classInitDone", classInitVar); return NULL; } ... ReadFileStream instanceMethod new (char *__streamName) { ReadFileStream super new __streamName; ReadFileStream classInit; return __streamName; }
These methods contain a lot of C code. There are several reasons for this. The first is that it is often easier, and sometimes necessary, to work directly with operating system functions and data in C.
The second reason is that when working with complex objects, it is possible that the program might contain a circular class reference - when a definition requires a class that isn’t defined yet, and the second class in turn relies on definitions from the first class.
If the declaration of the new object contains complex instance variables, especially instance variables that have constructor methods of their own, your constructor needs to construct them also.
For classes that have only a value
instance variable, it is
generally okay to let Ctalk’s internal object creation routines
create a simple object. But constructors do not automatically call
other constructors, so simply declaring instance variables of
complex classes does not mean they get constructed automatically.
If the constructor needs to create a complex instance variable, it must call that instance variable’s constructor explicitly.
Here’s an example, somewhat abbreviated, from the
ANSIScrollingListBoxPane
class, which contains several
ANSILabelPane
instance variables and an ANSIScrollPane
instance variable. The constructor creates the variables like normal
objects, then adds them to the new object using become
(Object
class).
ANSIWidgetPane class ANSIScrollingListBoxPane; ... ANSIScrollingListBoxPane instanceVariable selectedContent ANSILabelPane NULL; ANSIScrollingListBoxPane instanceVariable oldSelectedContent ANSILabelPane NULL; ... ANSIScrollingListBoxPane instanceVariable scrollBar ANSIScrollPane NULL; ... ANSIScrollingListBoxPane instanceMethod new (String __paneName) { ANSIWidgetPane super new __paneName; ANSIScrollPane new scrollPane; ANSILabelPane new selectedContent; ANSILabelPane new oldSelectedContent; ... __paneName scrollBar become scrollPane; ... __paneName selectedContent become selectedContent; __paneName oldSelectedContent become oldSelectedContent; ... return __paneName; }
Note that the scrollPane
is a local object, while
__paneName scrollBar
is an instance variable. The become
method creates separate objects for the instance variables. The same
is true of the selectedContent
and oldSelectedContent
objects.
This procedure works with objects of any class, whether or not the
instance variable’s class is related to the constructor’s receiver
class. To create the instance variables from the class’s superclasses, the constructor uses the statement ANSIWidgetPane super new __paneName
.
Using an expression like ANSIWidgetPane super new __paneName
is
a normal way to tell the constructor to instantiate the new object’s
instance variables from its superclasses. As this section explained
earlier, this is the statement that performs a search back up the class
hierarchy and adds the instance variables that each superclass
defines.
However, it’s necessary, both for reliability and to avoid serious
confusion later in the program, to use super new
when the
object is first created.
If, for some reason, a program needs to instantiate instance variables from the superclasses elsewhere in a program, the __ctalkInstanceVarsFromClassObject () library function performs the same task. It replaces the previous instance variables with fresh copies. But the program should take care that both the old and new variables are either correctly referenced or disposed of, since the interactions between them can become quite complex.
Ctalk defines many macros that provide a standard interface to C
language features. The macros are defined in the include file
ctalkdefs.h
. They include macros like IS_OBJECT,
which
checks if a pointer is a valid object; FMT_0XHEX,
which formats
a pointer into its string representation, and STR_0XHEX_TO_PTR,
which does the converse; when used with a function like
sscanf(), it converts the string representation of a pointer
into an actual pointer.
To include the definitions in a program, include ctalkdefs.h
in
a source or class library file.
#include <ctalk/ctalkdefs.h>
The Ctalk Language Reference provides a complete list of the
macros that ctalkdefs.h
defines.