Writing a Glish C/C++/Fortran Client

Table of Contents

Introduction

As a user of aips++ or the GBT you may have occasion to extend the glish command language by adding clients written in C++, C, or Fortran. This note is intended to help overcome the initial hurdle of getting the million little details right to compile and run a client. You do not necessarily need to know much about C++ to write a client. Most, if not all of the C++ code required is given in the examples at the end of this document. It should be possible to replace the core of one of the simple C++ functions with your own C or Fortran code. The replaceable code is marked in the examples.

The main purpose of the example file is to show how to pass variables back and forth between glish and a C++ client. Once the variable values are in the C++ environment, you can use them like any other C or C++ variable. We show here a separate function for each glish variable type since many client functions can be implemented with the exchange of a single variable or variable array. The glish record variable allows a quite complex exchange of data within one variable.

There are many more ways to implement clients using the glish C++ library than are shown here, but this will get you started. See "The Glish Client Library" chapter in the Glish User Manual under.

http://aips2.nrao.edu/aips++/docs/html/aips++.html

Glish communicates with a client through events that act much like interrupts between hardware and software. Events are an uncommon concept to non-real-time programming, but we can hide events in glish functions as is shown in the glish script.

There are two types of glish events for communicating with a client, an asynchronous event and a request/reply event. If you are using your client events in a straight line programming evironment such as data analysis, request/reply events are the most convenient. Asynchronous events are best for a real time programming problem like hardware monitor and control or a graphical user interface. Mixing the two event types in the same glish code is a bit risky because an asynchronous event can clobber a request/reply exchange. All of the examples given here use request/reply.

If you are programming for the aips++ environment you will want to take advantage of the additional C++ glish classes within the aips++ system.

http://aips2.nrao.edu/aips++/docs/aips/implement/Glish.html

This example uses only native glish classes and may be compiled, linked, and run independently of aips++. The glish libraries shown in the example makefile do happen to reside in the aips++ directories, however.

The C/C++/Fortran client

The C++ code and Fortran code examples are shown at the end of this document. The basic client must be written in C++, but all of the essential code is given. Areas where your own C code or Fortran subroutine calls may be substituted are marked between vvvvvv/^^^^^^ comment lines.

The main program establishes a connection to glish by creating a Client object, c, and declares a GlishEvent pointer, e. The client then executes the c.NextEvent() function which enters a sleep state until an event from glish is received. An event causes the while() loop to execute once. The name of the event is then tested against the expected string values, and when a match is found the corresponding function is executed. All functions have the same arguments, a reference to the client object used for the reply to glish and the pointer to the event for retrieving the value sent by the event. In glish the "int_squared" event will be triggered by a statement like

	isq := request sf->int_squared(13);
where sf is the client handle that was created with
	sf := client("ex_client");
and ex_client is the executable file name of our compiled client. In the newest versions of glish the request keyword is no longer required. If not, you'll get a warning message.

A annotated example of how the event value is extracted and a reply to glish is generated is given in the int_squared function. First, a value pointer is retrieved through the event pointer. Then the value type is checked to see that it matches the expected type. If so, the value itself is copied to a C/C++ variable which may then be used by your own code. In the example, we square the received integer. Next, a glish return variable is created from the C/C++ variable to be returned, and the glish variable is sent with the reply. Finally, the glish variable is deleted to recover its allocated memory. All of the other functions do essentially the same thing but with other variable types.

Any variable can be an array. We give examples of only integer and double types since they are the most common. The array length may be retrieved with the value Length() function as shown in the int_array_squared function. In C/C++ an array is handled with a pointer so a pointer is retrieved from the glish event value. This actually allocates new memory for the array which you must be careful to release with the delete command before returning from the function. The same caution applies to string pointers as in the reverse_string function.

Retrieving glish record values is a two step process as shown in the record_bitwise_and function. First you get the record, then you get the individual fields by name. In our example the glish record variable would look like [bits1=21, bits2=7]. A field may be of any variable type, even another record, and is treated like a glish variable. When returning a record to glish you must first create a record object in C++ and then create and assign each field.

There is no reason that an event must return the same variable type that it receives. We just happen to have done it that way in most of the example functions.

If you want to access Fortran code by way of a glish client, you must compile your Fortran subroutine(s) with a Fortran compiler and then link the object file(s) to the C++ client code. Compatibility between Fortran and C/C++ object files may be compiler dependent, but it seems to work with Sun and GNU compilers.

