[comp.lang.c] Source File Organization

rfarris@rfengr.com (Rick Farris) (02/26/91)

I have a problem that I'm sure has been solved in the C
language before; would someone point me in the right
direction?

I have an enumerated type:

typedef enum { A, B, C, D } CMD;

and a corresponding array of ascii representations :

char ltrs[] = { 'A', 'B', 'C', 'D' };

used for printing and other various purposes.  (Of course
the real problem is much more complicated than this, but
this simple example is sufficient for illustrative
purposes.) 

My problem is: How do I keep the darn things in sync?

Suppose I add a new CMD, "Z", is there any way to ensure
that ltrs[] is updated?

The problem is exacerbated by the fact that the CMD enum,
being a typedef, is in a header file that is included in
many places.  Since ltrs[] is an instantiated variable, it
*can't* live in the same place.  Where should it live?  

Thanks!


--
Rick Farris  RF Engineering POB M Del Mar, CA 92014  voice (619) 259-6793
rfarris@rfengr.com     ...!ucsd!serene!rfarris      serenity bbs 259-7757

pkr@media01.UUCP (Peter Kriens) (02/27/91)

> typedef enum { A, B, C, D } CMD;
> and a corresponding array of ascii representations :
> char ltrs[] = { 'A', 'B', 'C', 'D' };
...
> The problem is exacerbated by the fact that the CMD enum,
> being a typedef, is in a header file that is included in
> many places.  Since ltrs[] is an instantiated variable, it
> *can't* live in the same place.  Where should it live?

You could place the table in the header file like

	typedef enum { A, B, C, D ) CMD;
	#ifdef _CMDDEF_
	char ltrs = { 'A', 'B', 'C', 'D' };
	#endif

This would at least take care of the fact that they are close together
which means that the change of noth being updated simulteneous is actually
not zero. The only thing you have to do in the source that is allowed
to define the table ltrs, is defining _CMDDEF_ before you include the
header file

Peter Kriens

peter@ficc.ferranti.com (Peter da Silva) (02/27/91)

In article <1991Feb26.045242.23453@rfengr.com> rfarris@rfengr.com (Rick Farris) writes:
> My problem is: How do I keep the darn things in sync?

Try this:

---- ltrs.h
typedef enum { A, B, C, D } CMD;
#ifdef LTRS_C
char ltrs[] = { 'A', 'B', 'C', 'D' };
#else
extern char ltrs[];
#endif
---- ltrs.c
#define LTRS_C
#include "ltrs.h"
-- 
Peter da Silva.  `-_-'  peter@ferranti.com
+1 713 274 5180.  'U`  "Have you hugged your wolf today?"

jls@yoda.Rational.COM (Jim Showalter) (02/27/91)

In Ada, you'd just use the 'IMAGE attribute on the enumerated type,
which would eliminate the double-entry bookkeeping problem you
describe at no cost to you.

Of course, Ada is a big language with lots of useless bells and 
whistles, according to every C hacker I've ever met.

(No smiley.)
--
***** DISCLAIMER: The opinions expressed herein are my own. Duh. Like you'd
ever be able to find a company (or, for that matter, very many people) with
opinions like mine. 
                   -- "When I want your opinion, I'll beat it out of you."

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (02/27/91)

In article <1991Feb26.045242.23453@rfengr.com>, rfarris@rfengr.com (Rick Farris) writes:
> I have a problem that I'm sure has been solved in the C
> language before; would someone point me in the right
> direction?

> typedef enum { A, B, C, D } CMD;
> char ltrs[] = { 'A', 'B', 'C', 'D' };

> My problem is: How do I keep the darn things in sync?

I've seen this one often enough that I think it belongs in the FAQ list.
The answer is that you *don't* do it by any special magic in the C source
code itself, but use some other tool to transform a "mini language" to
both files.  For example, write a little file like
	cmd.defs
	-----------
	A	"Alfa"
	B	"Bravo"
	...
	D	"Delta"
and two awk scripts:
	cmd.awk
	-------------
	BEGIN	{ print "typedef enum {" }
		{ print $1, "," }
	END	{ print "} CMD;" }
