Jim is one of the founders of Unir Technology Inc., and can be contacted there at 184 Shuman Blvd., Naperville, IL 60563; or at 708-305-0600 or 1-800-222-8647.
The C+@ programming language, an object-oriented language derived from AT&T Bell Lab's Calico programming language, was developed to provide programmers with a true object-based language and development environment. C+@ (pronounced "cat") has the syntax of C and the power of Smalltalk. Unlike C++, C+@ includes a mature class library with more than 350 classes used throughout the system. The C+@ compiler itself is written in C+@, and all of the source for the class libraries is included with development systems.
The Calico project was started at AT&T Bell Labs in the early '80s, after the introduction of Smalltalk and at the same time as C++. Calico was originally used for rapid prototyping of telecommunication services; hence, its heavy emphasis on keeping the language syntax simple and showcasing the power of the graphical development environment.
The name "C+@" is derived from C++ and Smalltalk. The @ method is used in Smalltalk (and C+@) to create objects of class Point from an x and y component. For example, the expression 12 @ 34 indicates that the @ method should be applied to the receiver 12 with the argument 34. Both 12 and 34 are objects of class Integer and are used to initialize the new Point object; see Figure 1. Points are used in graphics and other 2-D applications.
In C+@, all data items are objects and behave uniformly. The programming environment is dynamic, responsive, and supports multideveloper collaboration using an open source storage facility with hierarchical views.
C+@ was designed to be a comfortable companion language to C and C++ rather than an extension to C. It retains C's expression syntax and control statements. C functions and data objects can be accessed directly from C+@. C+@ supports most of the features of Smalltalk as well as multiple inheritance. The complete C+@ graphical development environment is written in C+@ and provides a powerful edit/compile/debug cycle. A GUI builder is included for quickly building applications visually by drawing them on the screen. C+@ can also be used in a command-line mode as an interactive object-oriented shell programming language.
C, C++, and C+@ each have a place in the development environment. C+@ offers significant advantages for rapid prototyping and for providing the object-oriented "backbone" of an application. C still offers an excellent solution where performance takes precedence over productivity. For most large applications, supplementing C with C+@ provides an effective trade-off between application speed and rapid delivery.
The binaries produced by the C+@ compiler are independent of the underlying machine architecture. Without recompiling, applications can be moved from SPARC to 68000 to Intel x86, and so on. C+@ is not interpretive--the binaries are encoded using a sophisticated "beading" technique developed at Bell Labs. Because of the streamlined language design, the C+@ compiler produces these portable binaries with extraordinary speed, without the need for preprocessing or front ends.
C+@ can be used in applications involving pen-based computing, real-time systems, GUIs, object-oriented database servers, and the like. For the past eight years, C+@ has been evolving and has reached a state where AT&T has decided to release it to the commercial world. C+@ is not intended to replace C (or C++), but certainly provides a streamlined companion. (Unir Corporation has released versions of C+@ for SPARC-compatible workstations.)
Comparisons between C+@ and C++ aren't really fair because C++ is syntactically very complex and designed to support a range of programming styles. C++ was conceived as an extension to C and, over the past ten years, has enabled programmers to experiment with object-oriented programming. Just because a program is written in C++ doesn't mean it's object-oriented. In many cases, C++ programmers don't use any of the object-oriented extensions of the language--they use C++ as a better C.
The best language to compare C+@ to is Smalltalk. Like Smalltalk, C+@ was designed for object-oriented programming from the outset. C+@ supports garbage collection and can be used as a typeless language. In C+@, the notion of a pointer is not visible to programmers because all variables can contain pointers. With C+@, new programmers can bypass all of the cryptic syntactic puzzles that continue to fill C++ programming books and have nothing to do with developing an application or maintaining high-quality software. C+@ is much easier to learn than C or C++.
Just as the Smalltalk environment is written in Smalltalk, the C+@ development environment is completely written in C+@.
The primary difference between C+@ and Smalltalk is in constructs for handling control flow of methods are written. In Smalltalk, conditional statements are implemented by sending messages and code. Evaluation is based on the value of the Boolean object. This is similar to the C construct <expression>?<statement>:<statement>;. This form sometimes makes Smalltalk programs difficult to read and maintain. C+@ supports all of the C control-flow constructs and can be easily read by C programmers.
Like Smalltalk, C+@ documentation refers to objects, classes, instances, methods, and messages. All C+@ data items are objects and are classified into classes which are themselves objects. Each object is said to be an instance of a particular class and the data fields of an object are called "instance variables." The class itself is called the "distinguished instance" and can contain global class variables. An object can be manipulated by invoking its methods via messages; only instance methods can access the encapsulated instance variables. Class methods are used to access global class variables.
Programming in C+@ is done by writing class descriptions that define class variables, instance variables, class methods and instance methods. These classes and methods are loaded as required, based on the interaction of the various objects in a workspace. Objects are instantiated by the distinguished instance which responds to class methods.
The class descriptions also describe the inheritance relationships of classes and methods. C+@ supports direct and delegate inheritance. Direct inheritance is commonly used with abstract classes which don't contain instance variables but provide common behavior to other classes; delegate inheritance is used when a class wants to take advantage of the capabilities of another class. Methods not recognized by a receiver will be passed to a delegate without the sender's knowledge.
For example, the Smalltalk Rectangle class can be created as in Figure 2. As the C+@ code shows, a class can be created with two instance variables, similar to a C struct with two fields (origin and corner). Unlike C, the keyword var indicates that these variables can contain values of any type. In Figure 2, the method origin_corner_ can be used to create an instance of a Rectangle object which references two Points created using the @ method. The class method origin_corner_ is applied to the distinguished instance of Rectangle. The origin_corner_ class method causes a new Rectangle object to be created (or instantiated). A reference (the address of the instance) is returned from origin_corner_ and stored in the variable p.
Methods for the class Rectangle can be written as small sequences of statements that look similar to C functions. These methods are grouped in the surrounding braces of the class Rectangle {...} construct, ensuring that variables and methods for a class are grouped in one syntactic construct that can be stored in a separate ASCII file. This provides a form of source-code encapsulation, allowing you to use standard UNIX tools (editors, make, and the like).
As with Smalltalk, C+@ supports binary and unary/keyword methods. The binary methods of C+@ and Smalltalk are very similar; see Figure 3. Smalltalk differentiates the unary methods with arguments, and the keyword methods via colons (:). C+@ only supports the dot-method form used in C++ and in many respects is more consistent than Smalltalk. The lack of a keyword construct has not been a limitation. Smalltalk keyword methods can easily be converted to C+@ using a simple convention.
Although C+@ and Smalltalk are similar at the high-level of class and method design, the actual control-flow statements are very different. In Smalltalk, messages to objects are used to control the flow of execution in a method. In C+@, the control-flow statements look like C statements, making it easier for experienced C programmers to pick up the language. In some cases algorithms have been converted from C to C+@ with little change in the control-flow source.
In Figure 4, the receiver (queue) is sent the message isEmpty which returns a Boolean, (True or False). In the C+@ example, this result is tested and the variable index is set to 0, or the result of applying the next method to the receiver (queue). In the Smalltalk example, the ifTrue:ifFalse: message is sent to the Boolean that results from applying the isEmpty method to the receiver (queue). When ifTrue:ifFalse: is sent, two arguments are passed and evaluated depending on the value of the receiver. If the value is True, then the block [index<=0] will be sent a message to be evaluated. If the value is False, then the block [index<=queue next] is evaluated, again by sending the block a message.
C+@ supports most of the features of Smalltalk with a C syntax. Since all of the C operators are handled as method selectors, operators like << and >> can be invented to shift integer values left or right. These same operators could be defined in another class to indicate some sort of special shifting, sorting, or movement operation. For example, the << operator applied to a Window class object may mean "move it left."
C+@ array constructs are handled just like any other method. The C+@ compiler is capable of rearranging the source code to allow array methods to be handled in this manner.
When the C+@ compiler encounters a statement such as a[i]=j;, the receiver a is sent the []= method with the arguments (i,j). This is equivalent to writing a.[]=(i,j) which most C programmers would not recognize. Therefore, the compiler handles the conversion. Array access is handled in a similar manner. For example, the C+@ statement i=a[j]; results in the [] method being sent to the receiver a with the argument j. This is equivalent to i=a.[](j); which, once again, would be foreign to a C programmer. The C+@ compiler handles the shift and allows you to think in terms of standard C.
Multidimensional arrays are also available in C+@, although the syntax of the indexes is slightly different from C. For example, if a is a two-dimensional array, then it's legal to write a[2,5]=j;. The C+@ compiler converts this to a.[]=(2,5,j) and the correct []= method is selected based on the number of arguments.
Because the standard C array operator is implemented as a normal method in C+@, you can have statements such as array["manager"]="mary wilson"; that's equivalent to array.[]=("manager","mary wilson"). The C+@ foundation class library contains classes for Arrays, Trees, Lists, and Tables. Many of these classes support the []= array assignment method and the [] array access method for indexes other than integers.
The Smalltalk block construct allows small fragments of C+@ programs to be grouped together into objects of class Block, and passed around. These objects can be sent messages to execute the fragment of code encapsulated in the Block object. This is useful when an algorithm needs to be applied to all of the members of an array. An instance method of objects of class Array can be used to iterate over each array element. The block of code can be executed for each element without knowledge of the contents of the block.
C+@ programming consists of declaring a series of classes that contain not only a description of the fields used to store data in an object but also the routines (or methods) used to manipulate these data fields. In Figure 5, these field definitions for instance, variables and the methods are nested inside of a class Name {...} construct. Inheritance relationships and class variables can also be defined (although they don't apply in Figure 5).
In Figure 5, there are two instance variables (x and y). Everytime an object of class Point is created, for example, the object's data area will have two fields that can be accessed in the methods for the object by referring to the variables x and y. In general, these variables will likely contain integer values, but since they were declared using the keyword var, any class of objects can be stored in a Point object's instance variables.
For class Point, there are class methods and instance methods. This is similar to Smalltalk. The class method x_y_ can be used to construct (or create) an object of class Point. The create keyword in the first line of the method manufactures an object of class Point with two instance variables, x and y.
The instance methods can be used to operate on the newly-created object. The setXY instance method is used to initialize the instance variables of the object and the x and y methods are used to access the values stored in the instance variables of the object. Methods look like functions in C, and class methods are denoted with the keyword class.
In all methods, the return value is passed back in a variable that was arbitrarily called r. Unlike Smalltalk, C+@ supports multiple return values which are useful in debugging when extra information needs to be returned with the primary result. The extra information can be ignored when the message is returned.
Once class Point is defined (see Figure 5), it can be used as in Figure 6. A Point object can be created by invoking the x_y_ class method. This method is applied to the distinguished instance object. Once Point is created (via the method x_y_) a pointer is returned and stored in the variable a_point. The instance methods (x or y) can be sent to the object referenced by the variable, a_point. The print message can then be sent to the object returned from the respective instance method. As shown, the print method causes a value to be printed.
A class can have more than one method defined with the same name but the number of arguments must be different. In Figure 7, Point can be extended to include methods that can be used to set the instance variables x or y. These methods will each have an argument and therefore can be distinguished from the original methods that were used to access the instance variables.
The keyword private (Figure 7) can be used instead of method to prevent external access to the method. The setXY instance method is used in the x_y_ class method to initialize the instance after creation.
The Clock class (Listing One, page 106) is one of over 350 classes included in the foundation class library. This class illustrates many of C+@'s features, including inheritance.
Besides providing a flavor for the syntax of C+@, the Clock class illustrates how lightweight processes or threads are supported. The variable process is initialized to contain a Process object that acts as a scheduler for updating the clock. The method clockLoop is sent to the Process object and a Timer object is eventually created to block the process.
The instance variables in the Clock class are tagged with the Class of the object that the variable will likely reference. In C+@, this can be used to provide you with additional information, but isn't essential. All of the instance variables could have been defined in the typeless manner.
One of main advantages--and real tests--of true object-oriented systems is the ability to reuse other people's classes. To this end, numerous people have contributed to the more than 350 reusable C+@ classes. The foundation class library is organized into a hierarchy using a standard file system. The library includes demos, basic library classes, graphics classes, the C+@ compiler, and various tests and system classes. There are also a variety of serial and visual tools that can be used for examples. Figure 8 illustrates a small subset of the directories used to organize the source for the foundation classes.
The binaries for all of the classes are also included with nondeveloper versions of the system. The binaries for the classes are also contained in standard files and are dynamically loaded when used. Usually the binaries are organized into a few directories, and are accessed via a view-path philosophy so that new classes can be tested by one user without impacting another user.
The binaries are portable across various machine architectures. This allows you to develop a class library on a SPARC workstation and move it to an Intel x86-based system without recompiling the source code. This makes it especially attractive for developers who must ship their application for several target architectures.
Besides the foundation classes, hundreds of other, more specialized classes have been developed for projects at AT&T Bell Labs. The largest such project was a prototype switching system used for demonstrations of customer-programmable telecommunication services. This system consists of 175 classes above and beyond the foundation class library and over 75,000 lines of code.
(a)
C+@ Binary Methods
a=b + c;
p=12 @ 34
C+@ Unary/keyword Methods
p=Rectangle.origin_corner_(12@34,100@200);
x=p.origin;
(b)
Smalltalk Binary Methods
a<=b+c
p<=12@34
Smalltalk Keyword Method
p<=Rectangle origin: 12@34 corner: 100@200
Smalltalk Unary Method
x<= p origin
(a)
if(queue.isEmpty) {
index=0;
}
else{
index=queue.next;
}
(b)
queue isEmpty
ifTrue: [ index <= 0]
ifFalse: [ index <= queue next]
class Point
{
/* instance variables */
var x; /* x coordinate */
var y; /* y coordinate */
/* class methods */
class method (r) x_y_ (a_x, a_y)
{
/* create an object of class Point and set x=a_x and y=a_y */
r = create;
r.setXY(a_x,a_y);
}
/* instance methods */
private setXY (x_coordinate, y_coordinate)
{
x = x_coordinate;
y = y_coordinate;
}
method (r) x
{
r = x;
}
method (r) x (value)
{
x = value;
r = self;
}
method (r) y
{
r = y;
}
method (r) y (value)
{
y = value;
r = self;
}
_THE C+@ PROGRAMMING LANGUAGE_
by Jim Fleming
[LISTING ONE]
class Clock {
/* An instance of Clock is a window system application
* displaying an analog clock face with Roman Numerals. */
inherit View view;
/* constants */
const minExtent = (96@112);
const font = Font.new(Rroman.8S);
const iconFont = Font.new(Rbold.12S);
/* instance variables */
BlankView analog;
Bitmap icon;
Integer minuteHand;
Integer hourHand;
Point center;
Point minutePoint;
Point hourPoint;
Rectangle dayRectangle;
Process process;
/* CATEGORY: Creation -- Create an instance of a Clock application. */
class method (_) new
{
_ = create;
Layer.new(_);
}
/* RESTRICTED CATEGORY: Initialization -- Initialize an instance of Clock. This
* method is required by View paradigm. It links into view hierarchy by using
* TaViewU as a delegate (of class View). */
method initialize (aView)
{
Point p;
Integer ox, oy, cx, cy;
Integer y;
Rectangle r;
view = aView;
p = view.extent;
if (p.x < minExtent.x || p.y < minExtent.y) {
view.topView.initializeFailed(true);
return;
}
/* Create a BlankView */
ox = 0;
oy = 0;
cx = p.x;
cy = p.y;
r = Rectangle.origin_corner_(ox@oy, cx@cy);
analog = BlankView.basicNew;
thisSelf.newSubView(r, 1, analog);
thisSelf.init;
}
/* RESTRICTED CATEGORY: Window System Event Handling */
/* Receive a window event. If it is a resize event then adjust our subViews. */
method (_) windowEvent(minor, p)
{
Integer ox, oy, cx, cy;
Integer y;
Rectangle r;
if (minor @! Event.WINDOW_RESIZE_EVENT) return;
p = view.extent;
if (p.x < minExtent.x || p.y < minExtent.y) return;
_ = thisSelf;
process.terminate;
ox = 0;
oy = 0;
cx = p.x;
cy = p.y;
r = Rectangle.origin_corner_(ox@oy, cx@cy);
analog.adjustSubView(r);
hourPoint = nil;
minutePoint = nil;
thisSelf.init;
}
method deleteLayerExit
{
process.terminate;
}
/* RESTRICTED CATEGORY: Private -- Initialize an instance of Clock. Draw analog
* clock face and start a surrogate process to update clock time periodically.*/
private init
{
Point p, q;
Integer radius, xor, i, j;
Float sin30, sin60;
Integer n30, n60;
Integer charHeight, charWidth;
/* set up bitmap for clock icon */
icon = Icon_OL.icon(RClockS, R R);
/* set up clock face */
charHeight = font.charHeight(TAU);
charWidth = font.charWidth(TAU);
p = analog.rectangle.extent;
p.y(p.y - 16);
dayRectangle = Rectangle.origin_corner_(0@p.y,analog.rectangle.corner);
if (p.x > p.y)
radius = (p.y / 2) - 2;
else
radius = (p.x / 2) - 2;
center = (p.x / 2)@(p.y / 2);
for (i=0; i < 3; i = i + 1)
analog.circle(center, radius-i, Bitmap.F_STORE);
sin30 = (Float.fromInteger(30) * Float.radiansPerDegree)
.sin;
sin60 = (Float.fromInteger(60) * Float.radiansPerDegree)
.sin;
radius = radius - charHeight*2;
n30 = (sin30 * Float.fromInteger(radius)).asInteger;
n60 = (sin60 * Float.fromInteger(radius)).asInteger;
q = (n30@n60) + center;
thisSelf.centerString(analog, RVS, font, q);
q = (n60@n30) + center;
thisSelf.centerString(analog, RIVS, font, q);
q = (radius@0) + center;
thisSelf.centerString(analog, RIIIS, font, q);
q = (n60@(-n30)) + center;
thisSelf.centerString(analog, RIIS, font, q);
q = (n30@(-n60)) + center;
thisSelf.centerString(analog, RIS, font, q);
q = (0@(-radius)) + center;
thisSelf.centerString(analog, RXIIS, font, q);
q = ((-n30)@(-n60)) + center;
thisSelf.centerString(analog, RXIS, font, q);
q = ((-n60)@(-n30)) + center;
thisSelf.centerString(analog, RXS, font, q);
q = ((-radius)@0) + center;
thisSelf.centerString(analog, RIXS, font, q);
q = ((-n60)@n30) + center;
thisSelf.centerString(analog, RVIIIS, font, q);
q = ((-n30)@n60) + center;
thisSelf.centerString(analog, RVIIS, font, q);
q = (0@radius) + center;
thisSelf.centerString(analog, RVIS, font, q);
j = radius / 20;
for (i=1;i<j;i=i+1)
analog.circle(center, i, Bitmap.F_STORE);
hourHand = radius / 2;
minuteHand = radius * 3 / 4;
/* create clock process */
process = Process.new(thisSelf);
process.clockLoop;
}
method centerString (aView, aString, aFont, aPoint)
{
Integer x,y;
Point p;
x = aFont.xOfString(aString);
y = aFont.yOfString(aString);
p = aPoint - ((x/2)@(y/2));
aView.string(aFont, aString, p, Bitmap.F_STORE);
}
method time (hours, minutes)
{
Integer hq, mq;
Integer length, x, y;
Float hrads, mrads;
String digital;
hours = hours % 12;
if (hours @= 0)
digital = R12S;
else if (hours < 10)
digital = R %S.sprintf(hours);
else
digital = R%S.sprintf(hours);
if (minutes < 10)
digital = digital // R:0%S.sprintf(minutes);
else
digital = digital // R:%S.sprintf(minutes);
Icon_OL.newString(digital, icon);
thisSelf.layer.newIcon(icon);
if (hours < 3)
hq = 1;
else if (hours < 6)
hq = 2;
else if (hours < 9)
hq = 3;
else
hq = 4;
if (minutes < 15)
mq = 1;
else if (minutes < 30)
mq = 2;
else if (minutes < 45)
mq = 3;
else
mq = 4;
hours = (hours % 3) * 60 + minutes;
if (hq @= 2 || hq @= 4) hours = 179 - hours;
minutes = minutes % 15;
if (mq @= 2 || mq @= 4) minutes = 14 - minutes;
hrads = Float.fromInteger(hours/2) * Float.radiansPerDegree;
mrads = Float.fromInteger(minutes*6) * Float.radiansPerDegree;
length = Float.fromInteger(hourHand);
x = (hrads.sin * length).asInteger;
y = (hrads.cos * length).asInteger;
if (hq @= 1)
y = -y;
else if (hq @= 3)
x = -x;
else if (hq @= 4) {
x = -x;
y = -y;
}
analog.batchOn;
if (hourPoint @! nil)
analog.vector(center, hourPoint, Bitmap.F_XOR);
hourPoint = center+(x@y);
analog.vector(center, hourPoint, Bitmap.F_XOR);
length = Float.fromInteger(minuteHand);
x = (mrads.sin * length).asInteger;
y = (mrads.cos * length).asInteger;
if (mq @= 1)
y = -y;
else if (mq @= 3)
x = -x;
else if (mq @= 4) {
x = -x;
y = -y;
}
if (minutePoint @! nil)
analog.vector(center, minutePoint, Bitmap.F_XOR);
minutePoint = center+(x@y);
analog.vector(center, minutePoint, Bitmap.F_XOR);
analog.batchOff;
}
method clockLoop
{
Date time, lastTime;
lastTime = Date.now - 3601*24;
for (;;) {
if (!view.layer.isAlive) {
/* Our layer has been deleted */
Process.running.terminate;
}
time = Date.now;
if (time.minute @! lastTime.minute) {
thisSelf.time(time.hour, time.minute);
if (time.dayOfMonth @! lastTime.dayOfMonth) {
analog.rectf(dayRectangle, Bitmap.F_CLR);
thisSelf.centerString(analog,
time.dayOfWeekString[0,3] << R, R <<
time.monthString <<
R R << time.dayOfMonth.asString,
iconFont, dayRectangle.center);
}
}
lastTime = time;
Timer.sleep(60 - Date.now.second);
}
}
/* end of class Clock */
}
Copyright © 1993, Dr. Dobb's Journal