This is NSScanner.m in view mode; [Download] [Up]
/* Implemenation of NSScanner class Copyright (C) 1996 Free Software Foundation, Inc. Author: Eric Norum <eric@skatter.usask.ca> Date: 1996 This file is part of the GNUstep Objective-C Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <Foundation/NSScanner.h> #include <Foundation/NSException.h> #include <float.h> #include <limits.h> #include <math.h> #include <ctype.h> /* FIXME: May go away once I figure out Unicode */ /* * Cache the default NSCharacterSet of characters to be skipped. * FIXME: This would be unecessary if NSCharacterSet did the caching. */ static NSCharacterSet *defaultCharactersToBeSkipped; @implementation NSScanner /* * Class initialization. * Fill in default NSCharacterSet of characters to be skipped. */ + (void)initialize { if (defaultCharactersToBeSkipped == nil) defaultCharactersToBeSkipped = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain]; } /* * Create and return a scanner that scans aString. */ + scannerWithString:(NSString *)aString { return [[[self alloc] initWithString: aString] autorelease]; } + localizedScannerWithString: (NSString*)locale { [self notImplemented: _cmd]; return Nil; } /* * Initialize a a newly-allocated scanner to scan aString. * Returns self. */ - initWithString:(NSString *)aString { [super init]; string = [aString copyWithZone: [self zone]]; len = [string length]; charactersToBeSkipped = defaultCharactersToBeSkipped; return self; } /* * Deallocate a scanner and all its associated storage. */ - (void)dealloc { [string release]; [locale release]; if (charactersToBeSkipped != defaultCharactersToBeSkipped) [charactersToBeSkipped release]; [super dealloc]; } /* * Like scanCharactersFromSet:intoString: but no initial skip * For internal use only. */ - (BOOL)_scanCharactersFromSet: (NSCharacterSet *)set intoString: (NSString **)value; { unsigned int start; if (scanLocation >= len) return NO; start = scanLocation; while (scanLocation < len) { if (![set characterIsMember: [string characterAtIndex:scanLocation]]) break; scanLocation++; } if (scanLocation == start) return NO; if (value) { NSRange range; range.location = start; range.length = scanLocation - start; *value = [string substringFromRange: range]; } return YES; } /* * Scan characters to be skipped. * Return YES if there are more characters to be scanned. * Return NO if the end of the string is reached. * For internal use only. */ - (BOOL) _skipToNextField { [self _scanCharactersFromSet: charactersToBeSkipped intoString: NULL]; if (scanLocation >= len) return NO; return YES; } /* * Returns YES if no more characters remain to be scanned. * Returns YES if all characters remaining to be scanned are to be skipped. * Returns NO if there are characters left to scan. */ - (BOOL) isAtEnd { unsigned int save_scanLocation; BOOL ret; if (scanLocation >= len) return YES; save_scanLocation = scanLocation; ret = ![self _skipToNextField]; scanLocation = save_scanLocation; return ret; } /* * Internal version of scanInt: method. * Does the actual work for scanInt: except for the initial skip. * For internal use only. This method may move the scan location * even if a valid integer is not scanned. * Based on the strtol code from the GNU C library. A little simpler since * we deal only with base 10. * FIXME: I don't use the decimalDigitCharacterSet here since it * includes many more characters than the ASCII digits. I don't * know how to convert those other characters, so I ignore them * for now. For the same reason, I don't try to support all the * possible Unicode plus and minus characters. */ - (BOOL) _scanInt: (int*)value { unsigned int num = 0; const unsigned int limit = UINT_MAX / 10; BOOL negative = NO; BOOL overflow = NO; BOOL got_digits = NO; /* Check for sign */ switch ([string characterAtIndex:scanLocation]) { case '+': scanLocation++; break; case '-': negative = YES; scanLocation++; break; } /* Process digits */ while (scanLocation < len) { unichar digit = [string characterAtIndex: scanLocation]; if ((digit < '0') || (digit > '9')) break; if (!overflow) { if (num >= limit) overflow = YES; else num = num * 10 + (digit - '0'); } scanLocation++; got_digits = YES; } /* Save result */ if (!got_digits) return NO; if (value) { if (num > (negative ? -(unsigned int)INT_MIN : (unsigned int)INT_MAX)) overflow = YES; if (overflow) num = negative ? INT_MIN: INT_MAX; if (negative) *value = -num; else *value = num; } return YES; } /* * Scan an int into value. */ - (BOOL) scanInt: (int*)value { unsigned int saveScanLocation = scanLocation; if ([self _skipToNextField] && [self _scanInt: value]) return YES; scanLocation = saveScanLocation; return NO; } /* * Scan a long long int into value. * Same as scanInt, except with different variable types and limits. */ - (BOOL)scanLongLong: (long long *)value { #if defined(LONG_LONG_MAX) unsigned long long num = 0; const unsigned long long limit = ULONG_LONG_MAX / 10; BOOL negative = NO; BOOL overflow = NO; BOOL got_digits = NO; unsigned int saveScanLocation = scanLocation; /* Skip whitespace */ if (![self _skipToNextField]) { scanLocation = saveScanLocation; return NO; } /* Check for sign */ switch ([string characterAtIndex:scanLocation]) { case '+': scanLocation++; break; case '-': negative = YES; scanLocation++; break; } /* Process digits */ while (scanLocation < len) { unichar digit = [string characterAtIndex:scanLocation]; if ((digit < '0') || (digit > '9')) break; if (!overflow) { if (num >= limit) overflow = YES; else num = num * 10 + (digit - '0'); } scanLocation++; got_digits = YES; } /* Save result */ if (!got_digits) { scanLocation = saveScanLocation; return NO; } if (value) { if (num > (unsigned long long)LONG_LONG_MAX) /* xxx When would this ever be true? Isn't is impossible? mccallum. */ overflow = YES; if (overflow) num = negative ? LONG_LONG_MIN: LONG_LONG_MAX; if (negative) *value = -num; else *value = num; } return YES; #else /* defined(LONG_LONG_MAX) */ /* * Provide compile-time warning and run-time exception. */ # warning "Can't use long long variables." [NSException raise: NSGenericException format:@"Can't use long long variables."]; return NO; #endif /* defined(LONG_LONG_MAX) */ } /* * Scan a double into value. * Returns YES if a valid floating-point expression was scanned. * Returns NO otherwise. * On overflow, HUGE_VAL or -HUGE_VAL is put into value and YES is returned. * On underflow, 0.0 is put into value and YES is returned. * Based on the strtod code from the GNU C library. */ - (BOOL)scanDouble:(double *)value { unichar decimal; unichar c = 0; double num = 0.0; long int exponent = 0; BOOL negative = NO; BOOL got_dot = NO; BOOL got_digit = NO; unsigned int saveScanLocation = scanLocation; /* Skip whitespace */ if (![self _skipToNextField]) { scanLocation = saveScanLocation; return NO; } /* * FIXME: Should get decimal point character from locale. The problem * is that I can't find anything in the OPENSTEP specification about * the format of the locale dictionary. */ decimal = '.'; /* Check for sign */ switch ([string characterAtIndex:scanLocation]) { case '+': scanLocation++; break; case '-': negative = YES; scanLocation++; break; } /* Process number */ while (scanLocation < len) { c = [string characterAtIndex: scanLocation]; if ((c >= '0') && (c <= '9')) { /* Ensure that the number being accumulated will not overflow. */ if (num >= (DBL_MAX / 10.000000001)) { ++exponent; } else { num = (num * 10.0) + (c - '0'); got_digit = YES; } /* Keep track of the number of digits after the decimal point. If we just divided by 10 here, we would lose precision. */ if (got_dot) --exponent; } else if (!got_dot && (c == decimal)) { /* Note that we have found the decimal point. */ got_dot = YES; } else { /* Any other character terminates the number. */ break; } scanLocation++; } if (!got_digit) { scanLocation = saveScanLocation; return NO; } /* Check for trailing exponent Numbers like 1.23eFOO are rejected. */ if ((scanLocation < len) && ((c == 'e') || (c == 'E'))) { int exp; scanLocation++; if (![self _scanInt: &exp]) { scanLocation = saveScanLocation; return NO; } /* Check for exponent overflow */ if (num) { if ((exponent > 0) && (exp > (LONG_MAX - exponent))) exponent = LONG_MAX; else if ((exponent < 0) && (exp < (LONG_MIN - exponent))) exponent = LONG_MIN; else exponent += exp; } } if (value) { if (num && exponent) num *= pow(10.0, (double) exponent); if (negative) *value = -num; else *value = num; } return YES; } /* * Scan a float into value. * Returns YES if a valid floating-point expression was scanned. * Returns NO otherwise. * On overflow, HUGE_VAL or -HUGE_VAL is put into value and YES is returned. * On underflow, 0.0 is put into value and YES is returned. */ - (BOOL)scanFloat: (float*)value { double num; if (value == NULL) return [self scanDouble:NULL]; if ([self scanDouble:&num]) { *value = num; return YES; } return NO; } /* * Scan as long as characters from aSet are encountered. * Returns YES if any characters were scanned. * Returns NO if no characters were scanned. * If value is non-NULL, and any characters were scanned, a string * containing the scanned characters is returned by reference in value. */ - (BOOL)scanCharactersFromSet:(NSCharacterSet *)aSet intoString:(NSString **)value; { unsigned int saveScanLocation = scanLocation; if ([self _skipToNextField] && [self _scanCharactersFromSet: aSet intoString: value]) return YES; scanLocation = saveScanLocation; return NO; } /* * Scan until a character from aSet is encountered. * Returns YES if any characters were scanned. * Returns NO if no characters were scanned. * If value is non-NULL, and any characters were scanned, a string * containing the scanned characters is returned by reference in value. */ - (BOOL)scanUpToCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)value; { unsigned int saveScanLocation = scanLocation; unsigned int start; if (![self _skipToNextField]) return NO; start = scanLocation; while (scanLocation < len) { if ([set characterIsMember:[string characterAtIndex: scanLocation]]) break; scanLocation++; } if (scanLocation == start) { scanLocation = saveScanLocation; return NO; } if (value) { NSRange range; range.location = start; range.length = scanLocation - start; *value = [string substringFromRange: range]; } return YES; } /* * Scans for aString. * Returns YES if the characters at the scan location match aString. * Returns NO if the characters at the scan location do not match aString. * If the characters at the scan location match aString. * If value is non-NULL, and the characters at the scan location match aString, * a string containing the matching string is returned by reference in value. */ - (BOOL)scanString:(NSString *)aString intoString:(NSString **)value; { NSRange range; unsigned int saveScanLocation = scanLocation; [self _skipToNextField]; range.location = scanLocation; range.length = [aString length]; range = [string rangeOfString:aString options:caseSensitive ? 0 : NSCaseInsensitiveSearch range:range]; if (range.length == 0) { scanLocation = saveScanLocation; return NO; } if (value) *value = [string substringFromRange:range]; scanLocation += range.length; return YES; } /* * Scans the string until aString is encountered.. * Returns YES if any characters were scanned. * Returns NO if no characters were scanned. * If value is non-NULL, and any characters were scanned, a string * containing the scanned characters is returned by reference in value. */ - (BOOL)scanUpToString:(NSString *)aString intoString:(NSString **)value; { NSRange range; NSRange found; unsigned int saveScanLocation = scanLocation; [self _skipToNextField]; range.location = scanLocation; range.length = len - scanLocation; found = [string rangeOfString:aString options:caseSensitive ? 0 : NSCaseInsensitiveSearch range:range]; if (found.length) range.length = found.location - scanLocation; if (range.length == 0) { scanLocation = saveScanLocation; return NO; } if (value) *value = [string substringFromRange:range]; scanLocation += range.length; return YES; } /* * Returns the string being scanned. */ - (NSString *)string { return string; } /* * Returns the character index at which the scanner * will begin the next scanning operation. */ - (unsigned)scanLocation { return scanLocation; } /* * Set the character location at which the scanner * will begin the next scanning operation to anIndex. */ - (void)setScanLocation:(unsigned int)anIndex { scanLocation = anIndex; } /* * Returns YES if the scanner makes a distinction * between upper and lower case characters. */ - (BOOL)caseSensitive { return caseSensitive; } /* * If flag is YES the scanner will consider upper and lower case * to be the same during scanning. If flag is NO the scanner will * not make a distinction between upper and lower case characters. */ - (void)setCaseSensitive:(BOOL)flag { caseSensitive = flag; } /* * Return a character set object containing the characters the scanner * will ignore when searching for the next element to be scanned. */ - (NSCharacterSet *)charactersToBeSkipped { return charactersToBeSkipped; } /* * Set the characters to be ignored when the scanner * searches for the next element to be scanned. */ - (void)setCharactersToBeSkipped:(NSCharacterSet *)aSet { if (charactersToBeSkipped != defaultCharactersToBeSkipped) [charactersToBeSkipped release]; charactersToBeSkipped = [aSet copyWithZone: [self zone]]; } /* * Returns a dictionary object containing the locale * information used by the scanner. */ - (NSDictionary *)locale { return locale; } /* * Set the dictionary containing the locale * information used by the scanner to localeDictionary. */ - (void)setLocale:(NSDictionary *)localeDictionary { locale = [localeDictionary retain]; } /* * NSCopying protocol */ - copyWithZone:(NSZone *)zone { NSScanner *n = [[self class] allocWithZone:zone]; [n initWithString: string]; [n setCharactersToBeSkipped: charactersToBeSkipped]; [n setLocale: locale]; [n setScanLocation: scanLocation]; [n setCaseSensitive: caseSensitive]; return n; } - copy { return [self copyWithZone: [self zone]]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.