and
	ltrs.awk
	-------------
	BEGIN	{ print "char ltrs[] = {" }
		{ print "  '" substr($2,1,1) "'," }
	END	{ print "};"}
and then put in your Makefile
	cmd.h: cmd.defs cmd.awk
		awk -f cmd.awk <cmd.defs >cmd.h
	ltrs.c: cmd.defs ltrs.awk
		awk -f ltrs.awk <cmd.defs >ltrs.c

(This is not a UNIX-specific solution: make and awk lookalikes are
available for other systems.  If you haven't got them, it's trivial
to do this in C itself.)

Moral: C source code is plain text that can be generated by other
programs.

-- 
The purpose of advertising is to destroy the freedom of the market.

rjohnson@shell.com (Roy Johnson) (02/27/91)

In article <jls.667625530@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
>In Ada, you'd just use the 'IMAGE attribute on the enumerated type,
>which would eliminate the double-entry bookkeeping problem you
>describe at no cost to you.

>Of course, Ada is a big language with lots of useless bells and 
>whistles, according to every C hacker I've ever met.

>(No smiley.)

Looks like we need a new newsgroup comp.religion.ada or some such,
for people to grouse about how Ada is really better than C, but no
one will admit it.

Get a life.
Sorry for wasting bandwidth...I hope this will prevent more of the
same.
--
======= !{sun,psuvax1,bcm,rice,decwrl,cs.utexas.edu}!shell!rjohnson =======
Feel free to correct me, but don't preface your correction with "BZZT!"
Roy Johnson, Shell Development Company

lfd@cbnewsm.att.com (Lee Derbenwick) (02/28/91)

In article <1991Feb26.045242.23453@rfengr.com>, rfarris@rfengr.com (Rick Farris) writes:
> I have an enumerated type:
> 
> typedef enum { A, B, C, D } CMD;
> 
> and a corresponding array of ascii representations :
> 
> char ltrs[] = { 'A', 'B', 'C', 'D' };
> 
> My problem is: How do I keep the darn things in sync?

This is a bit kludgy, and it doesn't _guarantee_ that they're in
sync (i.e., no protection from typos), but its flexible and
general, and _very_ easy to do.  The key is an include file using
macros that are defined differently in different contexts.

letters.h contains:

  LETTER(A, 'A')
  LETTER(B, 'B')
  LETTER(C, 'C')
  LETTER(D, 'D')
  #undef LETTER

Then, to get your two examples:

  #define LETTER(A,B) A
  typedef enum {
  #include "letters.h"
  } CMD;

  #define LETTER(A,B) B
  char ltrs[] = {
  #include "letters.h"
  };

This trick can also be useful for creating a number of tables
that are really views of a single relation.

 -- Speaking strictly for myself,
 --   Lee Derbenwick, AT&T Bell Laboratories, Warren, NJ
 --   lfd@cbnewsm.ATT.COM  or  <wherever>!att!cbnewsm!lfd

lfd@cbnewsm.att.com (Lee Derbenwick) (02/28/91)

In article <1991Feb27.182525.29758@cbnewsm.att.com>,
I gave a trick for keeping stuff in sync; but I left commas out of
the two macro definitions (SORRY ABOUT THAT!):
> 
> letters.h contains:
> 
>   LETTER(A, 'A')
>   LETTER(B, 'B')
>   LETTER(C, 'C')
>   LETTER(D, 'D')
>   #undef LETTER
> 
> Then, to get your two examples:
> 
/*
>   #define LETTER(A,B) A
*/
    #define LETTER(A,B) A,	/* should have been */
>   typedef enum {
>   #include "letters.h"
>   } CMD;
> 
/*
>   #define LETTER(A,B) B
*/
    #define LETTER(A,B) B,	/* should have been */
>   char ltrs[] = {
>   #include "letters.h"
>   };

This trick relies on C allowing a trailing comma in an enum or
initializer list; this and other machine-generated C code were
the reasons that the trailing comma _is_ allowed.

 -- Speaking strictly for myself,
 --   Lee Derbenwick, AT&T Bell Laboratories, Warren, NJ
 --   lfd@cbnewsm.ATT.COM  or  <wherever>!att!cbnewsm!lfd

