/* Last edited: Mar 11 10:22 1997 (birney) */ %{ #include "wisebase.h" #define AlnColumnLISTLENGTH 32 #define AlnBlockLISTLENGTH 32 #define AlnBlockListLISTLENGTH 32 #define AlnUnitSCORENUMBER 8 %} api object AlnBlock des free_AlnBlock func dump_ascii_AlnBlock endobject object AlnColumn des free_AlnColumn func at_end_AlnColumn endobject object AlnUnit des free_AlnUnit func bio_start_AlnUnit func bio_end_AlnUnit endobject object AlnSequence des free_AlnSequence endobject endapi friend AlnColumn friend AlnUnit friend AlnSequence struct AlnUnit int start !def=(-1) // start position in the sequence int end !def=(-1) // end position in the sequence int label // not used char * text_label !def="NULL" !link // text label of this position AlnUnit * next; !link // next AlnUnit in this sequence int score[AlnUnitSCORENUMBER]; // a series of scores for this position. boolean in_column !def="TRUE" // not used AlnSequence * seq !link %info This is the basic unit of the label alignment. It describes a single mark-up over one sequence: being a start, an end and a text_label. %% struct AlnColumn AlnUnit ** alu !list // list of the AlnUnits in this column AlnColumn * next; !link // the next AlnColumn in this block %info This is a coupling of AlnUnits from different sequences. Each AlnUnit is meant to represent *the equivalent* piece of biological information in some sense (ie, they are alignmed), even though quite possibly they are very different types of information %% struct AlnSequence AlnUnit * start; // the first AlnUnit of this sequence int data_type; // not used void * data; !link // not used - don't use! int bio_start !def="1" // start of this sequence in its 'bio' coordinates int bio_end !def="-1" // end of this sequence in its 'bio' coordinates %info Each Sequence in an AlnBlock is represented by one of these, and in many ways this is an orthogonal way of looking at the alignment than the AlnColumns. If you look at the alignment from one AlnSequence you will just see the individual labels on this sequence %% struct AlnBlock AlnColumn * start; // the first AlnColumn in the alignment AlnSequence ** seq !list // a list of AlnSequences in the alignment int length; // not used int score; // not used %info AlnBlock is the main representation of alignments from Dynamite. Each AlnBlock represents any number of 'sequences', of any type, which share things in common. The alignment is represented by a series of /AlnColumns (linked list) in which each AlnColumn has a series of AlnUnits, each Unit being a start/end/text_label triple. Alternatively, one can see each sequence in isolation, and not ask what it is aligned to, but rather what labels it has on it. %% struct AlnBlockList AlnBlock ** alb !list %info Sometimes - you need a list of AlnBlocks! %% %{ #include "aln.h" %func This function assummes that the first AlnSequence in the current and the adding AlnBlock are the same, and that you want to add the second AlnSequence of the second alnblock to the first, anchored on the first sequence. In other words, this builds up an anchored alignment, on the first sequence This AlnBlock, like many others, consumes the sequence in the second alnblock, unsetting things so that it is valid. This makes for some pretty hairy coding. %% boolean add_to_anchored_AlnBlock(AlnBlock * growing,AlnBlock * add) { AlnColumn * anc; AlnColumn * new; AlnColumn * insert; AlnUnit * temp; AlnUnit ** prev; int i; assert(growing); assert(add); anc = growing->start; new = add->start; add_AlnBlock(growing,add->seq[1]); /* this should prevent freeing the sequence we are about to use */ add->len = 1; prev = &(add->seq[1]->start); /* wait until new is inside anc */ for(;new->alu[0]->start != anc->alu[0]->start;anc = anc->next) { /*fprintf(stderr,"Moving to %d-%d\n",anc->alu[0]->start,anc->alu[0]->end);*/ if( anc->alu[0]->start > new->alu[0]->start ) { warn("Somehow new has overrun anc, new %d-%d and anc %d-%d",new->alu[0]->start,new->alu[0]->end,anc->alu[0]->start,anc->alu[0]->end); return FALSE; } /* add a blank unit onto the anc */ temp = AlnUnit_alloc(); temp->score[0] = 0; temp->start = temp->end = 0; temp->text_label = "INSERT"; add_AlnColumn(anc,temp); *prev = temp; prev = &(temp->next); } /* ok - now we are at the start. We loop over for each 'matched' state */ *prev = new->alu[1]; for(;new->next != NULL;) { /* if the anchor state is actually an insert here, then * this is an insert not in the added sequence (if it were, * we would have added it last time through the loop) * * So - skip insert points */ if( anc->alu[0]->start == anc->alu[0]->end ) { temp = AlnUnit_alloc(); temp->score[0] = 0; temp->start = temp->end = new->alu[1]->end; temp->text_label = "INSERT"; *prev = temp; prev = &(temp->next); temp->next = new->alu[1]; add_AlnColumn(anc,temp); anc = anc->next; continue; } /* sanity check that we are at the same position on the two sequences */ if( anc->alu[0]->start != new->alu[0]->start || anc->alu[0]->end != new->alu[0]->end ) { warn("SANITY CHECK failed! new and anchor out of sync! anchor at %d-%d and new %d-%d",anc->alu[0]->start,anc->alu[0]->end,new->alu[0]->start,new->alu[0]->end); return FALSE; } add_AlnColumn(anc,new->alu[1]); /* see whether the next state is actually an insert */ prev = &(new->alu[1]->next); for(new = new->next;new != NULL && new->alu[0]->start == new->alu[0]->end;new = new->next) { /*fprintf(stderr,"Adding in insert states\n");*/ /* bail out if we have reached the end of anc */ if( anc->next == NULL ) { warn("HELP - run out of add positions!"); return FALSE; } /* does anc already have an insert state here? */ if( anc->next->alu[0]->start == anc->next->alu[0]->end ) { /* yes! - just add to the end */ anc = anc->next; add_AlnColumn(anc,new->alu[1]); } else { /* we have to drop in a new alncolumn */ insert = AlnColumn_alloc_std(); insert->next = anc->next; anc->next = insert; /*fprintf(stderr,"There are %d states to add to\n",anc->len);*/ for(i=0;ilen-1;i++) { temp = AlnUnit_alloc(); temp->score[0] = 0; temp->start = temp->end = anc->alu[i]->end; temp->text_label = "INSERT"; temp->next = anc->alu[i]->next; anc->alu[i]->next = temp; add_AlnColumn(insert,temp); } anc = anc->next; /* could be anc = insert */ /* add the new seq */ add_AlnColumn(insert,new->alu[1]); prev = &(new->alu[1]->next); } /* end of else add in an insert */ } /* end of looping over possible new inserts */ /*fprintf(stderr,"moving on one in anchor (new already moved!) %d-%d\n",anc->alu[0]->start,anc->alu[0]->end);*/ prev = &(new->alu[1]->next); anc = anc->next; } /* end of new alignment */ /*fprintf(stderr,"Left alignment at %d-%d\n",anc->alu[0]->start,anc->alu[0]->end);*/ /* now fill into the end of the anchored alignment */ for(;anc != NULL;anc = anc->next ) { /* add a blank unit onto the anc */ temp = AlnUnit_alloc(); temp->score[0] = 0; temp->start = temp->end = 0; temp->text_label = "INSERT"; add_AlnColumn(anc,temp); *prev = temp; prev = &(temp->next); } *prev = NULL; return TRUE; } %func This function makes a new AlnBlock of length len and depth one, with each Block having the given label and start = 0 end = start +1; It starts with a alu going from -1 to 0 %% AlnBlock * single_unit_AlnBlock(int len,char * label) { AlnBlock * out; AlnSequence * als; AlnColumn * alc; AlnUnit * alu; AlnColumn ** prev; AlnUnit ** aprev; int i; out = AlnBlock_alloc_std(); als = AlnSequence_alloc(); add_AlnBlock(out,als); prev = &(out->start); aprev = &(als->start); for(i=-1;istart = i; alu->end = i+1; alu->text_label = label; add_AlnColumn(alc,alu); *aprev = alu; prev = &(alc->next); aprev = &(alu->next); } *prev = NULL; *aprev = NULL; return out; } %func This function splits an AlnBlock into separate alignments (stored in the resulting AlnBlockList). The alb is split wherever there is a column that returns true to the is_spacer_column function, discarding these columns This function completely destroys the AlnBlock object that is passed in, but to make sure that API functions dont get confused, the alb that is passed in is simply stripped of its AlnColumn information (so it is still a valid alb, if empty). But - beware - all the alncolumns might or might not be there, so dont pass in albs and hold on to anything inside them! %% AlnBlockList * split_AlnBlock(AlnBlock * alb,boolean (*is_spacer_column)(const AlnColumn * alc)) { AlnBlockList * out; AlnBlock * temp; AlnColumn * alc; AlnColumn * prev; AlnSequence * als; int i; if( is_spacer_column == NULL ) { warn("You must pass in function for the spacer columns to split_AlnBlock"); } if( alb == NULL ) { warn("Unable to split a NULL alnblock!"); } out = AlnBlockList_alloc_std(); assert(out); for(alc=alb->start;alc != NULL;) { for(;alc != NULL && (*is_spacer_column)(alc) == TRUE;alc = alc->next) { if( alc == NULL) break; /* we need to detach this alncolumn from the entire alignment * as it will stop being valid. */ /* this is cheating, but it is still relevant to catch problems */ if( alc->dynamite_hard_link > 1 ) { warn("Detaching a alncolumn in split_AlnBlock that is being held somewhere else. This is very dangerous"); /* ok - give it a fighting chance of not crashing the client */ for(i=0;ilen;i++) { /* alnunits don't point to next */ alc->alu[i]->next = NULL; } alc->next = NULL; } /* now - free it. It *may* be being held onto by * someone else. Pity him... */ free_AlnColumn(alc); } /* alc is now the first alncolumn of a new alb */ if( alc == NULL) { if( out->len == 0 ) { warn("Unable to find any alignments in AlnBlock splitting!"); } break; /* will return */ } temp = AlnBlock_alloc_std(); add_AlnBlockList(out,temp); /* we need to make an exact copy of the AlnSequences */ for(i=0;ilen;i++) { assert(alc->alu[i]); als = AlnSequence_alloc(); add_AlnBlock(temp,als); als->data_type = alb->seq[i]->data_type; als->data = alb->seq[i]->data; als->bio_start = alb->seq[i]->bio_start; als->bio_end = alb->seq[i]->bio_end; /* unit is the unit in this column */ als->start = alc->alu[i]; } /* attach alc as the first column to temp */ temp->start = alc; /* keep walking along the list now until we hit * a random column. All the columns and units are * already correctly laid out in memory */ for(prev = alc;alc != NULL && (*is_spacer_column)(alc) == FALSE;) { prev = alc; alc = alc->next; } /* prev is the last column. detach it */ prev->next = NULL; for(i=0;ilen;i++) prev->alu[i]->next = NULL; /* return to eating empty columns */ } /* alb is now completely defunct. We need to make it * impotent with regard to the old stuff it used to have * * this basically makes alb complete empty */ alb->len =0; alb->start = NULL; return out; } %func gets the score out for a particular alb sequence line %% int score_line_from_AlnBlock(AlnBlock * alb,int seqno) { AlnUnit * alu; int score = 0; assert(alb); if( seqno > alb->len ) { warn("Asking for a score number of a sequence more than alb. giving back 0"); return 0; } for(alu = alb->seq[seqno]->start;alu != NULL;alu = alu->next) { score += alu->score[0]; } return score; } %func This tells you whether the AlnColumn is at the end without passing NULL's around %simple at_end %arg alc r AlnColumn %% boolean at_end_AlnColumn(AlnColumn * alc) { if( alc->next == NULL ) return 1; return 0; } %func Tells the bio-coordinate of the start point of this alnunit %simple bio_start %% int bio_start_AlnUnit(AlnUnit * alu) { if( alu->seq == NULL ) { warn("A mis-connected AlnUnit (no sequence!) - Returning a start as if start == 1"); return alu->start + 1 + 1; } return alu->start + alu->seq->bio_start + 1; } %func Tells the bio-coordinate of the end point of this alnunit %simple bio_end %% int bio_end_AlnUnit(AlnUnit * alu) { if( alu->seq == NULL ) { warn("A mis-connected AlnUnit (no sequence!) - Returning a start as if start == 1"); return alu->end + 1; } return alu->end + alu->seq->bio_start; } %func This function will 'swallow' any number of AlnColumns as long as the comparison function of the labels match (the basic comp function would be something like strcmp(a,b) == 0 ? TRUE : FALSE) The columns are 'swallowed' into master and come from eaten. (these columns could be in the same linked list, though it only makes sense if the master is before the eaten). It returns the first column that it could not swallow. you use this to collapse regions of the label alignment. %arg master column which will eat other columns eaten column which will be consumed comp_func comparison function for label set %% AlnColumn * swallow_AlnColumn_multiple(AlnColumn * master,AlnColumn * eaten,boolean (*comp_func)(char *,char *)) { register int i; for(i=0;eaten != NULL; eaten = eaten->next) { if( swallow_AlnColumn(master,eaten,comp_func) == FALSE ) break; } return eaten; } %func Basicaly the same as /swallow_AlnColumn_mulitple but there is a maximum number of columns it will swallow %arg master column which will eat other columns eaten column which will be consumed comp_func comparison function for label set num max number of columns to eat return number of columns eaten %% int swallow_AlnColumn_number(AlnColumn * master,AlnColumn * eaten,int num,boolean (*comp_func)(char *,char *)) { register int i; for(i=0;i < num && eaten != NULL; eaten = eaten->next) { if( swallow_AlnColumn(master,eaten,comp_func) == FALSE ) break; } return i; } %func This is the function that actually does the 'swallowing'. It will try to swallow eaten into master. If comp_func does not give us an ok (actually using /can_swallow_AlnColumn it returns FALSE. Otherwise it moves on the end of AlnColumn in master to eaten and adds the score of eaten to master. %arg master column which will eat eaten column which will dissappear into master if eatable comp_func comparison function for labels %% boolean swallow_AlnColumn(AlnColumn * master,AlnColumn * eaten,boolean (*comp_func)(char *,char *)) { register int i; if( can_swallow_AlnColumn(master,eaten,comp_func) == FALSE ) { return FALSE; } for(i=0;ilen;i++) { master->alu[i]->end = eaten->alu[i]->end; /*** ok, for the moment, only add the 1st score, but eventually ***/ /*** we should add all of them... ***/ master->alu[i]->score[0] += eaten->alu[i]->score[0]; } return TRUE; } %func checks to see if two columns are mergable from comp_func. First uses /identical_labels_in_AlnColumn to see if labels can be merged Then checks that starts in master are greater than starts in eaten %type internal %% boolean can_swallow_AlnColumn(AlnColumn * master,AlnColumn * eaten,boolean (*comp_func)(char *,char *)) { register int i; if( identical_labels_in_AlnColumn(master,eaten,comp_func) == FALSE ) return FALSE; for(i=0;ilen;i++) if( master->alu[i]->start >= eaten->alu[i]->start) { warn("In trying to compare to AlnColumns, have some 'eatable' starts greater than master starts %d %d in row %d",master->alu[i]->start,eaten->alu[i]->start,i); return FALSE; } return TRUE; } %func checks to see if two AlnColumns has mergable labels by comp_func. calls /identical_labels_in_AlnUnits for the actual comparison. %type internal %% boolean identical_labels_in_AlnColumn(AlnColumn * one,AlnColumn * two,boolean (*comp_func)(char *,char *)) { register int i; if( one->len != two->len ) { warn("Attempting to see if two AlnColumns with *different numbers of units* %d,%d are identical...serious problem",one->len,two->len); return FALSE; } for(i=0;ilen;i++) { if( identical_labels_in_AlnUnits(one->alu[i],two->alu[i],comp_func) == FALSE ) return FALSE; } return TRUE; } %func actually calls the comp_func for the label compairson %type internal %% boolean identical_labels_in_AlnUnits(AlnUnit * one,AlnUnit * two,boolean (*comp_func)(char *,char *)) { if( (*comp_func)(one->text_label,two->text_label) == TRUE ) return TRUE; else return FALSE; } %func Linked list manipulation function Puts insert between start and end, and free's from start->next onwards. *Beware* if start is linked to end before calling this function thsi wil free end and everything chained to it. Think before you call this! %% void replace_and_free_AlnColumn_with_one(AlnColumn * start,AlnColumn * end,AlnColumn * insert) { replace_AlnColumn_with_one(start,end,insert); free_AlnColumn(start->next); } %func Linked list manipulation function places insert between start and end. If start/end are not continuous then it will loop out the start/end region %% void replace_AlnColumn_with_one(AlnColumn * start,AlnColumn * end,AlnColumn * insert) { start->next = insert; insert->next = end->next; end->next = NULL; } %func Linked list manipulation function places insert just after start: links insert up to what start was linked to %% void insert_AlnColumn(AlnColumn * start,AlnColumn * insert) { insert->next = start->next; start->next = insert; } %func Linked list movement function A nasty function to reverse up a singly linked list by going to the start and coming back until you are in the current position. yuk. %% AlnColumn * go_back_n_AlnColumn(AlnBlock * alb,AlnColumn * start,int n) { /*** really quite hacky ****/ AlnColumn * cursor; AlnColumn * back; register int i; for(i=0,cursor = alb->start;i < n && cursor != NULL && cursor != start;i++,cursor = cursor->next) ; if( i < n ) { return NULL; /** should I post an error? **/ } for(back = alb->start;cursor != NULL && cursor != start;cursor = cursor->next, back = back->next) ; if( cursor == NULL ) { warn("could not find you AlnColumn in AlnBlock at all... so could not get xxx positions back"); return NULL; } return back; } /*******************************/ /* show functions for aln block*/ /* */ /* these are really basic and */ /* should use alndisplay for */ /* ascii display */ /*******************************/ %func Dumps the alignment in rereadable ascii form. Not really for human consumption %arg alb AlnBlock to dump ofp File stream to dump to %% void dump_ascii_AlnBlock(AlnBlock * alb,FILE * ofp) { int i; AlnColumn * alc; for(alc=alb->start;alc != NULL;alc = alc->next) { fprintf(ofp,"[%d:%d \"%s\" %d]",alc->alu[0]->start,alc->alu[0]->end,alc->alu[0]->text_label,alc->alu[0]->score[0]); for(i=1;ilen;i++) { fprintf(ofp,",[%d:%d \"%s\" %d]",alc->alu[i]->start,alc->alu[i]->end,alc->alu[i]->text_label,alc->alu[i]->score[0]); } fprintf(ofp,"\n"); } fprintf(ofp,"\\"); } %func Shows a list of AlnBlocks with an arbitary mapping of the score to some other system %arg alb AlnBlock to dump ofp File stream to dump to %% void mapped_ascii_AlnBlockList(AlnBlockList *al,double (*score_to_double)(int),FILE * ofp) { int i; assert(al); assert(ofp); assert(score_to_double); for(i=0;ilen;i++) { fprintf(ofp,"\n\nAlignment %d\n",i+1); mapped_ascii_AlnBlock(al->alb[i],score_to_double,0,ofp); } } %func Shows AlnBlock with an arbitary mapping of the score to some other system. %arg alb AlnBlock to dump ofp File stream to dump to %% void mapped_ascii_AlnBlock(AlnBlock * alb,double (*score_to_double)(int),int do_cumlative,FILE * ofp) { int i; AlnColumn * alc; int cuml = 0; if( alb == NULL || score_to_double == NULL || ofp == NULL ) { warn("Passing in null objects to mapped_ascii_AlnBlock - unable to show!"); return; } for(alc=alb->start;alc != NULL;alc = alc->next) { cuml = cuml + alc->alu[0]->score[0]; fprintf(ofp,"%3.2f ",(double)(*score_to_double)(alc->alu[0]->score[0])); fprintf(ofp,"[%d:%d \"%s\" %d]",alc->alu[0]->start,alc->alu[0]->end,alc->alu[0]->text_label,alc->alu[0]->score[0]); for(i=1;ilen;i++) { fprintf(ofp,",[%d:%d \"%s\" %d]",alc->alu[i]->start,alc->alu[i]->end,alc->alu[i]->text_label,alc->alu[i]->score[0]); } if( do_cumlative == 1 ) { fprintf(ofp," {%3.2f} ",(double)(*score_to_double)(cuml),cuml); } fprintf(ofp,"\n"); } } %func Reads an ascii dumped alignment %arg ifp File stream to read from %% AlnBlock * read_ascii_dump_AlnBlock(FILE * ifp) { char buffer[MAXLINE]; AlnBlock * out; AlnColumn ** attach; AlnColumn * new; out = AlnBlock_alloc_std(); attach = &out->start; while( fgets(buffer,MAXLINE,ifp) != NULL ) { if( strstartcmp(buffer,"//") == 0 ) break; new = read_dumped_ascii_AlnColumn_line(buffer); if( new == NULL ){ warn("Unable to read entire AlnBlock. Returning no alignment"); free_AlnBlock(out); return NULL; } *attach = new; attach = &new->next; } return out; } %func Reads one line of an ascii dumped alignment %type internal %arg line line to be read %% AlnColumn * read_dumped_ascii_AlnColumn_line(char * line) { char ** base, **bstr; AlnUnit * alu; AlnColumn * out; int start; int end; int score; char buffer[128]; /* implies max label 128 characters. Worrying? */ out = AlnColumn_alloc_len(2); /** most times we are reading in pairwise alignments **/ /** split on comma, then sscanf each one **/ base = bstr = breakstring_protect(line,",","\""); for(;*bstr != NULL;bstr++) { sscanf(*bstr,"[%d,%d \"%s\" %d",&start,&end,buffer,&score); alu = AlnUnit_alloc(); alu->start = start; alu->end = end; alu->score[0] = score; alu->text_label = stringalloc(buffer); add_AlnColumn(out,alu); } return out; } %func Shows the AlnBlock in vaguely human readable form %simple show %arg alb AlnBlock to show ofp output %% void show_flat_AlnBlock(AlnBlock * alb,FILE * ofp) { AlnColumn * alc; register int i; for(i=0,alc = alb->start;alc != NULL;alc = alc->next,i++) { fprintf(ofp,"Column %d:\n",i); show_flat_AlnColumn(alc,ofp); } } %func sub for show_flat_AlnBlock %type internal %% void show_flat_AlnColumn(AlnColumn * alc,FILE * ofp) { register int i; for(i=0;ilen;i++) { fprintf(ofp,"Unit %2d- ",i); show_flat_AlnUnit(alc->alu[i],ofp); } fprintf(ofp,"\n"); } %func sub for show_flat_AlnUnit %type internal %% void show_flat_AlnUnit(AlnUnit * alu,FILE * ofp) { fprintf(ofp,"[%4d-%4d] [%s]\n",alu->start,alu->end,alu->text_label == NULL ? "No Label" : alu->text_label); } %func Not used currently. To read in the flat output format %type internal %% AlnUnit * read_flat_AlnUnit_line(char * line,int * ret_pos) { AlnUnit * out; char buffer[MAXLINE]; int start; int end; int pos; int num; sscanf(line,"Unit %d- Start: [%d], End: [%d] Label: number [%d] text [%s]",&pos,&start,&end,&num,buffer); out = AlnUnit_alloc(); if( out == NULL ) return NULL; out->start = start; out->end = end; out->label = num; out->text_label = stringalloc(buffer); if( ret_pos != NULL ) *ret_pos = pos; return out; } /***********************************/ /* movement functions around Aln's */ /***********************************/ %func Not sure if this is used! %% AlnColumn * get_second_end_AlnColumn(AlnBlock * alb) { AlnColumn * end = NULL; AlnColumn * prev = NULL; for(end = alb->start;end->next != NULL;prev = end,end = end->next) ; return prev; } %func To get to the last AlnColumn. If this was a doubly linked list, life would be much easier %% AlnColumn * get_end_AlnColumn(AlnBlock * alb) { AlnColumn * end; for(end = alb->start;end->next != NULL;end = end->next) ; return end; } %func Links up all AlnUnits to their parent sequences %% boolean link_AlnUnits_AlnBlock(AlnBlock * alb) { AlnSequence * aseq; AlnUnit * au; int i; for(i=0;ilen;i++) { aseq = alb->seq[i]; for(au = aseq->start;au != NULL;au++) { au->seq = aseq; } } return TRUE; } /************************************/ /* constructors/deconstructors to */ /* deal with linked list aspect of */ /* data */ /************************************/ %func Function as a constructor for the special case of a pairwise alignment. Makes an AlnColumn and puts in two AlnUnits all ready to be linked in. %% AlnColumn * new_pairwise_AlnColumn(void) { AlnColumn * out; out = AlnColumn_alloc_len(2); add_AlnColumn(out,AlnUnit_alloc()); add_AlnColumn(out,AlnUnit_alloc()); return out; } %func Specilased deconstructor needed because of linked list nature of the data structure %% !deconstructor AlnColumn * free_AlnColumn(AlnColumn * obj) { if( obj == NULL ) { warn("passed a NULL object into free_AlnColumn!"); return NULL; } if( obj->dynamite_hard_link > 1 ) { obj->dynamite_hard_link--; return NULL; } if( obj->next != NULL ) free_AlnColumn(obj->next); if( obj->alu != NULL ) ckfree(obj->alu); /*** units free'd from linked list ***/ ckfree(obj); return NULL; } %func Specilased deconstructor needed because of linked list nature of the data structure %% !deconstructor AlnUnit * free_AlnUnit(AlnUnit * obj) { if( obj == NULL ) return NULL; if( obj->dynamite_hard_link > 1 ) { obj->dynamite_hard_link--; return NULL; } if( obj->next != NULL ) free_AlnUnit(obj->next); ckfree(obj); return NULL; } %}