1#
2# CTables - code to generate Tcl C extensions that implement tables out of
3# C structures
4#
5#
6# $Id$
7#
8
9namespace eval ctable {
10    variable ctablePackageVersion
11    variable table
12    variable tables
13    variable booleans
14    variable nonBooleans
15    variable fields
16    variable fieldList
17    variable keyField
18    variable keyFieldName
19    variable ctableTypes
20    variable ctableErrorInfo
21    variable withPgtcl
22    variable withCasstcl
23    variable withSharedTables
24    variable withSharedTclExtension
25    variable sharedTraceFile	;# default none
26    variable poolRatio		;# default 16
27    variable withDirty
28    variable reservedWords
29    variable errorDebug
30
31    variable genCompilerDebug
32    variable showCompilerCommands
33    variable withPipe
34    variable memDebug
35    variable sharedGuard
36    variable sharedLog
37    variable sanityChecks
38    variable keyCompileVariables
39
40    # If loaded directly, rather than as a package
41    if {![info exists srcDir]} {
42	set srcDir .
43    }
44
45    source [file join $srcDir sysconfig.tcl]
46
47    source [file join $srcDir config.tcl]
48
49    set ctablePackageVersion $sysconfig(ctablePackageVersion)
50
51    set withPgtcl [info exists sysconfig(pgtclprefix)]
52    set withCasstcl [info exists sysconfig(casstclprefix)]
53
54
55    variable leftCurly
56    variable rightCurly
57
58    set leftCurly \173
59    set rightCurly \175
60
61    # Important compile settings, used in generating the ID
62    set keyCompileVariables {
63	fullInline
64	fullStatic
65	withPgtcl
66	withCasstcl
67	withSharedTables
68	withSharedTclExtension
69	sharedTraceFile
70	sharedBase
71	withDirty
72	genCompilerDebug
73	memDebug
74	sanityChecks
75	sharedGuard
76	ctablePackageVersion
77    }
78
79    set ctableErrorInfo ""
80
81    set tables ""
82
83    namespace eval fields {}
84
85    set cvsID {#CTable generator ID: $Id$}
86
87    ## ctableTypes must line up with the enumerated typedef "ctable_types"
88    ## in ctable.h
89    set ctableTypes "boolean fixedstring varstring char mac short int long wide float double inet tclobj key"
90
91    set reservedWords "bool char short int long wide float double"
92
93set fp [open $srcDir/template.c-subst]
94set metaTableSource [read $fp]
95close $fp
96
97set fp [open $srcDir/init-exten.c-subst]
98set initExtensionSource [read $fp]
99close $fp
100
101set fp [open $srcDir/exten-frag.c-subst]
102set extensionFragmentSource [read $fp]
103close $fp
104
105#
106# cmdBodySource - code we run subst over to generate the second chunk of the
107#  body that implements the methods that work on the table.
108#
109set fp [open $srcDir/command-body.c-subst]
110set cmdBodySource [read $fp]
111close $fp
112
113#
114# emit - emit a string to the file being generated
115#
116proc emit {text} {
117    variable ofp
118
119    puts $ofp $text
120}
121
122#
123# cquote -- quote a string so the C compiler will see the same thing
124#  if it occurs inside double-quotes
125#
126proc cquote {string {meta {"}}} {
127  # first, escape the metacharacters (quote) and backslash
128  append meta {\\}
129  regsub -all "\[$meta]" $string {\\&} string
130
131  # Now loop over the string looking for nonprinting characters
132  set quoted ""
133  while {
134    [regexp {([[:graph:]]*)([^[:graph:]])(.*)} $string _ plain char string]
135  } {
136    append quoted $plain
137    # gratuitously make \n and friends look nice
138    set index [string first $char "\r\n\t\b\f "]
139    if {$index == -1} {
140      scan $char %c decimal
141      set plain [format {\%03o} $decimal]
142    } else {
143      set plain [lindex {{\r} {\n} {\t} {\b} {\f} { }} $index]
144    }
145    append quoted $plain
146  }
147  append quoted $string
148  return $quoted
149}
150
151#
152# Special normally-illegal field names
153#
154variable specialFieldNames {
155    _key
156    _dirty
157}
158
159#
160# is_key - is this field a "key" or a normal field
161#
162proc is_key {fieldName} {
163    # If called before special "_key" field is set up.
164    if {"$fieldName" == "_key"} {
165	return 1
166    }
167
168    # Otherwise go by type
169    upvar ::ctable::fields::$fieldName field
170    if {[info exists field(type)] && "$field(type)" == "key"} {
171	return 1
172    }
173
174    return 0
175}
176
177#
178# is_hidden - hidden fields are not returned in arrays or lists by default
179#
180proc is_hidden {fieldName} {
181    return [string match {[._]*} $fieldName]
182}
183
184#
185# field_to_enum - return a field mapped to the name we'll use when
186#  creating or referencing an enumerated list of field names.
187#
188#  for example, creating table fa_position and field longitude, this
189#   routine will return FIELD_FA_POSITION_LONGITUDE
190#
191proc field_to_enum {fieldName} {
192    variable table
193
194    if {[regexp {^[._](.*)$} $fieldName _ pseudoName]} {
195	return "SPECIAL_[string toupper $table]_[string toupper $pseudoName]"
196    }
197    return "FIELD_[string toupper $table]_[string toupper $fieldName]"
198}
199
200#
201# field_to_var - generate a unique variable name
202#
203proc field_to_var {table fieldName varName} {
204    if [regexp {^[._](.*)} $fieldName _ pseudoName] {
205	return "_${table}_${pseudoName}_$varName"
206    }
207    return "${table}_${fieldName}_$varName"
208}
209#
210# field_to_nameObj - return a field mapped to the Tcl name object we'll
211# use to expose the name to Tcl
212#
213proc field_to_nameObj {table fieldName} {
214    return [field_to_var $table $fieldName nameObj]
215}
216
217#
218# gen_allocate - return the code to allocate memory
219#
220proc gen_allocate_private {ctable size} {
221    return "(char *) ckalloc($size)"
222}
223
224proc gen_allocate {ctable size {private 0} {may_fail 0}} {
225    variable withSharedTables
226    variable table
227    set priv [gen_allocate_private $ctable $size]
228
229    if {$may_fail} {
230	set pub "${table}_allocate_may_fail($ctable, $size)"
231    } else {
232	set pub "${table}_allocate($ctable, $size)"
233    }
234
235    if {!$withSharedTables || "$private" == "1" || "$private" == "TRUE"} {
236	return $priv
237    }
238
239    if {"$private" == "0" || "$private" == "FALSE"} {
240	return $pub
241    }
242
243    return "(($private) ? $priv : $pub)"
244}
245
246proc gen_allocate_may_fail {ctable size {private 0}} {
247    return [gen_allocate $ctable $size $private 1]
248}
249
250#
251# Oposite function for free
252#
253proc gen_deallocate_private {ctable pointer} {
254    return "ckfree((char *)($pointer))"
255}
256
257proc gen_deallocate {ctable pointer {private 0}} {
258    variable withSharedTables
259    set priv [gen_deallocate_private $ctable $pointer]
260
261    set pub "shmfree(($ctable)->share, (void *)($pointer))"
262
263    if {!$withSharedTables || "$private" == "1" || "$private" == "TRUE"} {
264	return $priv
265    }
266
267    if {"$private" == "0" || "$private" == "FALSE"} {
268	return "(($ctable)->share_type == CTABLE_SHARED_MASTER ? $pub : $priv)"
269    }
270
271    return "( (($ctable)->share_type != CTABLE_SHARED_MASTER || ($private)) ? $priv : $pub)"
272}
273
274variable allocateSource {
275void ${table}_shmpanic(CTable *ctable)
276{
277    Tcl_Panic (
278	"Out of shared memory for \"%s\".", ctable->share_file
279    );
280}
281
282void *${table}_allocate(CTable *ctable, size_t amount)
283{
284    if(ctable->share_type == CTABLE_SHARED_MASTER) {
285	void *memory = shmalloc(ctable->share, amount);
286	if(!memory)
287	    ${table}_shmpanic(ctable);
288	return memory;
289    }
290    return (void *)ckalloc(amount);
291}
292
293void *${table}_allocate_may_fail(CTable *ctable, size_t amount)
294{
295    void *memory;
296
297    if(ctable->share_type == CTABLE_SHARED_MASTER) {
298	memory = shmalloc(ctable->share, amount);
299    } else {
300	memory = (void *)ckalloc(amount);
301    }
302
303    return memory;
304}
305
306}
307
308proc gen_allocate_function {table} {
309    variable withSharedTables
310    variable allocateSource
311    if {$withSharedTables} {
312	emit [string range [subst -nobackslashes -nocommands $allocateSource] 1 end-1]
313    }
314}
315
316variable sanitySource {
317void ${table}_sanity_check_pointer(CTable *ctable, void *ptr, int indexCtl, CONST char *where)
318{
319#ifdef WITH_SHARED_TABLES
320    if(indexCtl != CTABLE_INDEX_NEW) {
321	if(ctable->share_type == CTABLE_SHARED_MASTER || ctable->share_type == CTABLE_SHARED_READER) {
322	    if(ctable->share == NULL)
323		Tcl_Panic("%s: ctable->share_type = %d but ctable->share = NULL", where, ctable->share_type);
324	    if((char *)ptr < (char *)ctable->share->map)
325		Tcl_Panic("%s: ctable->share->map = 0x%lX but ptr == 0x%lX", where, (long)ctable->share->map, (long)ptr);
326	    if((size_t)((char *)ptr - (char *)ctable->share->map) > ctable->share->size)
327		Tcl_Panic("%s: ctable->share->size = %ld but ptr is at %ld offset from map", where, (long)ctable->share->size, (long)((char *)ptr - (char *)ctable->share->map));
328	}
329    }
330#endif
331}
332}
333
334proc gen_sanity_checks {table} {
335    variable sanityChecks
336    variable sanitySource
337    if {$sanityChecks} {
338	emit [string range [subst -nobackslashes -nocommands $sanitySource] 1 end-1]
339    }
340}
341
342variable reinsertRowSource {
343int ${table}_reinsert_row(Tcl_Interp *interp, CTable *ctable, char *value, struct ${table} *row, int indexCtl)
344{
345    ctable_HashEntry *newrow, *oldrow;
346    int isNew = 0;
347    int flags = KEY_VOLATILE;
348    char *key = value;
349#ifdef WITH_SHARED_TABLES
350    // shmallocated copy of key, if needed
351    char *mem = NULL;
352#endif
353
354    // Check for duplicates
355    oldrow = ctable_FindHashEntry(ctable->keyTablePtr, value);
356    if(oldrow) {
357	Tcl_AppendResult (interp, "Duplicate key '", value, "' when setting key field", (char *)NULL);
358	return TCL_ERROR;
359    }
360
361    if(indexCtl == CTABLE_INDEX_NORMAL) {
362#ifdef WITH_SHARED_TABLES
363	// Make a new copy of the key
364        if(ctable->share_type == CTABLE_SHARED_MASTER) {
365	    mem = (char*)shmalloc(ctable->share, strlen(value)+1);
366	    if(!mem) {
367		if(ctable->share_panic) ${table}_shmpanic(ctable);
368		Tcl_AppendResult (interp, "out of shared memory when setting key field", (char *)NULL);
369		return TCL_ERROR;
370	    }
371
372	    // Good to go
373	    key = mem;
374	    strcpy(mem, value);
375	    flags = KEY_STATIC;
376	}
377#endif
378
379        // Remove old key.
380	${table}_deleteHashEntry (ctable, row);
381    } else {
382        // This shouldn't be possible, but just in case
383	ckfree(row->hashEntry.key);
384	row->hashEntry.key = ctable->nullKeyValue;
385    }
386
387    // Insert existing row with new key
388    newrow = ctable_StoreHashEntry(ctable->keyTablePtr, key, &row->hashEntry, flags, &isNew);
389
390#ifdef SANITY_CHECKS
391    ${table}_sanity_check_pointer(ctable, (void *)newrow, CTABLE_INDEX_NORMAL, "${table}_reinsert_row");
392#endif
393
394    if(!isNew) {
395	Tcl_AppendResult (interp, "Duplicate key '", value, "' after setting key field!", (char *)NULL);
396#ifdef WITH_SHARED_TABLES
397	if(mem) {
398	    /* Don't need to "shmfree" because the key was never made
399	     * visible to any readers.
400	     */
401	    shmdealloc(ctable->share, mem);
402	}
403#endif
404	return TCL_ERROR;
405    }
406
407    if(indexCtl == CTABLE_INDEX_NEW) {
408	int field;
409        // Add to indexes.
410        for(field = 0; field < ${TABLE}_NFIELDS; field++) {
411	    if (ctable_InsertIntoIndex (interp, ctable, row, field) == TCL_ERROR) {
412		return TCL_ERROR;
413	    }
414	}
415    }
416
417    return TCL_OK;
418}
419}
420
421proc gen_reinsert_row_function {table} {
422    set TABLE [string toupper $table]
423    variable reinsertRowSource
424    emit [string range [subst -nobackslashes -nocommands $reinsertRowSource] 1 end-1]
425}
426
427#
428# preambleCannedSource -- stuff that goes at the start of the file we generate
429#
430variable preambleCannedSource {
431#include "ctable.h"
432
433#ifndef HAVE_ETHERS
434#include "ethers.c"
435#endif
436}
437
438variable nullIndexDuringSetSource {
439	        if (ctable->skipLists[field] != NULL) {
440		    if (indexCtl == CTABLE_INDEX_NORMAL) {
441			indexCtl = CTABLE_INDEX_NEW; // inhibit a second removal
442		        ctable_RemoveFromIndex (ctable, row, field);
443		    }
444		}
445}
446
447#
448# nullCheckDuringSetSource - standard stuff for handling nulls during set
449#
450variable nullCheckDuringSetSource {
451	int obj_is_null = ${table}_obj_is_null (obj);
452	if (obj_is_null) {
453	    if (!row->_${fieldName}IsNull) {
454$handleNullIndex
455	        // field wasn't null but now is
456		row->_${fieldName}IsNull = 1;
457	    } else {
458		// No change, don't do anything
459	        return TCL_OK;
460	    }
461	}
462}
463
464#
465# gen_null_check_during_set_source - generate standard null checking
466#  for a set
467#
468proc gen_null_check_during_set_source {table fieldName {elseCase ""}} {
469    variable nullCheckDuringSetSource
470    variable nullIndexDuringSetSource
471    variable fields
472
473    upvar ::ctable::fields::$fieldName field
474
475    if {[info exists field(notnull)] && $field(notnull)} {
476        return $elseCase
477    }
478
479    if {[info exists field(indexed)] && $field(indexed)} {
480        set handleNullIndex $nullIndexDuringSetSource
481    } else {
482        set handleNullIndex ""
483    }
484
485    if {"$elseCase" != ""} {
486	set elseCase " else { if(!obj_is_null) { $elseCase } }"
487    }
488
489    return [string range [subst -nobackslashes -nocommands $nullCheckDuringSetSource] 1 end-1]$elseCase
490}
491
492variable unsetNullDuringSetSource {
493	if (!obj_is_null && row->_${fieldName}IsNull) {
494	    if ((indexCtl == CTABLE_INDEX_NORMAL) && (ctable->skipLists[field] != NULL)) {
495	        indexCtl = CTABLE_INDEX_NEW; // inhibit a second removal
496		ctable_RemoveFromIndex (ctable, row, field);
497	    }
498
499	    row->_${fieldName}IsNull = 0;
500	}
501}
502
503variable unsetNullDuringSetSource_unindexed {
504	if (!obj_is_null && row->_${fieldName}IsNull) {
505	    row->_${fieldName}IsNull = 0;
506	}
507}
508
509# gen_if_equal - generate code if $v1 == $v2
510proc gen_if_equal {v1 v2 code} {
511    if {"$v1" == "$v2"} {return $code}
512    return ""
513}
514
515#
516# gen_unset_null_during_set_source - generate standard null unsetting
517#  for a set
518#
519proc gen_unset_null_during_set_source {table fieldName {elsecode {}}} {
520    variable unsetNullDuringSetSource
521    variable unsetNullDuringSetSource_unindexed
522    variable fields
523
524    upvar ::ctable::fields::$fieldName field
525
526    if {[info exists field(notnull)] && $field(notnull)} {
527	if {"$elsecode" == ""} {
528	    return ""
529	} else {
530            return "        $elsecode"
531	}
532    } else {
533	if {"$elsecode" != ""} {
534	    set elsecode " else { if(!obj_is_null) { $elsecode } }"
535	}
536	if {[info exists field(indexed)] && $field(indexed)} {
537	    return "[string range [subst -nobackslashes -nocommands $unsetNullDuringSetSource] 1 end-1]$elsecode"
538	} else {
539	    return "[string range [subst -nobackslashes -nocommands $unsetNullDuringSetSource_unindexed] 1 end-1]$elsecode"
540	}
541    }
542}
543
544#####
545#
546# Generating Code To Set Values In Rows
547#
548#####
549
550variable removeFromIndexSource {
551	    if ((indexCtl == CTABLE_INDEX_NORMAL) && (ctable->skipLists[field] != NULL)) {
552		ctable_RemoveFromIndex (ctable, row, field);
553	    }
554}
555
556#
557# gen_ctable_remove_from_index - return code to remove the specified field
558# from an index, or nothing if the field is not indexable -- requires
559# interp, ctable, row and field to be defined and in scope in the C target.
560#
561proc gen_ctable_remove_from_index {fieldName} {
562    variable fields
563    variable removeFromIndexSource
564
565    upvar ::ctable::fields::$fieldName field
566
567    if {[info exists field(indexed)] && $field(indexed)} {
568        return $removeFromIndexSource
569    } else {
570        return ""
571    }
572}
573
574variable insertIntoIndexSource {
575	if ((indexCtl != CTABLE_INDEX_PRIVATE) && (ctable->skipLists[field] != NULL)) {
576	    if (ctable_InsertIntoIndex (interp, ctable, row, field) == TCL_ERROR) {
577	        return TCL_ERROR;
578	    }
579	}
580}
581
582#
583# gen_ctable_insert_into_index - return code to insert the specified field
584# into an index, or nothing if the field is not indexable -- requires
585# interp, ctable, row and field to be defined and in scope in the C target.
586#
587proc gen_ctable_insert_into_index {fieldName} {
588    variable fields
589    variable insertIntoIndexSource
590
591    upvar ::ctable::fields::$fieldName field
592
593    if {[info exists field(indexed)] && $field(indexed)} {
594        return $insertIntoIndexSource
595    } else {
596        return ""
597    }
598}
599
600#
601# boolSetSource - code we run subst over to generate a set of a boolean (bit)
602#
603variable boolSetSource {
604      case $optname: {
605        int boolean = 0;
606
607[gen_null_check_during_set_source $table $fieldName \
608        "if (Tcl_GetBooleanFromObj (interp, obj, &boolean) == TCL_ERROR) {
609            Tcl_AppendResult (interp, \" while converting $fieldName\", (char *)NULL);
610            return TCL_ERROR;
611        }"]
612[gen_unset_null_during_set_source $table $fieldName \
613	"if (row->$fieldName == boolean)
614	    return TCL_OK;"]
615
616        row->$fieldName = boolean;
617        [gen_if_equal $fieldName _dirty "return TCL_OK; // Don't set dirty for meta-fields"]
618	break;
619      }
620}
621
622#
623# numberSetSource - code we run subst over to generate a set of a standard
624#  number such as an integer, long, double, and wide integer.  (We have to
625#  handle shorts and floats specially due to type coercion requirements.)
626#
627variable numberSetSource {
628      case $optname: {
629        $typeText value;
630[gen_null_check_during_set_source $table $fieldName \
631	"if ($getObjCmd (interp, obj, &value) == TCL_ERROR) {
632	    Tcl_AppendResult (interp, \" while converting $fieldName\", (char *)NULL);
633	    return TCL_ERROR;
634	}"]
635[gen_unset_null_during_set_source $table $fieldName \
636	"if (row->$fieldName == value)
637	    return TCL_OK;"]
638[gen_ctable_remove_from_index $fieldName]
639	row->$fieldName = value;
640[gen_ctable_insert_into_index $fieldName]
641	break;
642      }
643}
644variable keySetSource {
645      case $optname: {
646        char *value = Tcl_GetString(obj);
647
648        if (row->hashEntry.key != ctable->nullKeyValue && *value == *row->hashEntry.key && strcmp(value, row->hashEntry.key) == 0)
649	    return TCL_OK;
650
651	switch (indexCtl) {
652	    case CTABLE_INDEX_PRIVATE: {
653		// fake hash entry for search
654		if(row->hashEntry.key != ctable->nullKeyValue) [gen_deallocate_private ctable row->hashEntry.key];
655		row->hashEntry.key = (char *)[gen_allocate_private ctable "strlen(value)+1"];
656		strcpy(row->hashEntry.key, value);
657		break;
658	    }
659	    case CTABLE_INDEX_NORMAL:
660	    case CTABLE_INDEX_NEW: {
661
662#ifdef SANITY_CHECKS
663		${table}_sanity_check_pointer(ctable, (void *)row, CTABLE_INDEX_NORMAL, "${table}::keySetSource");
664#endif
665		if (${table}_reinsert_row(interp, ctable, value, row, indexCtl) == TCL_ERROR)
666		    return TCL_ERROR;
667		break;
668	    }
669	}
670	break;
671      }
672}
673
674#
675# varstringSetSource - code we run subst over to generate a set of a string.
676#
677# strings are char *'s that we manage automagically.
678#
679# If the string isn't changed, return immediately.
680#
681# Remove from index if we're indexed.
682#
683# If the new string doesn't fit in the allocated space:
684#
685#    If space has been allocated for the existing string (not null and not initialized to the
686#    static default string), free the old string.
687#
688#    Allocate space for the new string (TODO: allocate strings to fixed size blocks to reduse fragmentation)
689#
690# Copy the string in and set the new length.
691#
692# Add back to index.
693#
694variable varstringSetSource {
695      case $optname: {
696	const char *stringPtr = NULL;
697	int   length;
698[gen_null_check_during_set_source $table $fieldName]
699
700	stringPtr = Tcl_GetStringFromObj (obj, &length);
701[gen_unset_null_during_set_source $table $fieldName "
702	if(length == row->_${fieldName}Length && *stringPtr == *row->$fieldName && strcmp(stringPtr, row->$fieldName) == 0)
703	        return TCL_OK;"]
704
705	// previous field isn't null, new field isn't null, and
706	// isn't the same as the previous field
707	[gen_ctable_remove_from_index $fieldName]
708
709	// new string value
710	// if the allocated length is less than what we need, get more,
711	// else reuse the previously allocated space
712	if (row->$fieldName == NULL || row->_${fieldName}AllocatedLength <= length) {
713	    // Allocating shmem may fail, so allocate mem ahead of time
714	    char *mem = (char*)[
715	        gen_allocate_may_fail ctable \
716			"length + 1" \
717			"indexCtl == CTABLE_INDEX_PRIVATE"
718	    ];
719	    if (!mem) {
720#ifdef WITH_SHARED_TABLES
721		if(ctable->share_panic) ${table}_shmpanic(ctable);
722#endif
723		Tcl_AppendResult (interp, \" out of memory allocating space for $fieldName\", (char *)NULL);
724		return TCL_ERROR;
725	    }
726
727	    if (row->_${fieldName}AllocatedLength > 0) {
728		[gen_deallocate ctable "row->$fieldName" "indexCtl == CTABLE_INDEX_PRIVATE"];
729	    }
730	    row->$fieldName = mem;
731	    row->_${fieldName}AllocatedLength = length + 1;
732	}
733	strncpy (row->$fieldName, stringPtr, length + 1);
734	row->_${fieldName}Length = length;
735
736	// if we got here and this field has an index, we've removed
737	// the old index either by removing a null index or by
738	// removing the prior index, now insert the new index
739[gen_ctable_insert_into_index $fieldName]
740	break;
741      }
742}
743
744#
745# charSetSource - code we run subst over to generate a set of a single char.
746#
747variable charSetSource {
748      case $optname: {
749	const char *stringPtr;
750[gen_null_check_during_set_source $table $fieldName]
751	stringPtr = Tcl_GetString (obj);
752[gen_unset_null_during_set_source $table $fieldName \
753	"if(row->$fieldName != stringPtr\[0])
754	    return TCL_OK;"]
755[gen_ctable_remove_from_index $fieldName]
756	row->$fieldName = stringPtr\[0\];
757[gen_ctable_insert_into_index $fieldName]
758	break;
759      }
760}
761
762#
763# fixedstringSetSource - code we run subst over to generate a set of a
764# fixed-length string.
765#
766variable fixedstringSetSource {
767      case $optname: {
768	const char *stringPtr;
769	int   len;
770[gen_null_check_during_set_source $table $fieldName]
771	stringPtr = Tcl_GetStringFromObj (obj, &len);
772[gen_unset_null_during_set_source $table $fieldName "
773	if (len == 0 && [expr [string length $default] > 0]) stringPtr = \"[cquote $default]\";
774	if (*stringPtr == *row->$fieldName && strncmp(row->$fieldName, stringPtr, $length) == 0)
775	    return TCL_OK;"]
776[gen_ctable_remove_from_index $fieldName]
777	if(len < $length) {
778		strncpy (row->$fieldName, "[cquote $default]", $length);
779		strncpy (row->$fieldName, stringPtr, len);
780	} else
781		strncpy (row->$fieldName, stringPtr, $length);
782[gen_ctable_insert_into_index $fieldName]
783	break;
784      }
785}
786
787#
788# inetSetSource - code we run subst over to generate a set of an IPv4
789# internet address.
790#
791variable inetSetSource {
792      case $optname: {
793        struct in_addr value = {INADDR_ANY};
794[gen_null_check_during_set_source $table $fieldName \
795	"if (!inet_aton (Tcl_GetString (obj), &value)) {
796	    Tcl_AppendResult (interp, \"expected IP address but got \\\"\", Tcl_GetString (obj), \"\\\" parsing field \\\"$fieldName\\\"\", (char *)NULL);
797	    return TCL_ERROR;
798	}"]
799[gen_unset_null_during_set_source $table $fieldName \
800	"if (memcmp (&row->$fieldName, &value, sizeof (struct in_addr)) == 0)
801            return TCL_OK;"]
802
803[gen_ctable_remove_from_index $fieldName]
804	row->$fieldName = value;
805[gen_ctable_insert_into_index $fieldName]
806	break;
807      }
808}
809
810#
811# macSetSource - code we run subst over to generate a set of an ethernet
812# MAC address.
813#
814variable macSetSource {
815      case $optname: {
816        struct ether_addr *mac = (struct ether_addr *) NULL;
817[gen_null_check_during_set_source $table $fieldName \
818	"{
819	    mac = ether_aton (Tcl_GetString (obj));
820	    if (mac == (struct ether_addr *) NULL) {
821	        Tcl_AppendResult (interp, \"expected MAC address but got \\\"\", Tcl_GetString (obj), \"\\\" parsing field \\\"$fieldName\\\"\", (char *)NULL);
822	        return TCL_ERROR;
823	    }
824	}"]
825
826[gen_unset_null_during_set_source $table $fieldName \
827	"if (memcmp (&row->$fieldName, mac, sizeof (struct ether_addr)) == 0)
828            return TCL_OK;"]
829[gen_ctable_remove_from_index $fieldName]
830	row->$fieldName = *mac;
831[gen_ctable_insert_into_index $fieldName]
832	break;
833      }
834}
835
836#
837# tclobjSetSource - code we run subst over to generate a set of a tclobj.
838#
839# tclobjs are Tcl_Obj *'s that we manage automagically.
840#
841variable tclobjSetSource {
842      case $optname: {
843
844	if (row->$fieldName != (Tcl_Obj *) NULL) {
845	    Tcl_DecrRefCount (row->$fieldName);
846	    row->$fieldName = NULL;
847	}
848[gen_null_check_during_set_source $table $fieldName \
849	"{
850	    row->$fieldName = obj;
851	    Tcl_IncrRefCount (obj);
852	}"]
853[gen_unset_null_during_set_source $table $fieldName]
854	break;
855      }
856}
857
858#####
859#
860# Generating Code For Sort Comparisons
861#
862#####
863
864#
865# nullSortSource - code to be inserted when null values are permitted for the
866#  field.
867#
868variable nullSortSource {
869        if (row1->_${fieldName}IsNull) {
870	    if (row2->_${fieldName}IsNull) {
871		result = 0;
872	        break;
873	    }
874
875	    return direction;
876	} else if (row2->_${fieldName}IsNull) {
877	    return -direction;
878	}
879}
880
881#
882# gen_null_check_during_sort_comp -
883#	emit null checking as part of field
884#  comparing in a sort
885#
886proc gen_null_check_during_sort_comp {table fieldName} {
887    variable nullSortSource
888    variable varstringSortCompareNullSource
889
890    upvar ::ctable::fields::$fieldName field
891
892    if {"$field(type)" == "varstring"} {
893	set source $varstringSortCompareNullSource
894    } elseif {[info exists field(notnull)] && $field(notnull)} {
895        set source ""
896    } else {
897	set source $nullSortSource
898    }
899
900    return [string range [subst -nobackslashes -nocommands $source] 1 end-1]
901}
902
903variable nullExcludeSource {
904	      if (row->_${fieldName}IsNull) {
905		  exclude = 1;
906		  break;
907	      }
908}
909
910proc gen_null_exclude_during_sort_comp {table fieldName} {
911    variable nullExcludeSource
912
913    upvar ::ctable::fields::$fieldName field
914
915    if {[info exists field(notnull)] && $field(notnull)} {
916        return ""
917    } else {
918	return [string range [subst -nobackslashes -nocommands $nullExcludeSource] 1 end-1]
919    }
920}
921
922#
923# boolSortSource - code we run subst over to generate a compare of a
924# boolean (bit) for use in a sort.
925#
926variable boolSortSource {
927	case $fieldEnum: {
928[gen_null_check_during_sort_comp $table $fieldName]
929          if (row1->$fieldName && !row2->$fieldName) {
930	      result = -direction;
931	      break;
932	  }
933
934	  if (!row1->$fieldName && row2->$fieldName) {
935	      result = direction;
936	      break;
937	  }
938
939	  result = 0;
940	  break;
941      }
942}
943
944#
945# numberSortSource - code we run subst over to generate a compare of a standard
946#  number such as an integer, long, double, and wide integer for use in a sort.
947#
948variable numberSortSource {
949      case $fieldEnum: {
950[gen_null_check_during_sort_comp $table $fieldName]
951        if (row1->$fieldName < row2->$fieldName) {
952	    result = -direction;
953	    break;
954	}
955
956	if (row1->$fieldName > row2->$fieldName) {
957	    result = direction;
958	    break;
959	}
960
961	result = 0;
962	break;
963      }
964}
965
966#
967# varstringSortCompareNullSource - compare against default empty string
968#   for sorting
969#
970# note there's also a varstringCompareNullSource that's pretty close to this
971# but returns everything instead of just returning on non-match.
972#
973variable varstringSortCompareNullSource {
974    if (!row1->$fieldName) {
975	if(!row2->$fieldName) {
976	    result = 0;
977	    break;
978	} else {
979	    return direction * -1;
980	}
981    } else {
982	if(!row2->$fieldName) {
983	    return direction;
984	}
985    }
986}
987
988#
989# varstringSortSource - code we run subst over to generate a compare of
990# a string for use in a sort.
991#
992variable varstringSortSource {
993      case $fieldEnum: {
994[gen_null_check_during_sort_comp $table $fieldName]
995
996        result = direction * strcmp (row1->$fieldName, row2->$fieldName);
997	break;
998      }
999}
1000
1001#
1002# fixedstringSortSource - code we run subst over to generate a comapre of a
1003# fixed-length string for use in a sort.
1004#
1005variable fixedstringSortSource {
1006      case $fieldEnum: {
1007[gen_null_check_during_sort_comp $table $fieldName]
1008        result = direction * strncmp (row1->$fieldName, row2->$fieldName, $length);
1009	break;
1010      }
1011}
1012
1013#
1014# binaryDataSortSource - code we run subst over to generate a comapre of a
1015# inline binary arrays (inets and mac addrs) for use in a sort.
1016#
1017variable binaryDataSortSource {
1018      case $fieldEnum: {
1019[gen_null_check_during_sort_comp $table $fieldName]
1020        result = direction * memcmp (&row1->$fieldName, &row2->$fieldName, $length);
1021	break;
1022      }
1023}
1024
1025#
1026# tclobjSortSource - code we run subst over to generate a compare of
1027# a tclobj for use in a sort.
1028#
1029variable tclobjSortSource {
1030      case $fieldEnum: {
1031        result = direction * strcmp (Tcl_GetString (row1->$fieldName), Tcl_GetString (row2->$fieldName));
1032	break;
1033      }
1034}
1035
1036#
1037# keySortSource - code we run subst over to generate a compare of
1038# a key for use in a sort.
1039#
1040variable keySortSource {
1041      case $fieldEnum: {
1042	if(*row1->hashEntry.key > *row2->hashEntry.key)
1043	    result = direction;
1044	else if(*row1->hashEntry.key < *row2->hashEntry.key)
1045	    result = -direction;
1046	else
1047            result = direction * strcmp (row1->hashEntry.key, row2->hashEntry.key);
1048	break;
1049      }
1050}
1051
1052#####
1053#
1054# Generating Code For Search Comparisons
1055#
1056#####
1057
1058#
1059# standardCompNullCheckSource - variable to substitute to do null
1060# handling in all comparison types
1061#
1062variable standardCompNullCheckSource {
1063	  if (row->_${fieldName}IsNull) {
1064	      if (compType == CTABLE_COMP_NULL) {
1065		  break;
1066	      }
1067	      exclude = 1;
1068	      break;
1069          }
1070
1071	  if (compType == CTABLE_COMP_NULL) {
1072	      exclude = 1;
1073	      break;
1074	  }
1075
1076	  if (compType == CTABLE_COMP_NOTNULL) {
1077	      break;
1078	  }
1079}
1080
1081#
1082# standardCompNotNullCheckSource - variable to substitute to do null
1083# comparison handling for fields defined notnull.
1084#
1085variable standardCompNotNullCheckSource {
1086	  if (compType == CTABLE_COMP_NULL) {
1087	      exclude = 1;
1088	      break;
1089          } else if (compType == CTABLE_COMP_NOTNULL) {
1090	      break;
1091	  }
1092}
1093
1094#
1095# gen_standard_comp_null_check_source - gen code to check null stuff
1096#  when generating search comparison routines
1097#
1098proc gen_standard_comp_null_check_source {table fieldName} {
1099    variable standardCompNullCheckSource
1100    variable standardCompNotNullCheckSource
1101    upvar ::ctable::fields::$fieldName field
1102
1103    if {[info exists field(notnull)] && $field(notnull)} {
1104        return [string range $standardCompNotNullCheckSource 1 end-1]
1105    } else {
1106	return [string range [subst -nobackslashes -nocommands $standardCompNullCheckSource] 1 end-1]
1107    }
1108}
1109
1110#
1111# standardCompSwitchSource -stuff that gets emitted in a number of compare
1112#  routines we generate
1113#
1114variable standardCompSwitchSource {
1115          switch (compType) {
1116	    case CTABLE_COMP_LT:
1117	        exclude = !(strcmpResult < 0);
1118		break;
1119
1120	    case CTABLE_COMP_LE:
1121	        exclude = !(strcmpResult <= 0);
1122		break;
1123
1124	    case CTABLE_COMP_EQ:
1125	        exclude = !(strcmpResult == 0);
1126		break;
1127
1128	    case CTABLE_COMP_NE:
1129	        exclude = !(strcmpResult != 0);
1130		break;
1131
1132	    case CTABLE_COMP_GE:
1133	        exclude = !(strcmpResult >= 0);
1134		break;
1135
1136	    case CTABLE_COMP_GT:
1137	        exclude = !(strcmpResult > 0);
1138		break;
1139
1140	    default:
1141	        Tcl_Panic ("compare type %d not implemented for field \"${fieldName}\"", compType);
1142	  }
1143	  break;
1144}
1145
1146#
1147# gen_standard_comp_switch_source - emit the standard compare source
1148#
1149proc gen_standard_comp_switch_source {fieldName} {
1150    variable standardCompSwitchSource
1151
1152    return [string range [subst -nobackslashes -nocommands $standardCompSwitchSource] 1 end-1]
1153}
1154
1155#
1156# boolCompSource - code we run subst over to generate a compare of a
1157# boolean (bit)
1158#
1159variable boolCompSource {
1160      case $fieldEnum: {
1161[gen_standard_comp_null_check_source $table $fieldName]
1162	switch (compType) {
1163	  case CTABLE_COMP_TRUE:
1164	     exclude = (!row->$fieldName);
1165	     break;
1166
1167	  case CTABLE_COMP_FALSE:
1168	    exclude = row->$fieldName;
1169	    break;
1170	}
1171	break;
1172      }
1173}
1174
1175#
1176# numberCompSource - code we run subst over to generate a compare of a standard
1177#  number such as an integer, long, double, and wide integer.  (We have to
1178#  handle shorts and floats specially due to type coercion requirements.)
1179#
1180variable numberCompSource {
1181        case $fieldEnum: {
1182[gen_standard_comp_null_check_source $table $fieldName]
1183          switch (compType) {
1184	    case CTABLE_COMP_LT:
1185	        exclude = !(row->$fieldName < row1->$fieldName);
1186		break;
1187
1188	    case CTABLE_COMP_LE:
1189	        exclude = !(row->$fieldName <= row1->$fieldName);
1190		break;
1191
1192	    case CTABLE_COMP_EQ:
1193	        exclude = !(row->$fieldName == row1->$fieldName);
1194		break;
1195
1196	    case CTABLE_COMP_NE:
1197	        exclude = !(row->$fieldName != row1->$fieldName);
1198		break;
1199
1200	    case CTABLE_COMP_GE:
1201	        exclude = !(row->$fieldName >= row1->$fieldName);
1202		break;
1203
1204	    case CTABLE_COMP_GT:
1205	        exclude = !(row->$fieldName > row1->$fieldName);
1206		break;
1207
1208	    case CTABLE_COMP_TRUE:
1209	        exclude = (!row->$fieldName);
1210		break;
1211
1212	    case CTABLE_COMP_FALSE:
1213	        exclude = row->$fieldName;
1214		break;
1215
1216	    default:
1217	        Tcl_Panic ("compare type %d not implemented for field \"${fieldName}\"", compType);
1218	  }
1219	  break;
1220        }
1221}
1222
1223#
1224# varstringCompSource - code we run subst over to generate a compare of
1225# a string.
1226#
1227variable varstringCompSource {
1228        case $fieldEnum: {
1229          int     strcmpResult;
1230
1231[gen_standard_comp_null_check_source $table $fieldName]
1232
1233	  if ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_NOTMATCH) || (compType == CTABLE_COMP_MATCH_CASE) || (compType == CTABLE_COMP_NOTMATCH_CASE)) {
1234[gen_null_exclude_during_sort_comp $table $fieldName]
1235	      // matchMeansKeep will be 1 if matching means keep,
1236	      // 0 if it means discard
1237	      int matchMeansKeep = ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_MATCH_CASE));
1238	      struct ctableSearchMatchStruct *sm = (struct ctableSearchMatchStruct *)component->clientData;
1239
1240	      if (sm->type == CTABLE_STRING_MATCH_ANCHORED) {
1241		  CONST char *field;
1242		  CONST char *match;
1243
1244		  exclude = !matchMeansKeep;
1245		  for (field = row->$fieldName, match = row1->$fieldName; *match != '*' && *match != '\0'; match++, field++) {
1246		      // printf("comparing '%c' and '%c'\n", *field, *match);
1247		      if (sm->nocase) {
1248			  if (tolower (*field) != tolower (*match)) {
1249			      exclude = matchMeansKeep;
1250			      break;
1251			  }
1252		      } else {
1253			  if (*field != *match) {
1254			      exclude = matchMeansKeep;
1255			      break;
1256			  }
1257		      }
1258		  }
1259		  // if we got here it was anchored and we now know the score
1260		  break;
1261	      } else if (sm->type == CTABLE_STRING_MATCH_UNANCHORED) {
1262	          exclude = (boyer_moore_search (sm, (unsigned char *)row->$fieldName, row->_${fieldName}Length, sm->nocase) == NULL);
1263		  if (!matchMeansKeep) exclude = !exclude;
1264		  break;
1265	      } else if (sm->type == CTABLE_STRING_MATCH_PATTERN) {
1266	          exclude = !(Tcl_StringCaseMatch (row->$fieldName, row1->$fieldName, ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_NOTMATCH))));
1267		  if (!matchMeansKeep) exclude = !exclude;
1268		  break;
1269              } else {
1270		  Tcl_Panic ("software bug, sm->type unknown match type");
1271	      }
1272	  }
1273
1274          strcmpResult = strcmp (row->$fieldName, row1->$fieldName);
1275[gen_standard_comp_switch_source $fieldName]
1276        }
1277}
1278
1279#
1280# fixedstringCompSource - code we run subst over to generate a comapre of a
1281# fixed-length string.
1282#
1283variable fixedstringCompSource {
1284        case $fieldEnum: {
1285          int     strcmpResult;
1286
1287[gen_standard_comp_null_check_source $table $fieldName]
1288          strcmpResult = strncmp (row->$fieldName, row1->$fieldName, $length);
1289[gen_standard_comp_switch_source $fieldName]
1290        }
1291}
1292
1293#
1294# binaryDataCompSource - code we run subst over to generate a comapre of a
1295# binary data.
1296#
1297variable binaryDataCompSource {
1298        case $fieldEnum: {
1299          int              strcmpResult;
1300
1301[gen_standard_comp_null_check_source $table $fieldName]
1302          strcmpResult = memcmp ((void *)&row->$fieldName, (void *)&row1->$fieldName, $length);
1303[gen_standard_comp_switch_source $fieldName]
1304        }
1305}
1306
1307#
1308# tclobjCompSource - code we run subst over to generate a compare of
1309# a tclobj for use in a search.
1310#
1311# this could be so wrong - there may be a way to keep it from generating
1312# the text -- right now we are doing a Tcl_GetStringFromObj in the
1313# routine that sets this up, maybe don't do that and figure out some
1314# way to compare objects (?)
1315#
1316variable tclobjCompSource {
1317        case $fieldEnum: {
1318          int      strcmpResult;
1319
1320[gen_standard_comp_null_check_source $table $fieldName]
1321          strcmpResult = strcmp (Tcl_GetString (row->$fieldName), Tcl_GetString (row1->$fieldName));
1322[gen_standard_comp_switch_source $fieldName]
1323        }
1324}
1325
1326#
1327# keyCompSource - code we run subst over to generate a compare of
1328# a string.
1329#
1330variable keyCompSource {
1331        case $fieldEnum: {
1332          int     strcmpResult;
1333
1334[gen_standard_comp_null_check_source $table $fieldName]
1335	  if ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_NOTMATCH) || (compType == CTABLE_COMP_MATCH_CASE) || (compType == CTABLE_COMP_NOTMATCH_CASE)) {
1336[gen_null_exclude_during_sort_comp $table $fieldName]
1337	      // matchMeansKeep will be 1 if matching means keep,
1338	      // 0 if it means discard
1339	      int matchMeansKeep = ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_MATCH_CASE));
1340	      struct ctableSearchMatchStruct *sm = (struct ctableSearchMatchStruct *)component->clientData;
1341
1342	      if (sm->type == CTABLE_STRING_MATCH_ANCHORED) {
1343		  char *field;
1344		  char *match;
1345
1346		  exclude = !matchMeansKeep;
1347		  for (field = row->hashEntry.key, match = row1->hashEntry.key; *match != '*' && *match != '\0'; match++, field++) {
1348		      // printf("comparing '%c' and '%c'\n", *field, *match);
1349		      if (sm->nocase) {
1350			  if (tolower (*field) != tolower (*match)) {
1351			      exclude = matchMeansKeep;
1352			      break;
1353			  }
1354		      } else {
1355			  if (*field != *match) {
1356			      exclude = matchMeansKeep;
1357			      break;
1358			  }
1359		      }
1360		  }
1361		  // if we got here it was anchored and we now know the score
1362		  break;
1363	      } else if (sm->type == CTABLE_STRING_MATCH_UNANCHORED) {
1364	          exclude = (boyer_moore_search (sm, (unsigned char *)row->hashEntry.key, strlen(row->hashEntry.key), sm->nocase) == NULL);
1365		  if (!matchMeansKeep) exclude = !exclude;
1366		  break;
1367	      } else if (sm->type == CTABLE_STRING_MATCH_PATTERN) {
1368	          exclude = !(Tcl_StringCaseMatch (row->hashEntry.key, row1->hashEntry.key, ((compType == CTABLE_COMP_MATCH) || (compType == CTABLE_COMP_NOTMATCH))));
1369		  if (!matchMeansKeep) exclude = !exclude;
1370		  break;
1371              } else {
1372		  Tcl_Panic ("software bug, sm->type unknown match type");
1373	      }
1374	  }
1375
1376          strcmpResult = strcmp (row->hashEntry.key, row1->hashEntry.key);
1377[gen_standard_comp_switch_source $fieldName]
1378        }
1379}
1380
1381
1382#####
1383#
1384# Generating Code To Set Fields In Rows
1385#
1386#####
1387
1388variable fieldObjSetSource {
1389ctable_BaseRow *${table}_make_empty_row (CTable *ctable) {
1390    struct $table *row;
1391
1392    row = (struct $table *)ckalloc (sizeof (struct $table));
1393    ${table}_init (ctable, row);
1394
1395    return (ctable_BaseRow*) row;
1396}
1397
1398//
1399// Wrapper for hash search.
1400//
1401// Always succeeds unless using shared memory and we run out of shared memory
1402//
1403// Must handle this in caller beacuse we're not passing an interpreter in
1404//
1405struct $table *${table}_find_or_create (Tcl_Interp *interp, CTable *ctable, const char *key, int *indexCtlPtr) {
1406    int flags = KEY_VOLATILE;
1407    const char *key_value = key;
1408    struct $table *row = NULL;
1409
1410    static struct $table *savedRow = NULL;
1411#ifdef WITH_SHARED_TABLES
1412    static struct $table *savedSharedRow = NULL;
1413    int isShared = ctable->share_type == CTABLE_SHARED_MASTER;
1414    struct $table *nextRow = isShared ? savedSharedRow : savedRow;
1415#else
1416    struct $table *nextRow = savedRow;
1417#endif
1418
1419    // Make sure the preallocated row is prepared
1420    if(!nextRow) {
1421#ifdef WITH_SHARED_TABLES
1422        if(isShared) {
1423	    nextRow = (struct $table *)shmalloc(ctable->share, sizeof(struct $table));
1424	    if(!nextRow) {
1425		if(ctable->share_panic) ${table}_shmpanic(ctable);
1426		TclShmError(interp, key);
1427	        return NULL;
1428	    }
1429	} else
1430#endif
1431	    nextRow = (struct $table *)ckalloc(sizeof(struct $table));
1432
1433        ${table}_init (ctable, nextRow);
1434    }
1435
1436#ifdef WITH_SHARED_TABLES
1437    if(isShared) {
1438        char *new_key_value = (char *)shmalloc(ctable->share, strlen(key)+1);
1439	if(!new_key_value) {
1440	    if(ctable->share_panic) ${table}_shmpanic(ctable);
1441	    TclShmError(interp, key);
1442	    return NULL;
1443	}
1444	strcpy(new_key_value, key);
1445	key_value = new_key_value;
1446	flags = KEY_STATIC;
1447    }
1448#endif
1449
1450    row = (struct $table *)ctable_StoreHashEntry (ctable->keyTablePtr, key_value, &nextRow->hashEntry, flags, indexCtlPtr);
1451
1452    // If we actually added a row, add it to the hash
1453    if (*indexCtlPtr) {
1454	ctable_ListInsertHead (&ctable->ll_head, (ctable_BaseRow *)row, 0);
1455	ctable->count++;
1456	// printf ("created new entry for '%s'\n", key);
1457
1458	// Discard the row we used
1459	nextRow = NULL;
1460    } else {
1461	// printf ("found existing entry for '%s'\n", key);
1462
1463#ifdef WITH_SHARED_TABLES
1464	// Discard the copy of the key we used
1465	if(flags == KEY_STATIC) {
1466	    // Don't need to "shmfree" because the key was never made visible to
1467	    // any readers.
1468	    shmdealloc(ctable->share, (char*)key_value);
1469	}
1470#endif
1471    }
1472
1473    // Remember what we allocated (or didn't).
1474#ifdef WITH_SHARED_TABLES
1475    if(isShared)
1476	savedSharedRow = nextRow;
1477    else
1478#endif
1479	savedRow = nextRow;
1480
1481    return row;
1482}
1483
1484int
1485${table}_set_fieldobj (Tcl_Interp *interp, CTable *ctable, Tcl_Obj *obj, struct $table *row, Tcl_Obj *fieldObj, int indexCtl, int nocomplain)
1486{
1487    int field;
1488
1489    if (Tcl_GetIndexFromObj (interp, fieldObj, ${table}_fields, "field", TCL_EXACT, &field) != TCL_OK) {
1490	if (nocomplain) {
1491	    Tcl_ResetResult(interp);
1492	    return TCL_OK;
1493	}
1494	return TCL_ERROR;
1495    }
1496
1497    return ${table}_set (interp, ctable, obj, row, field, indexCtl);
1498}
1499}
1500
1501variable fieldSetSource {
1502int
1503${table}_set (Tcl_Interp *interp, CTable *ctable, Tcl_Obj *obj, ctable_BaseRow *vRow, int field, int indexCtl) $leftCurly
1504    ${table} *row = (${table}*) vRow;
1505}
1506
1507variable fieldSetSwitchSource {
1508    switch ((enum ${table}_fields) field) $leftCurly
1509}
1510
1511variable fieldObjGetSource {
1512ctable_BaseRow *${table}_find (CTable *ctable, CONST char *key) {
1513    ctable_HashEntry *hashEntry;
1514
1515    hashEntry = ctable_FindHashEntry (ctable->keyTablePtr, key);
1516    if (hashEntry == (ctable_HashEntry *) NULL) {
1517        return (ctable_BaseRow *) NULL;
1518    }
1519
1520	return (ctable_BaseRow *)((char*)hashEntry - offsetof(ctable_BaseRow, hashEntry));
1521}
1522
1523Tcl_Obj *
1524${table}_get_fieldobj (Tcl_Interp *interp, struct $table *row, Tcl_Obj *fieldObj)
1525{
1526    int field;
1527
1528    if (Tcl_GetIndexFromObj (interp, fieldObj, ${table}_fields, "field", TCL_EXACT, &field) != TCL_OK) {
1529        return (Tcl_Obj *)NULL;
1530    }
1531
1532    return ${table}_get (interp, row, field);
1533}
1534
1535int
1536${table}_lappend_field (Tcl_Interp *interp, Tcl_Obj *destListObj, ctable_BaseRow *vPointer, int field)
1537{
1538    struct $table *row = (struct $table *) vPointer;
1539
1540    Tcl_Obj *obj = ${table}_get (interp, row, field);
1541
1542    if (Tcl_ListObjAppendElement (interp, destListObj, obj) == TCL_ERROR) {
1543        return TCL_ERROR;
1544    }
1545
1546    return TCL_OK;
1547}
1548
1549int
1550${table}_lappend_fieldobj (Tcl_Interp *interp, ctable_BaseRow *vPointer, Tcl_Obj *fieldObj)
1551{
1552    struct $table *row = (struct $table*) vPointer;
1553    Tcl_Obj *obj = ${table}_get_fieldobj (interp, row, fieldObj);
1554
1555    if (obj == NULL) {
1556        return TCL_ERROR;
1557    }
1558
1559    if (Tcl_ListObjAppendElement (interp, Tcl_GetObjResult(interp), obj) == TCL_ERROR) {
1560        return TCL_ERROR;
1561    }
1562
1563    return TCL_OK;
1564}
1565}
1566
1567variable lappendFieldAndNameObjSource {
1568int
1569${table}_lappend_field_and_name (Tcl_Interp *interp, Tcl_Obj *destListObj, ctable_BaseRow *vPointer, int field)
1570{
1571    struct $table *row = (struct $table *) vPointer;
1572    Tcl_Obj   *obj;
1573
1574    if (Tcl_ListObjAppendElement (interp, destListObj, ${table}_NameObjList[field]) == TCL_ERROR) {
1575        return TCL_ERROR;
1576    }
1577
1578    obj = ${table}_get (interp, row, field);
1579    if (Tcl_ListObjAppendElement (interp, destListObj, obj) == TCL_ERROR) {
1580        return TCL_ERROR;
1581    }
1582
1583    return TCL_OK;
1584}
1585
1586int
1587${table}_lappend_field_and_nameobj (Tcl_Interp *interp, ctable_BaseRow *vPointer, Tcl_Obj *fieldObj)
1588{
1589    int        field;
1590
1591    if (Tcl_GetIndexFromObj (interp, fieldObj, ${table}_fields, "field", TCL_EXACT, &field) != TCL_OK) {
1592        return TCL_ERROR;
1593    }
1594
1595    return ${table}_lappend_field_and_name (interp, Tcl_GetObjResult(interp), vPointer, field);
1596}
1597
1598}
1599
1600variable lappendNonnullFieldAndNameObjSource {
1601int
1602${table}_lappend_nonnull_field_and_name (Tcl_Interp *interp, Tcl_Obj *destListObj, ctable_BaseRow *vPointer, int field)
1603{
1604    struct $table *row = (struct $table *)vPointer;
1605    Tcl_Obj   *obj;
1606
1607    obj = ${table}_get (interp, row, field);
1608    if (obj == ${table}_NullValueObj) {
1609        return TCL_OK;
1610    }
1611
1612    if (Tcl_ListObjAppendElement (interp, destListObj, ${table}_NameObjList[field]) == TCL_ERROR) {
1613        return TCL_ERROR;
1614    }
1615
1616    if (Tcl_ListObjAppendElement (interp, destListObj, obj) == TCL_ERROR) {
1617        return TCL_ERROR;
1618    }
1619
1620    return TCL_OK;
1621}
1622
1623int
1624${table}_lappend_nonnull_field_and_nameobj (Tcl_Interp *interp, ctable_BaseRow *vPointer, Tcl_Obj *fieldObj)
1625{
1626    int        field;
1627
1628    if (Tcl_GetIndexFromObj (interp, fieldObj, ${table}_fields, "field", TCL_EXACT, &field) != TCL_OK) {
1629        return TCL_ERROR;
1630    }
1631
1632    return ${table}_lappend_nonnull_field_and_name (interp, Tcl_GetObjResult(interp), vPointer, field);
1633}
1634
1635}
1636
1637variable arraySetFromFieldSource {
1638int
1639${table}_array_set (Tcl_Interp *interp, Tcl_Obj *arrayNameObj, ctable_BaseRow *vPointer, int field)
1640{
1641    struct $table *row = (struct $table *)vPointer;
1642    Tcl_Obj   *obj;
1643
1644    obj = ${table}_get (interp, row, field);
1645    if (obj == ${table}_NullValueObj) {
1646        // it's null?  unset it from the array, might not be there, ignore error
1647        Tcl_UnsetVar2 (interp, Tcl_GetString (arrayNameObj), ${table}_fields[field], 0);
1648        return TCL_OK;
1649    }
1650
1651    if (Tcl_ObjSetVar2 (interp, arrayNameObj, ${table}_NameObjList[field], obj, TCL_LEAVE_ERR_MSG) == (Tcl_Obj *)NULL) {
1652        return TCL_ERROR;
1653    }
1654
1655    return TCL_OK;
1656}
1657
1658int
1659${table}_array_set_with_nulls (Tcl_Interp *interp, Tcl_Obj *arrayNameObj, ctable_BaseRow *vPointer, int field)
1660{
1661    struct $table *row = (struct $table*)vPointer;
1662    Tcl_Obj   *obj;
1663
1664    obj = ${table}_get (interp, row, field);
1665    if (Tcl_ObjSetVar2 (interp, arrayNameObj, ${table}_NameObjList[field], obj, TCL_LEAVE_ERR_MSG) == (Tcl_Obj *)NULL) {
1666        return TCL_ERROR;
1667    }
1668
1669    return TCL_OK;
1670}
1671
1672}
1673
1674#####
1675#
1676# Generating Code To Get Fields From A Rows
1677#
1678#####
1679
1680variable fieldGetSource {
1681Tcl_Obj *
1682${table}_get (Tcl_Interp *interp, ctable_BaseRow *vPointer, int field) $leftCurly
1683    struct $table *row = (struct $table*) vPointer;
1684
1685    switch ((enum ${table}_fields) field) $leftCurly
1686}
1687
1688variable fieldGetStringSource {
1689CONST char *
1690${table}_get_string (const ctable_BaseRow *vPointer, int field, int *lengthPtr, Tcl_Obj *utilityObj) $leftCurly
1691    int length;
1692    const struct $table *row = (const struct $table*) vPointer;
1693
1694    if (lengthPtr == (int *) NULL) {
1695        lengthPtr = &length;
1696    }
1697
1698    switch ((enum ${table}_fields) field) $leftCurly
1699}
1700
1701#####
1702#
1703# Generating Code To Read And Write Tab-Separated Rows
1704#
1705#####
1706
1707variable tabSepFunctionsSource {
1708
1709void ${table}_dumpFieldNums(int *fieldNums, int nFields, CONST char *msg)
1710{
1711    int i;
1712
1713    fprintf(stderr, "%s, %d fields: ", msg, nFields);
1714
1715    for(i = 0; i < nFields; i++) {
1716	int num = fieldNums[i];
1717	if(num == -1) fprintf(stderr, "* ");
1718	else fprintf(stderr, "%d=%s ", num, ${table}_fields[num]);
1719    }
1720
1721    fprintf(stderr, "\n");
1722}
1723
1724void
1725${table}_dstring_append_get_tabsep (CONST char *key, ctable_BaseRow *vPointer, int *fieldNums, int nFields, Tcl_DString *dsPtr, int noKeys, CONST char *sepstr, int quoteType, CONST char *nullString) {
1726    int              i;
1727    CONST char      *string;
1728    int              nChars;
1729    Tcl_Obj         *utilityObj = Tcl_NewObj();
1730    struct $table *row = (struct $table *) vPointer;
1731
1732    if (!noKeys) {
1733	int copy = 0;
1734	if(quoteType) {
1735	    copy = ctable_quoteString(&key, NULL, quoteType, sepstr);
1736	}
1737	Tcl_DStringAppend (dsPtr, key, -1);
1738	if(copy) {
1739	    ckfree((char *)key);
1740	    key = NULL;
1741	}
1742    }
1743
1744    for (i = 0; i < nFields; i++) {
1745	if (!noKeys || (i > 0)) {
1746	    Tcl_DStringAppend (dsPtr, sepstr, -1);
1747	}
1748
1749	if(nullString && ${table}_is_null(row, fieldNums[i])) {
1750	    Tcl_DStringAppend (dsPtr, nullString, -1);
1751	    continue;
1752	}
1753
1754	string = ${table}_get_string (row, fieldNums[i], &nChars, utilityObj);
1755	if (nChars != 0) {
1756	    int copy = 0;
1757	    if (quoteType && ${table}_needs_quoting[fieldNums[i]]) {
1758		copy = ctable_quoteString(&string, &nChars, quoteType, sepstr);
1759	    }
1760	    Tcl_DStringAppend (dsPtr, string, nChars);
1761	    if(copy) {
1762		ckfree((char *)string);
1763		string = NULL;
1764	    }
1765	}
1766    }
1767    Tcl_DStringAppend (dsPtr, "\n", 1);
1768    Tcl_DecrRefCount (utilityObj);
1769}
1770
1771void
1772${table}_dstring_append_fieldnames (int *fieldNums, int nFields, Tcl_DString *dsPtr, int noKeys, CONST char *sepstr)
1773{
1774    int i;
1775
1776    if(!noKeys) {
1777    	Tcl_DStringAppend(dsPtr, "_key", 4);
1778    }
1779
1780    for (i = 0; i < nFields; i++) {
1781	if (!noKeys || (i > 0)) {
1782	    Tcl_DStringAppend (dsPtr, sepstr, -1);
1783	}
1784
1785	Tcl_DStringAppend(dsPtr, ${table}_fields[fieldNums[i]], -1);
1786    }
1787    Tcl_DStringAppend (dsPtr, "\n", 1);
1788}
1789
1790// TODO: stringPtr argument should probably be CONST and not modified.
1791int
1792${table}_get_fields_from_tabsep (Tcl_Interp *interp, char *stringPtr, int *nFieldsPtr, int **fieldNumsPtr, int *noKeysPtr, CONST char *sepstr, int nocomplain)
1793{
1794    int    i;
1795    int    field;
1796    char  *tab;
1797    char   save = '\0';
1798    int    seplen = strlen(sepstr);
1799    int   *fieldNums = NULL;
1800    char  *s;
1801    int    nColumns;
1802    int    keyCol = -1;
1803
1804    *noKeysPtr = 1;
1805
1806    // find the number of fields and allocate space
1807    nColumns = 2;
1808    s = stringPtr;
1809    while((s = strstr(s, sepstr))) {
1810	nColumns++;
1811	s += strlen(sepstr);
1812    }
1813    fieldNums = (int *)ckalloc(nColumns * sizeof(*fieldNums));
1814
1815    field = 0;
1816    while(stringPtr) {
1817	if ( (tab = strstr(stringPtr, sepstr)) ) {
1818	    save = *tab;
1819	    *tab = 0;
1820	}
1821
1822	if(*noKeysPtr && field == 0 && strcmp(stringPtr, "_key") == 0) {
1823	    *noKeysPtr = 0;
1824	    keyCol = 0;
1825	} else {
1826	    int num = -1;
1827	    for(i = 0; ${table}_fields[i]; i++) {
1828	        if(strcmp(stringPtr, ${table}_fields[i]) == 0) {
1829		    num = i;
1830		    break;
1831		}
1832	    }
1833
1834	    if(!nocomplain && num == -1) {
1835                Tcl_AppendResult (interp, "Unknown field \"", stringPtr, "\" in ${table}", (char *)NULL);
1836		ckfree((char *)fieldNums);
1837                return TCL_ERROR;
1838            }
1839
1840	    if(num == ${table}_keyField) {
1841		if(keyCol>= 0)
1842		    num = -1;
1843		else
1844		    keyCol = num;
1845	    }
1846
1847	    fieldNums[field++] = num;
1848	}
1849
1850	if(tab) {
1851	    *tab = save;
1852	    tab += seplen;
1853	}
1854
1855	stringPtr = tab;
1856    }
1857
1858    *nFieldsPtr = field;
1859    *fieldNumsPtr = fieldNums;
1860
1861    return TCL_OK;
1862}
1863
1864int
1865${table}_export_tabsep (Tcl_Interp *interp, CTable *ctable, CONST char *channelName, int *fieldNums, int nFields, char *pattern, int noKeys, int withFieldNames, CONST char *sepstr, CONST char *term, int quoteType, CONST char *nullString) {
1866    Tcl_Channel             channel;
1867    int                     mode;
1868    Tcl_DString             dString;
1869    char                   *key;
1870    ctable_BaseRow         *row;
1871
1872    if ((channel = Tcl_GetChannel (interp, channelName, &mode)) == NULL) {
1873        return TCL_ERROR;
1874    }
1875
1876    if ((mode & TCL_WRITABLE) == 0) {
1877	Tcl_AppendResult (interp, "channel \"", channelName, "\" not writable", (char *)NULL);
1878        return TCL_ERROR;
1879    }
1880
1881    Tcl_DStringInit (&dString);
1882
1883    if (withFieldNames) {
1884
1885        Tcl_DStringSetLength (&dString, 0);
1886
1887	${table}_dstring_append_fieldnames (fieldNums, nFields, &dString, noKeys, sepstr);
1888
1889	if (Tcl_WriteChars (channel, Tcl_DStringValue (&dString), Tcl_DStringLength (&dString)) < 0) {
1890	    Tcl_AppendResult (interp, "write error on channel \"", channelName, "\"", (char *)NULL);
1891	    Tcl_DStringFree (&dString);
1892	    return TCL_ERROR;
1893	}
1894
1895    }
1896
1897    CTABLE_LIST_FOREACH (ctable->ll_head, row, 0) {
1898	// if there's no pattern and no keys has been set, no need to
1899	// get the key
1900        if ((pattern == NULL) && noKeys) {
1901	    key = NULL;
1902	} else {
1903	    // key is needed and if there's a pattern, check it
1904	    key = row->hashEntry.key;
1905	    if ((pattern != NULL) && (!Tcl_StringCaseMatch (key, pattern, 1))) continue;
1906	}
1907
1908        Tcl_DStringSetLength (&dString, 0);
1909
1910	${table}_dstring_append_get_tabsep (key, (struct ${table} *)row, fieldNums, nFields, &dString, noKeys, sepstr, quoteType, nullString);
1911
1912	if (Tcl_WriteChars (channel, Tcl_DStringValue (&dString), Tcl_DStringLength (&dString)) < 0) {
1913	    Tcl_AppendResult (interp, "write error on channel \"", channelName, "\"", (char *)NULL);
1914	    Tcl_DStringFree (&dString);
1915	    return TCL_ERROR;
1916	}
1917    }
1918
1919    Tcl_DStringFree (&dString);
1920
1921    if(term) {
1922	if (Tcl_WriteChars (channel, term, strlen(term)) < 0 || Tcl_WriteChars(channel, "\n", 1) < 0) {
1923	    Tcl_AppendResult (interp, "write error on channel \"", channelName, "\"", (char *)NULL);
1924	    return TCL_ERROR;
1925	}
1926    }
1927
1928    return TCL_OK;
1929}
1930
1931int
1932${table}_set_from_tabsep (Tcl_Interp *interp, CTable *ctable, CONST char *stringPtr, int *fieldIds, int nFields, int keyColumn, CONST char *sepstr, CONST char *nullString, int quoteType, int dirty) {
1933    struct $table *row;
1934    const char    *key;
1935    int            indexCtl;
1936    int            i;
1937    int		   col;
1938    Tcl_Obj       *utilityObj = Tcl_NewObj ();
1939    char           keyNumberString[32];
1940	char           blankString[] = { '\0' };
1941    char	  *keyCopy = NULL;
1942    int		   seplen = strlen(sepstr);
1943
1944    if (keyColumn == -1) {
1945        sprintf (keyNumberString, "%d", ctable->autoRowNumber++);
1946        key = keyNumberString;
1947    } else {
1948        // find the beginning of the "keyColumn"th column in the string.
1949        for (key = stringPtr, i = 0; key && i < keyColumn; i++) {
1950			key = strstr(key, sepstr);
1951			if(key) key += seplen;
1952        }
1953        if (key) {
1954			int keyLength;
1955			CONST char *keyEnd = strstr(key, sepstr);
1956			if(keyEnd) {
1957				keyLength = keyEnd - key;
1958			} else {
1959				keyLength = strlen(key);
1960			}
1961			keyCopy = (char *) ckalloc(keyLength+1);
1962			if(quoteType) {
1963			    ctable_copyDequoted(keyCopy, key, keyLength, quoteType);
1964			} else {
1965				strncpy(keyCopy, key, keyLength);
1966				keyCopy[keyLength] = '\0';
1967			}
1968			key = keyCopy;
1969        }
1970		if(!key) {
1971			keyNumberString[0] = '\0';
1972			key = keyNumberString;
1973		}
1974    }
1975
1976    row = ${table}_find_or_create (interp, ctable, key, &indexCtl);
1977	if(keyCopy) {
1978		// done with keyCopy so free it now before checking the row result.
1979		ckfree(keyCopy);
1980		key = keyCopy = NULL;
1981	}
1982    if(!row) {
1983		return TCL_ERROR;
1984    }
1985
1986    for (col = i = 0; col < nFields; i++) {
1987		const char *field;
1988		int fieldLength;
1989
1990		if(stringPtr) {
1991			field = stringPtr;
1992            stringPtr = strstr (stringPtr, sepstr);
1993			if(stringPtr) {
1994				fieldLength = (stringPtr - field);
1995				stringPtr += seplen;
1996			} else {
1997				fieldLength = -1;
1998			}
1999		} else {
2000			field = (nullString != NULL ? nullString : "");
2001			fieldLength = -1;
2002		}
2003
2004		if(i == keyColumn) {
2005			continue;
2006		}
2007		if(fieldIds[col] == -1) {
2008			col++;
2009			continue;
2010		}
2011
2012		if(nullString == NULL ||
2013		   ${table}_nullable_fields[fieldIds[col]] == 0 ||
2014		   (field != nullString &&
2015			field[0] != nullString[0] &&
2016			strcmp(field, nullString) != 0)
2017		) {
2018			Tcl_SetStringObj (utilityObj, field, fieldLength);
2019
2020			if (${table}_needs_quoting[fieldIds[col]] && quoteType && field != blankString) {
2021				fieldLength = ctable_dequoteString(Tcl_GetString(utilityObj), -1, quoteType);
2022				Tcl_SetObjLength(utilityObj, fieldLength);
2023			}
2024
2025			if (${table}_set (interp, ctable, utilityObj, row, fieldIds[col], indexCtl) == TCL_ERROR) {
2026				Tcl_DecrRefCount (utilityObj);
2027				return TCL_ERROR;
2028			}
2029		}
2030
2031		col++;
2032    }
2033
2034    if(dirty) {
2035        if (${table}_dirty (interp, ctable, row) == TCL_ERROR) {
2036	    Tcl_DecrRefCount (utilityObj);
2037	    return TCL_ERROR;
2038        }
2039    }
2040
2041
2042    if (indexCtl == CTABLE_INDEX_NEW) {
2043        if(${table}_index_defaults(interp, ctable, row) == TCL_ERROR) {
2044	    Tcl_DecrRefCount (utilityObj);
2045	    return TCL_ERROR;
2046	}
2047    }
2048
2049    Tcl_DecrRefCount (utilityObj);
2050    return TCL_OK;
2051}
2052
2053int
2054${table}_import_tabsep (Tcl_Interp *interp, CTable *ctable, CONST char *channelName, int *fieldNums, int nFields, CONST char *pattern, int noKeys, int withFieldNames, CONST char *sepstr, CONST char *skip, CONST char *term, int nocomplain, int withNulls, int quoteType, CONST char *nullString, int poll_interval, Tcl_Obj *poll_code, int poll_foreground, int dirty) {
2055    Tcl_Channel      channel;
2056    int              mode;
2057    Tcl_Obj         *lineObj = NULL;
2058    char            *stringPtr;                  // TODO: should probably be CONST and not modified.
2059    int              recordNumber = 0;
2060    char             keyNumberString[32];
2061    int		     keyColumn;
2062    int		     i;
2063    int		     seplen = strlen(sepstr);
2064    int		     col;
2065    int		    *newFieldNums = NULL;
2066    int	             status = TCL_OK;
2067    int		     poll_counter = 0;
2068
2069    if ((channel = Tcl_GetChannel (interp, channelName, &mode)) == NULL) {
2070        return TCL_ERROR;
2071    }
2072
2073    if ((mode & TCL_READABLE) == 0) {
2074	Tcl_AppendResult (interp, "channel \"", channelName, "\" not readable", (char *)NULL);
2075        return TCL_ERROR;
2076    }
2077
2078    /* Don't allocate this until necessary */
2079    lineObj = Tcl_NewObj();
2080
2081    /* If no fields, read field names from first row */
2082    if(withFieldNames) {
2083	do {
2084            Tcl_SetStringObj (lineObj, "", 0);
2085            if (Tcl_GetsObj (channel, lineObj) <= 0) {
2086	        Tcl_DecrRefCount (lineObj);
2087	        return TCL_OK;
2088	    }
2089	    stringPtr = Tcl_GetString (lineObj);
2090	} while(skip && Tcl_StringMatch(stringPtr, skip));
2091
2092	if (${table}_get_fields_from_tabsep(interp, stringPtr, &nFields, &newFieldNums, &noKeys, sepstr, nocomplain) == TCL_ERROR) {
2093	    status = TCL_ERROR;
2094	    goto cleanup;
2095	}
2096	fieldNums = newFieldNums;
2097    }
2098
2099    if(noKeys) {
2100	keyColumn = -1;
2101    } else {
2102	keyColumn = 0;
2103    }
2104
2105    for(col = i = 0; i < nFields; i++) {
2106	if(fieldNums[i] == ${table}_keyField) {
2107	    keyColumn = i;
2108	} else {
2109	    if(col != i) {
2110	        fieldNums[col] = fieldNums[i];
2111	    }
2112	    col++;
2113	}
2114    }
2115    if(col != i)
2116	nFields--;
2117
2118//${table}_dumpFieldNums(fieldNums, nFields, "after key check");
2119    if(withNulls && !nullString) {
2120	int nullLen;
2121
2122	nullString = Tcl_GetStringFromObj (${table}_NullValueObj, &nullLen);
2123    }
2124
2125    while (1) {
2126
2127	if (poll_interval) {
2128	    if (++poll_counter >= poll_interval) {
2129		poll_counter = 0;
2130		if (poll_code) {
2131		    int result = Tcl_EvalObjEx (interp, poll_code, 0);
2132		    if (poll_foreground) {
2133			switch (result) {
2134		            case TCL_ERROR: {
2135				Tcl_AppendResult (interp, " in -poll_code", (char *)NULL);
2136				status = TCL_ERROR;
2137				goto cleanup;
2138			    }
2139			    case TCL_BREAK: {
2140				status = TCL_OK;
2141				goto cleanup;
2142			    }
2143			    case TCL_RETURN: {
2144				status = TCL_RETURN;
2145				goto cleanup;
2146			    }
2147			}
2148		    } else if(result == TCL_ERROR) {
2149			Tcl_BackgroundError(interp);
2150			Tcl_ResetResult(interp);
2151			// Stop polling if the poll command fails
2152			poll_interval = 0;
2153		    }
2154		} else {
2155		    Tcl_DoOneEvent(0);
2156		}
2157	    }
2158	}
2159
2160	do {
2161            Tcl_SetStringObj (lineObj, "", 0);
2162	    if (Tcl_GetsObj (channel, lineObj) <= 0) {
2163		goto done;
2164	    }
2165
2166	    stringPtr = Tcl_GetString (lineObj);
2167
2168	    if(term && term[0] && Tcl_StringCaseMatch (stringPtr, term, 1)) goto done;
2169	} while(skip && Tcl_StringMatch(stringPtr, skip));
2170
2171	// if pattern exists, see if it does not match key and if so, skip
2172	if (pattern != NULL) {
2173		char *key;
2174	    for (key = stringPtr, i = 0; key && i < keyColumn; i++) {
2175		key = strstr(key, sepstr);
2176		if(key) key += seplen;
2177	    }
2178	    if (key) {
2179			char *keyEnd = strstr(key, sepstr);
2180	        if(keyEnd) {
2181				char save = *keyEnd;   // modifying read-only strings is gross.
2182				*keyEnd = '\0';
2183				if (!Tcl_StringCaseMatch (stringPtr, pattern, 1)) {
2184					*keyEnd = save;
2185					continue;
2186				}
2187				*keyEnd = save;
2188			} else {
2189				if (!Tcl_StringCaseMatch (stringPtr, pattern, 1)) continue;
2190			}
2191
2192
2193	    }
2194	}
2195
2196	if (${table}_set_from_tabsep (interp, ctable, stringPtr, fieldNums, nFields, keyColumn, sepstr, nullString, quoteType, dirty) == TCL_ERROR) {
2197	    char lineNumberString[32];
2198
2199	    sprintf (lineNumberString, "%d", recordNumber + 1);
2200            Tcl_AppendResult (interp, " while reading line ", lineNumberString, " of input", (char *)NULL);
2201	    status = TCL_ERROR;
2202	    goto cleanup;
2203	}
2204
2205	recordNumber++;
2206    }
2207done:
2208
2209    if(noKeys)
2210    {
2211       sprintf (keyNumberString, "%d", ctable->autoRowNumber - 1);
2212       Tcl_SetObjResult (interp, Tcl_NewStringObj (keyNumberString, -1));
2213    }
2214
2215cleanup:
2216    if(lineObj) {
2217	Tcl_DecrRefCount (lineObj);
2218    }
2219
2220    if(newFieldNums) {
2221	ckfree((char *)newFieldNums);
2222    }
2223
2224    return status;
2225}
2226}
2227
2228#
2229# new_table - the proc that starts defining a table, really, a meta table, and
2230#  also following it will be the definition of the structure itself. Clears
2231# all per-table variables in ::ctable::
2232#
2233proc new_table {name} {
2234    variable table
2235    variable booleans
2236    variable nonBooleans
2237    variable fields
2238    variable fieldList
2239    variable keyField
2240    variable keyFieldName
2241    variable filters
2242    variable rawCode
2243
2244    set table $name
2245
2246    set booleans ""
2247    set nonBooleans ""
2248    unset -nocomplain fields
2249    set fieldList ""
2250    unset -nocomplain keyField
2251    unset -nocomplain keyFieldName
2252    unset -nocomplain filters
2253    unset -nocomplain rawCode
2254
2255    foreach var [info vars ::ctable::fields::*] {
2256        unset -nocomplain $var
2257    }
2258}
2259
2260#
2261# end_table - proc that declares the end of defining a table - currently does
2262#  nothing
2263#
2264proc end_table {} {
2265}
2266
2267#
2268# filters
2269#
2270
2271#
2272# Defining word for a C filter in a CTable - mostly error checking
2273#
2274proc cfilter {filterName args} {
2275    variable filters
2276    variable reservedWords
2277
2278    if {[lsearch -exact $reservedWords $filterName] >= 0} {
2279        error "illegal filter name \"$filterName\" -- it's a reserved word"
2280    }
2281
2282    if {![is_legal $filterName]} {
2283        error "filter name \"$filterName\" must start with a letter and can only contain letters, numbers, and underscores"
2284    }
2285
2286    if {[llength $args] % 2 != 0} {
2287        error "number of values in filter '$filterName' definition arguments ('$args') must be even"
2288    }
2289
2290    if {[info exists filters($filterName)]} {
2291	error "duplicate definition of filter '$filterName'"
2292    }
2293
2294    array set filter $args
2295
2296    if {![info exists filter(code)]} {
2297	error "no code provided for cfilter '$filterName'"
2298    }
2299
2300    set filters($filterName) $args
2301}
2302
2303#
2304# Generate code to declare a memoized argument from a list of arguments
2305#
2306proc gen_decl_filter_arg {type name} {
2307    # Note spacing
2308    array set type_map {
2309	boolean		"int "
2310	varstring	"char *"
2311	fixedstring	"char *"
2312	short		"long "
2313	int		"long "
2314	long		"long "
2315	float		"double "
2316	double		"double "
2317	key		"char *"
2318    }
2319    array set init_map {
2320	boolean		" = 0"
2321	varstring	" = NULL"
2322	fixedstring	" = NULL"
2323	short		" = 0"
2324	int		" = 0"
2325	long		" = 0"
2326	float		" = 0.0"
2327	double		" = 0.0"
2328	key		" = NULL"
2329    }
2330
2331    if [info exists type_map($type)] {
2332	emit "    static $type_map($type)$name$init_map($type);"
2333    } else {
2334	error "Type '$type' not supported in cfilter auto_generated code"
2335    }
2336}
2337
2338#
2339# Generate code to extract one argument from a list of arguments
2340#
2341proc gen_get_filter_arg {type name source} {
2342    if {"$type" == "varstring" || "$type" == "fixedstring" || "$type" == "key"} {
2343	emit "        $name = Tcl_GetString ($source);"
2344    } elseif {"$type" == "float" || "$type" == "double"} {
2345	emit "        if(Tcl_GetDoubleFromObj (interp, $source, &$name) != TCL_OK)"
2346	emit "            return TCL_ERROR;"
2347    } elseif {"$type" == "boolean"} {
2348	emit "        if(Tcl_GetBooleanFromObj (interp, $source, &$name) != TCL_OK)"
2349	emit "            return TCL_ERROR;"
2350    } else { # it's an integer
2351	emit "        if(Tcl_GetLongFromObj (interp, $source, &$name) != TCL_OK)"
2352	emit "            return TCL_ERROR;"
2353    }
2354}
2355
2356#
2357# make sure an argument name is valid
2358#
2359proc validate_arg_name {name} {
2360    variable reservedWords
2361
2362    if {[lsearch -exact $reservedWords $name] >= 0} {
2363        error "illegal argument name \"$name\" -- it's a reserved word"
2364    }
2365
2366    if {![is_legal $name]} {
2367        error "argument name \"$name\" must start with a letter and can only contain letters, numbers, and underscores"
2368    }
2369}
2370
2371#
2372# Generate filter structs and procs
2373#
2374proc gen_filters {} {
2375    variable table
2376    variable filters
2377    variable leftCurly
2378    variable rightCurly
2379
2380    set filterList [lsort [array names filters]]
2381
2382    emit "#define [string toupper $table]_NFILTERS [llength $filterList]"
2383
2384    # Define filter functions
2385    foreach name $filterList {
2386	catch {array unset filter}
2387	array set filter $filters($name)
2388	emit "int ${table}_filter_${name} (Tcl_Interp *interp, struct CTable *ctable, ctable_BaseRow *vRow, Tcl_Obj *filter, int sequence)"
2389	emit "$leftCurly"
2390	emit "    struct ${table} *row = (struct ${table}*)vRow;"
2391        if [info exists filter(args)] {
2392	    if {[llength $filter(args)] == 3 && "[lindex $filter(args) 0]" == "list"} {
2393
2394		set listCount [lindex $filter(args) 1]
2395		set listName [lindex $filter(args) 2]
2396		validate_arg_name $listCount
2397		validate_arg_name $listName
2398
2399		emit "    Tcl_Obj **$listName;"
2400		emit "    int       $listCount;\n"
2401		emit "    if(Tcl_ListObjGetElements(interp, filter, &$listCount, &$listName) != TCL_OK)"
2402		emit "        return TCL_ERROR;"
2403
2404	    } else {
2405
2406	        emit "    static int lastSequence = 0;"
2407	        foreach {type name} $filter(args) {
2408		    validate_arg_name $name
2409		    gen_decl_filter_arg $type $name
2410	        }
2411
2412	        emit "\n    if (sequence != lastSequence) $leftCurly"
2413	        emit "        lastSequence = sequence;"
2414
2415	        if {[llength $filter(args)] == 2} {
2416		    gen_get_filter_arg [lindex $filter(args) 0] [lindex $filter(args) 1] filter
2417	        } else {
2418		    emit "        Tcl_Obj **filterList;"
2419        	    emit "        int       filterCount;\n"
2420        	    emit "        if (Tcl_ListObjGetElements(interp, filter, &filterCount, &filterList) != TCL_OK)"
2421          	    emit "             return TCL_ERROR;\n"
2422		    set argNames ""
2423		    set nArguments [expr {[llength $filter(args)] / 2}]
2424		    foreach {type name} $filter(args) {
2425		        append argNames "$name, "
2426		    }
2427		    emit "        if (filterCount != $nArguments) {"
2428		    emit "            Tcl_WrongNumArgs (interp, 0, NULL, \"filter requires $nArguments arguments: [string range $argNames 0 end-2]\");"
2429		    emit "            return TCL_ERROR;"
2430		    emit "        }\n"
2431
2432		    set index 0
2433		    foreach {type name} $filter(args) {
2434		        gen_get_filter_arg $type $name "filterList\[$index]"
2435			incr index
2436		    }
2437	        }
2438
2439	        emit "    $rightCurly"
2440	    }
2441	}
2442
2443        emit $filter(code)
2444        emit "$rightCurly\n"
2445    }
2446
2447    # Define filter lookup table
2448    emit "static CONST char *${table}_filterNames\[] = $leftCurly"
2449    foreach name $filterList {
2450	emit "    \"$name\","
2451    }
2452    emit "    (char *) NULL"
2453    emit "$rightCurly;\n"
2454
2455    emit "static CONST filterFunction_t ${table}_filterFunctions\[] = $leftCurly"
2456    foreach name $filterList {
2457	emit "    ${table}_filter_${name},"
2458    }
2459    emit "    (filterFunction_t) NULL"
2460    emit "$rightCurly;\n"
2461}
2462
2463#
2464# Is this a legal field name.
2465#
2466# Special fields are automatically legal.
2467#
2468proc is_legal {fieldName} {
2469    variable specialFieldNames
2470    if {[lsearch $specialFieldNames $fieldName] != -1} {
2471	return 1
2472    }
2473    return [regexp {^[a-zA-Z][_a-zA-Z0-9]*$} $fieldName]
2474}
2475
2476#
2477# deffield - helper for defining fields.
2478#
2479proc deffield {fieldName argList {listName nonBooleans}} {
2480    variable fields
2481    variable fieldList
2482    variable $listName
2483    variable ctableTypes
2484    variable reservedWords
2485
2486    if {[lsearch -exact $reservedWords $fieldName] >= 0} {
2487        error "illegal field name \"$fieldName\" -- it's a reserved word"
2488    }
2489
2490    if {![is_legal $fieldName]} {
2491        error "field name \"$fieldName\" must start with a letter and can only contain letters, numbers, and underscores"
2492    }
2493
2494    if {[llength $argList] % 2 != 0} {
2495        error "number of values in field '$fieldName' definition arguments ('$argList') must be even"
2496    }
2497
2498    array set argHash $argList
2499
2500    # If "key" is still in the option list, then it's not on the right type
2501    if {[info exists argHash(key)] && $argHash(key)} {
2502	error "field '$fieldName' is the wrong type for a key"
2503    }
2504
2505    # If it's got a default value, then it must be notnull
2506    if {[info exists argHash(default)]} {
2507	if {[info exists argHash(notnull)]} {
2508	    if {!$argHash(notnull)} {
2509		error "field '$fieldName' must not be null"
2510	    }
2511	} else {
2512	    set argHash(notnull) 1
2513	    lappend argList notnull 1
2514	}
2515    }
2516
2517    set fields($fieldName) [linsert $argList 0 name $fieldName]
2518    array set ::ctable::fields::$fieldName $fields($fieldName)
2519
2520    lappend fieldList $fieldName
2521    lappend $listName $fieldName
2522}
2523
2524#
2525# boolean - define a boolean field
2526#
2527proc boolean {fieldName args} {
2528    deffield $fieldName [linsert $args 0 type boolean] booleans
2529}
2530
2531#
2532# fixedstring - define a fixed-length string field
2533#
2534proc fixedstring {fieldName length args} {
2535    array set field $args
2536
2537    # if it's defined notnull, it must have a default string
2538    if {[info exists field(notnull)] && $field(notnull)} {
2539	if {![info exists field(default)]} {
2540	    error "fixedstring \"$fieldName\" is defined notnull but has no default string, which is required"
2541	}
2542    }
2543
2544    # if there's a default string, it must be the correct width
2545    if {[info exists field(default)]} {
2546        if {[string length $field(default)] != $length} {
2547	    error "fixedstring \"$fieldName\" default string \"[cquote $field(default)]\" must match length \"$length\""
2548	}
2549    }
2550
2551    deffield $fieldName [linsert $args 0 type fixedstring length $length needsQuoting 1]
2552}
2553
2554#
2555# varstring - define a variable-length string field
2556#
2557# If "key 1" is in the argument list, make it a "key" instead
2558#
2559proc varstring {fieldName args} {
2560    if {[set i [lsearch -exact $args "key"]] % 2 == 0} {
2561	incr i
2562	if {[lindex $args $i]} {
2563	    return [eval [list key $fieldName] $args]
2564	}
2565    }
2566    deffield $fieldName [linsert $args 0 type varstring needsQuoting 1]
2567}
2568
2569#
2570# char - define a single character field -- this should probably just be
2571#  fixedstring[1] but it's simpler.  shrug.
2572#
2573proc char {fieldName args} {
2574    deffield $fieldName [linsert $args 0 type char needsQuoting 1]
2575}
2576
2577#
2578# mac - define a mac address field
2579#
2580proc mac {fieldName args} {
2581    deffield $fieldName [linsert $args 0 type mac]
2582}
2583
2584#
2585# short - define a short integer field
2586#
2587proc short {fieldName args} {
2588    deffield $fieldName [linsert $args 0 type short]
2589}
2590
2591#
2592# int - define an integer field
2593#
2594proc int {fieldName args} {
2595    deffield $fieldName [linsert $args 0 type int]
2596}
2597
2598#
2599# long - define a long integer field
2600#
2601proc long {fieldName args} {
2602    deffield $fieldName [linsert $args 0 type long]
2603}
2604
2605#
2606# wide - define a wide integer field -- should always be at least 64 bits
2607#
2608proc wide {fieldName args} {
2609    deffield $fieldName [linsert $args 0 type wide]
2610}
2611
2612#
2613# float - define a floating point field
2614#
2615proc float {fieldName args} {
2616    deffield $fieldName [linsert $args 0 type float]
2617}
2618
2619#
2620# double - define a double-precision floating point field
2621#
2622proc double {fieldName args} {
2623    deffield $fieldName [linsert $args 0 type double]
2624}
2625
2626#
2627# inet - define an IPv4 address field
2628#
2629proc inet {fieldName args} {
2630    deffield $fieldName [linsert $args 0 type inet]
2631}
2632
2633#
2634# tclobj - define an straight-through Tcl_Obj
2635#
2636proc tclobj {fieldName args} {
2637    deffield $fieldName [linsert $args 0 type tclobj needsQuoting 1]
2638}
2639
2640#
2641# key - define a pseudofield for the key
2642#
2643proc key {name args} {
2644    # Sanitize arguments
2645    if {[set i [lsearch -exact  $args key]] % 2 == 0} {
2646	set args [lreplace $args $i [expr {$i + 1}]]
2647    }
2648
2649    # Only allow one key field
2650    if [info exists ::ctable::keyFieldName] {
2651	# But only complain if it's not an internal "special" field
2652        if {[lsearch $::ctable::specialFieldNames $name] == -1} {
2653	    error "Duplicate key field"
2654	}
2655	return
2656    }
2657
2658    deffield $name [linsert $args 0 type key needsQuoting 1 notnull 1]
2659    set ::ctable::keyField [lsearch $::ctable::fieldList $name]
2660    if {$::ctable::keyField == -1} {
2661	unset ::ctable::keyField
2662    } else {
2663	set ::ctable::keyFieldName $name
2664    }
2665}
2666
2667#
2668# putfield - write out a field definition when emitting a C struct
2669#
2670proc putfield {type fieldName {comment ""}} {
2671    if {[string index $fieldName 0] != "*"} {
2672        set fieldName " $fieldName"
2673    }
2674
2675    if {$comment != ""} {
2676        set comment " /* $comment */"
2677    }
2678    emit [format "    %-20s %s;%s" $type $fieldName $comment]
2679}
2680
2681#
2682# ctable_type_to_enum - return a type mapped to the name we use when
2683#  creating or referencing an enumerated list of ctable types.
2684#
2685proc ctable_type_to_enum {type} {
2686    return "CTABLE_TYPE_[string toupper $type]"
2687}
2688
2689#
2690# gen_ctable_type_stuff - # generate an array of char pointers to the type names
2691#
2692proc gen_ctable_type_stuff {} {
2693    variable ctableTypes
2694    variable leftCurly
2695    variable rightCurly
2696
2697    emit "static CONST char *ctableTypes\[\] = $leftCurly"
2698    foreach type $ctableTypes {
2699        emit "    \"$type\","
2700    }
2701    emit "    (char *) NULL"
2702    emit "$rightCurly;"
2703    emit ""
2704}
2705
2706#
2707# gen_defaults_subr - gen code to set a row to default values
2708#
2709proc gen_defaults_subr {struct} {
2710    variable table
2711    variable fields
2712    variable withSharedTables
2713    variable withDirty
2714    variable fieldList
2715    variable leftCurly
2716    variable rightCurly
2717
2718    set baseCopy ${struct}_basecopy
2719
2720    emit "void ${struct}_init(CTable *ctable, struct $struct *row) $leftCurly"
2721    emit "    static int firstPass = 1;"
2722    emit "    static struct $struct $baseCopy;"
2723    emit ""
2724    emit "    if (firstPass) $leftCurly"
2725    emit "        int i;"
2726    emit "        firstPass = 0;"
2727    emit ""
2728    emit "        $baseCopy.hashEntry.key = ctable->nullKeyValue;"
2729
2730    if {$withSharedTables} {
2731        emit "        $baseCopy._row_cycle = LOST_HORIZON;"
2732    }
2733
2734    emit ""
2735    emit "        for(i = 0; i < [string toupper $table]_NLINKED_LISTS; i++) $leftCurly"
2736    emit "	      $baseCopy._ll_nodes\[i].next = NULL;"
2737    emit "	      $baseCopy._ll_nodes\[i].prev = NULL;"
2738    emit "	      $baseCopy._ll_nodes\[i].head = NULL;"
2739    emit "	  $rightCurly"
2740    emit ""
2741
2742    set fieldNum 0
2743    foreach fieldName $fieldList {
2744	upvar ::ctable::fields::$fieldName field
2745
2746	switch $field(type) {
2747	    key {
2748		# No work to do
2749	    }
2750
2751	    varstring {
2752		set initLength 0
2753		if {[info exists field(default)]} {
2754		    set initValue "${table}_defaultStrings\[$fieldNum]"
2755		    set initLength [string length $field(default)]
2756		} elseif {[info exists field(notnull)] && $field(notnull)} {
2757		    set initValue "${table}_defaultStrings\[$fieldNum]"
2758		} else {
2759		    set initValue "NULL"
2760		}
2761	        emit "        $baseCopy.$fieldName = (char *) $initValue;"
2762		emit "        $baseCopy._${fieldName}Length = $initLength;"
2763		emit "        $baseCopy._${fieldName}AllocatedLength = 0;"
2764
2765		if {![info exists field(notnull)] || !$field(notnull)} {
2766		    if {[info exists field(default)]} {
2767			emit "        $baseCopy._${fieldName}IsNull = 0;"
2768		    } else {
2769			emit "        $baseCopy._${fieldName}IsNull = 1;"
2770		    }
2771		}
2772	    }
2773
2774	    fixedstring {
2775	        if {[info exists field(default)]} {
2776		    emit "        strncpy ($baseCopy.$fieldName, \"[cquote $field(default)]\", $field(length));"
2777		    if {![info exists field(notnull)] || !$field(notnull)} {
2778			emit "        $baseCopy._${fieldName}IsNull = 0;"
2779		    }
2780		} else {
2781		    if {![info exists field(notnull)] || !$field(notnull)} {
2782			emit "        $baseCopy._${fieldName}IsNull = 1;"
2783		    }
2784		}
2785	    }
2786
2787	    mac {
2788		if {[info exists field(default)]} {
2789		    emit "        $baseCopy.$fieldName = *ether_aton (\"$field(default)\");"
2790		    if {![info exists field(notnull)] || !$field(notnull)} {
2791			emit "        $baseCopy._${fieldName}IsNull = 0;"
2792		    }
2793		} else {
2794		    if {![info exists field(notnull)] || !$field(notnull)} {
2795			emit "        $baseCopy._${fieldName}IsNull = 1;"
2796		    }
2797		}
2798	    }
2799
2800	    inet {
2801		if {[info exists field(default)]} {
2802		    emit "        inet_aton (\"$field(default)\", &$baseCopy.$fieldName);"
2803		    if {![info exists field(notnull)] || !$field(notnull)} {
2804			emit "        $baseCopy._${fieldName}IsNull = 0;"
2805		    }
2806		} else {
2807		    if {![info exists field(notnull)] || !$field(notnull)} {
2808			emit "        $baseCopy._${fieldName}IsNull = 1;"
2809		    }
2810		}
2811	    }
2812
2813	    char {
2814	        if {[info exists field(default)]} {
2815		    emit "        $baseCopy.$fieldName = '[cquote [string index $field(default) 0] {'}]';"
2816		    if {![info exists field(notnull)] || !$field(notnull)} {
2817			emit "        $baseCopy._${fieldName}IsNull = 0;"
2818		    }
2819		} else {
2820		    if {![info exists field(notnull)] || !$field(notnull)} {
2821			emit "        $baseCopy._${fieldName}IsNull = 1;"
2822		    }
2823		}
2824	    }
2825
2826	    tclobj {
2827	        emit "        $baseCopy.$fieldName = (Tcl_Obj *) NULL;"
2828		if {![info exists field(notnull)] || !$field(notnull)} {
2829		    emit "        $baseCopy._${fieldName}IsNull = 1;"
2830		}
2831	    }
2832
2833	    default {
2834	        if {[info exists field(default)]} {
2835	            emit "        $baseCopy.$fieldName = $field(default);"
2836		    if {![info exists field(notnull)] || !$field(notnull)} {
2837			emit "        $baseCopy._${fieldName}IsNull = 0;"
2838		    }
2839		} else {
2840		    if {![info exists field(notnull)] || !$field(notnull)} {
2841			emit "        $baseCopy._${fieldName}IsNull = 1;"
2842		    }
2843		}
2844	    }
2845	}
2846        incr fieldNum;
2847    }
2848
2849    emit "    $rightCurly"
2850    emit ""
2851    emit "    *row = $baseCopy;"
2852
2853    # Poke in shared default strings where needed.
2854    if {$withSharedTables} {
2855        set fieldNum 0
2856	foreach fieldName $fieldList {
2857	    upvar ::ctable::fields::$fieldName field
2858
2859	    if {$field(type) == "varstring"} {
2860		if {[info exists field(default)] || ([info exists field(notnull)] && $field(notnull))} {
2861		    emit "    row->$fieldName = (char *) ctable->defaultStrings\[$fieldNum];"
2862		}
2863	    }
2864	    incr fieldNum
2865	}
2866    }
2867
2868    emit "$rightCurly"
2869    emit ""
2870
2871    emit "int ${struct}_index_defaults(Tcl_Interp *interp, CTable *ctable, struct $struct *row) $leftCurly"
2872
2873if 0 {
2874emit "printf(\"${struct}_index_defaults(...);\\n\");"
2875}
2876    set fieldnum 0 ; # postincremented 0 .. fields
2877    set listnum 0  ; # preincrementd 1 .. lists+1
2878    foreach fieldName $fieldList {
2879	upvar ::ctable::fields::$fieldName field
2880
2881	# Index everything null or otherwise
2882	if {[info exists field(indexed)] && $field(indexed)} {
2883	    incr listnum
2884	    if {[info exists field(default)]} {
2885		set def $field(default)
2886	    } else {
2887		set def ""
2888	    }
2889	    emit "// Field \"$fieldName\" ($fieldnum) index $listnum:"
2890if 0 {
2891emit "printf(\"ctable->skipLists\[$fieldnum] == %08lx\\n\",  (long)ctable->skipLists\[$fieldnum]);"
2892emit "printf(\"row->_ll_nodes\[$listnum].head == %08lx\\n\",  (long)row->_ll_nodes\[$listnum].head);"
2893}
2894
2895	    emit "    if(ctable->skipLists\[$fieldnum] && row->_ll_nodes\[$listnum].prev == NULL) $leftCurly"
2896if 0 {
2897emit "fprintf(stderr, \"row->_ll_nodes\[$listnum] = { 0x%lx 0x%lx 0x%lx }\","
2898emit "    (long)row->_ll_nodes\[$listnum].head,"
2899emit "    (long)row->_ll_nodes\[$listnum].prev,"
2900emit "    (long)row->_ll_nodes\[$listnum].next);"
2901emit "fprintf(stderr, \"Inserting $fieldName into new row for $struct\\n\");"
2902}
2903	    emit "        if (ctable_InsertIntoIndex (interp, ctable, row, $fieldnum) == TCL_ERROR)"
2904	    emit "            return TCL_ERROR;"
2905	    emit "    $rightCurly"
2906	} else {
2907	    emit "// Field \"$fieldName\" ($fieldnum) not indexed"
2908        }
2909        incr fieldnum
2910    }
2911
2912    emit "    return TCL_OK;"
2913
2914    emit "$rightCurly"
2915    emit ""
2916}
2917
2918variable deleteRowHelperSource {
2919void ${table}_deleteKey(CTable *ctable, struct ${table} *row, int free_shared)
2920{
2921    if(row->hashEntry.key == ctable->nullKeyValue)
2922	return;
2923
2924#ifdef WITH_SHARED_TABLES
2925    if(ctable->share_type == CTABLE_SHARED_MASTER) {
2926	if(free_shared)
2927	    shmfree(ctable->share, (void *)row->hashEntry.key);
2928    } else
2929#endif
2930    ckfree(row->hashEntry.key);
2931    row->hashEntry.key = ctable->nullKeyValue;
2932}
2933
2934void ${table}_deleteHashEntry(CTable *ctable, struct ${table} *row)
2935{
2936#ifdef WITH_SHARED_TABLES
2937    if(row->hashEntry.key != ctable->nullKeyValue && ctable->share_type == CTABLE_SHARED_MASTER) {
2938	shmfree(ctable->share, (void *)row->hashEntry.key);
2939	row->hashEntry.key = ctable->nullKeyValue;
2940    }
2941#endif
2942    ctable_DeleteHashEntry (ctable->keyTablePtr, &row->hashEntry, ctable->nullKeyValue);
2943}
2944}
2945
2946#
2947# gen_delete_subr - gen code to delete (free) a row
2948#
2949proc gen_delete_subr {subr struct} {
2950    variable table
2951    variable fields
2952    variable fieldList
2953    variable leftCurly
2954    variable rightCurly
2955    variable withSharedTables
2956    variable deleteRowHelperSource
2957
2958    emit [string range [subst -nobackslashes -nocommands $deleteRowHelperSource] 1 end-1]
2959
2960    emit "void ${subr}(CTable *ctable, ctable_BaseRow *vRow, int indexCtl) {"
2961    emit "    struct $struct *row = (struct $struct *)vRow;"
2962    if {$withSharedTables} {
2963        emit "    // 'final' means 'shared memory will be deleted anyway, just zero out'"
2964	emit "    int             final = indexCtl == CTABLE_INDEX_DESTROY_SHARED;"
2965	emit "    int             is_master = ctable->share_type == CTABLE_SHARED_MASTER;"
2966	emit "    int             is_shared = ctable->share_type != CTABLE_SHARED_NONE;"
2967    }
2968    emit ""
2969    emit "    switch (indexCtl) $leftCurly"
2970    emit "      case CTABLE_INDEX_NORMAL:"
2971    emit "        // If there's an index, AND we're not deleting all indices"
2972    emit "        ctable_RemoveFromAllIndexes (ctable, row);"
2973    if {$withSharedTables} {
2974	emit "        ${table}_deleteKey(ctable, row, TRUE);"
2975    }
2976    emit "        ctable_DeleteHashEntry (ctable->keyTablePtr, &row->hashEntry, ctable->nullKeyValue);"
2977    emit "        break;"
2978    emit "      case CTABLE_INDEX_FASTDELETE: // Key has already been deleted"
2979    emit "        break;"
2980    emit "      case CTABLE_INDEX_DESTROY_SHARED: // Row is in dead pool"
2981    emit "      case CTABLE_INDEX_PRIVATE: // Key is never indexed"
2982    emit "        ${table}_deleteKey(ctable, row, FALSE);"
2983    emit "        break;"
2984    emit "      default: // Not in hash, shared (should not happen)"
2985    emit "        ${table}_deleteKey(ctable, row, TRUE);"
2986    emit "        break;"
2987    emit "    $rightCurly;"
2988
2989    emit ""
2990
2991    foreach fieldName $fieldList {
2992	upvar ::ctable::fields::$fieldName field
2993
2994	switch $field(type) {
2995	    varstring {
2996    		if {$withSharedTables} {
2997	            emit "    if (row->_${fieldName}AllocatedLength > 0) {"
2998		    emit "	  if(!is_shared || indexCtl == CTABLE_INDEX_PRIVATE)"
2999		    emit "            ckfree((char *)row->$fieldName);"
3000		    emit "        else if(is_master && !final)"
3001		    emit "            shmfree(ctable->share, (char *)row->$fieldName);"
3002		    emit "    }"
3003		} else {
3004	            emit "    if (row->_${fieldName}AllocatedLength > 0) ckfree((char *)row->$fieldName);"
3005		}
3006	    }
3007	}
3008    }
3009    if {$withSharedTables} {
3010        emit "    if(!is_shared || indexCtl == CTABLE_INDEX_PRIVATE)"
3011	emit "        ckfree((char *)row);"
3012	emit "    else if(is_master && !final)"
3013	emit "        shmfree(ctable->share, (char *)row);"
3014    } else {
3015        emit "    ckfree((char *)row);"
3016    }
3017
3018    emit "}"
3019    emit ""
3020}
3021
3022
3023variable isNullSubrSource {
3024int ${table}_obj_is_null(Tcl_Obj *obj) {
3025    char     *objString;
3026    int       objStringLength;
3027
3028     objString = Tcl_GetStringFromObj (obj, &objStringLength);
3029
3030     if (objStringLength == ${table}_NullValueSize) {
3031	if (objStringLength == 0) {
3032	    // strings are both zero length, a match on empty strings
3033	    return 1;
3034	}
3035
3036	return (strncmp (${table}_NullValueString, objString, ${table}_NullValueSize) == 0);
3037     }
3038
3039    // string lengths didn't match so strings don't match
3040    return 0;
3041}
3042
3043}
3044
3045#
3046# gen_is_null_subr - gen code to determine if an object contains the null value
3047#
3048proc gen_obj_is_null_subr {} {
3049    variable table
3050    variable isNullSubrSource
3051
3052    emit [string range [subst -nobackslashes -nocommands $isNullSubrSource] 1 end-1]
3053}
3054
3055#
3056# sanity_check - prior to generating everything, make sure what we're being
3057#  asked to do is reasonable
3058#
3059proc sanity_check {} {
3060    variable fieldList
3061    variable table
3062
3063    if {[llength $fieldList] == 0} {
3064        error "no fields defined in table \"$table\" -- at least one field must be defined in a table"
3065    }
3066}
3067
3068#
3069# determine_how_many_linked_lists - count up the number of indexed
3070# nodes and any other stuff we want linked lists in the row for
3071#
3072# currently one defined for every row for a master linked list and one
3073# defined for each field that is defined indexed and not unique
3074# for use with skip lists to have indexes on fields of rows that have
3075# duplicate entries like, for instance, latitude and/or longitude.
3076#
3077proc determine_how_many_linked_lists_and_gen_field_index_table {} {
3078    variable nonBooleans
3079    variable fields
3080    variable fieldList
3081    variable booleans
3082    variable table
3083    variable leftCurly
3084    variable rightCurly
3085
3086    set result "int ${table}_index_numbers\[\] = $leftCurly"
3087    set nLinkedLists 1
3088    foreach fieldName $fieldList {
3089	upvar ::ctable::fields::$fieldName field
3090
3091        # if the "indexed" field doesn't exist or is 0, skip it
3092        if {![info exists field(indexed)] || !$field(indexed)} {
3093	    append result "\n    -1,"
3094            continue
3095        }
3096
3097        # we're going to use linked lists even if it's not unique
3098if 0 {
3099        # if the "unique" field doesn't exist or isn't set to 0
3100        if {![info exists field(unique)] || $field(unique)} {
3101	    append result "\n    -1,"
3102            continue
3103        }
3104}
3105
3106        # if we got here it's indexed and not unique,
3107        # i.e. field args include "indexed 1 unique 0"
3108        # generate them a list entry
3109
3110	append result "\n[format "%6d" $nLinkedLists],"
3111
3112        incr nLinkedLists
3113    }
3114
3115    emit "[string range $result 0 end-1]\n$rightCurly;"
3116
3117    return $nLinkedLists
3118}
3119
3120#
3121# gen_struct - gen the C structure of the table being defined
3122#
3123proc gen_struct {} {
3124    variable table
3125    variable booleans
3126    variable nonBooleans
3127    variable fields
3128    variable fieldList
3129    variable leftCurly
3130    variable rightCurly
3131
3132    set nLinkedLists [determine_how_many_linked_lists_and_gen_field_index_table]
3133    set NLINKED_LISTS [string toupper $table]_NLINKED_LISTS
3134    emit "#define $NLINKED_LISTS $nLinkedLists"
3135    emit ""
3136
3137    emit "struct $table : public ctable_BaseRow $leftCurly"
3138
3139	# this array must be first, since it will overlap with the empty list defined at the end of ctable_BaseRow.
3140    putfield "ctable_LinkedListNode"  "_ll_nodes\[$NLINKED_LISTS\]"
3141
3142    foreach fieldName $nonBooleans {
3143	upvar ::ctable::fields::$fieldName field
3144
3145	switch $field(type) {
3146	    varstring {
3147		putfield char "*$field(name)"
3148		putfield int  "_$field(name)Length"
3149		putfield int  "_$field(name)AllocatedLength"
3150	    }
3151
3152	    fixedstring {
3153		putfield char "$field(name)\[$field(length)]"
3154	    }
3155
3156	    wide {
3157		putfield "Tcl_WideInt" $field(name)
3158	    }
3159
3160	    mac {
3161		putfield "struct ether_addr" $field(name)
3162	    }
3163
3164	    inet {
3165		putfield "struct in_addr" $field(name)
3166	    }
3167
3168	    tclobj {
3169		putfield "struct Tcl_Obj" "*$field(name)"
3170	    }
3171
3172	    key {
3173		# Do nothing, it's in the hashEntry
3174	    }
3175
3176	    default {
3177		putfield $field(type) $field(name)
3178	    }
3179	}
3180    }
3181
3182    foreach fieldName $booleans {
3183	putfield "unsigned int" "$fieldName:1"
3184    }
3185
3186    foreach fieldName $fieldList {
3187	upvar ::ctable::fields::$fieldName field
3188
3189	if {![info exists field(notnull)] || !$field(notnull)} {
3190	    putfield "unsigned int" _${fieldName}IsNull:1
3191	}
3192    }
3193
3194    emit "$rightCurly;"
3195    emit ""
3196}
3197
3198#
3199# emit_set_num_field - emit code to set a numeric field
3200#
3201proc emit_set_num_field {fieldName type} {
3202    variable numberSetSource
3203    variable table
3204    variable withSharedTables
3205
3206    set typeText $type
3207
3208    switch $type {
3209        short {
3210	    set newObjCmd Tcl_NewIntObj
3211	    set getObjCmd Tcl_GetIntFromObj
3212	    set typeText "int"
3213	}
3214
3215        int {
3216	    set newObjCmd Tcl_NewIntObj
3217	    set getObjCmd Tcl_GetIntFromObj
3218	}
3219
3220	long {
3221	    set newObjCmd Tcl_NewLongObj
3222	    set getObjCmd Tcl_GetLongFromObj
3223
3224	}
3225
3226	wide {
3227	    set type "Tcl_WideInt"
3228	    set newObjCmd Tcl_NewWideIntObj
3229	    set getObjCmd Tcl_GetWideIntFromObj
3230	    set typeText "Tcl_WideInt"
3231	}
3232
3233	float {
3234	    set newObjCmd Tcl_NewDoubleObj
3235	    set getObjCmd Tcl_GetDoubleFromObj
3236	    set typeText "double"
3237	}
3238
3239	double {
3240	    set newObjCmd Tcl_NewDoubleObj
3241	    set getObjCmd Tcl_GetDoubleFromObj
3242	}
3243
3244	default {
3245	    error "unknown numeric field type: $type"
3246	}
3247    }
3248
3249    set optname [field_to_enum $fieldName]
3250
3251    emit [string range [subst $numberSetSource] 1 end-1]
3252}
3253
3254#
3255# emit_set_standard_field - emit code to set a field that has a
3256# "set source" string to go with it and gets managed in a standard
3257#  way
3258#
3259proc emit_set_standard_field {fieldName setSourceVarName} {
3260    variable $setSourceVarName
3261    variable table
3262
3263    set optname [field_to_enum $fieldName]
3264    emit [string range [subst [set $setSourceVarName]] 1 end-1]
3265}
3266
3267#
3268# emit_set_varstring_field - emit code to set a varstring field
3269#
3270proc emit_set_varstring_field {table fieldName} {
3271    variable varstringSetSource
3272
3273    set optname [field_to_enum $fieldName]
3274
3275    emit [string range [subst $varstringSetSource] 1 end-1]
3276}
3277
3278#
3279# emit_set_fixedstring_field - emit code to set a fixedstring field
3280#
3281proc emit_set_fixedstring_field {fieldName length} {
3282    variable fixedstringSetSource
3283    variable table
3284
3285    upvar ::ctable::fields::$fieldName field
3286
3287    if {[info exists field(default)]} {
3288	set default $field(default)
3289    } else {
3290	set default ""
3291    }
3292
3293    set optname [field_to_enum $fieldName]
3294
3295    emit [string range [subst $fixedstringSetSource] 1 end-1]
3296}
3297
3298variable fieldIncrSource {
3299int
3300${table}_incr (Tcl_Interp *interp, CTable *ctable, Tcl_Obj *obj, struct $table *row, int field, int indexCtl) $leftCurly
3301
3302    switch ((enum ${table}_fields) field) $leftCurly
3303}
3304
3305variable numberIncrNullCheckSource {
3306	if (row->_${fieldName}IsNull) {
3307	    // incr of a null field, default to 0
3308	    if ((indexCtl == CTABLE_INDEX_NORMAL) && ctable->skipLists[field] != NULL) {
3309		ctable_RemoveFromIndex (ctable, row, field);
3310	    }
3311	    row->_${fieldName}IsNull = 0;
3312	    row->$fieldName = incrAmount;
3313
3314	    if ((indexCtl != CTABLE_INDEX_PRIVATE) && (ctable->skipLists[field] != NULL)) {
3315		if (ctable_InsertIntoIndex (interp, ctable, row, field) == TCL_ERROR) {
3316		    return TCL_ERROR;
3317		}
3318	    }
3319	    break;
3320	}
3321}
3322
3323#
3324# gen_number_incr_null_check_code - return code to check for null stuff
3325#  inside incr code, if the field doesn't prevent it by having notnull set,
3326#  in which case return nothing.
3327#
3328proc gen_number_incr_null_check_code {table fieldName} {
3329    variable numberIncrNullCheckSource
3330    upvar ::ctable::fields::$fieldName field
3331
3332    if {[info exists field(notnull)] && $field(notnull)} {
3333        return ""
3334    } else {
3335        return [string range [subst -nobackslashes -nocommands $numberIncrNullCheckSource] 1 end-1]
3336    }
3337}
3338
3339#
3340# gen_set_notnull_if_notnull - if the field has not been defined "not null",
3341#  return code to set that it isn't null
3342#
3343proc gen_set_notnull_if_notnull {table fieldName} {
3344    upvar ::ctable::fields::$fieldName field
3345
3346    if {[info exists field(notnull)] && $field(notnull)} {
3347        return ""
3348    } else {
3349	return "row->_${fieldName}IsNull = 0;"
3350    }
3351}
3352
3353#
3354# numberIncrSource - code we run subst over to generate a set of a standard
3355#  number such as an integer, long, double, and wide integer.  (We have to
3356#  handle shorts and floats specially due to type coercion requirements.)
3357#
3358variable numberIncrSource {
3359      case $optname: {
3360	int incrAmount;
3361
3362	if (Tcl_GetIntFromObj (interp, obj, &incrAmount) == TCL_ERROR) {
3363	    Tcl_AppendResult (interp, " while converting $fieldName increment amount", (char *)NULL);
3364	    return TCL_ERROR;
3365	}
3366[gen_number_incr_null_check_code $table $fieldName]
3367
3368	if ((indexCtl == CTABLE_INDEX_NORMAL) && ctable->skipLists\[field] != NULL) {
3369	    ctable_RemoveFromIndex (ctable, row, field);
3370	}
3371
3372	row->$fieldName += incrAmount;
3373[gen_set_notnull_if_notnull $table $fieldName]
3374	if ((indexCtl != CTABLE_INDEX_PRIVATE) && (ctable->skipLists\[field] != NULL)) {
3375	    if (ctable_InsertIntoIndex (interp, ctable, row, field) == TCL_ERROR) {
3376		return TCL_ERROR;
3377	    }
3378	}
3379	break;
3380      }
3381}
3382
3383variable illegalIncrSource {
3384      case $optname: {
3385	Tcl_ResetResult (interp);
3386	Tcl_AppendResult (interp, "can't incr non-numeric field '$fieldName'", (char *)NULL);
3387	    return TCL_ERROR;
3388	}
3389}
3390
3391variable incrFieldObjSource {
3392int
3393${table}_incr_fieldobj (Tcl_Interp *interp, CTable *ctable, Tcl_Obj *obj, struct $table *row, Tcl_Obj *fieldObj, int indexCtl)
3394{
3395    int field;
3396
3397    if (Tcl_GetIndexFromObj (interp, fieldObj, ${table}_fields, "field", TCL_EXACT, &field) != TCL_OK) {
3398        return TCL_ERROR;
3399    }
3400
3401    return ${table}_incr (interp, ctable, obj, row, field, indexCtl);
3402}
3403}
3404
3405#
3406# emit_incr_num_field - emit code to incr a numeric field
3407#
3408proc emit_incr_num_field {fieldName} {
3409    variable numberIncrSource
3410    variable table
3411
3412    set optname [field_to_enum $fieldName]
3413
3414    emit [string range [subst $numberIncrSource] 1 end-1]
3415}
3416
3417#
3418# emit_incr_illegal_field - we run this to generate code that will cause
3419#  an error on attempts to incr the field that's being processed -- for
3420#  when incr is not a reasonable thing
3421#
3422proc emit_incr_illegal_field {fieldName} {
3423    variable illegalIncrSource
3424
3425    set optname [field_to_enum $fieldName]
3426    emit [string range [subst -nobackslashes -nocommands $illegalIncrSource] 1 end-1]
3427}
3428
3429#
3430# gen_incrs - emit code to incr all of the incr'able fields of the table being
3431# defined
3432#
3433proc gen_incrs {} {
3434    variable table
3435    variable booleans
3436    variable fields
3437    variable fieldList
3438    variable leftCurly
3439    variable rightCurly
3440
3441    foreach fieldName $fieldList {
3442	upvar ::ctable::fields::$fieldName field
3443
3444	switch $field(type) {
3445	    int {
3446		emit_incr_num_field $fieldName
3447	    }
3448
3449	    long {
3450		emit_incr_num_field $fieldName
3451	    }
3452
3453	    wide {
3454		emit_incr_num_field $fieldName
3455	    }
3456
3457	    double {
3458		emit_incr_num_field $fieldName
3459	    }
3460
3461	    short {
3462		emit_incr_num_field $fieldName
3463	    }
3464
3465	    float {
3466	        emit_incr_num_field $fieldName
3467	    }
3468
3469	    default {
3470	        emit_incr_illegal_field $fieldName
3471	    }
3472	}
3473    }
3474}
3475
3476#
3477# gen_incr_function - create a *_incr routine that takes a pointer to the
3478# tcl interp, an object, a pointer to a table row and a field number,
3479# and incrs that field in that row by the the value extracted from the obj
3480#
3481proc gen_incr_function {table} {
3482    variable fieldIncrSource
3483    variable incrFieldObjSource
3484    variable leftCurly
3485    variable rightCurly
3486
3487    emit [string range [subst -nobackslashes -nocommands $fieldIncrSource] 1 end-1]
3488
3489    gen_incrs
3490
3491    emit "    $rightCurly"
3492    emit "    return TCL_OK;"
3493    emit "$rightCurly"
3494
3495    emit [string range [subst -nobackslashes -nocommands $incrFieldObjSource] 1 end-1]
3496}
3497
3498#
3499# gen_sets - emit code to set all of the fields of the table being defined
3500#
3501proc gen_sets {} {
3502    variable table
3503    variable booleans
3504    variable fields
3505    variable fieldList
3506    variable leftCurly
3507    variable rightCurly
3508
3509    foreach fieldName $fieldList {
3510	upvar ::ctable::fields::$fieldName field
3511
3512	switch $field(type) {
3513	    key {
3514		emit_set_standard_field $fieldName keySetSource
3515	    }
3516
3517	    int {
3518		emit_set_num_field $fieldName int
3519	    }
3520
3521	    long {
3522		emit_set_num_field $fieldName long
3523	    }
3524
3525	    wide {
3526		emit_set_num_field $fieldName wide
3527	    }
3528
3529	    double {
3530		emit_set_num_field $fieldName double
3531	    }
3532
3533	    short {
3534		emit_set_num_field $fieldName int
3535	    }
3536
3537	    float {
3538		emit_set_num_field $fieldName float
3539	    }
3540
3541	    fixedstring {
3542		emit_set_fixedstring_field $fieldName $field(length)
3543	    }
3544
3545	    varstring {
3546		emit_set_varstring_field $table $fieldName
3547	    }
3548
3549	    boolean {
3550		emit_set_standard_field $fieldName boolSetSource
3551	    }
3552
3553	    char {
3554		emit_set_standard_field $fieldName charSetSource
3555	    }
3556
3557	    inet {
3558	        emit_set_standard_field $fieldName inetSetSource
3559	    }
3560
3561	    mac {
3562	        emit_set_standard_field $fieldName macSetSource
3563	    }
3564
3565	    tclobj {
3566	        emit_set_standard_field $fieldName tclobjSetSource
3567	    }
3568
3569	    default {
3570	        error "attempt to emit set field of unknown type $field(type)"
3571	    }
3572	}
3573    }
3574}
3575
3576#
3577# setNullSource - code that gets substituted for nonnull fields for set_null
3578#
3579variable setNullSource {
3580	case $optname: {
3581		${table} *row = (${table} *)vRow;
3582		if (row->_${myField}IsNull) {
3583			break;
3584		}
3585
3586		if ((indexCtl == CTABLE_INDEX_NORMAL) && (ctable->skipLists[field] != NULL)) {
3587			ctable_RemoveFromIndex (ctable, row, field);
3588		}
3589		row->_${myField}IsNull = 1;
3590		if ((indexCtl != CTABLE_INDEX_PRIVATE) && (ctable->skipLists[field] != NULL)) {
3591			if (ctable_InsertIntoIndex (interp, ctable, row, field) == TCL_ERROR) {
3592				return TCL_ERROR;
3593			}
3594		}
3595		break;
3596	}
3597}
3598
3599variable setNullNotNullSource {
3600      case $optname:
3601        Tcl_AppendResult (interp, "can't set non-null field \"${myField}\" to be null", (char *)NULL);
3602	return TCL_ERROR;
3603}
3604
3605#
3606# gen_set_null_function - emit C routine to set a specific field to null
3607#  in a given table and row
3608#
3609proc gen_set_null_function {table} {
3610    variable fieldList
3611    variable leftCurly
3612    variable rightCurly
3613    variable setNullSource
3614    variable setNullNotNullSource
3615
3616    emit "int"
3617    emit "${table}_set_null (Tcl_Interp *interp, CTable *ctable, ctable_BaseRow *vRow, int field, int indexCtl) $leftCurly"
3618    emit "    switch ((enum ${table}_fields) field) $leftCurly"
3619
3620    foreach myField $fieldList {
3621        upvar ::ctable::fields::$myField field
3622
3623        set optname [field_to_enum $myField]
3624
3625        if {[info exists field(notnull)] && $field(notnull)} {
3626            emit [subst -nobackslashes -nocommands $setNullNotNullSource]
3627        } else {
3628            emit [subst -nobackslashes -nocommands $setNullSource]
3629        }
3630    }
3631
3632    emit "    $rightCurly"
3633    emit "    return TCL_OK;"
3634    emit "$rightCurly"
3635    emit ""
3636}
3637
3638#
3639# gen_is_null_function - emit C routine to test if a specific field is null
3640#  in a given table and row
3641#
3642proc gen_is_null_function {table} {
3643    variable fieldList
3644    variable leftCurly
3645    variable rightCurly
3646
3647    emit "int"
3648    emit "${table}_is_null (struct $table *row, int field) $leftCurly"
3649
3650    emit "    switch ((enum ${table}_fields) field) $leftCurly"
3651
3652    foreach myField $fieldList {
3653        upvar ::ctable::fields::$myField field
3654
3655        set optname [field_to_enum $myField]
3656
3657        if {!([info exists field(notnull)] && $field(notnull))} {
3658            emit "        case [field_to_enum $myField]:"
3659            emit "            return row->_${myField}IsNull;"
3660        }
3661    }
3662
3663    emit "        default:"
3664    emit "            return 0;"
3665    emit "    $rightCurly"
3666    emit "$rightCurly"
3667    emit ""
3668}
3669
3670#
3671# put_metatable_source - emit the code to define the meta table (table-defining
3672# command)
3673#
3674proc put_metatable_source {table} {
3675    variable metaTableSource
3676
3677    set Id {CTable template Id}
3678
3679    emit [subst -nobackslashes -nocommands $metaTableSource]
3680}
3681
3682#
3683# put_init_command_source - emit the code to initialize create within Tcl
3684# the command that will invoke the C command defined by
3685# put_metatable_source
3686#
3687proc put_init_command_source {table} {
3688    variable extensionFragmentSource
3689
3690    set Id {init extension Id}
3691    set NFIELDS [string toupper $table]_NFIELDS
3692    set NLINKED_LISTS [string toupper $table]_NLINKED_LISTS
3693    set NFILTERS [string toupper $table]_NFILTERS
3694
3695    emit [subst -nobackslashes -nocommands $extensionFragmentSource]
3696}
3697
3698#
3699# put_init_extension_source - emit the code to create the C functions that
3700# Tcl will expect to find when loading the shared library.
3701#
3702proc put_init_extension_source {extension extensionVersion} {
3703    variable initExtensionSource
3704    variable tables
3705
3706    set Id {init extension Id}
3707    emit [subst -nobackslashes -nocommands $initExtensionSource]
3708}
3709
3710variable noCleanDirtyTableSource {
3711CONST int
3712${table}_clean(Tcl_Interp *interp, CTable *ctable)
3713{
3714    Tcl_AppendResult(interp, "Dirty bits not implemented.", NULL);
3715    return TCL_ERROR;
3716}
3717
3718CONST int
3719${table}_dirty(Tcl_Interp *interp, CTable *ctable)
3720{
3721    Tcl_AppendResult(interp, "Dirty bits not implemented.", NULL);
3722    return TCL_ERROR;
3723}
3724}
3725
3726variable cleanDirtyTableSource {
3727CONST int
3728${table}_clean(Tcl_Interp *interp, CTable *ctable)
3729{
3730    ctable_BaseRow *row = NULL;
3731
3732#ifdef WITH_SHARED_TABLES
3733    if(ctable->share_type == CTABLE_SHARED_READER) {
3734	Tcl_AppendResult(interp, "Clean not possible in a shared reader.", NULL);
3735	Tcl_SetErrorCode (interp, "speedtables", "read_only", NULL);
3736	return TCL_ERROR;
3737     }
3738#endif
3739
3740    CTABLE_LIST_FOREACH (ctable->ll_head, row, 0) {
3741	((${table} *)row)->_dirty = 0;
3742    }
3743
3744    return TCL_OK;
3745}
3746
3747CONST int
3748${table}_dirty(Tcl_Interp *interp, CTable *ctable, ctable_BaseRow *row)
3749{
3750#ifdef WITH_SHARED_TABLES
3751    if(ctable->share_type == CTABLE_SHARED_READER) {
3752	Tcl_AppendResult(interp, "Dirty not possible in a shared reader.", NULL);
3753	Tcl_SetErrorCode (interp, "speedtables", "read_only", NULL);
3754	return TCL_ERROR;
3755     }
3756#endif
3757
3758    if (row) {
3759	((${table} *)row)->_dirty = 1;
3760    } else {
3761        CTABLE_LIST_FOREACH (ctable->ll_head, row, 0) {
3762	    ((${table} *)row)->_dirty = 1;
3763        }
3764    }
3765
3766    return TCL_OK;
3767}
3768}
3769
3770#
3771# gen_clean_function - create a *_clean function to clean the dirty bits in the table
3772#
3773proc gen_clean_function {table} {
3774    variable withDirty
3775    variable cleanDirtyTableSource
3776    variable noCleanDirtyTableSource
3777
3778    if {!$withDirty} {
3779	emit [subst -nobackslashes -nocommands $noCleanDirtyTableSource]
3780    } else {
3781	set _dirty "SPECIAL_[string toupper $table]_DIRTY"
3782	emit [subst -nobackslashes -nocommands $cleanDirtyTableSource]
3783    }
3784}
3785
3786#
3787# gen_set_function - create a *_set routine that takes a pointer to the
3788# tcl interp, an object, a pointer to a table row and a field number,
3789# and sets the value extracted from the obj into the field of the row
3790#
3791proc gen_set_function {table} {
3792    variable withDirty
3793    variable withSharedTables
3794    variable sanityChecks
3795    variable fieldObjSetSource
3796    variable fieldSetSource
3797    variable fieldSetSwitchSource
3798    variable leftCurly
3799    variable rightCurly
3800
3801    emit [string range [subst -nobackslashes -nocommands $fieldSetSource] 1 end-1]
3802
3803    if {$withSharedTables} {
3804        emit "    if (ctable->share_type == CTABLE_SHARED_MASTER) $leftCurly"
3805	if {$sanityChecks} {
3806	    emit "        if(ctable->share->map->cycle == LOST_HORIZON)"
3807	    emit "            Tcl_Panic(\"map->cycle not updated?\");"
3808	}
3809	emit "        row->_row_cycle = ctable->share->map->cycle;"
3810	emit "    $rightCurly"
3811    }
3812
3813    emit [string range [subst -nobackslashes -nocommands $fieldSetSwitchSource] 1 end-1]
3814    gen_sets
3815
3816    emit "    $rightCurly"
3817
3818    if {$withDirty} {
3819	emit "    row->_dirty = 1;"
3820    }
3821
3822    emit "    return TCL_OK;"
3823    emit "$rightCurly"
3824
3825    emit [string range [subst -nobackslashes -nocommands $fieldObjSetSource] 1 end-1]
3826
3827}
3828
3829#
3830# gen_get_function - create a *_get routine that takes a pointer to the
3831#  tcl interp, an object pointer, a pointer to a table row and a field number,
3832#  and gets the value from the field of the row and store it into the
3833#  object.
3834#
3835#  Also create a *_get_fieldobj function that takes pointers to the same
3836#  tcl interpreter, object, and table row but takes an object containg
3837#  a string identifying the field, which is then looked up to identify
3838#  the field number and used in a call to the *_get function.
3839#
3840proc gen_get_function {table} {
3841    variable fieldObjGetSource
3842    variable lappendFieldAndNameObjSource
3843    variable lappendNonnullFieldAndNameObjSource
3844    variable arraySetFromFieldSource
3845    variable tabSepFunctionsSource
3846    variable fieldGetSource
3847    variable fieldGetStringSource
3848    variable leftCurly
3849    variable rightCurly
3850
3851    emit [string range [subst -nobackslashes -nocommands $fieldGetSource] 1 end-1]
3852    gen_gets_cases
3853    emit "    $rightCurly"
3854    emit "    return TCL_OK;"
3855    emit "$rightCurly"
3856
3857    emit [string range [subst -nobackslashes -nocommands $fieldObjGetSource] 1 end-1]
3858
3859    emit [string range [subst -nobackslashes -nocommands $lappendFieldAndNameObjSource] 1 end-1]
3860
3861    emit [string range [subst -nobackslashes -nocommands $lappendNonnullFieldAndNameObjSource] 1 end-1]
3862
3863    emit [string range [subst -nobackslashes -nocommands $fieldGetStringSource] 1 end-1]
3864    gen_gets_string_cases
3865    emit "    $rightCurly"
3866    emit "    return TCL_OK;"
3867    emit "$rightCurly"
3868
3869    emit [string range [subst -nobackslashes -nocommands $tabSepFunctionsSource] 1 end-1]
3870
3871    emit [string range [subst -nobackslashes -nocommands $arraySetFromFieldSource] 1 end-1]
3872}
3873
3874#
3875# gen_setup_routine - emit code to be run for this table type at shared
3876#  libary load time
3877#
3878proc gen_setup_routine {table} {
3879    variable fieldList
3880    variable fields
3881    variable leftCurly
3882    variable rightCurly
3883
3884    emit "void ${table}_setup (void) $leftCurly"
3885
3886    # create and initialize all of the NameObj objects containing field
3887    # names as Tcl objects and increment their reference counts so
3888    # (hopefully, heh) they'll never be deleted.
3889    #
3890    # also populate the *_NameObjList table
3891    #
3892    set position 0
3893    foreach fieldName $fieldList {
3894	upvar ::ctable::fields::$fieldName field
3895
3896	set nameObj [field_to_nameObj $table $fieldName]
3897        emit "    ${table}_NameObjList\[$position\] = $nameObj = Tcl_NewStringObj (\"$fieldName\", -1);"
3898	emit "    Tcl_IncrRefCount ($nameObj);"
3899	emit ""
3900	incr position
3901    }
3902    emit "    ${table}_NameObjList\[$position\] = (Tcl_Obj *) NULL;"
3903    emit ""
3904
3905    set emptyObj ${table}_DefaultEmptyStringObj
3906    emit "    $emptyObj = Tcl_NewObj ();"
3907    emit "    Tcl_IncrRefCount ($emptyObj);"
3908    emit ""
3909
3910    emit "    // initialize the null string object to the default (empty) value"
3911    emit "    ${table}_NullValueObj = Tcl_NewObj ();"
3912    emit "    ${table}_NullValueString = Tcl_GetStringFromObj (${table}_NullValueObj, &${table}_NullValueSize);"
3913    emit "    Tcl_IncrRefCount (${table}_NullValueObj);"
3914
3915    emit "$rightCurly"
3916    emit ""
3917}
3918
3919# Generate allocator for shared ctables
3920proc gen_shared_string_allocator {} {
3921    variable withSharedTables
3922    variable table
3923    variable leftCurly
3924    variable rightCurly
3925    variable fieldList
3926
3927    if {!$withSharedTables} {
3928	return
3929    }
3930
3931    emit "int ${table}_setupDefaultStrings(CTable *ctable) $leftCurly"
3932    emit "    volatile char **defaultList;"
3933    emit "    volatile char *bundle;"
3934    emit ""
3935
3936    emit "    // If it's not a shared table, just use constants"
3937    emit "    if(ctable->share_type == CTABLE_SHARED_NONE) $leftCurly"
3938    emit "        ctable->emptyString = \"\";"
3939    emit "        ctable->defaultStrings = ${table}_defaultStrings;"
3940    emit "        return TRUE;"
3941    emit "    $rightCurly"
3942    emit ""
3943
3944    emit "    // reader table, use the master table"
3945    emit "    if(ctable->share_type == CTABLE_SHARED_READER) $leftCurly"
3946    emit "        ctable->emptyString = ctable->share_ctable->emptyString;"
3947    emit "        ctable->defaultStrings = ctable->share_ctable->defaultStrings;"
3948    emit "        return TRUE;"
3949    emit "    $rightCurly"
3950    emit ""
3951
3952    # Generate and save the assignments
3953    # to the shared bundle, and set up bundle
3954    set bundle {\0}
3955    set bundleLen 1
3956    set fieldNum 0
3957    foreach fieldName $fieldList {
3958	upvar ::ctable::fields::$fieldName field
3959
3960	if {$field(type) != "varstring"} {
3961	    lappend sets "    defaultList\[$fieldNum] = NULL;"
3962	} elseif {![info exists field(default)]} {
3963	    lappend sets "    defaultList\[$fieldNum] = &bundle\[0];"
3964	} else {
3965	    set def [cquote $field(default)]
3966	    lappend sets "    defaultList\[$fieldNum] = &bundle\[$bundleLen];"
3967
3968	    append bundle $def
3969	    incr bundleLen [string length $field(default)]
3970	    append bundle {\0}
3971	    incr bundleLen
3972	}
3973	incr fieldNum
3974    }
3975
3976    emit "    // Allocate array and the strings themselves in one chunk"
3977
3978    set totalSize "$fieldNum * sizeof (char *) + $bundleLen"
3979    emit "    defaultList = (volatile char **)shmalloc(ctable->share, $totalSize);"
3980    emit "    if (!defaultList) {"
3981    emit "        if(ctable->share_panic) ${table}_shmpanic(ctable);"
3982    emit "        return FALSE;"
3983    emit "    }"
3984    emit ""
3985
3986    emit "    bundle = (char *)&defaultList\[$fieldNum];"
3987    emit ""
3988
3989    emit "    memcpy((char *)bundle, \"$bundle\", $bundleLen);"
3990    emit ""
3991
3992    emit "   ctable->emptyString = (char *)&bundle\[0];"
3993    emit "   ctable->defaultStrings = (const char **)defaultList;"
3994    emit ""
3995
3996    emit [join $sets "\n"]
3997    emit ""
3998    emit "    return TRUE;"
3999    emit "$rightCurly"
4000}
4001
4002#
4003# gen_code - generate all of the code for the underlying methods for
4004#  managing a created table
4005#
4006proc gen_code {} {
4007    variable table
4008    variable booleans
4009    variable fields
4010    variable fieldList
4011    variable withSharedTables
4012    variable leftCurly
4013    variable rightCurly
4014    variable cmdBodySource
4015
4016    # Used in cmdBodySubst:
4017    variable extension
4018    variable keyFieldName
4019
4020    #set pointer "${table}_ptr"
4021    set pointer p
4022
4023    set Id {CTable template Id}
4024
4025    set nFields [string toupper $table]_NFIELDS
4026
4027    set rowStruct $table
4028
4029    gen_sanity_checks $table
4030
4031    gen_allocate_function $table
4032
4033    gen_reinsert_row_function $table
4034
4035    gen_clean_function $table
4036
4037    gen_set_function $table
4038
4039    gen_set_null_function $table
4040
4041    gen_is_null_function $table
4042
4043    gen_get_function $table
4044
4045    gen_incr_function $table
4046
4047    gen_field_compare_functions
4048
4049    gen_sort_compare_function
4050
4051    gen_search_compare_function
4052
4053    gen_make_key_functions
4054
4055    gen_shared_string_allocator
4056
4057    emit [subst -nobackslashes -nocommands $cmdBodySource]
4058}
4059
4060#
4061# gen_new_obj - given a data type, pointer name and field name, return
4062#  the C code to generate a Tcl object containing that element from the
4063#  pointer pointing to the named field.
4064#
4065proc gen_new_obj {type fieldName} {
4066    variable table
4067    upvar ::ctable::fields::$fieldName field
4068
4069    switch $type {
4070	key {
4071	    return "Tcl_NewStringObj (row->hashEntry.key, -1)"
4072	}
4073
4074	short {
4075	    if {![info exists field(notnull)] || !$field(notnull)} {
4076		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewIntObj (row->$fieldName)"
4077	    } else {
4078		return "Tcl_NewIntObj (row->$fieldName)"
4079	    }
4080	}
4081
4082	int {
4083	    if {![info exists field(notnull)] || !$field(notnull)} {
4084		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewIntObj (row->$fieldName)"
4085	    } else {
4086		return "Tcl_NewIntObj (row->$fieldName)"
4087	    }
4088	}
4089
4090	long {
4091	    if {![info exists field(notnull)] || !$field(notnull)} {
4092		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewLongObj (row->$fieldName)"
4093	    } else {
4094		return "Tcl_NewLongObj (row->$fieldName)"
4095	    }
4096	}
4097
4098	wide {
4099	    if {![info exists field(notnull)] || !$field(notnull)} {
4100		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewWideIntObj (row->$fieldName)"
4101	    } else {
4102		return "Tcl_NewWideIntObj (row->$fieldName)"
4103	    }
4104	}
4105
4106	double {
4107	    if {![info exists field(notnull)] || !$field(notnull)} {
4108		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewDoubleObj (row->$fieldName)"
4109	    } else {
4110		return "Tcl_NewDoubleObj (row->$fieldName)"
4111	    }
4112	}
4113
4114	float {
4115	    if {![info exists field(notnull)] || !$field(notnull)} {
4116		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewDoubleObj (row->$fieldName)"
4117	    } else {
4118		return "Tcl_NewDoubleObj (row->$fieldName)"
4119	    }
4120	}
4121
4122	boolean {
4123	    if {![info exists field(notnull)] || !$field(notnull)} {
4124		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewBooleanObj (row->$fieldName)"
4125	    } else {
4126		return "Tcl_NewBooleanObj (row->$fieldName)"
4127	    }
4128	}
4129
4130	varstring {
4131	    if {![info exists field(notnull)] || !$field(notnull)} {
4132		return "(row->_${fieldName}IsNull || !row->$fieldName) ? ${table}_NullValueObj : Tcl_NewStringObj (row->$fieldName, row->_${fieldName}Length)"
4133	    } else {
4134		return "Tcl_NewStringObj (row->$fieldName, row->_${fieldName}Length)"
4135	    }
4136	}
4137
4138	char {
4139	    if {![info exists field(notnull)] || !$field(notnull)} {
4140		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewStringObj (&row->$fieldName, 1)"
4141	    } else {
4142		return "Tcl_NewStringObj (&row->$fieldName, 1)"
4143	    }
4144	}
4145
4146	fixedstring {
4147	    if {![info exists field(notnull)] || !$field(notnull)} {
4148		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewStringObj (row->$fieldName, $field(length))"
4149	    } else {
4150		return "Tcl_NewStringObj (row->$fieldName, $field(length))"
4151	    }
4152	}
4153
4154	inet {
4155	    if {![info exists field(notnull)] || !$field(notnull)} {
4156		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewStringObj (inet_ntoa (row->$fieldName), -1)"
4157	    } else {
4158		return "Tcl_NewStringObj (inet_ntoa (row->$fieldName), -1)"
4159	    }
4160	}
4161
4162	mac {
4163	    if {![info exists field(notnull)] || !$field(notnull)} {
4164		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : Tcl_NewStringObj (ether_ntoa (&row->$fieldName), -1)"
4165	    } else {
4166		return "Tcl_NewStringObj (ether_ntoa (&row->$fieldName), -1)"
4167	    }
4168	}
4169
4170	tclobj {
4171	    if {![info exists field(notnull)] || !$field(notnull)} {
4172		return "row->_${fieldName}IsNull ? ${table}_NullValueObj : ((row->$fieldName == (Tcl_Obj *) NULL) ? Tcl_NewObj () : row->$fieldName)"
4173	    } else {
4174		return "((row->$fieldName == (Tcl_Obj *) NULL) ? Tcl_NewObj () : row->$fieldName)"
4175	    }
4176	}
4177
4178	default {
4179	    error "no code to gen obj for type $type"
4180	}
4181    }
4182}
4183
4184#
4185# gen_get_set_obj - given an object, a data type, pointer name and field name,
4186#  return the C code to set a Tcl object to contain that element from the
4187#  pointer pointing to the named field.
4188#
4189# note: this is an inefficient way to get the value of varstrings,
4190# fixedstrings and chars, and can't even do tclobjs.
4191#
4192# do what gen_get_string_cases does, or call its parent anyway *_get_string,
4193# to get string representations of those efficiently.
4194#
4195proc gen_get_set_obj {obj type fieldName} {
4196    variable fields
4197    variable table
4198
4199    switch $type {
4200	short {
4201	    return "Tcl_SetIntObj ($obj, row->$fieldName)"
4202	}
4203
4204	int {
4205	    return "Tcl_SetIntObj ($obj, row->$fieldName)"
4206	}
4207
4208	long {
4209	    return "Tcl_SetLongObj ($obj, row->$fieldName)"
4210	}
4211
4212	wide {
4213	    return "Tcl_SetWideIntObj ($obj, row->$fieldName)"
4214	}
4215
4216	double {
4217	    return "Tcl_SetDoubleObj ($obj, row->$fieldName)"
4218	}
4219
4220	float {
4221	    return "Tcl_SetDoubleObj ($obj, row->$fieldName)"
4222	}
4223
4224	boolean {
4225	    return "Tcl_SetBooleanObj ($obj, row->$fieldName)"
4226	}
4227
4228	varstring {
4229	    return "Tcl_SetStringObj ($obj, row->$fieldName, row->_${fieldName}Length)"
4230	}
4231
4232	char {
4233	    return "Tcl_SetStringObj ($obj, &row->$fieldName, 1)"
4234	}
4235
4236	fixedstring {
4237	    upvar ::ctable::fields::$fieldName field
4238
4239	    return "Tcl_SetStringObj ($obj, row->$fieldName, $field(length))"
4240	}
4241
4242	inet {
4243	    return "Tcl_SetStringObj ($obj, inet_ntoa (row->$fieldName), -1)"
4244	}
4245
4246	mac {
4247	    return "Tcl_SetStringObj ($obj, ether_ntoa (&row->$fieldName), -1)"
4248	}
4249
4250	tclobj {
4251	    error "can't set a string to a tclobj (field \"$fieldName\") -- you have to handle this outside of gen_get_set_obj"
4252	}
4253
4254	default {
4255	    error "no code to gen obj for type $type"
4256	}
4257    }
4258}
4259
4260#
4261# set_list_obj - generate C code to emit a Tcl obj containing the named
4262#  field into a list that's being cons'ed up
4263#
4264proc set_list_obj {position type fieldName} {
4265    emit "    listObjv\[$position] = [gen_new_obj $type $fieldName];"
4266}
4267
4268#
4269# append_list_element - generate C code to append a list element to the
4270#  output object.  used by code that lets you get one or more named fields.
4271#
4272proc append_list_element {type fieldName} {
4273    return "Tcl_ListObjAppendElement (interp, Tcl_GetObjResult(interp), [gen_new_obj $type $fieldName])"
4274}
4275
4276#
4277# gen_list - generate C code to emit an entire row into a Tcl list
4278#
4279proc gen_list {} {
4280    variable table
4281    variable booleans
4282    variable fields
4283    variable fieldList
4284    variable leftCurly
4285    variable rightCurly
4286
4287    set lengthDef [string toupper $table]_NFIELDS
4288
4289    emit "Tcl_Obj *${table}_genlist (Tcl_Interp *interp, ctable_BaseRow *vRow) $leftCurly"
4290    emit "    struct $table *row = (struct $table *)vRow;"
4291
4292    emit "    Tcl_Obj *listObjv\[$lengthDef];"
4293    emit ""
4294
4295    set position 0
4296    foreach fieldName $fieldList {
4297	if {[is_hidden $fieldName]} {
4298	    continue
4299	}
4300
4301	upvar ::ctable::fields::$fieldName field
4302
4303	set_list_obj $position $field(type) $fieldName
4304
4305	incr position
4306    }
4307
4308    emit "    return Tcl_NewListObj ($position, listObjv);"
4309    emit "$rightCurly"
4310    emit ""
4311}
4312
4313#
4314# gen_keyvalue_list - generate C code to emit an entire row into a Tcl list in
4315#  "array set" format
4316#
4317proc gen_keyvalue_list {} {
4318    variable table
4319    variable booleans
4320    variable fields
4321    variable fieldList
4322    variable leftCurly
4323    variable rightCurly
4324
4325    set lengthDef [string toupper $table]_NFIELDS
4326
4327    emit "Tcl_Obj *${table}_gen_keyvalue_list (Tcl_Interp *interp, ctable_BaseRow *vRow) $leftCurly"
4328    emit "    struct $table *row = (struct $table *)vRow;"
4329
4330    emit "    Tcl_Obj *listObjv\[$lengthDef * 2];"
4331    emit ""
4332
4333    set position 0
4334    foreach fieldName $fieldList {
4335	if {[is_hidden $fieldName]} {
4336	    continue
4337	}
4338
4339	upvar ::ctable::fields::$fieldName field
4340
4341	emit "    listObjv\[$position] = [field_to_nameObj $table $fieldName];"
4342	incr position
4343
4344	set_list_obj $position $field(type) $fieldName
4345	incr position
4346
4347	emit ""
4348    }
4349
4350    emit "    return Tcl_NewListObj ($position, listObjv);"
4351    emit "$rightCurly"
4352    emit ""
4353}
4354
4355#
4356# gen_nonnull_keyvalue_list - generate C code to emit all of the nonnull
4357#   values in an entire row into a Tcl list in "array set" format
4358#
4359proc gen_nonnull_keyvalue_list {} {
4360    variable table
4361    variable booleans
4362    variable fields
4363    variable fieldList
4364    variable leftCurly
4365    variable rightCurly
4366
4367    set lengthDef [string toupper $table]_NFIELDS
4368
4369    emit "Tcl_Obj *${table}_gen_nonnull_keyvalue_list (Tcl_Interp *interp, struct $table *row) $leftCurly"
4370
4371    emit "    Tcl_Obj *listObjv\[$lengthDef * 2];"
4372    emit "    int position = 0;"
4373    emit "    Tcl_Obj *obj;"
4374    emit ""
4375
4376    foreach fieldName $fieldList {
4377	if {[is_hidden $fieldName]} {
4378	    continue
4379	}
4380
4381	upvar ::ctable::fields::$fieldName field
4382
4383	if {[is_key $fieldName]} {
4384	    emit "    listObjv\[position++] = [field_to_nameObj $table $fieldName];"
4385	    emit "    listObjv\[position++] = [gen_new_obj $field(type) $fieldName];"
4386	} else {
4387	    emit "    obj = [gen_new_obj $field(type) $fieldName];"
4388	    emit "    if (obj != ${table}_NullValueObj) $leftCurly"
4389	    emit "        listObjv\[position++] = [field_to_nameObj $table $fieldName];"
4390	    emit "        listObjv\[position++] = obj;"
4391	    emit "    $rightCurly"
4392	}
4393    }
4394
4395    emit "    return Tcl_NewListObj (position, listObjv);"
4396    emit "$rightCurly"
4397    emit ""
4398}
4399
4400#
4401# gen_make_key_functions - Generate C code to return the key fields as a list
4402#
4403proc gen_make_key_functions {} {
4404    gen_make_key_from_keylist
4405}
4406
4407proc gen_make_key_from_keylist {} {
4408    variable table
4409    variable fields
4410    variable keyFieldName
4411    variable fieldList
4412    variable leftCurly
4413    variable rightCurly
4414
4415    emit "Tcl_Obj *${table}_key_from_keylist (Tcl_Interp *interp, Tcl_Obj **objv, int objc) $leftCurly"
4416
4417    if {"$keyFieldName" != ""} {
4418	emit "    int      i;"
4419        emit ""
4420
4421        emit "    for(i = 0; i < objc; i+=2)"
4422	emit "        if(strcmp(Tcl_GetString(objv\[i]), \"$keyFieldName\") == 0)"
4423	emit "            return objv\[i+1];"
4424        emit ""
4425    }
4426    emit "    return (Tcl_Obj *)NULL;"
4427
4428    emit "$rightCurly"
4429    emit ""
4430}
4431
4432#
4433# gen_field_names - generate C code containing an array of pointers to strings
4434#  comprising the names of all of the fields in a row of the table being
4435#  defined.  Also generate an enumerated type of all of the field names
4436#  mapped to uppercase and prepended with FIELD_ for use with
4437#  Tcl_GetIndexFromObj in figuring out what fields are wanted
4438#
4439proc gen_field_names {} {
4440    variable table
4441    variable booleans
4442    variable fields
4443    variable fieldList
4444    variable leftCurly
4445    variable rightCurly
4446    variable keyField
4447    variable withSharedTables
4448
4449    emit "#define [string toupper $table]_NFIELDS [llength $fieldList]"
4450    emit ""
4451
4452    emit "int      ${table}_keyField = $keyField;"
4453
4454    emit "static CONST char *${table}_fields\[] = $leftCurly"
4455    foreach fieldName $fieldList {
4456	emit "    \"$fieldName\","
4457    }
4458    emit "    (char *) NULL"
4459    emit "$rightCurly;\n"
4460
4461    set fieldenum "enum ${table}_fields $leftCurly"
4462    foreach myField $fieldList {
4463	append fieldenum "\n    [field_to_enum $myField],"
4464    }
4465    set fieldenum "[string range $fieldenum 0 end-1]\n$rightCurly;\n"
4466    emit $fieldenum
4467
4468    set typeList "enum ctable_types ${table}_types\[\] = $leftCurly"
4469    foreach myField $fieldList {
4470	upvar ::ctable::fields::$myField field
4471
4472	append typeList "\n    [ctable_type_to_enum $field(type)],"
4473    }
4474    emit "[string range $typeList 0 end-1]\n$rightCurly;\n"
4475
4476    emit "// define per-field array for ${table} saying what fields need quoting"
4477    set needsQuoting "static int ${table}_needs_quoting\[\] = $leftCurly"
4478    foreach myField $fieldList {
4479	upvar ::ctable::fields::$myField field
4480
4481	if {[info exists field(needsQuoting)] && $field(needsQuoting)} {
4482	    set quoting 1
4483	} else {
4484	    set quoting 0
4485	}
4486	append needsQuoting "\n    $quoting,"
4487    }
4488    emit "[string range $needsQuoting 0 end-1]\n$rightCurly;\n"
4489
4490    emit "// define per-field array for ${table} saying what fields are unique"
4491    set unique "int ${table}_unique\[\] = $leftCurly"
4492    foreach myField $fieldList {
4493	upvar ::ctable::fields::$myField field
4494
4495	if {[info exists field(unique)] && $field(unique)} {
4496	    set uniqueVal 1
4497	} else {
4498	    set uniqueVal 0
4499	}
4500	append unique "\n    $uniqueVal,"
4501    }
4502    emit "[string range $unique 0 end-1]\n$rightCurly;\n"
4503
4504    emit "// define objects that will be filled with the corresponding field names"
4505    foreach fieldName $fieldList {
4506        emit "Tcl_Obj *[field_to_nameObj $table $fieldName];"
4507    }
4508    emit ""
4509
4510    emit "// define field property list keys and values to allow introspection"
4511
4512    # do keys
4513    foreach fieldName $fieldList {
4514	upvar ::ctable::fields::$fieldName field
4515
4516	set propstring "static CONST char *[field_to_var $table $fieldName propkeys]\[] = $leftCurly"
4517
4518	foreach fieldName [lsort [array names field]] {
4519	    append propstring "\"$fieldName\", "
4520	}
4521	emit "${propstring}(CONST char *)NULL$rightCurly;"
4522    }
4523    emit ""
4524
4525    set propstring "static CONST char **${table}_propKeys\[] = $leftCurly"
4526    foreach fieldName $fieldList {
4527        append propstring "[field_to_var $table $fieldName propkeys],"
4528    }
4529    emit "[string range $propstring 0 end-1]$rightCurly;"
4530    emit ""
4531    # end of keys
4532
4533    # do values, replica of keys, needs to be collapsed
4534    foreach fieldName $fieldList {
4535	upvar ::ctable::fields::$fieldName field
4536
4537	set propstring "static CONST char *[field_to_var $table $fieldName propvalues]\[] = $leftCurly"
4538
4539	foreach fieldName [lsort [array names field]] {
4540	    append propstring "\"$field($fieldName)\", "
4541	}
4542	emit "${propstring}(CONST char *)NULL$rightCurly;"
4543    }
4544    emit ""
4545
4546    set propstring "static CONST char **${table}_propValues\[] = $leftCurly"
4547    foreach fieldName $fieldList {
4548        append propstring "[field_to_var $table $fieldName propvalues],"
4549    }
4550    emit "[string range $propstring 0 end-1]$rightCurly;"
4551    emit ""
4552    # end of values
4553
4554    emit "static Tcl_Obj *${table}_NameObjList\[[string toupper $table]_NFIELDS + 1\];"
4555    emit ""
4556
4557    emit "static Tcl_Obj *${table}_DefaultEmptyStringObj;"
4558    emit ""
4559
4560    emit "// define the null value object"
4561    emit "static Tcl_Obj *${table}_NullValueObj;"
4562    emit "static char *${table}_NullValueString;"
4563    emit "static int ${table}_NullValueSize;"
4564    emit ""
4565
4566    set nullableList {}
4567
4568    foreach myField $fieldList {
4569	upvar ::ctable::fields::$myField field
4570
4571        set value 1
4572        if {[info exists field(notnull)] && $field(notnull)} {
4573	    set value 0
4574        }
4575
4576	lappend nullableList $value
4577    }
4578
4579    emit "// define fields that may be null"
4580    emit "static int ${table}_nullable_fields\[] = { [join $nullableList ", "] };"
4581    emit ""
4582
4583    if {$withSharedTables} {
4584	set defaultStrings {}
4585
4586        foreach myField $fieldList {
4587	    upvar ::ctable::fields::$myField field
4588
4589	    if {$field(type) == "varstring" && [info exists field(default)]} {
4590	        lappend defaultStrings [cquote $field(default)]
4591	    } else {
4592	        lappend defaultStrings ""
4593	    }
4594        }
4595
4596        emit "// define default string list"
4597        emit "static CONST char *${table}_defaultStrings\[] = $leftCurly"
4598        emit "    \"[join $defaultStrings {", "}]\""
4599        emit "$rightCurly;"
4600        emit ""
4601    }
4602}
4603
4604#
4605# gen_gets_cases - generate case statements for each field, each case fetches
4606#  field from row and returns a new Tcl_Obj set with that field's value
4607#
4608proc gen_gets_cases {} {
4609    variable table
4610    variable booleans
4611    variable fields
4612    variable fieldList
4613    variable leftCurly
4614    variable rightCurly
4615
4616    foreach myField $fieldList {
4617	upvar ::ctable::fields::$myField field
4618
4619	emit "      case [field_to_enum $myField]:"
4620	emit "        return [gen_new_obj $field(type) $myField];"
4621	emit ""
4622    }
4623}
4624
4625#
4626# gen_gets_string_cases - generate case statements for each field, each case
4627#  generates a return of a char * to a string representing that field's
4628#  value and sets a passed-in int * to the length returned.
4629#
4630proc gen_gets_string_cases {} {
4631    variable table
4632    variable booleans
4633    variable fields
4634    variable fieldList
4635    variable leftCurly
4636    variable rightCurly
4637
4638    foreach myField $fieldList {
4639	upvar ::ctable::fields::$myField field
4640
4641	emit "      case [field_to_enum $myField]:"
4642
4643	if {![info exists field(notnull)] || !$field(notnull)} {
4644	    emit "        if (row->_${myField}IsNull) $leftCurly"
4645	    emit "            return Tcl_GetStringFromObj (${table}_NullValueObj, lengthPtr);"
4646	    emit "        $rightCurly"
4647	}
4648
4649	switch $field(type) {
4650	  "key" {
4651	    emit "        *lengthPtr = strlen(row->hashEntry.key);"
4652	    emit "        return row->hashEntry.key;"
4653	  }
4654
4655	  "varstring" {
4656	    if {![info exists field(notnull)] || $field(notnull) == 0} {
4657	        emit "        if (row->${myField} == NULL) $leftCurly"
4658	        emit "            return Tcl_GetStringFromObj (${table}_DefaultEmptyStringObj, lengthPtr);"
4659	        emit "        $rightCurly"
4660	    }
4661	    emit "        *lengthPtr = row->_${myField}Length;"
4662	    emit "        return row->$myField;"
4663	  }
4664
4665	  "fixedstring" {
4666	      emit "        *lengthPtr = $field(length);"
4667	      emit "        return row->$myField;"
4668	  }
4669
4670	  "char" {
4671	      emit "        *lengthPtr = 1;"
4672	      emit "        return &row->$myField;"
4673	  }
4674
4675	  "tclobj" {
4676	    emit "        if (row->$myField == NULL) $leftCurly"
4677	    emit "            return Tcl_GetStringFromObj (${table}_DefaultEmptyStringObj, lengthPtr);"
4678	    emit "        $rightCurly"
4679	    emit "        return Tcl_GetStringFromObj (row->$myField, lengthPtr);"
4680	  }
4681
4682	  default {
4683	      emit "        [gen_get_set_obj utilityObj $field(type) $myField];"
4684	      emit "        return Tcl_GetStringFromObj (utilityObj, lengthPtr);"
4685	  }
4686	}
4687	emit ""
4688    }
4689}
4690
4691# Static utility routines for command body that aren't ctable specific
4692
4693variable sharedStaticSource {
4694
4695// Call write-lock at least once during command. Cursors do their own locking and unlocking.
4696static INLINE void begin_write(CTable *ctable)
4697{
4698  if(ctable->share_type == CTABLE_SHARED_MASTER && !ctable->cursors) {
4699    write_lock(ctable->share);
4700    ctable->was_locked = 1;
4701  }
4702}
4703
4704// Call write-unlock once at the end of the command, IFF it was locked
4705static INLINE void end_write(CTable *ctable)
4706{
4707  if(ctable->was_locked && ctable->share_type == CTABLE_SHARED_MASTER && !ctable->cursors) {
4708    write_unlock(ctable->share);
4709    ctable->was_locked = 0;
4710  }
4711}
4712
4713}
4714
4715variable unsharedStaticSource {
4716// Dummy lock/unlock functions, since we're not doing shared ctables.
4717#  define begin_write(ct)
4718#  define end_write(ct)
4719}
4720
4721#
4722# gen_preamble - generate stuff that goes at the head of the C file
4723#  we're generating
4724#
4725proc gen_preamble {} {
4726    variable fullInline
4727    variable fullStatic
4728    variable withPgtcl
4729    variable withCasstcl
4730    variable withSharedTables
4731    variable withSharedTclExtension
4732    variable sanityChecks
4733    variable sharedTraceFile
4734    variable sharedBase
4735    variable sharedGuard
4736    variable sharedLog
4737    variable poolRatio
4738    variable preambleCannedSource
4739    variable sharedStaticSource
4740    variable unsharedStaticSource
4741    variable localDefines
4742    variable localCode
4743
4744	emit "/* -*- mode: c++; buffer-read-only: 1; -*- */"
4745    emit "/* autogenerated by ctable table generator [clock format [clock seconds]] */"
4746    emit "/* DO NOT EDIT */"
4747    emit ""
4748
4749    if {$fullInline} {
4750	emit "#define INLINE inline"
4751    } else {
4752	emit "#define INLINE"
4753    }
4754
4755    if {$fullStatic} {
4756	# Make all possible symbols static except for explicitly exported ones.
4757	emit "#define CTABLE_INTERNAL static"
4758	emit "#define CTABLE_EXTERNAL extern \"C\""
4759	emit "#if defined(__GNUC__) && (__GNUC__ >= 4)"
4760	emit "  #define CTABLE_EXTERNAL2 __attribute__ ((visibility (\"default\")))"
4761	emit "#elif defined(_WIN32) || defined(__CYGWIN__)"
4762	emit "  #define CTABLE_EXTERNAL2 __declspec(dllexport)"
4763	emit "#else"
4764	emit "  #define CTABLE_EXTERNAL2"
4765	emit "#endif"
4766	emit "#define FULLSTATIC"
4767    } else {
4768	# Leave all symbols exported.
4769	emit "#define CTABLE_INTERNAL"
4770	emit "#define CTABLE_EXTERNAL extern \"C\""
4771	emit "#define CTABLE_EXTERNAL2"
4772    }
4773
4774    emit ""
4775    if {$withPgtcl} {
4776        emit "#define WITH_PGTCL"
4777        emit ""
4778    }
4779
4780    if {$withCasstcl} {
4781        emit "#define WITH_CASSTCL"
4782        emit ""
4783    }
4784
4785    if {$sanityChecks} {
4786	emit "#define SANITY_CHECKS"
4787        emit ""
4788    }
4789
4790    if {$withSharedTables} {
4791	emit "#define WITH_SHARED_TABLES"
4792	emit "#define WITH_TCL"
4793
4794	if {$withSharedTclExtension} {
4795	    emit "#define SHARED_TCL_EXTENSION"
4796	}
4797
4798	emit ""
4799        if {[info exists sharedTraceFile]} {
4800	    if {"$sharedTraceFile" != "-none"} {
4801	        emit "#define SHM_DEBUG_TRACE"
4802	        if {"$sharedTraceFile" != "-stderr"} {
4803		    emit "#define SHM_DEBUG_TRACE_FILE \"$sharedTraceFile\""
4804	        }
4805	    }
4806        }
4807
4808	if {[info exists sharedBase] && "$sharedBase" != "NULL"} {
4809	    emit "#define SHARE_BASE ((char *)$sharedBase)"
4810	}
4811
4812	if {[info exists sharedLog] && "$sharedLog" != "-none"} {
4813	    emit "#define SHARED_LOG \"$sharedLog\""
4814	    set sharedGuard 1
4815	}
4816
4817        if {[info exists sharedGuard] && $sharedGuard} {
4818	    emit "#define SHARED_GUARD"
4819	}
4820	emit "#define POOL_RATIO $poolRatio"
4821    }
4822
4823    emit $preambleCannedSource
4824    if {$withSharedTables} {
4825	if {[info exists sharedBase] && "$sharedBase" != "NULL"} {
4826	    emit "char *set_share_base = NULL;"
4827	}
4828
4829        emit $sharedStaticSource
4830    } else {
4831        emit $unsharedStaticSource
4832    }
4833
4834    if [array exists localDefines] {
4835	foreach {name value} [array get localDefines] {
4836	    emit "#define $name $value"
4837	}
4838    }
4839
4840    if [info exists localCode] {
4841	emit $localCode
4842    }
4843}
4844
4845#####
4846#
4847# Field Compare Function Generation
4848#
4849#####
4850
4851#
4852# fieldCompareNullCheckSource - this checks for nulls when comparing a field
4853#
4854variable fieldCompareNullCheckSource {
4855    // nulls sort high
4856    if (row1->_${fieldName}IsNull) {
4857	if (row2->_${fieldName}IsNull) {
4858	    return 0;
4859	}
4860	return 1;
4861    } else if (row2->_${fieldName}IsNull) {
4862	return -1;
4863    }
4864}
4865
4866#
4867# gen_field_compare_null_check_source - return code to be emitted into a field
4868#  compare, nothing if the field is not null else code to check for null
4869#
4870proc gen_field_compare_null_check_source {table fieldName} {
4871    variable fieldCompareNullCheckSource
4872    variable varstringCompareNullSource
4873    variable varstringCompareEmptySource
4874    upvar ::ctable::fields::$fieldName field
4875
4876    if {[info exists field(notnull)] && $field(notnull)} {
4877        set source ""
4878    } elseif {"$field(type)" == "varstring"} {
4879	set source $varstringCompareNullSource
4880    } else {
4881	set source $fieldCompareNullCheckSource
4882    }
4883
4884    return [string range [subst -nobackslashes -nocommands $source] 1 end-1]
4885}
4886
4887#
4888# fieldCompareHeaderSource - code for defining a field compare function
4889#
4890variable fieldCompareHeaderSource {
4891// field compare function for field '$fieldName' of the '$table' table...
4892int ${table}_field_${fieldName}_compare(const ctable_BaseRow *vPointer1, const ctable_BaseRow *vPointer2) $leftCurly
4893    struct ${table} *row1, *row2;
4894
4895    row1 = (struct $table *) vPointer1;
4896    row2 = (struct $table *) vPointer2;
4897
4898#ifdef SANITY_CHECKS
4899    if(!row1) Tcl_Panic("NULL row1 for ${table}_field_${fieldName}_compare, row2 == 0x%lx", (long)row2);
4900    if(!row2) Tcl_Panic("NULL row2 for ${table}_field_${fieldName}_compare, row1 == 0x%lx", (long)row1);
4901#endif
4902
4903}
4904
4905variable fieldCompareTrailerSource {
4906$rightCurly
4907}
4908
4909#
4910# keyCompareSource - code for defining a key compare function
4911#
4912variable keyCompareSource {
4913// field compare function for key of the '$table' table...
4914int ${table}_key_compare(const ctable_BaseRow *vPointer1, const ctable_BaseRow *vPointer2) $leftCurly
4915    struct ${table} *row1, *row2;
4916
4917    row1 = (struct $table *) vPointer1;
4918    row2 = (struct $table *) vPointer2;
4919    if (*row1->hashEntry.key != *row2->hashEntry.key) {
4920        if (*row1->hashEntry.key < *row2->hashEntry.key) {
4921	    return -1;
4922	} else {
4923	    return 1;
4924	}
4925    }
4926    return strcmp(row1->hashEntry.key, row2->hashEntry.key);
4927$rightCurly
4928}
4929
4930#
4931# boolFieldCompSource - code we run subst over to generate a compare of a
4932# boolean (bit) for use in a field comparison routine.
4933#
4934variable boolFieldCompSource {
4935    if (row1->$fieldName && !row2->$fieldName) {
4936	return -1;
4937    }
4938
4939    if (!row1->$fieldName && row2->$fieldName) {
4940	return 1;
4941    }
4942
4943    return 0;
4944}
4945
4946#
4947# numberFieldSource - code we run subst over to generate a compare of a standard
4948#  number such as an integer, long, double, and wide integer for use in field
4949#  compares.
4950#
4951variable numberFieldCompSource {
4952    if (row1->$fieldName < row2->$fieldName) {
4953        return -1;
4954    }
4955
4956    if (row1->$fieldName > row2->$fieldName) {
4957	return 1;
4958    }
4959
4960    return 0;
4961}
4962
4963#
4964# varstringFieldCompSource - code we run subst over to generate a compare of
4965# a string for use in searching, sorting, etc.
4966#
4967# NOTE - this code has NO safety net. This code must NEVER be exposed without the safety net.
4968#
4969variable varstringFieldCompSource {
4970    if (*row1->$fieldName != *row2->$fieldName) {
4971        if (*row1->$fieldName < *row2->$fieldName) {
4972            return -1;
4973        } else {
4974            return 1;
4975        }
4976    }
4977    return strcmp (row1->$fieldName, row2->$fieldName);
4978}
4979
4980
4981#
4982# varstringCompareNullSource - compare against default empty string
4983#
4984# note there's also a varstringSortCompareNullSource that's pretty close to
4985# this but sets a result variable and does a break to get out of a case
4986# statement rather than returning something
4987#
4988variable varstringCompareNullSource {
4989    // NULL sorts high
4990    if (row1->_${fieldName}IsNull || !row1->$fieldName) {
4991	if(row2->_${fieldName}IsNull || !row2->$fieldName) {
4992	    return 0;
4993	} else {
4994	    return 1;
4995	}
4996    } else {
4997	if(row2->_${fieldName}IsNull || !row2->$fieldName) {
4998	    return -1;
4999	}
5000    }
5001}
5002
5003#
5004# fixedstringFieldCompSource - code we run subst over to generate a comapre of a
5005# fixed-length string for use in a searching, sorting, etc.
5006#
5007variable fixedstringFieldCompSource {
5008    if (*row1->$fieldName != *row2->$fieldName) {
5009        if (*row1->$fieldName < *row2->$fieldName) {
5010	    return -1;
5011	} else {
5012	    return 1;
5013	}
5014    }
5015    return strncmp (row1->$fieldName, row2->$fieldName, $length);
5016}
5017
5018#
5019# binaryDataFieldCompSource - code we run subst over to generate a comapre of a
5020# inline binary arrays (inets and mac addrs) for use in searching and sorting.
5021#
5022variable binaryDataFieldCompSource {
5023    return memcmp (&row1->$fieldName, &row2->$fieldName, $length);
5024}
5025
5026#
5027# tclobjFieldCompSource - code we run subst over to generate a compare of
5028# a tclobj for use in searching and sorting.
5029#
5030variable tclobjFieldCompSource {
5031    return strcmp (Tcl_GetString (row1->$fieldName), Tcl_GetString (row2->$fieldName));
5032}
5033
5034#
5035# gen_field_comp - emit code to compare a field for a field comparison routine
5036#
5037proc gen_field_comp {fieldName} {
5038    variable table
5039    variable booleans
5040    variable fields
5041    variable fieldList
5042    variable leftCurly
5043    variable rightCurly
5044
5045    variable numberFieldCompSource
5046    variable fixedstringFieldCompSource
5047    variable binaryDataFieldCompSource
5048    variable varstringFieldCompSource
5049    variable boolFieldCompSource
5050    variable keyCompSource
5051    variable tclobjFieldCompSource
5052
5053    upvar ::ctable::fields::$fieldName field
5054
5055    # First, handle nulls
5056    emit [gen_field_compare_null_check_source $table $fieldName]
5057
5058    switch $field(type) {
5059	key {
5060	    emit [string range [subst -nobackslashes -nocommands $keyCompSource] 1 end-1]
5061	}
5062
5063	int {
5064	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5065	}
5066
5067	long {
5068	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5069	}
5070
5071	wide {
5072	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5073	}
5074
5075	double {
5076	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5077	}
5078
5079	short {
5080	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5081	}
5082
5083	float {
5084	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5085	}
5086
5087	char {
5088	    emit [string range [subst -nobackslashes -nocommands $numberFieldCompSource] 1 end-1]
5089	}
5090
5091	fixedstring {
5092	    set length $field(length)
5093	    emit [string range [subst -nobackslashes -nocommands $fixedstringFieldCompSource] 1 end-1]
5094	}
5095
5096	varstring {
5097	    emit [string range [subst -nobackslashes -nocommands $varstringFieldCompSource] 1 end-1]
5098	}
5099
5100	boolean {
5101	    emit [string range [subst -nobackslashes -nocommands $boolFieldCompSource] 1 end-1]
5102	}
5103
5104	inet {
5105	    set length "sizeof(struct in_addr)"
5106	    emit [string range [subst -nobackslashes -nocommands $binaryDataFieldCompSource] 1 end-1]
5107	}
5108
5109	mac {
5110	    set length "sizeof(struct ether_addr)"
5111	    emit [string range [subst -nobackslashes -nocommands $binaryDataFieldCompSource] 1 end-1]
5112	}
5113
5114	tclobj {
5115	    emit [string range [subst -nobackslashes -nocommands $tclobjFieldCompSource] 1 end-1]
5116	}
5117
5118	default {
5119	    error "attempt to emit sort compare source for field of unknown type $field(type)"
5120	}
5121    }
5122}
5123#
5124# gen_field_compare_functions - generate functions for each field that will
5125# compare that field from two row pointers and return -1, 0, or 1.
5126#
5127proc gen_field_compare_functions {} {
5128    variable table
5129    variable leftCurly
5130    variable rightCurly
5131    variable fieldCompareHeaderSource
5132    variable fieldCompareTrailerSource
5133    variable keyCompareSource
5134    variable fieldList
5135
5136    # generate all of the field compare functions
5137    foreach fieldName $fieldList {
5138	if [is_key $fieldName] {
5139	    emit [subst -nobackslashes $keyCompareSource]
5140	    continue
5141	}
5142	emit [string range [subst -nobackslashes $fieldCompareHeaderSource] 1 end-1]
5143	gen_field_comp $fieldName
5144	emit [string range [subst -nobackslashes -nocommands $fieldCompareTrailerSource] 1 end-1]
5145    }
5146
5147    # generate an array of pointers to field compare functions for this type
5148    emit "// array of table's field compare routines indexed by field number"
5149    emit "fieldCompareFunction_t ${table}_compare_functions\[] = $leftCurly"
5150    set typeList ""
5151    foreach fieldName $fieldList {
5152	if [is_key $fieldName] {
5153	    append typeList "\n    ${table}_key_compare,"
5154	} else {
5155	    append typeList "\n    ${table}_field_${fieldName}_compare,"
5156	}
5157    }
5158    emit "[string range $typeList 0 end-1]\n$rightCurly;\n"
5159}
5160
5161#####
5162#
5163# Sort Comparison Function Generation
5164#
5165#####
5166
5167variable sortCompareHeaderSource {
5168
5169int ${table}_sort_compare(void *clientData, const ctable_BaseRow *vRow1, const ctable_BaseRow *vRow2) $leftCurly
5170    CTableSort *sortControl = (CTableSort *)clientData;
5171    const struct $table *row1 = *(const struct $table **)vRow1;
5172    const struct $table *row2 = *(const struct $table **)vRow2;
5173    int              i;
5174    int              direction;
5175    int              result = 0;
5176
5177//fprintf (stderr, "sort comp p1 %p, p2 %p\n", row1, row2);
5178
5179    for (i = 0; i < sortControl->nFields; i++) $leftCurly
5180        direction = sortControl->directions[i];
5181        switch (sortControl->fields[i]) $leftCurly
5182}
5183
5184variable sortCompareTrailerSource {
5185        $rightCurly // end of switch
5186
5187	// if they're not equal, we're done.  if they are, we may need to
5188	// compare a subordinate sort field (if there is one)
5189	if (result != 0) {
5190	    break;
5191	}
5192    $rightCurly // end of for loop on sort fields
5193    return result;
5194$rightCurly
5195}
5196
5197#
5198# gen_sort_compare_function - generate a function that will compare fields
5199# in two ctable structures for use by qsort
5200#
5201proc gen_sort_compare_function {} {
5202    variable table
5203    variable leftCurly
5204    variable rightCurly
5205    variable sortCompareHeaderSource
5206    variable sortCompareTrailerSource
5207
5208    emit [string range [subst -nobackslashes -nocommands $sortCompareHeaderSource] 1 end-1]
5209
5210    gen_sort_comp
5211
5212    emit [string range [subst -nobackslashes -nocommands $sortCompareTrailerSource] 1 end-1]
5213}
5214
5215#
5216# gen_sort_comp - emit code to compare fields for sorting
5217#
5218proc gen_sort_comp {} {
5219    variable table
5220    variable booleans
5221    variable fields
5222    variable fieldList
5223    variable leftCurly
5224    variable rightCurly
5225
5226    variable numberSortSource
5227    variable fixedstringSortSource
5228    variable binaryDataSortSource
5229    variable varstringSortSource
5230    variable boolSortSource
5231    variable keySortSource
5232    variable tclobjSortSource
5233
5234    foreach fieldName $fieldList {
5235	upvar ::ctable::fields::$fieldName field
5236
5237	set fieldEnum [field_to_enum $fieldName]
5238
5239	switch $field(type) {
5240	    key {
5241		emit [string range [subst -nobackslashes $keySortSource] 1 end-1]
5242	    }
5243
5244	    int {
5245		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5246	    }
5247
5248	    long {
5249		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5250	    }
5251
5252	    wide {
5253		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5254	    }
5255
5256	    double {
5257		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5258	    }
5259
5260	    short {
5261		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5262	    }
5263
5264	    float {
5265		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5266	    }
5267
5268	    char {
5269		emit [string range [subst -nobackslashes $numberSortSource] 1 end-1]
5270	    }
5271
5272	    fixedstring {
5273	        set length $field(length)
5274		emit [string range [subst -nobackslashes $fixedstringSortSource] 1 end-1]
5275	    }
5276
5277	    varstring {
5278		emit [string range [subst $varstringSortSource] 1 end-1]
5279	    }
5280
5281	    boolean {
5282		emit [string range [subst -nobackslashes $boolSortSource] 1 end-1]
5283	    }
5284
5285	    inet {
5286	        set length "sizeof(struct in_addr)"
5287		emit [string range [subst -nobackslashes $binaryDataSortSource] 1 end-1]
5288	    }
5289
5290	    mac {
5291		set length "sizeof(struct ether_addr)"
5292		emit [string range [subst -nobackslashes $binaryDataSortSource] 1 end-1]
5293	    }
5294
5295	    tclobj {
5296		emit [string range [subst -nobackslashes $tclobjSortSource] 1 end-1]
5297	    }
5298
5299	    default {
5300	        error "attempt to emit sort compare source for field $fieldName of unknown type $field(type)"
5301	    }
5302	}
5303    }
5304}
5305
5306#####
5307#
5308# Search Comparison Function Generation
5309#
5310#####
5311
5312variable searchCompareHeaderSource {
5313
5314// compare a row to a block of search components and see if it matches
5315int ${table}_search_compare(Tcl_Interp *interp, CTableSearch *searchControl, ctable_BaseRow *vPointer) $leftCurly
5316    struct $table *row = (struct $table *)vPointer;
5317    struct $table *row1;
5318
5319    int                                 i;
5320    int                                 exclude = 0;
5321    int                                 compType;
5322    CTableSearchComponent              *component;
5323    int					inIndex;
5324
5325
5326#ifdef SANITY_CHECKS
5327    ${table}_sanity_check_pointer(searchControl->ctable, vPointer, CTABLE_INDEX_NORMAL, "${table}_search_compare");
5328#endif
5329
5330    for (i = 0; i < searchControl->nComponents; i++) $leftCurly
5331      if (i == searchControl->alreadySearched)
5332	continue;
5333
5334      component = &searchControl->components[i];
5335
5336      row1 = (struct $table *)component->row1;
5337      compType = component->comparisonType;
5338
5339      // Take care of the common code first
5340      switch (compType) {
5341	case CTABLE_COMP_IN:
5342	  if(component->inListRows == NULL && ctable_CreateInRows(interp, searchControl->ctable, component) == TCL_ERROR) {
5343              return TCL_ERROR;
5344	  }
5345
5346	  for(inIndex = 0; inIndex < component->inCount; inIndex++) {
5347	      if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)component->inListRows[inIndex]) == 0) {
5348		  break;
5349	      }
5350	  }
5351
5352	  if(inIndex >= component->inCount) {
5353	      return TCL_CONTINUE;
5354	  }
5355	  continue;
5356
5357	case CTABLE_COMP_LT:
5358	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) < 0) {
5359	      continue;
5360	  }
5361	  return TCL_CONTINUE;
5362
5363	case CTABLE_COMP_LE:
5364	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) <= 0) {
5365	      continue;
5366	  }
5367	  return TCL_CONTINUE;
5368
5369	case CTABLE_COMP_EQ:
5370	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) == 0) {
5371	      continue;
5372	  }
5373	  return TCL_CONTINUE;
5374
5375	case CTABLE_COMP_NE:
5376	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) != 0) {
5377	      continue;
5378	  }
5379	  return TCL_CONTINUE;
5380
5381	case CTABLE_COMP_GE:
5382	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) >= 0) {
5383	      continue;
5384	  }
5385	  return TCL_CONTINUE;
5386
5387	case CTABLE_COMP_GT:
5388	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) > 0) {
5389	      continue;
5390	  }
5391	  return TCL_CONTINUE;
5392
5393        case CTABLE_COMP_RANGE: {
5394	  struct $table *row2;
5395
5396	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row1) < 0) {
5397	      return TCL_CONTINUE;
5398	  }
5399
5400	  row2 = (struct $table *)component->row2;
5401
5402	  if (component->compareFunction ((ctable_BaseRow *)row, (ctable_BaseRow *)row2) >= 0) {
5403	      return TCL_CONTINUE;
5404	  }
5405	  continue;
5406	}
5407      }
5408
5409      switch (component->fieldID) $leftCurly
5410}
5411
5412variable searchCompareTrailerSource {
5413       $rightCurly // end of switch on field ID
5414
5415        // if exclude got set, we're done.
5416	if (exclude) {
5417	    return TCL_CONTINUE;
5418	}
5419    $rightCurly // end of for loop on search fields
5420    return TCL_OK;
5421$rightCurly
5422}
5423
5424#
5425# gen_search_compare_function - generate a function that see if a row in
5426# a ctable matches the search criteria
5427#
5428proc gen_search_compare_function {} {
5429    variable table
5430    variable leftCurly
5431    variable rightCurly
5432    variable searchCompareHeaderSource
5433    variable searchCompareTrailerSource
5434
5435    emit [string range [subst -nobackslashes -nocommands $searchCompareHeaderSource] 1 end-1]
5436
5437    gen_search_comp
5438
5439    emit [string range [subst -nobackslashes -nocommands $searchCompareTrailerSource] 1 end-1]
5440}
5441
5442#
5443# gen_search_comp - emit code to compare fields for searching
5444#
5445proc gen_search_comp {} {
5446    variable table
5447    variable booleans
5448    variable fields
5449    variable fieldList
5450    variable leftCurly
5451    variable rightCurly
5452
5453    variable numberCompSource
5454    variable fixedstringCompSource
5455    variable binaryDataCompSource
5456    variable varstringCompSource
5457    variable boolCompSource
5458    variable keyCompSource
5459    variable tclobjCompSource
5460
5461    variable standardCompSwitchSource
5462    variable standardCompNullCheckSource
5463
5464    set value sandbag
5465
5466    foreach fieldName $fieldList {
5467	upvar ::ctable::fields::$fieldName field
5468
5469	set fieldEnum [field_to_enum $fieldName]
5470	set type $field(type)
5471        set typeText $field(type)
5472
5473	switch $type {
5474	    int {
5475		set getObjCmd Tcl_GetIntFromObj
5476		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5477	    }
5478
5479	    long {
5480		set getObjCmd Tcl_GetLongFromObj
5481		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5482	    }
5483
5484	    wide {
5485		set getObjCmd Tcl_GetWideIntFromObj
5486		set typeText "Tcl_WideInt"
5487		set type "Tcl_WideInt"
5488		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5489	    }
5490
5491	    double {
5492		set getObjCmd Tcl_GetDoubleFromObj
5493		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5494	    }
5495
5496	    short {
5497		set typeText "int"
5498		set getObjCmd Tcl_GetIntFromObj
5499		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5500	    }
5501
5502	    float {
5503		set typeText "double"
5504		set getObjCmd Tcl_GetDoubleFromObj
5505		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5506	    }
5507
5508	    char {
5509		set typeText "int"
5510		set getObjCmd Tcl_GetIntFromObj
5511		emit [string range [subst -nobackslashes $numberCompSource] 1 end-1]
5512	    }
5513
5514	    fixedstring {
5515		set getObjCmd Tcl_GetString
5516	        set length $field(length)
5517		emit [string range [subst -nobackslashes $fixedstringCompSource] 1 end-1]
5518	    }
5519
5520	    varstring {
5521		set getObjCmd Tcl_GetString
5522		emit [string range [subst -nobackslashes $varstringCompSource] 1 end-1]
5523	    }
5524
5525	    boolean {
5526		set getObjCmd Tcl_GetBooleanFromObj
5527		emit [string range [subst -nobackslashes $boolCompSource] 1 end-1]
5528	    }
5529
5530	    inet {
5531		set getObjCmd Tcl_GetStringFromObj
5532	        set length "sizeof(struct in_addr)"
5533		emit [string range [subst -nobackslashes $binaryDataCompSource] 1 end-1]
5534	    }
5535
5536	    mac {
5537		set getObjCmd Tcl_GetStringFromObj
5538		set length "sizeof(struct ether_addr)"
5539		emit [string range [subst -nobackslashes $binaryDataCompSource] 1 end-1]
5540	    }
5541
5542	    tclobj {
5543		set getObjCmd Tcl_GetStringFromObj
5544		emit [string range [subst -nobackslashes $tclobjCompSource] 1 end-1]
5545	    }
5546
5547	    key {
5548		set getObjCmd Tcl_GetString
5549	        set length "strlen(row->hashEntry.key)"
5550		emit [string range [subst -nobackslashes $keyCompSource] 1 end-1]
5551	    }
5552
5553	    default {
5554	        error "attempt to emit search compare source for field of unknown type $field(type)"
5555	    }
5556	}
5557    }
5558}
5559
5560#####
5561#
5562# Invoking the Compiler
5563#
5564#####
5565
5566proc myexec {command} {
5567    variable showCompilerCommands
5568
5569    if {$showCompilerCommands} {
5570	puts $command; flush stdout
5571    }
5572
5573    eval exec $command
5574}
5575
5576#
5577# Generate the fully qualified path to a file
5578#
5579proc target_name {name version {ext .cpp}} {
5580    return [file join [target_path $name] $name-$version$ext]
5581}
5582
5583#
5584# Generate the path to the target files
5585#
5586# Either buildPath or buildPath/$name
5587#
5588# And make sure it exists!
5589#
5590proc target_path {name} {
5591    variable buildPath
5592    variable withSubdir
5593    variable dirStatus
5594
5595    set path $buildPath
5596
5597    if {$withSubdir} {
5598        set path [file join $path $name]
5599    }
5600
5601    if {![info exists dirStatus($path)]} {
5602	set dirStatus($path) [file isdirectory $path]
5603	if {!$dirStatus($path)} {
5604	    file mkdir $path
5605	}
5606    }
5607
5608    return $path
5609}
5610
5611#
5612# compile - compile and link the shared library
5613#
5614proc compile {fileFragName version} {
5615    global tcl_platform
5616    variable buildPath
5617    variable sysFlags
5618    variable withPgtcl
5619    variable withCasstcl
5620    variable genCompilerDebug
5621    variable memDebug
5622    variable withPipe
5623    variable withSubdir
5624
5625    variable sysconfig
5626
5627    set include [target_path include]
5628    set targetPath [target_path $fileFragName]
5629    set sourceFile [target_name $fileFragName $version]
5630    set objFile [target_name $fileFragName $version .o]
5631    set buildFile [target_name $fileFragName $version .sh]
5632
5633    if {$withPipe} {
5634	set pipeFlag "-pipe"
5635    } else {
5636	set pipeFlag ""
5637    }
5638
5639    set stubs [info exists sysconfig(stub)]
5640
5641    if {$genCompilerDebug} {
5642	set optflag "-Os"
5643	set dbgflag $sysconfig(dbg)
5644
5645	if {$memDebug} {
5646	    set memSuffix m
5647	} else {
5648	    set memSuffix ""
5649	}
5650
5651	if {$stubs} {
5652		set stub "$sysconfig(stubg)$memSuffix"
5653	}
5654	set lib "$sysconfig(libg)$memSuffix"
5655
5656    } else {
5657	set optflag $sysconfig(opt)
5658	set dbgflag ""
5659
5660	if {$stubs} {
5661	    set stub " $sysconfig(stub)"
5662	}
5663	set lib $sysconfig(lib)
5664    }
5665
5666    if {$stubs} {
5667	set stubString "-DUSE_TCL_STUBS=$stubs"
5668    } else {
5669	set stubString ""
5670    }
5671
5672    # put -DTCL_MEM_DEBUG in there if you're building with
5673    # memory debugging (see Tcl docs)
5674    if {$memDebug} {
5675	set memDebugString "-DTCL_MEM_DEBUG=1"
5676    } else {
5677	set memDebugString ""
5678    }
5679
5680    if {[info exists sysFlags($tcl_platform(os))]} {
5681	set sysString $sysFlags($tcl_platform(os))
5682    } else {
5683	set sysString ""
5684    }
5685
5686    if {$withPgtcl} {
5687	set pgString -I$sysconfig(pgtclprefix)/include
5688	if [info exists sysconfig(pqinclude)] {
5689	    if {"$sysconfig(pqinclude)" != "$sysconfig(pgtclprefix)/include"} {
5690	        append pgString " -I$sysconfig(pqinclude)"
5691	    }
5692	} elseif [info exists sysconfig(pqprefix)] {
5693	    if {"$sysconfig(pqprefix)" != "$sysconfig(pgtclprefix)"} {
5694	        append pgString " -I$sysconfig(pqprefix)/include"
5695	    }
5696	}
5697    } else {
5698	set pgString ""
5699    }
5700
5701    if {$withCasstcl} {
5702	set cassString -I$sysconfig(cassprefix)/include
5703    } else {
5704	set cassString ""
5705    }
5706
5707    # Keep sysconfig(ccflags) from overriding optimization level
5708    regsub -all { -O[0-9] } " $sysconfig(ccflags) " { } sysconfig(ccflags)
5709
5710    set cc_cmd "$sysconfig(cxx) $sysString $optflag $dbgflag $sysconfig(ldflags) $sysconfig(ccflags) -I$include $sysconfig(warn) $pgString $cassString $stubString $memDebugString -c $sourceFile -o $objFile 2>@stderr"
5711    myexec $cc_cmd
5712
5713    set ld_cmd "$sysconfig(cxxld) $dbgflag -o $targetPath/lib${fileFragName}$sysconfig(shlib) $objFile"
5714
5715    if {$withPgtcl} {
5716	set pgtcl_libdir $sysconfig(pgtclprefix)
5717	set pgtcl_ver $sysconfig(pgtclver)
5718	set pgtcl_lib pgtcl$pgtcl_ver
5719	set pq_libdir /usr/local/lib
5720	if {[info exists sysconfig(pqlibdir)]} {
5721	    set pq_libdir $sysconfig(pqlibdir)
5722	}
5723
5724	append ld_cmd " -Wl,-rpath,$pgtcl_libdir"
5725	append ld_cmd " -L$pgtcl_libdir -l$pgtcl_lib"
5726	append ld_cmd " -L$pq_libdir -lpq"
5727    }
5728
5729    if {$withCasstcl} {
5730	set casstcl_libdir $sysconfig(casstclprefix)
5731	set casstcl_ver $sysconfig(casstclver)
5732	#set casstcl_lib casstcl$casstcl_ver
5733	set casstcl_lib casstcl
5734
5735	append ld_cmd " -Wl,-rpath,$casstcl_libdir"
5736	append ld_cmd " -L$casstcl_libdir"
5737	append ld_cmd " -l$casstcl_lib -L/usr/local/lib -lcassandra"
5738    }
5739
5740    append ld_cmd " $sysconfig(ldflags) $stub"
5741    myexec "$ld_cmd 2>@stderr"
5742
5743    set fp [open $buildFile w]
5744    puts $fp "# Rebuild $fileFragName $version"
5745    puts $fp $cc_cmd
5746    puts $fp $ld_cmd
5747    close $fp
5748
5749    if {$withSubdir} {
5750	set pkg_args [list $buildPath */*.tcl */*[info sharedlibextension]]
5751    } else {
5752	set pkg_args [list $buildPath]
5753    }
5754
5755    variable showCompilerCommands
5756    if {$showCompilerCommands} {
5757	puts [concat + pkg_mkIndex -verbose $pkg_args]
5758	eval pkg_mkIndex -verbose $pkg_args
5759    } else {
5760	eval pkg_mkIndex $pkg_args
5761    }
5762}
5763
5764proc EndExtension {} {
5765    variable tables
5766    variable extension
5767    variable withSharedTables
5768    variable extensionVersion
5769    variable rightCurly
5770    variable ofp
5771    variable memDebug
5772
5773    put_init_extension_source [string totitle $extension] $extensionVersion
5774
5775    if {$withSharedTables} {
5776	emit "    Shared_Init(interp);"
5777    }
5778
5779    foreach name $tables {
5780	put_init_command_source $name
5781    }
5782
5783    emit "    return TCL_OK;"
5784    emit $rightCurly
5785
5786    close $ofp
5787    unset ofp
5788
5789    compile $extension $::ctable::extensionVersion
5790}
5791
5792#
5793# extension_already_built - see if the extension already exists unchanged
5794#  from what's being asked for
5795#
5796proc extension_already_built {name version code} {
5797    # if open of the stash file fails, it ain't built
5798    if {[catch {open [target_name $name $version .ct]} fp] == 1} {
5799        #puts ".ct file not there, build required"
5800        return 0
5801    }
5802
5803    # read the first line for the prior CVS ID, if failed, report not built
5804    if {[gets $fp controlLine] < 0} {
5805        #puts "first line read of .ct file failed, build required"
5806        close $fp
5807	return 0
5808    }
5809
5810    # See if this file's control line matches the line in the .ct file.
5811    # If not, rebuild not built.
5812    if {$controlLine != [control_line]} {
5813        #puts "control line does not match, build required"
5814        close $fp
5815	return 0
5816    }
5817
5818    set priorCode [read -nonewline $fp]
5819    close $fp
5820
5821    # if the prior code and current code aren't identical, report not built
5822    if {$priorCode != $code} {
5823        #puts "extension code changed, build required"
5824	return 0
5825    }
5826
5827    #puts "prior code and generator cvs match, build not required"
5828    return 1
5829}
5830
5831# This is a unique ID that should change whenever anything significant
5832# changes in ctables
5833proc control_line {} {
5834    variable srcDir
5835    variable cvsID
5836    variable keyCompileVariables
5837
5838    foreach v $keyCompileVariables {
5839	variable $v
5840	if [info exists $v] {
5841	    lappend compileSettings "$v=[set $v]"
5842	} else {
5843	    lappend compileSettings "$v?"
5844	}
5845    }
5846    set compileSettings [join $compileSettings ":"]
5847
5848    return "$cvsID $compileSettings [file mtime $srcDir] [info patchlevel]"
5849}
5850
5851#
5852# save_extension_code - after a successful build, cache the extension
5853#  definition so extension_already_built can see if it's necessary to
5854#  generate, compile and link the shared library next time we're run
5855#
5856proc save_extension_code {name version code} {
5857    set filename [target_name $name $version .ct]
5858    set fp [open $filename w]
5859
5860    puts $fp [control_line]
5861    puts $fp $code
5862
5863    close $fp
5864}
5865
5866#
5867# install_ch_files - install .h in the target dir if something like it
5868#  isn't there already
5869#
5870proc install_ch_files {includeDir} {
5871    variable srcDir
5872    variable withSharedTables
5873
5874    lappend subdirs skiplists hash
5875
5876    set copyFiles {
5877	ctable.h ctable_search.c ctable_lists.c ctable_batch.c
5878	boyer_moore.c jsw_rand.c jsw_rand.h jsw_slib.c jsw_slib.h
5879	speedtables.h speedtableHash.c ctable_io.c ctable_qsort.c
5880	ethers.c
5881    }
5882
5883    if {$withSharedTables} {
5884	lappend copyFiles shared.c shared.h
5885	lappend subdirs shared
5886    }
5887
5888    emit "// Importing .c and .h files to '$includeDir'\n//"
5889    foreach file $copyFiles {
5890	set fullName [file join $srcDir $file]
5891
5892	if {![file exists $fullName]} {
5893	    unset fullName
5894
5895	    foreach dir $subdirs {
5896		set fullName [file join $srcDir $dir $file]
5897
5898		if {![file exists $fullName]} {
5899		    unset fullName
5900		} else {
5901		    break
5902		}
5903	    }
5904	}
5905
5906	if [info exists fullName] {
5907            file copy -force $fullName $includeDir
5908	    emit "// Imported '$fullName'"
5909	} else {
5910	    return -code error "Can't find $file in $srcDir"
5911	}
5912    }
5913    emit "// Import complete\n"
5914}
5915
5916#
5917# get_error_info - to keep tracebacks from containing lots of internals
5918#  of ctable stuff, we scarf errorInfo into ctableErrorInfo if we get
5919#  an error interpreting a CExtension/CTable definition.  This allows
5920#  one to get the error info if debugging is required, etc.
5921#
5922proc get_error_info {} {
5923    variable ctableErrorInfo
5924
5925    return $ctableErrorInfo
5926}
5927
5928}
5929
5930#
5931# CExtension - define a C extension
5932#
5933proc CExtension {name version code} {
5934    uplevel 1 [list _speedtables $name $version $code]
5935}
5936
5937#
5938# speedtables - define a Speedtable package
5939#
5940proc speedtables {name version code} {
5941    if {![string is upper [string index $name 0]]} {
5942	error "Speed Tables package name must start with an uppercase letter"
5943    }
5944    foreach char [split $name ""] {
5945	if [string is digit $char] {
5946	    error "Speed Tables package name can not include any digits"
5947	}
5948    }
5949    uplevel 1 [list _speedtables $name $version $code]
5950}
5951
5952#
5953# _speedtables - Common code to define a package
5954#
5955proc _speedtables {name version code} {
5956    global tcl_platform errorInfo errorCode
5957
5958    # clear the error info placeholder
5959    set ctableErrorInfo ""
5960
5961    if {![info exists ::ctable::buildPath]} {
5962        CTableBuildPath stobj
5963    }
5964
5965    set path [file normalize $::ctable::buildPath]
5966    file mkdir $path
5967
5968    if {[::ctable::extension_already_built $name $version $code]} {
5969        #puts stdout "extension $name $version unchanged"
5970	return
5971    }
5972
5973    set ::ctable::sourceCode $code
5974    set ::ctable::sourceFile [::ctable::target_name $name $version]
5975    set ::ctable::extension $name
5976    set ::ctable::extensionVersion $version
5977    set ::ctable::tables ""
5978
5979    if {[catch {namespace eval ::ctable $code} result] == 1} {
5980        set ::ctable::ctableErrorInfo $errorInfo
5981
5982	if $::ctable::errorDebug {
5983	    return -code error -errorcode $errorCode -errorinfo $errorInfo
5984	} else {
5985            return -code error -errorcode $errorCode "$result\n(run ::ctable::get_error_info to see ctable's internal errorInfo)"
5986	}
5987    }
5988
5989    ::ctable::EndExtension
5990
5991    ::ctable::save_extension_code $name $version $code
5992}
5993
5994##
5995## start_ctable_codegen - can't be run until the ctable is loaded
5996##
5997proc start_codegen {} {
5998    if [info exists ::ctable::ofp] {
5999	return
6000    }
6001
6002    set ::ctable::ofp [open $::ctable::sourceFile w]
6003
6004    ::ctable::gen_preamble
6005
6006    ::ctable::gen_ctable_type_stuff
6007
6008    # This runs here so we have the log of where we got files from in
6009    # the right place
6010    ::ctable::install_ch_files [::ctable::target_path include]
6011
6012    ::ctable::emit "#include \"ctable_io.c\""
6013
6014    ::ctable::emit "#include \"ctable_search.c\""
6015
6016    ::ctable::emit "static CONST char *sourceCode = \"[::ctable::cquote "CExtension $::ctable::extension $::ctable::extensionVersion { $::ctable::sourceCode }"]\";"
6017    ::ctable::emit ""
6018
6019    ::ctable::emit "static CONST char *ctablePackageVersion = \"$::ctable::ctablePackageVersion\";"
6020
6021    if [info exists ::ctable::rawCode] {
6022	::ctable::emit "// BEGIN imported C Code"
6023        foreach block $::ctable::rawCode {
6024	    ::ctable::emit "$block\n"
6025	}
6026	::ctable::emit "// END imported C Code"
6027    }
6028}
6029
6030#
6031# ccode - pass C code (such as helper functions) through
6032#
6033proc ccode {block} {
6034    lappend ::ctable::rawCode $block
6035}
6036
6037#
6038# CTable - define a C meta table
6039#
6040proc CTable {name data} {
6041    uplevel 1 [list table $name $data]
6042}
6043
6044#
6045# table - define a Speed Tables table
6046#
6047proc table {name data} {
6048    ::ctable::new_table $name
6049    lappend ::ctable::tables $name
6050
6051    namespace eval ::ctable $data
6052
6053    ::ctable::sanity_check
6054
6055    start_codegen
6056
6057    # Create a key field if there isn't already one
6058    ::ctable::key _key
6059
6060    if {$::ctable::withDirty} {
6061        # Create a 'dirty' field
6062        ::ctable::boolean _dirty notnull 1 default 0
6063    }
6064
6065    ::ctable::gen_struct
6066
6067    ::ctable::gen_field_names
6068
6069    ::ctable::gen_filters
6070
6071    ::ctable::gen_setup_routine $name
6072
6073    ::ctable::gen_defaults_subr $name
6074
6075    ::ctable::gen_delete_subr ${name}_delete $name
6076
6077    ::ctable::gen_obj_is_null_subr
6078
6079    ::ctable::gen_list
6080
6081    ::ctable::gen_keyvalue_list
6082
6083    ::ctable::gen_nonnull_keyvalue_list
6084
6085    ::ctable::gen_code
6086
6087    ::ctable::put_metatable_source $name
6088
6089}
6090
6091#
6092# CTableBuildPath - set the path for where we're building CTable stuff
6093#
6094proc CTableBuildPath {{dir ""}} {
6095    if {$dir == ""} {
6096	if {![info exists ::ctable::buildPath]} {
6097	    CTableBuildPath stobj
6098	}
6099	return $::ctable::buildPath
6100    }
6101
6102    set ::ctable::buildPath $dir
6103
6104    if {[lsearch -exact $::auto_path $dir] < 0} {
6105        lappend ::auto_path $dir
6106    }
6107}
6108
6109package provide ctable $::ctable::ctablePackageVersion
6110package provide speedtable $::ctable::ctablePackageVersion
6111
6112# vim: set ts=8 sw=4 sts=4 noet :
6113