harrison@necssd.NEC.COM (Mark Harrison) (02/28/91)

In article <1991Feb26.045242.23453@rfengr.com>,
rfarris@rfengr.com (Rick Farris) writes:

> I have an enumerated type:
> typedef enum { A, B, C, D } CMD;
> 
> and a corresponding array of ascii representations :
> char ltrs[] = { 'A', 'B', 'C', 'D' };
> 
> My problem is: How do I keep the darn things in sync?

One way:  Have a definition file that builds a header file
with the typedef and a C file with the array.  I have used
this method successfully for things like symbol table string
constants and error messages.

Example:

file foo.def
------------
CMD:	A B C D
TOKENS:	Q X Y

generates foo.h:
----------------
typedef enum { A, B, C, D } CMD;
typedef enum { Q, X, Y } TOKENS;

and foo.c:
----------
char CMD_ltrs[] = { 'A', 'B', 'C', 'D' };
char TOKENS_ltrs[] = { 'Q', 'X', 'Y' };

Awk and Perl are good languages for building tools like this.
-- 
Mark Harrison             harrison@necssd.NEC.COM
(214)518-5050             {necntc, cs.utexas.edu}!necssd!harrison
standard disclaimers apply...

black@blake.u.washington.edu (Jim Black) (02/28/91)

Rick Farris writes :

>I have an enumerated type:
>
>typedef enum { A, B, C, D } CMD;
>
>and a corresponding array of ascii representations :
>
>char ltrs[] = { 'A', 'B', 'C', 'D' };
>
> [...]
>
>My problem is: How do I keep the darn things in sync?
>
>Suppose I add a new CMD, "Z", is there any way to ensure
>that ltrs[] is updated?
>
>The problem is exacerbated by the fact that the CMD enum,
>being a typedef, is in a header file that is included in
>many places.  Since ltrs[] is an instantiated variable, it
>*can't* live in the same place.  Where should it live?  
>

One was is to use the C preprocessor, as follows:

1.  Use a macro in an include file that groups all the related items together.
Eg, in "CmdStuff.h":
/*       CMD ltr etc  */
CmdStuff (A, 'A', ..)
CmdStuff (B, 'B', ..)
CmdStuff (C, 'C', ..)
CmdStuff (D, 'D', ..)
CmdStuff (Z, 'Z', ..)

2.  Then in each source file (or header, as appropriate), define the CmdStuff 
macro to do what you want in the particular case, and #include "CmdStuff.h"
locally.

The CMD enum definition would look like this:

#define CmdStuff(enumval, ltr, ..)  enumval,
typedef enum {
 #include "CmdStuff.h"  /* enum values get enumerated here */
 } CMD;
#undef CmdStuff 

And the ltrs[] definition would look like this:

#define CmdStuff(enumval, ltr, ..)  ltr,
char ltrs[] = { 
 #include "CmdStuff.h"   /* corresponding letter codes get enumerated here */
 };
#undef CmdStuff

That's the basic idea, you can do more with this than these examples show.

Everything is declared together in one place ("CmdStuff.h", here):  
each time you add a CmdStuff() entry you will be forced to declare 
all appropriate parallel values, as intended.


