home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / DevTools / ClassEditor.0.4 / Source / CEClassEditor+FileHandling.m < prev    next >
Encoding:
Text File  |  1995-06-07  |  17.0 KB  |  694 lines

  1. /* CEClassEditor+FileHandling.m                 
  2.  *
  3.  * This object controls the data of a beaker (molecules, cameras, groups etc.)
  4.  * It is the main document of BeakerBoy and controls everything from loading to
  5.  * setting up the browser which does most of the other work.
  6.  *
  7.  * For interface-info see the header file. The comments in this file mostly
  8.  * cover only the real implementation details.
  9.  *
  10.  * Written by:         Thomas Engel
  11.  * Created:            23.10.1993 (Copyleft)
  12.  * Last modified:     12.11.1994
  13.  */
  14.  
  15. #import <CEClassEditor+FileHandling.h>
  16. #import <CEMethod.h>
  17. #import <Text_MiscExtensions.h>
  18. #import <MiscSources.subproj/MiscStringArray_List.h>
  19. #import <MiscSources.subproj/MiscEmacsText.h>
  20.  
  21. #import <misckit/MiscString.h>
  22. #import <misckit/MiscStringArray.h>
  23. #import <misckit/MiscSortedList.h>
  24.  
  25. @implementation CEClassEditor ( FileHandling )
  26.  
  27. - _try:path withAlternatives:anArray forText:aText andGetStyle:(int *)aStyle andSetPath:realPath
  28. {
  29.     id    ourPath;
  30.     id    theFilename;
  31.     id    aSearchPath;
  32.     int    trys;
  33.  
  34.     trys = 0;
  35.     *aStyle = CE_FILETYPE_NONE;
  36.     ourPath = [path copy];
  37.     theFilename = [ourPath fileName];
  38.  
  39.     while( *aStyle == CE_FILETYPE_NONE &&
  40.            trys <= [anArray count] )
  41.     {
  42.         [self _readFile:ourPath forText:aText 
  43.                 andGetStyle:(int *)aStyle andSetPath:realPath];
  44.  
  45.         aSearchPath = [anArray objectAt:trys];
  46.         [ourPath free];
  47.         ourPath = nil;
  48.         if( aSearchPath != nil )
  49.         {
  50.             // If it is a dot file then we should try the original path
  51.             // again...actualle we shoul just skip it...I know..but this 
  52.             // works too.
  53.             
  54.             if( [aSearchPath cmp:"."] == 0 )
  55.                     ourPath = [path pathName];            
  56.             else if( [aSearchPath spotOfStr:"./"] == 0 )
  57.             {
  58.                     ourPath = [path pathName];
  59.                     [ourPath addChar:[MiscString pathSeparator]];
  60.                     [ourPath concatenate:aSearchPath];
  61.                     
  62.                     // NOTE: We have to remove the local Dot reference
  63.                     // because somehow it does not work !!
  64.                     
  65.                     [ourPath replaceEveryOccurrenceOf:"/./" with:"/"];
  66.             }
  67.             else    ourPath = [aSearchPath copy];
  68.             
  69.             [ourPath addChar:[MiscString pathSeparator]];
  70.             [ourPath concatenate:theFilename];
  71.         }        
  72.         trys++;
  73.     }
  74.     
  75.     if( ourPath != nil ) [ourPath free];
  76.     [theFilename free];
  77.     return self;
  78. }
  79.  
  80. - _readFile:path forText:aText andGetStyle:(int *)aStyle andSetPath:realPath
  81. {
  82.     // We will try to load the requested file...and try to set the right
  83.     // text type info.
  84.     
  85.     NXStream     * aStream;
  86.  
  87.     // By default we won't allow anybody to edit a none existing text.
  88.     // It won't get stored anyway because we do not have a path for
  89.     // it.
  90.     
  91.     [aText setEditable:NO];
  92.  
  93.     [path replaceTildeWithHome];
  94.     if( [path doesExistInFileSystem] == NO )
  95.         return self;
  96.     
  97.     // Looks like we have a file...so lets get it into our text object.
  98.     // We have to ensure that all the setting are in the proper fashion.
  99.     // Setting the tabs for ASCII Files in one of them
  100.         
  101.     if( [self _isFileRTF:path] )
  102.     {
  103.         *aStyle = CE_FILETYPE_RTF;
  104.         [aText setMonoFont:NO];
  105.     }
  106.     else    
  107.     {
  108.         *aStyle = CE_FILETYPE_ASCII;
  109.         [aText setMonoFont:YES];
  110.     }
  111.     
  112.     [aText setEditable:YES];
  113.      
  114.     aStream = NXMapFile( [path stringValue], NX_READONLY );
  115.     if( *aStyle == CE_FILETYPE_ASCII )
  116.             [aText readText:aStream];
  117.     else     [aText readRichText:aStream];
  118.     NXCloseMemory( aStream, NX_FREEBUFFER );
  119.     
  120.     // Now lets remember where we really did load it from.
  121.     
  122.     [realPath takeStringValueFrom:path];
  123.         
  124.     return self; 
  125. }
  126.  
  127. - (BOOL)_isFileRTF:path
  128. {
  129.     // Tells us if the requested file is a RTF text.
  130.     // Lets get the first characters and test for "{\rtf0"
  131.     // If it is there...we should load the same text as RTF again.
  132.     // Well quite stupid code..
  133.  
  134.     FILE *     aStream;
  135.     char    magic[10];
  136.     id        aString;
  137.     BOOL    answer;
  138.     
  139.     magic[6] = 0;
  140.     answer = NO;
  141.     
  142.     if( [path doesExistInFileSystem] )
  143.     {
  144.         aStream = fopen( [path stringValue], "r" );
  145.         fgets( magic, 7, aStream );
  146.         fclose( aStream );
  147.         aString = [MiscString newWithString:magic];
  148.     
  149.         if( [aString cmp:"{\\rtf0"] == 0 ) 
  150.             answer = YES;
  151.         
  152.         [aString free];
  153.     }    
  154.     return answer;
  155. }
  156.  
  157. - (const char *)filename
  158. {
  159.     return [filename stringValue];
  160. }
  161.  
  162. /*
  163.  * Here come the methods we will implement to satisfy our window.
  164.  * They are useful to change the inspector and handle oher tasks.
  165.  */
  166.  
  167. - _parseMethodFile
  168. {
  169.     id    newLine;
  170.     id    newMethod;
  171.     int    lines;
  172.     int    i;
  173.  
  174.     lines = [headerFile lineFromPosition:[headerFile textLength]];
  175.  
  176.     for( i=1; i<lines; i++ )
  177.     {
  178.         newLine = [headerFile substringFromLine:i];
  179.         if ( [newLine charAt:0] == '-' ||
  180.              [newLine charAt:0] == '+' )
  181.         {
  182.             newMethod = [[CEMethod alloc] initFromText:newLine];
  183.             [methodList addObject:newMethod];
  184.         }
  185.         [newLine free];
  186.     }
  187.         
  188.     return self;
  189. }
  190.  
  191. - _writeText:aText withStyle:(int)aStyle to:path
  192. {
  193.     // If the file does no exist...we will create it.
  194.     // But it there is no real text for that file...we won't save anything !
  195.     // RTFD requires a differnt style of saving !
  196.     
  197.     FILE        * dummyStream;
  198.     NXStream     * aStream;
  199.     
  200.     if( aStyle == CE_FILETYPE_NONE ) return self;
  201.     
  202.     else if( aStyle == CE_FILETYPE_RTFD )
  203.         return self;
  204.     
  205.     else
  206.     {
  207.         // Well the streams won't truncate a file so we have to do it on
  208.         // our own.
  209.         // We should do a backup of existing files here if requested.
  210.         
  211.         dummyStream = fopen( [path stringValue], "w" );
  212.         if( dummyStream ) fclose( dummyStream );
  213.         
  214.         aStream = NXMapFile( [path stringValue], NX_WRITEONLY );
  215.         switch( aStyle )
  216.         {
  217.             case CE_FILETYPE_RTF:
  218.                 [aText writeRichText:aStream];
  219.                 break;
  220.             default:
  221.                 [aText writeText:aStream];
  222.         }    
  223.         NXSaveToFile( aStream, [path stringValue] );
  224.         NXCloseMemory( aStream, NX_FREEBUFFER );
  225.     }
  226.     return self;
  227. }
  228.  
  229. - (BOOL)_selectSourceForMethod:aMethod
  230. {
  231.     // This private method searches for the method inside the SourceFile
  232.     // and _selects_ the code-portion of this method.
  233.     // This really is only for finding the part. 
  234.     // Returns success and sets the internal range information.
  235.     
  236.     // Now the text[] part is more then just ugly..sorry.
  237.     
  238.     char    text[10000]; 
  239.     NXSelPt    from;
  240.     NXSelPt    to;
  241.     int     i;
  242.     int        first;
  243.     int        last;
  244.     BOOL    gotIt;
  245.  
  246.     // Find the definition of the method.
  247.  
  248.     _sourceOriginStart = 0;
  249.     _sourceOriginEnd = 0;    
  250.     [sourceFile setSel:0 :0];
  251.     gotIt = [sourceFile findText:[aMethod name] 
  252.                     ignoreCase:NO backwards:NO wrap:NO];
  253.  
  254.     if( ! gotIt ) return NO;
  255.  
  256.     // Now we assume that people stick to the rules and have the
  257.     // '{' and '}' as the first characters of each line.
  258.     
  259.     [sourceFile getSel:&from :&to];
  260.     [sourceFile getSubstring:text start:from.cp length:9990];
  261.     
  262.     // The first '{' is our start. A '0' terminates the string and the
  263.     // search !
  264.  
  265.     gotIt = NO;
  266.     first = -1;
  267.     for( i=0; i<9990; i++ )
  268.     {
  269.         if( text[i] == 0 ) 
  270.             break;
  271.         else if( text[i] == '{' )
  272.         {
  273.             gotIt = YES;
  274.             first = i;
  275.             break;
  276.         }
  277.     }
  278.     
  279.     // Now we need a new-line followed by a '}'. A '0' terminates the string
  280.     // and the search ! Same as before.
  281.     
  282.     gotIt = NO;
  283.     last = -1;
  284.     for( i=0; i<9990; i++ )
  285.     {
  286.         if( text[i] == 0 ) 
  287.             break;
  288.         else if( text[i] == '\n' &&
  289.                  text[i+1] == '}' )
  290.         {
  291.             gotIt = YES;
  292.             last = i+2;
  293.             break;
  294.         }
  295.     }
  296.     
  297.     // Now select the right area.
  298.     
  299.     if( gotIt )
  300.     {
  301.         _sourceOriginStart = from.cp+first;
  302.         _sourceOriginEnd = from.cp+last;
  303.         [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
  304.     }
  305.  
  306.     return gotIt;    
  307. }
  308.  
  309. - checkDocumentation:sender
  310. {
  311.     // Now this method tries to get the documentation up to date.
  312.     // Missing parts will be inserted...etc.pp.
  313.     
  314.     id    aString;
  315.     id    aMethod;
  316.     int    i;
  317.     id    aList;
  318.     
  319.     // Be care full. we have the remember all the Docu and Source settings
  320.     // becaus ehte method s we use will change them !!!
  321.     
  322.     
  323.     // If there is no docu we ask the app to provide the default template.
  324.     
  325.     if( docuStyle == CE_FILETYPE_NONE )
  326.     {
  327.         [[NXApp delegate] copyClassDocuTemplate];
  328.         [docuFile setMonoFont:NO];
  329.         [docuFile setEditable:YES];
  330.         [docuFile selectAll:self];
  331.         [docuFile pasteFrom:_tempPb];
  332.  
  333.         // Be sure that we now have the right text type and a valid 
  334.         // path to save the docu to.
  335.  
  336.         docuStyle = CE_FILETYPE_RTF;
  337.         _docuPath = [filename copy];
  338.         [_docuPath cat:".rtf"];
  339.  
  340.         // The main template does need some customization to be useful
  341.         // First the version
  342.  
  343.         [docuFile setSel:0 :0];
  344.         
  345.         if( [docuFile findText:"MyVersion" 
  346.                       ignoreCase:NO backwards:NO wrap:NO] )
  347.             [docuFile replaceSel:"0.1"];
  348.  
  349.         // Then the Date
  350.  
  351.         if( [docuFile findText:"1994" 
  352.                       ignoreCase:NO backwards:NO wrap:NO] )
  353.             [docuFile replaceSel:"1995"];
  354.  
  355.         // Now the Copyright part
  356.  
  357.         if( [docuFile findText:"MyCompany" 
  358.                       ignoreCase:NO backwards:NO wrap:NO] )
  359.             [docuFile replaceSel:"ClassEditor"];
  360.  
  361.         // Its time for the class name
  362.     
  363.         if( [docuFile findText:"MyClass" 
  364.                       ignoreCase:NO backwards:NO wrap:NO] )
  365.         {
  366.             aString = [filename fileName];
  367.             [docuFile replaceSel:[aString stringValue]];
  368.             [aString free];
  369.         }
  370.         
  371.         // Some header infos please
  372.         
  373.         if( [docuFile findText:"MyClass.h" 
  374.                       ignoreCase:NO backwards:NO wrap:NO] )
  375.         {
  376.             aString = [filename fileName];
  377.             [aString cat:".h"];
  378.             [docuFile replaceSel:[aString stringValue]];
  379.             [aString free];
  380.         }
  381.     }
  382.     
  383.     // Now we should have a documentation...but lets see if there is one
  384.     // for every method. If not we should try to find the right place
  385.     // to place a template.
  386.     
  387.     // We add the in a sorted order. 
  388.     // Because the _addDocu method does not take care of ordering...we
  389.     // have to sort things the other way around.
  390.     
  391.     aList = [MiscSortedList new];
  392.     [aList setSortOrder:Misc_DESCENDING];
  393.     [aList setSortEnabled:YES];
  394.  
  395.     for( i=0; i<[methodList count];i++ )
  396.         [aList addObject:[methodList objectAt:i]];
  397.     
  398.     for( i=0; i<[aList count];i++ )
  399.     {
  400.         aMethod = [aList objectAt:i];
  401.         if( [self _selectDocuForMethod:aMethod] == 0 )
  402.             [self _addDocuForMethod:aMethod];
  403.     }
  404.     [aList free];
  405.     
  406.     // That's it for today..
  407.  
  408.     return self;
  409. }
  410.  
  411. - (BOOL)_selectDocuForMethod:aMethod
  412. {
  413.     // This private method searches for the method inside the DocuFile
  414.     // and _selects_ the docu-portion of this method.
  415.     // This really is only for finding the part. 
  416.     // Returns the offset to the description text. An offset of '0' meens that
  417.     // no method descript was found.
  418.     // "See also:" is included..AND the blank lines below.
  419.     
  420.     // BUG: Now the text[] part is more then just ugly..sorry.
  421.     // This limits us to methods with descriptions shorter then approx 100
  422.     // lines. I hope that is enough.
  423.     // I really should take the time and witch to a NXStream....
  424.     
  425.     char    text[10000]; 
  426.     NXSelPt    from;
  427.     NXSelPt    to;
  428.     NXSelPt    tmpFrom;
  429.     NXSelPt    tmpTo;
  430.     int        first;
  431.     int        last;
  432.     int     i;
  433.     BOOL    gotIt;
  434.     int        blanks;
  435.     int        instancesStart = 0;
  436.     id        aString;
  437.  
  438.     // We have to find the right start-selection inside the documentation
  439.     // first. This depends on the type of method.
  440.     
  441.     _docuOriginStart = 0;
  442.     _docuOriginEnd = 0;
  443.     [docuFile setSel:0 :0];
  444.     
  445.     gotIt = [docuFile findText:"Instance Methods" 
  446.                               ignoreCase:NO backwards:NO wrap:NO
  447.                               font:[Font newFont:"Helvetica-Bold" size:18]];
  448.     [docuFile getSel:&tmpFrom :&tmpTo];
  449.     if( gotIt ) instancesStart = tmpFrom.cp;
  450.     
  451.     // If the method is a class method we should search for the right start
  452.     // pos. The instanceStart is necessary anyway..because we need to check if 
  453.     // the found selector belongs to the right category.
  454.     // Becasue this problem can only happen when we search for a class method
  455.     // (they come first) we should be save that way.
  456.      
  457.     if( ![aMethod isInstanceMethod] )
  458.     {
  459.         [docuFile setSel:0 :0];
  460.         gotIt = [docuFile findText:"Class Methods" 
  461.                               ignoreCase:NO backwards:NO wrap:NO
  462.                               font:[Font newFont:"Helvetica-Bold" size:18]];
  463.     }
  464.     if( !gotIt ) return NO;
  465.     
  466.     // Well finding the right section is not enough.. we have to find the
  467.     // right selector too. And sure...the font has to be correct. We
  468.     // Don't want the find every ref to that method.
  469.     // We also add a newline character to the end of the selector
  470.     // because we are searching for the whole selector..and not
  471.     // for a prefix inside another method.
  472.  
  473.     aString = [MiscString newWithString:[aMethod selectorName]];
  474.     [aString cat:"\n"];
  475.  
  476.     gotIt = [docuFile findText:[aString stringValue] 
  477.                     ignoreCase:NO 
  478.                     backwards:NO 
  479.                     wrap:NO
  480.                     font:[Font newFont:"Helvetica-Bold" size:14]];
  481.     [aString free];
  482.     if( !gotIt ) return NO;
  483.  
  484.     // Well now we should have the first selector for that method.
  485.     // Now if the docu was created with CM it should work if we grab for
  486.     // those right numbers of linefeeds.
  487.     // Really nasty. Don't look at it.
  488.     
  489.     [docuFile getSel:&from :&to];
  490.     
  491.     // Now if the "class" method is inside the instance docu section
  492.     // then we really don't have a method !
  493.     
  494.     if( ![aMethod isInstanceMethod] &&
  495.         from.cp > instancesStart &&
  496.         instancesStart > 0 ) 
  497.         return NO;
  498.     
  499.     // Otherwise read the string...brrr...we really should have a stream here.
  500.     
  501.     [docuFile getSubstring:text start:from.cp length:9990];
  502.     
  503.     // Now we need the 2 last new-lines. A '0' terminates the string and the
  504.     // search ! This is how we find the end of the docu.
  505.     
  506.     gotIt = NO;
  507.     last = -1;
  508.     for( i=0; i<9990; i++ )
  509.     {
  510.         if( text[i] == 0 ) 
  511.             break;
  512.         else if( text[i] == '\n' &&
  513.                  text[i+1] == '\n' &&
  514.                  text[i+2] == '\n'  )
  515.         {
  516.             gotIt = YES;
  517.             last = i;
  518.             break;
  519.         }
  520.     }
  521.     if( !gotIt ) return NO;
  522.     
  523.     // We always search for 2 blank lines (but they never come right after
  524.     // each other..look at the styles!). If we don't find a newLine char
  525.     // with the small Helvetica-Bold 3pt font then it seams like we have an
  526.     // old docu style...this qives us one line less to search for.
  527.     // A '0' terminates the string and the
  528.     // search !
  529.  
  530.     blanks = 1;
  531.     
  532.     if( [docuFile findText:"\n" 
  533.                     ignoreCase:NO 
  534.                     backwards:NO 
  535.                     wrap:NO
  536.                     font:[Font newFont:"Helvetica-Bold" size:3]] == YES )
  537.     {
  538.         [docuFile getSel:&tmpFrom :&tmpTo];
  539.         
  540.         // now if that small line really was in the scope of our currnet
  541.         // method..then we have to pass 2 blank lines.
  542.         // Btw..last is always valid because otherwise we won't be here.
  543.         
  544.         if( tmpFrom.cp < from.cp+last ) blanks = 0;
  545.     }
  546.  
  547.     gotIt = NO;
  548.     first = -1;
  549.     for( i=0; i<9990; i++ )
  550.     {
  551.         if( text[i] == 0 ) 
  552.             break;
  553.         else if( text[i] == '\n' &&
  554.                  text[i+1] == '\n' )
  555.         {
  556.             blanks++;
  557.             if( blanks == 2 ) 
  558.             {
  559.                 gotIt = YES;
  560.                 first = i+2;
  561.                 break;
  562.             }
  563.         }
  564.     }
  565.     
  566.     // Now select the right area.
  567.     
  568.     if( gotIt )
  569.     {    
  570.         _docuOriginStart = from.cp+first;
  571.         _docuOriginEnd = from.cp+last;
  572.          [docuFile setSel:_docuOriginStart :_docuOriginEnd];
  573.     }
  574.     
  575.     return gotIt;
  576. }
  577.  
  578. - _addDocuForMethod:aMethod
  579. {
  580.     // This method adds a template for the documentation of a certain method.
  581.     // It DOES corrupt the pasteboard !
  582.     // BUG: We also don't care about sorting ! This is bad !
  583.     
  584.     NXSelPt    from;
  585.     NXSelPt    to;
  586.     int     i;
  587.     int        paramCount;
  588.     BOOL        gotIt;
  589.     id        tokens;
  590.     id        aString;
  591.  
  592.     // We have to find the right start-selection inside the documentation
  593.     // first. This depends on the type of method.
  594.     
  595.     [docuFile setSel:0 :0];
  596.     
  597.     if( [aMethod isInstanceMethod] )
  598.             gotIt = [docuFile findText:"Instance Methods" 
  599.                               ignoreCase:NO backwards:NO wrap:NO
  600.                               font:[Font newFont:"Helvetica-Bold" size:18]];
  601.     else    gotIt = [docuFile findText:"Class Methods" 
  602.                               ignoreCase:NO backwards:NO wrap:NO
  603.                               font:[Font newFont:"Helvetica-Bold" size:18]];
  604.     
  605.     
  606.     if( !gotIt ) return self;
  607.  
  608.     // Now this is ugly...we should sort the methods alpahbetically !!
  609.     // Buggy because we can only HOPE that nobody adds something behind the
  610.     // "* Methods" section.
  611.     
  612.     [docuFile getSel:&from :&to];
  613.     [docuFile setSel:to.cp+2 :to.cp+2];
  614.  
  615.     [[NXApp delegate] copyMethodDocuTemplate];
  616.     [docuFile pasteFrom:_tempPb];
  617.     [docuFile setSel:to.cp+2 :to.cp+2];
  618.  
  619.     [docuFile findText:"_CE_myMethod" 
  620.               ignoreCase:NO backwards:NO wrap:NO];
  621.     [docuFile replaceSel:[aMethod selectorName]];
  622.  
  623.     [docuFile findText:"_CE_myMethod" 
  624.               ignoreCase:NO backwards:NO wrap:NO];
  625.     [docuFile getSel:&from :&to];
  626.     [docuFile replaceSel:[aMethod name]];
  627.     
  628.     // First lets fix the method prefix of the class methods and
  629.     // remove the dulicate prefix"
  630.     
  631.     [docuFile setSel:from.cp-2 :from.cp-1];
  632.     
  633.     if( [aMethod isInstanceMethod] )
  634.     {
  635.         [docuFile findText:"- " ignoreCase:NO backwards:NO wrap:NO];
  636.         [docuFile delete:self];
  637.     }
  638.     else
  639.     {
  640.         [docuFile replaceSel:"+"];
  641.         [docuFile findText:"+ " ignoreCase:NO backwards:NO wrap:NO];
  642.         [docuFile delete:self];
  643.     }
  644.     
  645.     // Now its time to fix all the fonts.
  646.     // Lets tokenize the methods name. This will create a list of
  647.     // all the parts that have a different font.
  648.     // We need to know how many parms the method takes to get a simpler
  649.     // algorithm for coosing the fonts.
  650.     
  651.     tokens = [aMethod methodTokens];
  652.     paramCount = [aMethod numberOfArguments];
  653.     
  654.     for( i=0; i<[tokens count]; i++ )
  655.     {
  656.         // Now select the new token...if we fail to find it..lets quit here.
  657.         
  658.         aString = [tokens objectAt:i];
  659.         if( [docuFile findText:[aString stringValue]
  660.                         ignoreCase:NO backwards:NO wrap:NO] == NO ) break;
  661.         
  662.         
  663.         // Depending on the type of token we will choose a different font.
  664.         // First check for casts and then for paramerters.
  665.         
  666.         if( [aString grep:")"] )
  667.             [docuFile setSelFont:[Font newFont:"Times-Roman" size:14]];
  668.         
  669.         // If it is not a cast the situation is a little bit trickier.
  670.         // Imagine a - (BOOL)delegate or a simple -free method.
  671.         // The token will be a part of the selector and still have no
  672.         // ':' attached because there ist no argument                        
  673.         
  674.         else if( [aString grep:":"] == NO && paramCount > 0 )
  675.         {
  676.             [docuFile setSelFont:[Font newFont:"Times-Italic" size:14]];
  677.         }
  678.     }
  679.     
  680.     // The tokens are bound to die now.
  681.     
  682.     [[tokens freeObjects] free];
  683.     
  684.     return self;
  685. }
  686.  
  687. @end
  688.  
  689. /*
  690.  * History: 13.01.95 Buh
  691.  *            
  692.  *
  693.  * Bugs: - ...
  694.  */