Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

C Preprocessing With Tcl


Dr. Dobb's Journal August 1998: C Preprocessing With Tcl

Jonathan is a software engineer for AT&T. You can reach him via e-mail at [email protected].


Traditionally, the C preprocessor (CPP) has been used to create macros and define constants in C programs. However, the C preprocessor cannot perform more complex compile-time operations such as unrolling a while loop.

The obvious solution is to use a preprocessing language that is more sophisticated than the traditional CPP. In this article, I'll introduce C pretty darn quick (CPDQ), a utility that uses Tcl (Tool Control Language) to allow more sophisticated preprocessing than the traditional CPP.

The CPDQ Preprocessor

Tcl is a simple shell scripting language. A C API is available, making it easy to extend the language and embed the interpreter into other applications. The language itself is similar to other familiar shell languages in that it allows for setting variables, echoing them to an output device, and creating shell functions.

Example 1(a) is a script that sets the variable a to the value abcdefg, then echos that variable to standard output. Example 1(b) shows a function built with Tcl. Tcl also supports more sophisticated functions such as a TCP/IP interface and a comprehensive set of math functions.

The CPDQ preprocessor lets you write complex compile-time macros in Tcl. CPDQ consists of a simple parser that recognizes a delimiter (in this case, #$), and interprets the delimited code. Because the code interpretation is delegated to the Tcl libraries, the source code is short -- only 70 or so lines of C source code (see Listing One). Compiling CPDQ requires linking in the Tcl libraries. You should consult Tcl documentation for information on how to compile programs using the Tcl libraries for your particular platform.

A Simple Example

CPDQ is used the same way any other UNIX-style filter is used. It reads from the standard input and writes to the standard output. The utility requires no command-line flags, start-up scripts, or RC files.

Using CPDQ is simple. Any Tcl scripts embedded into your C source code are executed at compile time. You can then compile the generated C source code just as you normally would.

Example 2(a) is a sample input file for use by CPDQ. The first line consists of a script delimiter that indicates the start of a Tcl script. Next, the value of include_file is set to <stdio.h>. The script then prints the value of include_file to standard output. The remainder of the file is left untouched by CPDQ and is printed to standard output, producing the code in Example 2(b) -- the familiar "Hello World" C program.

Because of the way in which the #$ delimiter is parsed, the CPDQ utility will choke on C programs containing the literal #$ string. Make sure that the #$ string does not exist in your source code except where used as a CPDQ delimiter.

Lookup Tables

One possible use for CPDQ is implementing lookup tables. You may want to implement the cosine function, for example, as a lookup table when performance is an issue. However, placing the values in a lookup table by hand can be painstaking, and the size of the lookup table may make it difficult to maintain.

Example 3(a) is an implementation of a cosine lookup table. The script first declares a variable to hold the values in the lookup table, then populates it with the values of cosine from zero to 180 degrees. Next, a function is created for the purpose of looking up these values in the table. The final step is to embed the shell function in a program for calculating the values of cosine. CPDQ generates the code in Example 3(b).

Generic Data Structures

Listing Two presents a linked list implemented in C with a generic API that may be used regardless of the stored data type. However, there are certain drawbacks. In particular, the generic linked list in C does not handle compile-time typechecking, and it requires typecasting and a run-time data element size. All of these problems stem from the use of a generic pointer (void *) to store the data.

Compile-time typechecking notifies you of any inconsistencies in the way that data is used. Because Listing Two uses a generic pointer to contain data, compile-time typechecking is impossible. If you use the wrong data type, the compiler will not inform you. This makes debugging more difficult, particularly when several linked lists containing different data types are used in the same routine.

Typecasting, which lets you define data of one type and use it as if it were another type, is the usual method for overriding generic pointers. append() and walk() in Listing Two show the proper casting in this particular application. If you cast incorrectly, the compiler has no way of notifying you of the error.

Also, because of the generic data pointer, you must specify the size of the data, and the linked list must store this size along with the data to function properly. The "size" data pertaining to the contents of the list produces unwanted overhead in the linked list, and the additional housekeeping requirement increases the possibility of error.

Listing Three addresses the aforementioned concerns by implementing a linked list using CPDQ macros. For clarity, Listing Four is the result of compiling Listing Three using CPDQ.

First, rather than define a single linked-list data type using a generic pointer to store data, Listing Three creates a specific linked-list implementation for each data type you want to store. Hence, the compiler can typecheck the linked list. Typecasting is no longer required, because the CPDQ macros automatically declare data of the appropriate type. Finally, the size of the list's contents is implicit in the declaration of the list. This removes the need for run-time data pertaining to data size, and removes unwanted data overhead.

One drawback of using CPDQ macros to implement the linked list is that the generated code gets bigger each time a new type of list is introduced. This is because every list that contains a unique data type causes the creation of a new type declaration and set of function calls to handle it.

Conclusion

Applications that are computationally intensive can benefit from these techniques by performing calculations at compile time rather than at run time. Using Tcl to implement these compile-time calculations keeps coding and maintenance times small, while generating efficient applications. Using Tcl as a preprocessor also lets you implement generic data structures and algorithms that are typesafe and efficient without having to invest significantly more programming time or maintenance.

DDJ

Listing One

#include <tcl.h>#define modeCC		1
#define modeCPDQ	2
#define BUFFER_SIZE	512


</p>
char *read_to_delimiter(FILE *f, char *delimiter);
int main(int argc, char **argv)
{
	Tcl_Interp	*interp;
	char	*s;
	int	mode = modeCC;
	FILE	*f,*fout;
	f = stdin;
	fout = stdout;
	interp = Tcl_CreateInterp();
	while (1) {
		s = read_to_delimiter(f, "#$");
		if (!*s) break;
		switch (mode) {
			case modeCC:
				fputs(s, fout);
				mode = modeCPDQ;
				break;
			case modeCPDQ:
				if (Tcl_Eval(interp, s) != TCL_OK) {
					fprintf(stderr, "%s\n", interp->result);
					exit(2);
				}
				mode = modeCC;
				break;
		}
		free(s);
		fflush(fout);
	}
	Tcl_DeleteInterp(interp);
}
char *read_to_delimiter(FILE *f, char *delimiter)
{
	char	*str, *ret;
	len = BUFFER_SIZE;
	ptr = 0;
	ret = str = (char *)malloc(sizeof(char) * len);
	i = 0;
	while (1) {
		*str = fgetc(f);
		if (feof(f)) break;
		if (*str == delimiter[i]) i++;
		else i = 0;
		if (delimiter[i] == '\0') {
			str -= strlen(delimiter)-1;
			break;
		}
		str++; ptr++;
		if (ptr == len) {
			len += BUFFER_SIZE;
			str = (char *)realloc(ret, sizeof(char) * len);
		}
	}
	*str = '\0';
	return ret;
}

Back to Article

Listing Two

#include <stdio.h>
#include <stdlib.h>

struct list {
	void	*data;
	int	size;
	struct list *next;
	struct list *current_walk;
};

static struct list *allocate(void)
{
	return (struct list *)malloc(sizeof(struct list));
}

struct list *open(int size)
{
	struct list *newlist;
	newlist = allocate();
	newlist->size = size;
	newlist->next = NULL;
	return newlist;
}

int append(struct list *list, void *data)
{
	struct list	*current_walk;
	current_walk = list;
	while (current_walk->next)
		current_walk = current_walk->next;

	current_walk->data = malloc(list->size);
	memcpy(current_walk->data, data, list->size);
	current_walk->next = allocate();
	current_walk = current_walk->next;
	current_walk->next = NULL;
}

int rewind(struct list *list)
{
	list->current_walk = list;
}

void *walk(struct list *list)
{
	void *temp_ptr;

	temp_ptr = list->current_walk->data;
	list->current_walk = list->current_walk->next;
	return list->current_walk ? temp_ptr : NULL;
}

/*
 * Declare normal C data structure.
 */
struct data_type_st {
	char	last[30];
	char	first[30];
	char	grade[2];
};


int main(int argc, char **argv)
{
	/*
	 * Instantiate a list of structures.
	 */
	struct list *list_head;
	struct data_type_st data;
	struct data_type_st	*ptr;

	/*
	 * Open the list.
	 */
	list_head = open(sizeof(struct data_type_st));

	/*
	 * Append some data to the list.
	 */

	strcpy(data.first, "Laura");
	strcpy(data.last, "Long");
	strcpy(data.grade "A");
	append(list_head, (void *)&data);

	strcpy(data.first, "Tom");
	strcpy(data.last, "Novack");
	strcpy(data.grade "D");
	append(list_head, (void *)&data);

	strcpy(data.first, "Jason");
	strcpy(data.last, "Steiner");
	strcpy(data.grade "A");
	append(list_head, (void *)&data);


	/*
	 * Rewind the list to the top.
	 */
	rewind(list_head);


	/*
	 * Walk through the list and display the data from it.
	 */

	while (ptr = (struct data_type_st *)walk(list_head)) {
		printf("Last Names %s\n", ptr->last);
	}

}

Back to Article

Listing Three

#include <stdio.h>
#include <stdlib.h>

#$
###########################################################################
# Generic C interface macros.
###########################################################################
proc code {thing} {
	puts stdout "$thing"
}

proc __get_type_id {type_name} {
	set type_id ""
	foreach i $type_name {
		set type_id "$type_id\_$i"
	}
	return $type_id
}

set _type_lookup(0) ""

proc __type_of {var_name} {
	global	_type_lookup
	return $_type_lookup($var_name);
}

proc __set_type {var_name type} {
	global _type_lookup
	set _type_lookup($var_name) $type
}

###########################################################################
# Define structure for list type.
# and create the functions to work with it.
###########################################################################
proc list_def {type_name} {
	set type_id [__get_type_id $type_name]
	set list_type __list$type_id

	code "struct $list_type {"
	code "	$type_name		data;"
	code "	struct $list_type	*next;"
	code "	struct $list_type	*current_walk;"
	code "};"
	code ""
	code "static struct $list_type *allocate$type_id\(void)"
	code "{"
	code "	return (struct $list_type *)malloc(sizeof(struct $list_type));"
	code "}"
	code ""
	code "struct $list_type *open$type_id\(void)"
	code "{"
	code "	struct $list_type	*newlist;"
	code "	newlist = allocate$type_id\();"
	code "	newlist->next = NULL;"
	code "	return newlist;"
	code "}"
	code ""
	code "int append$type_id\(struct $list_type *list, $type_name *data)"
	code "{"
	code "	struct $list_type	*current_walk;"
	code "	current_walk = list;"
	code "	while (current_walk->next)"
	code "		current_walk = current_walk->next;"
	code "	memcpy(¤t_walk->data, data, sizeof($type_name));"
	code "	current_walk->next = allocate$type_id\();"
	code "	current_walk = current_walk->next;"
	code "	current_walk->next = NULL;"
	code "}"
	code ""
	code "int rewind$type_id\(struct $list_type *list)"
	code "{"
	code "	list->current_walk = list;"
	code "}"
	code ""
	code "$type_name *walk$type_id\(struct $list_type *list)"
	code "{"
	code "	$type_name *temp_ptr;"
	code "	temp_ptr = &list->current_walk->data;"
	code "	list->current_walk = list->current_walk->next;"
	code "	return list->current_walk ? temp_ptr : ($type_name *)0;"
	code "}"

}

###########################################################################
# List open function
###########################################################################
proc open {list} {
	set type_id [__get_type_id [__type_of $list]]
	code "$list = open$type_id\();"
}

###########################################################################
# List rewind function.
###########################################################################
proc rewind {list} {
	set type_id [__get_type_id [__type_of $list]]
	code "rewind$type_id\($list);"
}

###########################################################################
# List append function.
###########################################################################
proc append {list data} {
	set type_id [__get_type_id [__type_of $list]]
	code "append$type_id\($list, $data);"
}


# List walk function
###########################################################################
proc walk {list ptr} {
	set type_id [__get_type_id [__type_of $list]]
	code "ptr = walk$type_id\($list)"
}


###########################################################################
# List declaration
###########################################################################
proc list_decl {type list} {
	set type_id [__get_type_id $type]
	__set_type $list $type
	code "struct __list$type_id	*$list;"
}

#$

/*
 * Declare normal C data structure.
 */
struct data_type_st {
	char	last[30];
	char	first[30];
	char	grade[2];
};

#$

###########################################################################
# Define a list of structures.
###########################################################################
list_def "struct data_type_st"

#$

int main(int argc, char **argv)
{
	/*
	 * Instantiate a list of structures.
	 */
	#$list_decl "struct data_type_st"	list_head#$

	struct data_type_st data;
	struct data_type_st	*ptr;

	/*
	 * Open the list.
	 */
	#$open list_head#$

	/*
	 * Append some data to the list.
	 */

	strcpy(data.first, "Lee");
	strcpy(data.last, "Hall");
	strcpy(data.grade "A");
	#$append list_head &data#$

	strcpy(data.first, "John");
	strcpy(data.last, "DeRaedt");
	strcpy(data.grade "A");
	#$append list_head &data#$

	strcpy(data.first, "Thom");
	strcpy(data.last, "Suplica");
	strcpy(data.grade "A");
	#$append list_head &data#$

	/*
	 * Rewind the list to the top.
	 */
	#$rewind list_head#$

	/*
	 * Walk through the list and display the data from it.
	 */

	while (#$walk list_head ptr#$) {
		printf("Last Names %s\n", ptr->last);
	}
}

Back to Article

Listing Four

#include <stdio.h>
#include <stdlib.h>



/*
 * Declare normal C data structure.
 */
struct data_type_st {
	char	last[30];
	char	first[30];
	char	grade[2];
};

struct __list_struct_data_type_st {
	struct data_type_st		data;
	struct __list_struct_data_type_st	*next;
	struct __list_struct_data_type_st	*current_walk;
};

static struct __list_struct_data_type_st *allocate_struct_data_type_st(void)
{
	return (struct __list_struct_data_type_st *)malloc(sizeof(struct __list_struct_data_type_st));
}

struct __list_struct_data_type_st *open_struct_data_type_st(void)
{
	struct __list_struct_data_type_st	*newlist;
	newlist = allocate_struct_data_type_st();
	newlist->next = NULL;
	return newlist;
}

int append_struct_data_type_st(struct __list_struct_data_type_st *list, struct data_type_st *data)
{
	struct __list_struct_data_type_st	*current_walk;
	current_walk = list;
	while (current_walk->next)
		current_walk = current_walk->next;
	memcpy(¤t_walk->data, data, sizeof(struct data_type_st));
	current_walk->next = allocate_struct_data_type_st();
	current_walk = current_walk->next;
	current_walk->next = NULL;
}

int rewind_struct_data_type_st(struct __list_struct_data_type_st *list)
{
	list->current_walk = list;
}

struct data_type_st *walk_struct_data_type_st(struct __list_struct_data_type_st *list)
{
	struct data_type_st *temp_ptr;
	temp_ptr = &list->current_walk->data;
	list->current_walk = list->current_walk->next;
	return list->current_walk ? temp_ptr : (struct data_type_st *)0;
}


int main(int argc, char **argv)
{
	/*
	 * Instantiate a list of structures.
	 */
	struct __list_struct_data_type_st	*list_head;


	struct data_type_st data;
	struct data_type_st	*ptr;

	/*
	 * Open the list.
	 */
	list_head = open_struct_data_type_st();


	/*
	 * Append some data to the list.
	 */

	strcpy(data.first, "Lee");
	strcpy(data.last, "Hall");
	strcpy(data.grade "A");
	append_struct_data_type_st(list_head, &data);


	strcpy(data.first, "John");
	strcpy(data.last, "DeRaedt");
	strcpy(data.grade "A");
	append_struct_data_type_st(list_head, &data);


	strcpy(data.first, "Thom");
	strcpy(data.last, "Suplica");
	strcpy(data.grade "A");
	append_struct_data_type_st(list_head, &data);


	/*
	 * Rewind the list to the top.
	 */
	rewind_struct_data_type_st(list_head);


	/*
	 * Walk through the list and display the data from it.
	 */

	while (ptr = walk_struct_data_type_st(list_head)) {
		printf("Last Names %s\n", ptr->last);
	}
}

Back to Article


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.