P.S.  I can't remember if some C compilers might complain about a trailing
comma in the aggregate initialization (mine doesn't) - but you can add a
trailing null/nil/bottom entry in such cases.  Often that's useful anyway.
--
Jim Black  (black@blake.u.washington.edu) 

lerman@stpstn.UUCP (Ken Lerman) (02/28/91)

In article <1991Feb26.045242.23453@rfengr.com> rfarris@rfengr.com (Rick Farris) writes:
............
>I have an enumerated type:
>
>typedef enum { A, B, C, D } CMD;
>
>and a corresponding array of ascii representations :
>
>char ltrs[] = { 'A', 'B', 'C', 'D' };
>
>used for printing and other various purposes.
.............

>My problem is: How do I keep the darn things in sync?
>
>Suppose I add a new CMD, "Z", is there any way to ensure
>that ltrs[] is updated?

>The problem is exacerbated by the fact that the CMD enum,
>being a typedef, is in a header file that is included in
>many places.  Since ltrs[] is an instantiated variable, it
>*can't* live in the same place.  Where should it live?  
..................
>--
>Rick Farris  RF Engineering POB M Del Mar, CA 92014  voice (619) 259-6793
>rfarris@rfengr.com     ...!ucsd!serene!rfarris      serenity bbs 259-7757


I saw this idea in the gnu C compiler.

In file defs.h:
/* establish the pairs */
xxx(A,'A'),
xxx(B,'B'),
xxx(C,'C'),
xxx(D,'D'),

Then in file use1.c:

#define xxx(a,b) a
typedef enum { 
#include "defs.h"
} CMD;
#undef xxx

In file use2.c (or in another place in the same file):

#define xxx(a,b) b
char ltrs[] = {
#include "defs.h"
};
#undef xxx

I haven't actually tried this, or there may be some typos, but I think
the idea is worth posting.

Ken

rlk@telesoft.com (Bob Kitzberger @sation) (02/28/91)

Rick Farris writes:

> typedef enum { A, B, C, D } CMD;
> 
> and a corresponding array of ascii representations :
> 
> char ltrs[] = { 'A', 'B', 'C', 'D' };

Yes, you asked for a C solution, but since this was cross-posted to
comp.software-eng I thought I'd point out Ada's solution to this.
The example below specifies a type 'color', and a call to a language-defined
attribute of this prefix, 'IMAGE. 

with text_io;
procedure foo is
  type color is (red, green, blue );
  my_color : color := green;
begin
  text_io.put_line( color'image(my_color) );
end foo;

All enumerated types have a corresponding 'IMAGE attribute, which implies 
that an ASCII (or EBCDIC ;-) representation of each enumerated value be 
present at execution time.  These image tables may be supressed at compile 
time.

	.Bob.
-- 
Bob Kitzberger               Internet : rlk@telesoft.com
TeleSoft                     uucp     : ...!ucsd.ucsd.edu!telesoft!rlk
5959 Cornerstone Court West, San Diego, CA  92121-9891  (619) 457-2700 x163
------------------------------------------------------------------------------
"Wretches, utter wretches, keep your hands from beans!"	-- Empedocles

dave@aspect.UUCP (Dave Corcoran) (03/01/91)

In article <1991Feb26.045242.23453@rfengr.com>, rfarris@rfengr.com (Rick Farris) writes:
> I have an enumerated type:
> 
> typedef enum { A, B, C, D } CMD;
> 
> and a corresponding array of ascii representations :
> 
> char ltrs[] = { 'A', 'B', 'C', 'D' };
> 
> Suppose I add a new CMD, "Z", is there any way to ensure
> that ltrs[] is updated?
> 

run this through m4 you might be pleasantly suprised; although anyone
maintaining your code may what to drop the adjective "pleasantly" :-)

define(awkdef,
	`syscmd((echo ' '$2' | sed 's/^(//;s/^)//' `>/tmp/am_$1))'
	`define($1,`syscmd(awk $'`2 -f /tmp/am_$1 ifelse($'`1,,,<<@@
$'`1
@@))')'
)

awkdef(xx,(  # note the leading (
	BEGIN {printf "enum {"}
	{
	for (i=1;i<=NF;i++) printf "%s,",$i
	}
	END {print "} CMD;"}
))	/* note the first paren MUST be flush against the left margin */


/* note the '\'' construct is needed to get a single ' in the output */

awkdef(yy,(
	BEGIN {printf "char ltrs[] = {"}
	{
	for (i=1;i<=NF;i++) printf "'\''%s'\'',",$i 
	}                                           
												
	END {print "};"}
))

define(letters,`xx($1)yy($1)')
letters(A B C D E F G H I J K L M)

/* if you need conditional compliation */

define(`letters',`ifdef(`header_file',`yy($1)',`xx($1)')')

/* this next line would be in a .c source */
letters(A B C)

/* these two would be in a .h header */
define(header_file)

letters(A B C)

-- 
David Corcoran		      -@@
uunet!aspect!dave	        ~
In a society where anything goes eventually everything will.