In the C++ code the Fortran routine must be declared as a C routine to prevent extensions being added to the function name by C++, and the name must have an underscore extension to match a Fortran compiler convention. See the code example. The function arguments must be declared as references in the C++ prototype, again to match a Fortran convention. Beyond that, the Fortran subroutine may be called just like a C function as shown in do_my_fortran. The Fortran subrourine code is shown below.

Glish wrapper functions

Normally you will not want to worry about the details of communicating with a client in your glish code. These can be hidden in a glish script which starts the client and defines glish functions which issue the event commands. The functions may simply pass an argument to the event and return the reply value as in int_array_squared, or they can add some interpretation and reformatting as with int_squared or record_bitwise_and. Just include this glish script when or after you start glish.

At the end of the glish script example is a set of test function calls. Notice that a few of the arguments must be forced into the expected variable type. This is because numeric constants default to integer or double precision.

The Makefile

The Makefile contains the site-dependent information needed to compile the client. This example uses the GNU C++ and Sun Fortran compilers and points to the glish libraries at the ATNF. It assumes that your UNIX environment PATH contains the compiler directories, in this case

	/usr/local/gnu/bin
	/opt/SUNWspro/bin

An option for the Green Bank glish libraries is shown commented out. The compiler paths there are

	/opt/local/gnu/bin
	/opt/SUNWspro/bin

Running the client

To run the client your UNIX environment variable LD_LIBRARY_PATH must contain the directory of the required shared libraries. This is generally the same as the LIBRARIES path in the Makefile. In the bourne and bash shells, execute

	export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/aips++/sun4sol_gnu/lib

or whatever path is appropriate for your site. In the c shell use

	setenv LD_LIBRARY_PATH "$LD_LIBRARY_PATH /aips++/sun4sol_gnu/lib"

Then start glish and include the script file. Our script assumes that the client executable is in the directory from which you start glish.

    bash$ glish
    Glish version 2.5.1. 
    - include "ex_client.g"
 
    Test functions:
    int_squared(4)                       = 16
    int_array_squared([1,2,3,4])         = [1 4 9 16] 
    half_of_double(9.667)                = 4.8335
    half_of_double_array([2.0, 5.6, 6.8])= [1 2.8 3.4] 
    invert_boolean(F)                    = T
    byte_plus_one(as_byte(23))           = 24
    bit_complement_short(as_short(23))   = -24
    arctan_of_float(as_float(1.0))       = 0.785398
    complex_conjugate(as_complex(5+8i))  = 5-8i
    double_complex_180(5+8i)             = -5-8i
    reverse_string('gnirts sdrawkcab')   = backwards string
    record_bitwise_and(21, 7)            = [bits1=21, bits2=7, result=5]
    my_fortran(9.6)                      = 28.8
    T 
    - 

Example code

The Makefile code

# Makefile for example glish client

# at the ATNF
BASE_DIR = /aips++
# at Green Bank
#BASE_DIR = /aips++/test

LIBRARIES = -L$(BASE_DIR)/sun4sol_gnu/lib

INCLUDES = -I$(BASE_DIR)/code/aips/glish/include

EXECUTABLES = ex_client

all: $(EXECUTABLES)

ex_client : ex_client.o my_fortran.o
	c++ -o ex_client ex_client.o my_fortran.o \
	$(LIBRARIES) -lglish -lsos -lnpd -lm

ex_client.o : ex_client.cc
	c++ $(INCLUDES) -c ex_client.cc 

my_fortran.o : my_fortran.f
	f77 -c my_fortran.f

C++ client code

#include <stdio.h>
#include <string.h>
#include <math.h>
#include "Glish/Client.h"

// Declare all of the C/C++ functions that you are going to use or put
// the main() function at the end.
void do_int_squared(Client &c, GlishEvent *e);
void do_int_array_squared(Client &c, GlishEvent *e);
void do_half_of_double(Client &c, GlishEvent *e);
void do_half_of_double_array(Client &c, GlishEvent *e);
void do_invert_boolean(Client &c, GlishEvent *e);
void do_byte_plus_one(Client &c, GlishEvent *e);
void do_bit_complement_short(Client &c, GlishEvent *e);
void do_arctan_of_float(Client &c, GlishEvent *e);
void do_complex_conjugate(Client &c, GlishEvent *e);
void do_double_complex_180(Client &c, GlishEvent *e);
void do_reverse_string(Client &c, GlishEvent *e);
void do_record_bitwise_and(Client &c, GlishEvent *e);
void do_my_fortran(Client &c, GlishEvent *e);

