home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / SourceCode / MiscKit1.2.6 / Source / MiscString.m < prev    next >
Encoding:
Text File  |  1994-05-19  |  15.2 KB  |  479 lines

  1. //
  2. //    MiscString.m -- a generic class to simplify manipulation of (char *)'s
  3. //        Written by Don Yacktman (c) 1993 by Don Yacktman.
  4. //                Version 1.95  All rights reserved.
  5. //        This notice may not be removed from this source code.
  6. //
  7. //    This object is included in the MiscKit by permission from the author
  8. //    and its use is governed by the MiscKit license, found in the file
  9. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  10. //    for a list of all applicable permissions and restrictions.
  11. //    
  12.  
  13. // This class is not specifically thread-safe.  If there is enough
  14. // demand, we can easily enough produce a thread-safe subclass.
  15. // Let us know what you'd like to see in the future!
  16.  
  17. //
  18. //    And now for an implementation note... (philosophy of this class)
  19. //    The object is to make a bulletproof class.  That means that NULL
  20. //    pointers and values that are out of range are ignored, or that
  21. //    nil is returned, or that the object tries to do what would be the
  22. //    intelligent thing to do in such boundary cases.  Obviously, all
  23. //    this error checking costs you something:  speed.  On the other hand,
  24. //    your code shouldn't end up with anywhere near as many bus errors
  25. //    and other silliness!  (This is not a panacea; don't rely on the
  26. //    MiscString to get everything right for you; if a boundary condition
  27. //    might exist, do be sure to check return values as they can signal
  28. //    problems for you.)  There is no substitue for good programming,
  29. //    but hopefully this class will help you survive silly mistakes.
  30. //    Most of the methods send to other methods to do the real work;
  31. //    it would run faster to implement each method separately, since
  32. //    some methods would then not need to do certain checks, and so on.
  33. //    On the other hand, it is a whole lot easier to maintain this code
  34. //    since there are a handful of core methods that do all the work.
  35. //    Someday I may attempt to make a MiscFastString class that will
  36. //    be optimized for speed... but only if there is either (1) a *whole*
  37. //    lot of demand for it, or (2) someone pays me to write it.  :-)
  38. //
  39. //    Returns:  I try to follow NeXT conventions.  Return self, nil, or
  40. //        whatever makes sense in the context.  Hopefully your idea of
  41. //        what makes sense will coincide with mine.  Read the docs...
  42. //
  43. //    For release (1.2), I have gone over every single method and
  44. //        tried to make sure it behaved sanely on all possible boundary
  45. //        cases, especially NULL pointers.  If there is any way to
  46. //        break this class, or cause a bus error in here, etc. or any
  47. //        other bugs I WANT to know about them... be sure to send bug
  48. //        reports to me at Don_Yacktman@byu.edu so I can fix them.  I
  49. //        want this class to be bulletproof.  Note that there are still
  50. //        a few possibilities to get memory leaks here.  I want to fix
  51. //        those, if at all possible, so remind me of the ones you find.
  52. //        I got rid of the most obvious and common ones.
  53. //
  54. //    For release (1.3) I have added a few suggested methods, cleaned
  55. //        up a few minor bugs that were discovered, and that's about
  56. //        it.  Nothing earth-shattering, but it is better than 1.2.
  57. //
  58. //    For release (1.4) (Unreleased) Split into categories.
  59. //
  60. //    For release (1.5) Added compatability with the MOKit's MOString
  61. //        so you can replace any instances of that class with this one
  62. //        and folded in methods contributed by David Lehn and Carl
  63. //        Lindberg.  Did some more testing.  Words, fields, etc. have
  64. //        the first "thing" being #0.  This alters a few methods that
  65. //        used to have NO field #0, with field #1 being the first.
  66. //        It is hoped that this won't break too many folks' code.  In
  67. //        most cases, it shouldn't, but there will be a few.  Sorry!
  68. //
  69. //    For release (1.6) Misc. bug fixes and tweaks.
  70. //
  71. //    For release (1.7) More bug fixes, tweaks, and a pile of new methods.
  72. //
  73. //    One memory leak possibility, and this happens in the test app a
  74. //    little bit:  you call a method that returns a new object, but
  75. //    then do something like this:
  76. //        printf("%s", [[aString right:5] stringValue]);
  77. //    A new instance is created, but never freed.  I have attempted to
  78. //    solve the problem with a -stringValueAndFree method.  Basically,
  79. //    there is now a global scratch MiscString that is never freed which
  80. //    holds the value.  Note that there is only one of these, so that
  81. //    means it is not at all thread-safe and you should set up, say, a
  82. //    lock on the _entire_ string class if you're gonna muck with it.
  83. //    (I don't really know how much of an issue this really is, but I
  84. //    figure you ought to be warned.)  If this is a problem, the best
  85. //  workaround is to make the scratch string an instance variable so
  86. //  that it is on a per string basis, and then have the string class
  87. //  itself lock it.  The current global variable solution uses far less
  88. //  storage space and is only a problem in multi-threaded situations.
  89. //    If you don't trust my method, as an alternative there is this:
  90. //        temp = [aString right:5];
  91. //        printf("%s", [temp stringValue]);
  92. //        [temp free];
  93. //    But that's a pain in the *ss...even if it removes the leaks.
  94. //    If you have any better ideas, let me know.
  95. // Thanks to dlehn@ARPA.MIL (David Lehn) for suggesting one way to get a
  96. // -stringValueAndFree method.  It's so obvious once you see it...
  97. //
  98. //    Note that if you can count on the appkit being there (which we cannot)
  99. //    you could use the appkit's object extension for delaying method calls,
  100. //    and just have a delayed free.
  101. //
  102.  
  103. #import <misckit/MiscString.h>
  104. #import <strings.h>
  105.  
  106. // this is a kludge to allow you to do a "delayed free"...actually,
  107. // rather than leaking lots of MiscStrings, we have one string that
  108. // is used as a scratch place for all strings, and we always have
  109. // a pointer to it.  That makes it a non-memory leak. :-)
  110. static id theKludgeString = nil; // used by all MiscString instances,
  111.         // meaning that this variable is NOT thread-safe!  Beware!!!
  112.  
  113. #define MISC_STRING_VERSION 1    // This version is used for archiving
  114.     // MiscStrings.  If we ever add instance vars, we'll need to bump
  115.     // it up.  Until then, we can leave it at 1.
  116.  
  117. #define MISCSTRING_MIN_BUFFER_SIZE 15 // all strings start with a buffer
  118.     // at least this big, so that NULL pointers aren't ever returned
  119.     // from -stringValue, etc.  This number should be a malloc good size - 1.
  120.  
  121. @interface MiscString(private)
  122.  
  123. // private methods: do not mess with these!
  124. - _unhookBuffer;
  125.  
  126. @end
  127.  
  128. char *MiscBuildStringFromFormatV(const char *formatStr, va_list param_list)
  129. // This function takes a format string and a variable argument list.
  130. // It returns a pointer to a newly allocated chunk of memory containing 
  131. // the results of sprintf'ing the format string and va_list into the 
  132. // new memory.
  133. {
  134.     NXStream *stream;
  135.     long l;
  136.     char *buf;
  137.  
  138.     stream = NXOpenMemory(NULL, 0, NX_READWRITE);
  139.     NXVPrintf(stream, formatStr, param_list);
  140.     NXFlush(stream);
  141.     NXSeek(stream, 0, NX_FROMEND);
  142.     l = NXTell(stream);
  143.     NXSeek(stream, 0, NX_FROMSTART);
  144.     NX_MALLOC(buf, char, l+1);
  145.     NXRead(stream, buf, l);
  146.     buf[l]='\0';
  147.     NXCloseMemory(stream, NX_FREEBUFFER);
  148.     return buf;
  149. }
  150.  
  151. @implementation MiscString
  152.  
  153. // These are the core methods for memory management and basic instance
  154. // variable querying/setting.
  155.  
  156. + initialize    // make sure the -stringValueAndFree stuff is set up
  157. {
  158.     if (self == [MiscString class]) {
  159.         if (!theKludgeString) {
  160.             theKludgeString = [[MiscString alloc] init];
  161.         }
  162.         [MiscString setVersion:MISC_STRING_VERSION];
  163.     }
  164.     return self;
  165. }
  166.  
  167. + new
  168. {
  169.     return [[self alloc] init];
  170. }
  171.  
  172. + newWithString:(const char *)aString
  173. // I just got tired of typing [[[MiscString alloc] init] setStringValue:xxx] 
  174. {
  175.     id newString = [[self alloc] init]; // self is a Class object, remember.
  176.     if ([newString setStringValue:aString]) return newString;
  177.     [newString free];
  178.     return nil;
  179. }
  180.  
  181. - new
  182. {
  183.     return [[[self class] alloc] init];
  184. }
  185.  
  186. - init
  187. {
  188.      [super init];
  189.      [self setStringOrderTable:NXDefaultStringOrderTable()];
  190.      if (buffer) free(buffer);
  191.      buffer = NULL;
  192.      length = 0;
  193.      _length = 0;
  194.      // This is a bit inefficient to do, but removes yet another
  195.      // chance for things to blow up; you could safely remove it
  196.      // if you need faster initializing and better memory usage.
  197.      [self allocateBuffer:MISCSTRING_MIN_BUFFER_SIZE fromZone:[self zone]];
  198.      return self;
  199. }
  200.  
  201. - initCapacity:(int)capacity
  202. {
  203.     return [self initCapacity:capacity fromZone:[self zone]];
  204. }
  205.  
  206. - initCapacity:(int)capacity fromZone:(NXZone *)zone
  207. {
  208.     [self init];
  209.     [self allocateBuffer:capacity fromZone:zone];
  210.     return self;
  211. }
  212.  
  213. - initString:(const char *)aString
  214. {
  215.     [self init];
  216.     return [self setStringValue:aString];
  217. }
  218.  
  219. - initFromFormat:(const char *)formatStr, ...
  220. // Initializes the string from printf style format string and arguments.
  221. {
  222.     va_list param_list;
  223.     char *buf;
  224.  
  225.     va_start(param_list, formatStr);
  226.     buf = MiscBuildStringFromFormatV(formatStr, param_list);
  227.     va_end(param_list);
  228.     [self initString:buf];
  229.     NX_FREE(buf);
  230.     return self;
  231. }
  232.  
  233. - allocateBuffer:(int)size
  234. {
  235.     return [self allocateBuffer:size fromZone:[self zone]];
  236. }
  237.  
  238. - allocateBuffer:(int)size fromZone:(NXZone *)zone
  239. {    // This is the only method that should be allowed to alter the
  240.     // _length instance variable.  That variable is used to track the
  241.     // amount of memory allocated in the buffer, and this method and
  242.     // freeString are the only ones that change that.  If you muck with
  243.     // it yourself, you will make the class less efficient, create a
  244.     // memory leak, or create crash possibilities.  So leave it alone!!!
  245.     
  246.     if (size <= _length) return self; // return is buffer is already big enough
  247.     
  248.     // if not big enough, free the old buffer and then create a new buffer
  249.     // of the right size.  Since the buffer is to be empty anyway, there's
  250.     // no point in calling realloc, and so this is just fine.
  251.     [self freeString];
  252.     if (!size) return self;
  253.     _length = size + 1; // just in case somebody forgot to take
  254.         // terminators into account, we'll make sure there is space.
  255.         // Note that if they did take it into account, we'll waste a
  256.         // little bit of space, but that's the price of stability...
  257.     buffer = (char *)NXZoneMalloc(zone, _length);
  258.     buffer[0] = '\0'; // terminator at zero for zero length string
  259.     buffer[size] = '\0';    // make 100% sure we'll be terminated
  260.     return self;
  261. }
  262.  
  263. - copyFromZone:(NXZone *)zone
  264. {
  265.     MiscString *myCopy = [super copyFromZone:zone];
  266.     // force child to have it's own copy of the string buffer
  267.     [myCopy _unhookBuffer];  // see below
  268.     [myCopy allocateBuffer:_length fromZone:zone]; // make a new buffer
  269.     [myCopy setStringValue:buffer fromZone:zone];  // and copy the string to it
  270.     return myCopy;
  271. }
  272.  
  273. - (char *)getCopyInto:(char *)buf
  274. {
  275.     if (!buf) {
  276.         NX_MALLOC(buf, char, length + 1);
  277.     }
  278.     strcpy(buf, buffer);
  279.     return buf;
  280. }
  281.  
  282. - _unhookBuffer
  283. { // used by the copy method so that we don't free the buffer from orig.
  284.     // If you call this method without knowing what you're doing,
  285.     // you will very likely create a memory leak; that's why it's got
  286.     // the underbar and is considered private, and is undocumented.
  287.     // I do it 'cos it solves a problem I couldn't solve any other way;
  288.     // see the -copyFromZone: method...
  289.     buffer = NULL; _length = 0;
  290.     return self;
  291. }
  292.  
  293. - freeString
  294. {    // Empty out the buffer, throw it away, and then we're zero length.
  295.     if (buffer) free(buffer);
  296.     buffer = NULL;
  297.     length = 0;
  298.     _length = 0;
  299.     return self;
  300. }
  301.  
  302. - free
  303. {
  304.      [self freeString];
  305.      return [super free];
  306. }
  307.  
  308. - (BOOL)emptyString
  309. {
  310.     return ((length > 0) ? NO : YES);
  311. }
  312.  
  313. - (char)charAt:(int)index
  314. {
  315.     if ((index < 0) || (index > length - 1)) return 0;
  316.     return (char)buffer[index];
  317. }
  318.  
  319. - (int)numOfChar:(char)aChar caseSensitive:(BOOL)sense
  320. {
  321.     int i, count = 0;
  322.  
  323.     if (sense) {
  324.         for (i=0; i<length; i++)
  325.             if (buffer[i] == aChar) count++;
  326.     } else {
  327.         for (i=0; i<length; i++)
  328.             if (NXToUpper(buffer[i]) == NXToUpper(aChar)) count++;
  329.     }
  330.     return count;
  331. }
  332.  
  333. - (int)numOfChar:(char)aChar
  334. {
  335.     return [self numOfChar:aChar caseSensitive:YES];
  336. }
  337.  
  338. - (int)numOfChars:(const char *)aString caseSensitive:(BOOL)sense
  339. {
  340.     int i, count = 0;
  341.     id tempStr;
  342.  
  343.     if (!aString) return 0;
  344.     tempStr = [MiscString newWithString:aString];
  345.     for (i=0; i<length; i++)
  346.         if ([tempStr spotOf:buffer[i] caseSensitive:sense] != -1) count++;
  347.     [tempStr free];
  348.     return count;
  349. }
  350.  
  351. - (int)numOfChars:(const char *)aString
  352. {
  353.     return [self numOfChars:aString caseSensitive:YES];
  354. }
  355.  
  356. - (int)length
  357. {
  358.     return length;
  359. }
  360.  
  361. - (unsigned)capacity
  362. {
  363.     return (unsigned)_length;
  364. }
  365.  
  366. - setCapacity:(unsigned)newCapacity
  367. {
  368.     char *tempBuffer = NXCopyStringBufferFromZone(buffer, [self zone]);
  369.     if (!tempBuffer) return nil; // something went wrong!
  370.     [self allocateBuffer:newCapacity fromZone:[self zone]];
  371.     [self setStringValue:tempBuffer fromZone:[self zone]];
  372.     free(tempBuffer);
  373.     return self;
  374. }
  375.  
  376. - fixStringLength // realloce buffer space; this will trim off any extra space
  377. {
  378.     char *tempBuffer = NXCopyStringBufferFromZone(buffer, [self zone]);
  379.     if (!tempBuffer) return nil; // something went wrong!
  380.     [self freeString];
  381.     buffer = tempBuffer;
  382.     length = strlen(buffer);
  383.     _length = length + 1;
  384.     return self;
  385. }
  386.  
  387. - fixStringLengthAt:(unsigned)index
  388. { // truncates the string to a certain length.
  389.     if (!buffer) return self;
  390.     if (_length <= index) return self;
  391.     buffer[index] = '\0';
  392.     length = index;
  393.     [self fixStringLength];
  394.     return(self);
  395. }
  396.  
  397.  
  398. - setStringOrderTable:(NXStringOrderTable *)table
  399. {
  400.     if (table) orderTable = table;
  401.     else orderTable = NXDefaultStringOrderTable(); // just in case...
  402.     return self;
  403. }
  404.  
  405. - (NXStringOrderTable *)stringOrderTable
  406. {
  407.     return orderTable;
  408. }
  409.  
  410. - (const char *)stringValue
  411. {
  412.     return buffer;
  413. }
  414.  
  415. - (NXAtom)uniqueStringValue
  416. {
  417.     return NXUniqueString(buffer);
  418. }
  419.  
  420. - (const char *)stringValueAndFree
  421. {    // you could use this as a model to build other ...AndFree methods,
  422.     // but this is by far the most useful one.
  423.     if (!buffer) [theKludgeString setStringValue:"" fromZone:[self zone]];
  424.     else [theKludgeString setStringValue:buffer fromZone:[self zone]];
  425.     [self free];
  426.     return [theKludgeString stringValue];
  427. }
  428.  
  429. - (int)intValue
  430. // Returns the string converted to an int.
  431. {
  432.     if (!buffer) return 0;
  433.     return atoi(buffer);
  434. }
  435.  
  436. - (float)floatValue
  437. // Returns the string converted to an float.
  438. {
  439.     if (!buffer) return 0.0;
  440.     return (float)atof(buffer);
  441. }
  442.  
  443. - (double)doubleValue
  444. // Returns the string converted to an double.
  445. {
  446.     if (!buffer) return 0.0;
  447.     return atof(buffer);
  448. }
  449.  
  450. - kitchenSink
  451. {    // OK, Carl, here it is.  Read it and weep.  Heheheh.
  452.     // You're probably the only one who will notice this in here.  :-)
  453.     return [[self class] newWithString:"Kitchen Sink"];
  454. }    // Yes, you've found the easter egg.  Sorry, I guess it's too late.
  455.     // Better get some sleep...
  456.  
  457. @end
  458.  
  459. // interesting ideas:  These will have to wait for a future release...
  460.  
  461. // extract    - add some extract... type methods that have methods such as:
  462. //     - extract:firstDelimiter :lastDelimiter
  463. // This would be great so that a parenthesized string could be examined
  464. // "functionA(firstParam,secondParam);"   lets you take out the
  465. // "firstParam,secondParam".
  466. // Bad example, but you get the idea.
  467. // Maybe add a delimiterList string so that you can set which characters
  468. // (strings??) will break up a string.
  469.  
  470. // fieldCount    - a method to return the number of fields delimited by
  471. // multiple delimiters
  472. // ex.  the number of field surrounded by parentheses.
  473.  
  474. // Two important points:  (1) There should be different start/end delimiters
  475. // (2) you have to balance them, like prens, so it would need to be a PDA.
  476. // Of course, if we are making a full blown PDA to do this we may as well
  477. // just do a proper lexical analyzer and be done with it.  Perhaps we could
  478. // build a subclass that is an interpreted version of lex(1)...
  479.