jih@ox.com (John Hritz) (03/02/91)

The approach I take when faced with this kind of problem is to place an
assert statement in the main() as a check.
	assert(sizeof(a)  == sizeof(b))
will probably do the trick.  These statements can be left out of the code
by setting a compile switch, usually NDEBUG.
-- 
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
     John I. Hritz                               Photons have mass?!
     jih@ox.com                                       I didn't know they
     313-930-9126                                             were catholic!

rjc@uk.ac.ed.cstr (Richard Caley) (03/02/91)

In article <1991Feb26.045242.23453@rfengr.com>, Rick Farris (rf) writes:

rf> My problem is: How do I keep the darn things in sync?

I had to do this for six or seven sets of things in a library. I
decided to write a short shell script to write all the code for me. I
created a file containing the names and any data to be associated with
them and the shell script creats a header and a c file

a file called people with...

	char *name	int age
fred	fred		24
mary	mary		30

would give a header person.h containing

enum person
	{
	pe_NIL= -1,
	pe_fred=0,
	pe_mary=1,
	pe_number=2,
	};

struct person_rec
	{
	char *name;
	int age;
	};

struct person_rec person_data;

enum person person_with_name(char *name);
enum person person_with_age(int age);

and a c file person.c contining the definition of person_data and the
code for the search routines (left as an exercise for the reader).

The Makefile takes care of recreating the files when needed and
nothing can get out of step. Also saves lots of tedious coding of
access routines which are isomorphic for all the tables.

(the reson for having a separate "name" field is that often things
 have names which are not legal c tokens).

--
rjc@cstr.ed.ac.uk

rlk@telesoft.com (Bob Kitzberger @sation) (03/03/91)

Folks, the intent was to ease an existing maintenance problem.  Adding
preprocessors such as awk or m4 into the loop only trades one maintenance
problem for another.

	.Bob.
-- 
Bob Kitzberger               Internet : rlk@telesoft.com
TeleSoft                     uucp     : ...!ucsd.ucsd.edu!telesoft!rlk
5959 Cornerstone Court West, San Diego, CA  92121-9891  (619) 457-2700 x163
------------------------------------------------------------------------------
"Wretches, utter wretches, keep your hands from beans!"	-- Empedocles

harrison@necssd.NEC.COM (Mark Harrison) (03/04/91)

In article <1210@telesoft.com>,
rlk@telesoft.com (Bob Kitzberger @sation) writes:

> Folks, the intent was to ease an existing maintenance problem.  Adding
> preprocessors such as awk or m4 into the loop only trades one maintenance
> problem for another.

That is correct, but the new maintenance problem is easier.  Our "bells
and whistles" version of the preprocessor ended up being about three
pages of AWK that hardly ever needed to be touched.  (The only times we
did touch it was to make changes in the generated troff documentation
to fit our documentation dept's. style).

Using MAKE ensured that the preprocessed files stayed in sync.  Being
able to generate documentation saved us days over the old "red-pencil"
method of dealing with our documentation group.
-- 
Mark Harrison             harrison@necssd.NEC.COM
(214)518-5050             {necntc, cs.utexas.edu}!necssd!harrison
standard disclaimers apply...

dave@bigguy.ocpt.ccur.com (David F. Carlson) (03/19/91)

> Rick Farris writes:
> 
> > typedef enum { A, B, C, D } CMD;
> > 
> > and a corresponding array of ascii representations :
> > 
> > char ltrs[] = { 'A', 'B', 'C', 'D' };
> 

I would usually overload the operation for this using the initialization
property of enum types and the fact that they are defined to hold an
integer:

typedef enum { A = (int) 'A', B = (int) 'B', C = (int) 'C', D = (int) 'D'} CMD;

or if lexically ASCII is assumed,

typedef enum { A = (int) 'A', B, C, D } CMD;

Since B, C and D will be sequential, they will be correctly initialized.


Or is that cheating? :-)
-- 
David F. Carlson, Concurrent Computer Co.
dave@bigguy.ocpt.ccur.com       Fairport, NY

"The faster I go, the behinder I get." --Lewis Carroll