// Make the fortran subroutine look like a C (not C++) function.
// The fortran compiler's subroutine naming convention adds an
// underscore at the end of the name which the C compiler does not.
// Fortran arguments are passed by reference.
// See the file my_fortran.f for the definition of this function.
extern "C" {
void my_fortran_(double &rcvd, double &ret);
}

int main (int argc, char **argv)
{
    // This creates a required client object.
    Client c(argc, argv);
    // The client can be invoked with arguments, but we`ll bypass that
    // complication.
    if (argc > 1) {
        printf ("Usage: cl := client('ex_client')\n");
        return 1;
    }
    // Create a pointer to be assigned to a received glish event.
    GlishEvent *e;

    // Stay in this loop until the client is terminated.  The c.NextEvent()
    // function blocks until it receives an event from glish.  It then
    // returns an event pointer that is used to access the values passed
    // from glish.
    while ((e = c.NextEvent())) {
        // Search for an expected event name and execute the appropriate
        // function when found.
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	if (!strcmp(e->Name(), "int_squared")) {
	    do_int_squared(c, e);
	} else if (!strcmp(e->Name(), "int_array_squared")) {
	    do_int_array_squared(c, e);
	} else if (!strcmp(e->Name(), "half_of_double")) {
	    do_half_of_double(c, e);
	} else if (!strcmp(e->Name(), "half_of_double_array")) {
	    do_half_of_double_array(c, e);
	} else if (!strcmp(e->Name(), "invert_boolean")) {
	    do_invert_boolean(c, e);
	} else if (!strcmp(e->Name(), "byte_plus_one")) {
	    do_byte_plus_one(c, e);
	} else if (!strcmp(e->Name(), "bit_complement_short")) {
	    do_bit_complement_short(c, e);
	} else if (!strcmp(e->Name(), "arctan_of_float")) {
	    do_arctan_of_float(c, e);
	} else if (!strcmp(e->Name(), "complex_conjugate")) {
	    do_complex_conjugate(c, e);
	} else if (!strcmp(e->Name(), "double_complex_180")) {
	    do_double_complex_180(c, e);
	} else if (!strcmp(e->Name(), "reverse_string")) {
	    do_reverse_string(c, e);
	} else if (!strcmp(e->Name(), "record_bitwise_and")) {
	    do_record_bitwise_and(c, e);
	} else if (!strcmp(e->Name(), "my_fortran")) {
	    do_my_fortran(c, e);
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
	} else {
	    // Report an error if an event name is not recognized.
	    c.Unrecognized();
	}
    }
    return 0;
}

