123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- //
- // FLEXRuntimeKeyPathTokenizer.m
- // FLEX
- //
- // Created by Tanner on 3/22/17.
- // Copyright © 2017 Tanner Bennett. All rights reserved.
- //
- #import "FLEXRuntimeKeyPathTokenizer.h"
- #define TBCountOfStringOccurence(target, str) ([target componentsSeparatedByString:str].count - 1)
- @implementation FLEXRuntimeKeyPathTokenizer
- #pragma mark Initialization
- static NSCharacterSet *firstAllowed = nil;
- static NSCharacterSet *identifierAllowed = nil;
- static NSCharacterSet *filenameAllowed = nil;
- static NSCharacterSet *keyPathDisallowed = nil;
- static NSCharacterSet *methodAllowed = nil;
- + (void)initialize {
- if (self == [self class]) {
- NSString *_methodFirstAllowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
- NSString *_identifierAllowed = [_methodFirstAllowed stringByAppendingString:@"1234567890"];
- NSString *_methodAllowedSansType = [_identifierAllowed stringByAppendingString:@":"];
- NSString *_filenameNameAllowed = [_identifierAllowed stringByAppendingString:@"-+?!"];
- firstAllowed = [NSCharacterSet characterSetWithCharactersInString:_methodFirstAllowed];
- identifierAllowed = [NSCharacterSet characterSetWithCharactersInString:_identifierAllowed];
- filenameAllowed = [NSCharacterSet characterSetWithCharactersInString:_filenameNameAllowed];
- methodAllowed = [NSCharacterSet characterSetWithCharactersInString:_methodAllowedSansType];
- NSString *_kpDisallowed = [_identifierAllowed stringByAppendingString:@"-+:\\.*"];
- keyPathDisallowed = [NSCharacterSet characterSetWithCharactersInString:_kpDisallowed].invertedSet;
- }
- }
- #pragma mark Public
- + (FLEXRuntimeKeyPath *)tokenizeString:(NSString *)userInput {
- if (!userInput.length) {
- return nil;
- }
- NSUInteger tokens = [self tokenCountOfString:userInput];
- if (tokens == 0) {
- return nil;
- }
- if ([userInput containsString:@"**"]) {
- @throw NSInternalInconsistencyException;
- }
- NSNumber *instance = nil;
- NSScanner *scanner = [NSScanner scannerWithString:userInput];
- FLEXSearchToken *bundle = [self scanToken:scanner allowed:filenameAllowed first:filenameAllowed];
- FLEXSearchToken *cls = [self scanToken:scanner allowed:identifierAllowed first:firstAllowed];
- FLEXSearchToken *method = tokens > 2 ? [self scanMethodToken:scanner instance:&instance] : nil;
- return [FLEXRuntimeKeyPath bundle:bundle
- class:cls
- method:method
- isInstance:instance
- string:userInput];
- }
- + (BOOL)allowedInKeyPath:(NSString *)text {
- if (!text.length) {
- return YES;
- }
-
- return [text rangeOfCharacterFromSet:keyPathDisallowed].location == NSNotFound;
- }
- #pragma mark Private
- + (NSUInteger)tokenCountOfString:(NSString *)userInput {
- NSUInteger escapedCount = TBCountOfStringOccurence(userInput, @"\\.");
- NSUInteger tokenCount = TBCountOfStringOccurence(userInput, @".") - escapedCount + 1;
- return tokenCount;
- }
- + (FLEXSearchToken *)scanToken:(NSScanner *)scanner allowed:(NSCharacterSet *)allowedChars first:(NSCharacterSet *)first {
- if (scanner.isAtEnd) {
- if ([scanner.string hasSuffix:@"."] && ![scanner.string hasSuffix:@"\\."]) {
- return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
- }
- return nil;
- }
- TBWildcardOptions options = TBWildcardOptionsNone;
- NSMutableString *token = [NSMutableString new];
- // Token cannot start with '.'
- if ([scanner scanString:@"." intoString:nil]) {
- @throw NSInternalInconsistencyException;
- }
- if ([scanner scanString:@"*." intoString:nil]) {
- return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
- } else if ([scanner scanString:@"*" intoString:nil]) {
- if (scanner.isAtEnd) {
- return FLEXSearchToken.any;
- }
-
- options |= TBWildcardOptionsPrefix;
- }
- NSString *tmp = nil;
- BOOL stop = NO, didScanDelimiter = NO, didScanFirstAllowed = NO;
- NSCharacterSet *disallowed = allowedChars.invertedSet;
- while (!stop && ![scanner scanString:@"." intoString:&tmp] && !scanner.isAtEnd) {
- // Scan word chars
- // In this block, we have not scanned anything yet, except maybe leading '\' or '\.'
- if (!didScanFirstAllowed) {
- if ([scanner scanCharactersFromSet:first intoString:&tmp]) {
- [token appendString:tmp];
- didScanFirstAllowed = YES;
- } else if ([scanner scanString:@"\\" intoString:nil]) {
- if (options == TBWildcardOptionsPrefix && [scanner scanString:@"." intoString:nil]) {
- [token appendString:@"."];
- } else if (scanner.isAtEnd && options == TBWildcardOptionsPrefix) {
- // Only allow standalone '\' if prefixed by '*'
- return FLEXSearchToken.any;
- } else {
- // Token starts with a number, period, or something else not allowed,
- // or token is a standalone '\' with no '*' prefix
- @throw NSInternalInconsistencyException;
- }
- } else {
- // Token starts with a number, period, or something else not allowed
- @throw NSInternalInconsistencyException;
- }
- } else if ([scanner scanCharactersFromSet:allowedChars intoString:&tmp]) {
- [token appendString:tmp];
- }
- // Scan '\.' or trailing '\'
- else if ([scanner scanString:@"\\" intoString:nil]) {
- if ([scanner scanString:@"." intoString:nil]) {
- [token appendString:@"."];
- } else if (scanner.isAtEnd) {
- // Ignore forward slash not followed by period if at end
- return [FLEXSearchToken string:token options:options | TBWildcardOptionsSuffix];
- } else {
- // Only periods can follow a forward slash
- @throw NSInternalInconsistencyException;
- }
- }
- // Scan '*.'
- else if ([scanner scanString:@"*." intoString:nil]) {
- options |= TBWildcardOptionsSuffix;
- stop = YES;
- didScanDelimiter = YES;
- }
- // Scan '*' not followed by .
- else if ([scanner scanString:@"*" intoString:nil]) {
- if (!scanner.isAtEnd) {
- // Invalid token, wildcard in middle of token
- @throw NSInternalInconsistencyException;
- }
- } else if ([scanner scanCharactersFromSet:disallowed intoString:nil]) {
- // Invalid token, invalid characters
- @throw NSInternalInconsistencyException;
- }
- }
- // Did we scan a trailing, un-escsaped '.'?
- if ([tmp isEqualToString:@"."]) {
- didScanDelimiter = YES;
- }
- if (!didScanDelimiter) {
- options |= TBWildcardOptionsSuffix;
- }
- return [FLEXSearchToken string:token options:options];
- }
- + (FLEXSearchToken *)scanMethodToken:(NSScanner *)scanner instance:(NSNumber **)instance {
- if (scanner.isAtEnd) {
- if ([scanner.string hasSuffix:@"."]) {
- return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
- }
- return nil;
- }
- if ([scanner.string hasSuffix:@"."] && ![scanner.string hasSuffix:@"\\."]) {
- // Methods cannot end with '.' except for '\.'
- @throw NSInternalInconsistencyException;
- }
-
- if ([scanner scanString:@"-" intoString:nil]) {
- *instance = @YES;
- } else if ([scanner scanString:@"+" intoString:nil]) {
- *instance = @NO;
- } else {
- if ([scanner scanString:@"*" intoString:nil]) {
- // Just checking... It has to start with one of these three!
- scanner.scanLocation--;
- } else {
- @throw NSInternalInconsistencyException;
- }
- }
- // -*foo not allowed
- if (*instance && [scanner scanString:@"*" intoString:nil]) {
- @throw NSInternalInconsistencyException;
- }
- if (scanner.isAtEnd) {
- return [FLEXSearchToken string:@"" options:TBWildcardOptionsSuffix];
- }
- return [self scanToken:scanner allowed:methodAllowed first:firstAllowed];
- }
- @end
|