void do_int_squared(Client &c, GlishEvent *e)
{
    // Get the pointer to the event's value.
    Value *val = e->Val();
    // initialize the return value to an illegal value as a flag;
    int return_value = -1;
    // Check the received value type
    if (val->Type() != TYPE_INT) {
	printf("Integer value expected from `int_squared'\n");
    } else {
	int received_value = val->IntVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value = received_value * received_value;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    // Create a reply value object.  This line of code creates a reply
    // value of the same type as the argument to Value(), in this case
    // it's an integer.
    Value *rep = new Value(return_value);
    // Send the reply with the value.
    c.Reply(rep);
    // Release the memory allocated for the reply event.  If you forget
    // this, you will have created a memory leak.
    Unref(rep);
}

void do_int_array_squared(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    int array_length = val->Length();
    int *return_value = new int[array_length];
    int i;
    for (i = 0; i < array_length; i++) {
        return_value[i] = 0;
    }
    if (val->Type() != TYPE_INT || array_length <= 1) {
	printf("Integer array expected from `int_array_squared'\n");
    } else {
	int *received_value = val->IntPtr();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	for (i = 0; i < array_length; i++) {
	    return_value[i] = received_value[i] * received_value[i];
	}
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
	delete received_value;
    }
    Value *rep = new Value(return_value, array_length, COPY_ARRAY);
    c.Reply(rep);
    delete return_value;
    Unref(rep);
}

void do_half_of_double(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    double return_value = -1.0E20;
    if (val->Type() != TYPE_DOUBLE) {
	printf("Double type value expected from `half_of_double'\n");
    } else {
	double received_value = val->DoubleVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value = received_value / 2.0;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_half_of_double_array(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    int array_length = val->Length();
    double *return_value = new double[array_length];
    int i;
    for (i = 0; i < array_length; i++) {
        return_value[i] = 0.0;
    }
    if (val->Type() != TYPE_DOUBLE || array_length <= 1) {
	printf("Double type array expected from `half_of_double_array'\n");
    } else {
	double *received_value = val->DoublePtr();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	for (i = 0; i < array_length; i++) {
	    return_value[i] = received_value[i] / 2.0;
	}
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
	delete received_value;
    }
    Value *rep = new Value(return_value, array_length, COPY_ARRAY);
    c.Reply(rep);
    delete return_value;
    Unref(rep);
}

void do_invert_boolean(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    glish_bool return_value = glish_false;
    if (val->Type() != TYPE_BOOL) {
	printf("Boolean type value expected from `invert_boolean'\n");
    } else {
	glish_bool received_value = val->BoolVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	if (received_value) {
	    return_value = glish_false;
	} else {
	    return_value = glish_true;
	}
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_byte_plus_one(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    byte return_value = 0;
    if (val->Type() != TYPE_BYTE) {
	printf("Byte type value expected from `byte_plus_one'\n");
    } else {
	byte received_value = val->ByteVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value = received_value + 1;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_bit_complement_short(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    short return_value = 0;
    if (val->Type() != TYPE_SHORT) {
	printf("Short type value expected from `complement_short'\n");
    } else {
	short received_value = val->ShortVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value = ~received_value;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_arctan_of_float(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    float return_value = 0.0;
    if (val->Type() != TYPE_FLOAT) {
	printf("Float type value expected from `arctan_of_float'\n");
    } else {
	float received_value = val->FloatVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value = atan(received_value);
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_complex_conjugate(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    complex return_value = 0;
    if (val->Type() != TYPE_COMPLEX) {
	printf("Complex type value expected from `complex_conjugate'\n");
    } else {
	complex received_value = val->ComplexVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value.r = received_value.r;
	return_value.i = -received_value.i;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_double_complex_180(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    dcomplex return_value = 0;
    if (val->Type() != TYPE_DCOMPLEX) {
	printf(
	    "Double complex type value expected from `double_complex_180'\n");
    } else {
	dcomplex received_value = val->ComplexVal();
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	return_value.r = -received_value.r;
	return_value.i = -received_value.i;
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_reverse_string(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    char return_value[100], temp[100];
    if (val->Type() != TYPE_STRING) {
	printf("String type value expected from `reverse_string'\n");
    } else {
        char *received_value = val->StringVal();
	strncpy(temp, received_value, 100);
	temp[99] = '\0';
	// The call of val->StringVal() implicitly allocates memory
	// for the string which must be deleted to avoida memory leak.
	delete received_value;
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
	char *ptr = temp;
	int i;
	for (i = strlen(temp) - 1; i >= 0; i--) {
	    return_value[i] = *ptr++;
	}
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

void do_record_bitwise_and(Client &c, GlishEvent *e)
{
    Value *val = e->Val();
    Value *rep;
    if (val->Type() != TYPE_RECORD) {
	printf("bitwise_and args: [bits1=, bits2=]\n");
	rep = new Value(0);
    } else {
// vvvvvvvvvvvv Your code substituted below here. vvvvvvvvvvvv
        // The field values or arrays in record members may be treated in
        // the same way as values and arrays of non-record variables.
	Value *bits1 = val->Field("bits1");
	Value *bits2 = val->Field("bits2");
	if (bits1 == NULL) {
	    printf("Did not find 'bits1' record member\n");
	    rep = new Value(0);
	} else if (bits2 == NULL) {
	    printf("Did not find 'bits2' record member\n");
	    rep = new Value(0);
	} else {
	    if ((bits1->Type() == TYPE_BYTE || bits1->Type() == TYPE_SHORT ||
		bits1->Type() == TYPE_INT) && (bits2->Type() == TYPE_BYTE ||
	        bits2->Type() == TYPE_SHORT || bits2->Type() == TYPE_INT)) {
		int b1 = bits1->IntVal();
		if (bits1->Type() == TYPE_BYTE) {
		    b1 &= 0x0FF;
		    } else if (bits1->Type() == TYPE_SHORT) {
		    b1 &= 0x0FFFF;
		}
		int b2 = bits2->IntVal();
		if (bits2->Type() == TYPE_BYTE) {
		    b2 &= 0x0FF;
		    } else if (bits2->Type() == TYPE_SHORT) {
		    b2 &= 0x0FFFF;
		}
		int var = b1 & b2;
		rep = create_record();
		rep->SetField("bits1", b1);
		rep->SetField("bits2", b2);
		rep->SetField("result", var);
	    } else {
		printf ("Unrecognized bit field type (byte, short, or int)\n");
		rep = new Value(0);
	    }
	}
// ^^^^^^^^^^^^ Your code substituted above here. ^^^^^^^^^^^^
    }
    c.Reply(rep);
    Unref(rep);
}

void do_my_fortran(Client &c, GlishEvent *e)
{
    // See do_int_squared() function for comments on statements which
    // are common to all functions.
    Value *val = e->Val();
    double return_value = -1.0E20;
    if (val->Type() != TYPE_DOUBLE) {
	printf("Double type value expected from `my_fortran'\n");
    } else {
	double received_value = val->DoubleVal();
// vvvvvvvvvvvv Your fortran call substituted below here. vvvvvvvvvvvv
	// See the file my_fortran.f for the definition of this function.
	// It just sets the second argument to 3 times the first argument.
	my_fortran_(received_value, return_value);
// ^^^^^^^^^^^^ Your fortran call substituted above here. ^^^^^^^^^^^^
    }
    Value *rep = new Value(return_value);
    c.Reply(rep);
    Unref(rep);
}

Fortran subroutine

	subroutine my_fortran(rcvd, ret)
	implicit real*8 (a-h,o-z)

        ret = rcvd * 3.0

	return
	end

Glish event wrapper functions

sf := client("ex_client")

int_squared := function ( valu )
{
	rv := request sf->int_squared(valu);
	if (rv < 0) {
		return F;
	} else {
		return rv;
	}
}
int_array_squared := function ( valu )
{
	return request sf->int_array_squared(valu);
}
half_of_double := function ( valu )
{
	rv := request sf->half_of_double(valu);
	if (rv < -1.0E19) {
		return F;
	} else {
		return rv;
	}
}
half_of_double_array := function ( valu )
{
	return request sf->half_of_double_array(valu);
}
invert_boolean := function ( valu )
{
	return request sf->invert_boolean(valu);
}
byte_plus_one := function ( valu )
{
	return request sf->byte_plus_one(valu);
}
bit_complement_short := function ( valu )
{
	return request sf->bit_complement_short(valu);
}
arctan_of_float := function ( valu )
{
	return request sf->arctan_of_float (valu);
}
complex_conjugate := function ( valu )
{
	return request sf->complex_conjugate (valu);
}
double_complex_180 := function ( valu )
{
	return request sf->double_complex_180 (valu);
}
reverse_string := function ( valu )
{
	return request sf->reverse_string (valu);
}
record_bitwise_and := function ( b1, b2 )
{
	return request sf->record_bitwise_and([bits1=b1, bits2=b2]);
}
my_fortran := function ( valu )
{
	return request sf->my_fortran(valu);
}

print;
print 'Test functions:';
print 'int_squared(4)                       =', int_squared(4);
print 'int_array_squared([1,2,3,4])         =', int_array_squared([1,2,3,4]);
print 'half_of_double(9.667)                =', half_of_double(9.667);
print 'half_of_double_array([2.0, 5.6, 6.8])=',
			 half_of_double_array([2.0, 5.6, 6.8]);
print 'invert_boolean(F)                    =', invert_boolean(F);
print 'byte_plus_one(as_byte(23))           =', byte_plus_one(as_byte(23));
print 'bit_complement_short(as_short(23))   =',
				bit_complement_short(as_short(23));
print 'arctan_of_float(as_float(1.0))       =', arctan_of_float(as_float(1.0));
print 'complex_conjugate(as_complex(5+8i))  =',
			complex_conjugate(as_complex(5+8i));
print 'double_complex_180(5+8i)             =', double_complex_180(5+8i);
print 'reverse_string(\'gnirts sdrawkcab\')   =',
			 reverse_string('gnirts sdrawkcab');
print 'record_bitwise_and(21, 7)            =', record_bitwise_and(23, 7);
print 'my_fortran(9.6)                      =', my_fortran(9.6);

This document last updated August 3, 1997.

rfisher@nrao.edu

Rick Fisher's Home Page