1
0
kopia lustrzana https://github.com/lint/TFDidThatSay synced 2025-07-04 00:26:47 +00:00
Files
TFDidThatSay/tweak/assets/MMMarkdown/MMScanner.m

500 wiersze
13 KiB
Objective-C

//
// MMScanner.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMScanner.h"
static NSString *__delimitersForCharacter(unichar character)
{
switch (character)
{
case '[':
case ']':
return @"[]";
case '(':
case ')':
return @"()";
case '<':
case '>':
return @"<>";
case '{':
case '}':
return @"{}";
default:
[NSException raise:@"Invalid delimiter character"
format:@"Character '%C' is not a valid delimiter", character];
return nil;
}
}
@interface MMScanner ()
@property (assign, nonatomic) NSUInteger startLocation;
@property (assign, nonatomic) NSRange currentRange;
@property (assign, nonatomic, readonly) NSRange currentLineRange;
@property (assign, nonatomic) NSUInteger rangeIndex;
@property (strong, nonatomic, readonly) NSMutableArray *transactions;
@end
@implementation MMScanner
#pragma mark - Public Methods
+ (id)scannerWithString:(NSString *)aString
{
return [[self.class alloc] initWithString:aString];
}
- (id)initWithString:(NSString *)aString
{
NSArray *lineRanges = [self _lineRangesForString:aString];
return [self initWithString:aString lineRanges:lineRanges];
}
+ (id)scannerWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
return [[self.class alloc] initWithString:aString lineRanges:theLineRanges];
}
- (id)initWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
NSParameterAssert(theLineRanges.count > 0);
self = [super init];
if (self)
{
_string = aString;
_lineRanges = theLineRanges;
_rangeIndex = 0;
_transactions = [NSMutableArray new];
self.startLocation = 0;
self.currentRange = self.currentLineRange;
}
return self;
}
- (void)beginTransaction
{
NSDictionary *transaction = @{
@"rangeIndex": @(self.rangeIndex),
@"location": @(self.location),
@"startLocation": @(self.startLocation),
};
[self.transactions addObject:transaction];
self.startLocation = self.location;
}
- (void)commitTransaction:(BOOL)shouldSave
{
if (!self.transactions.count)
[NSException raise:@"Transaction underflow" format:@"Could not commit transaction because the stack is empty"];
NSDictionary *transaction = [self.transactions lastObject];
[self.transactions removeLastObject];
self.startLocation = [[transaction objectForKey:@"startLocation"] unsignedIntegerValue];
if (!shouldSave)
{
self.rangeIndex = [[transaction objectForKey:@"rangeIndex"] unsignedIntegerValue];
self.location = [[transaction objectForKey:@"location"] unsignedIntegerValue];
}
}
- (BOOL)atBeginningOfLine
{
return self.location == self.currentLineRange.location;
}
- (BOOL)atEndOfLine
{
return self.location == NSMaxRange(self.currentLineRange);
}
- (BOOL)atEndOfString
{
return self.atEndOfLine && self.rangeIndex == self.lineRanges.count - 1;
}
- (unichar)previousCharacter
{
if (self.atBeginningOfLine)
return '\0';
return [self.string characterAtIndex:self.location - 1];
}
- (unichar)nextCharacter
{
if (self.atEndOfLine)
return '\n';
return [self.string characterAtIndex:self.location];
}
- (NSString *)previousWord
{
return [self previousWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}
- (NSString *)nextWord
{
return [self nextWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}
- (NSString *)previousWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSUInteger start = MAX(self.currentLineRange.location, self.startLocation);
NSUInteger end = self.currentRange.location;
NSRange range = NSMakeRange(start, end-start);
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:NSBackwardsSearch
range:range];
if (result.location == NSNotFound)
return [self.string substringWithRange:range];
NSUInteger wordLocation = NSMaxRange(result);
NSRange wordRange = NSMakeRange(wordLocation, end-wordLocation);
return [self.string substringWithRange:wordRange];
}
- (NSString *)nextWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:0
range:self.currentRange];
if (result.location == NSNotFound)
return [self.string substringWithRange:self.currentRange];
NSRange wordRange = self.currentRange;
wordRange.length = result.location - wordRange.location;
return [self.string substringWithRange:wordRange];
}
- (void)advance
{
if (self.atEndOfLine)
return;
self.location += 1;
}
- (void)advanceToNextLine
{
// If at the last line, just go to the end of the line
if (self.rangeIndex == self.lineRanges.count - 1)
{
self.location = NSMaxRange(self.currentLineRange);
}
// Otherwise actually go to the next line
else
{
self.rangeIndex += 1;
self.currentRange = self.currentLineRange;
}
}
- (BOOL)matchString:(NSString *)string
{
if (self.currentRange.length < string.length)
return NO;
NSUInteger location = self.location;
for (NSUInteger idx=0; idx<string.length; idx++)
{
if ([string characterAtIndex:idx] != [self.string characterAtIndex:location+idx])
return NO;
}
self.location += string.length;
return YES;
}
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet
{
NSRange searchRange = self.currentRange;
NSRange range = [self.string rangeOfCharacterFromSet:[aSet invertedSet]
options:0
range:searchRange];
NSUInteger current = self.location;
if (range.location == NSNotFound)
{
self.currentRange = NSMakeRange(NSMaxRange(self.currentRange), 0);
}
else
{
self.currentRange = NSMakeRange(range.location, NSMaxRange(self.currentRange)-range.location);
}
return self.location - current;
}
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet max:(NSUInteger)maxToSkip
{
NSUInteger idx=0;
for (; idx<maxToSkip; idx++)
{
unichar character = [self nextCharacter];
if ([aSet characterIsMember:character])
[self advance];
else
break;
}
return idx;
}
- (NSUInteger)skipEmptyLines
{
NSUInteger skipped = 0;
while (![self atEndOfString])
{
[self beginTransaction];
[self skipWhitespace];
if (!self.atEndOfLine)
{
[self commitTransaction:NO];
break;
}
[self commitTransaction:YES];
[self advanceToNextLine];
skipped++;
}
return skipped;
}
- (NSUInteger)skipIndentationUpTo:(NSUInteger)maxSpacesToSkip
{
NSUInteger skipped = 0;
[self beginTransaction];
while (!self.atEndOfLine && skipped < maxSpacesToSkip)
{
unichar character = self.nextCharacter;
if (character == ' ')
skipped += 1;
else if (character == '\t')
{
skipped -= skipped % 4;
skipped += 4;
}
else
break;
[self advance];
}
[self commitTransaction:skipped <= maxSpacesToSkip];
return skipped;
}
- (NSUInteger)skipNestedBracketsWithDelimiter:(unichar)delimiter
{
NSString *delimiters = __delimitersForCharacter(delimiter);
if (delimiters == nil)
return 0;
unichar openDelimiter = [delimiters characterAtIndex:0];
unichar closeDelimeter = [delimiters characterAtIndex:1];
if ([self nextCharacter] != openDelimiter)
return 0;
[self beginTransaction];
NSUInteger location = self.location;
[self advance];
NSString *specialChars = [NSString stringWithFormat:@"%@\\", delimiters];
NSCharacterSet *boringChars = [[NSCharacterSet characterSetWithCharactersInString:specialChars] invertedSet];
NSUInteger nestingLevel = 1;
while (nestingLevel > 0)
{
if (self.atEndOfLine)
{
[self commitTransaction:NO];
return 0;
}
[self skipCharactersFromSet:boringChars];
unichar nextChar = self.nextCharacter;
[self advance];
if (nextChar == openDelimiter)
{
nestingLevel++;
}
else if (nextChar == closeDelimeter)
{
nestingLevel--;
}
else if (nextChar == '\\')
{
// skip a second character after a backslash
[self advance];
}
}
[self commitTransaction:YES];
return self.location - location;
}
- (NSUInteger)skipToEndOfLine
{
NSUInteger length = self.currentRange.length;
self.location = NSMaxRange(self.currentRange);
return length;
}
- (NSUInteger)skipToLastCharacterOfLine
{
NSUInteger length = self.currentRange.length - 1;
self.location = NSMaxRange(self.currentRange) - 1;
return length;
}
- (NSUInteger)skipWhitespace
{
return [self skipCharactersFromSet:NSCharacterSet.whitespaceCharacterSet];
}
- (NSUInteger)skipWhitespaceAndNewlines
{
NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet;
NSUInteger length = 0;
while (!self.atEndOfString)
{
if (self.atEndOfLine)
{
[self advanceToNextLine];
length++;
}
else
{
NSUInteger spaces = [self skipCharactersFromSet:whitespaceSet];
if (spaces == 0)
break;
length += spaces;
}
}
return length;
}
#pragma mark - Public Properties
- (NSUInteger)location
{
return self.currentRange.location;
}
- (void)setLocation:(NSUInteger)location
{
// If the new location isn't a part of the current range, then find the range it belongs to.
if (!NSLocationInRange(location, self.currentLineRange))
{
__block NSUInteger index = 0;
[self.lineRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSRange range = [obj rangeValue];
*stop = NSLocationInRange(location, range) || location == NSMaxRange(range);
index = idx;
}];
self.rangeIndex = index;
}
self.currentRange = NSMakeRange(location, NSMaxRange(self.currentLineRange)-location);
}
#pragma mark - Private Methods
- (NSArray *)_lineRangesForString:(NSString *)aString
{
NSMutableArray *result = [NSMutableArray array];
NSUInteger location = 0;
NSUInteger idx;
for (idx=0; idx<aString.length; idx++)
{
unichar character = [aString characterAtIndex:idx];
if (character == '\r' || character == '\n')
{
NSRange range = NSMakeRange(location, idx-location);
[result addObject:[NSValue valueWithRange:range]];
// If it's a carriage return, check for a line feed too
if (character == '\r')
{
if (idx + 1 < aString.length && [aString characterAtIndex:idx + 1] == '\n')
{
idx += 1;
}
}
location = idx + 1;
}
}
// Add the final line if the string doesn't end with a newline
if (location < aString.length)
{
NSRange range = NSMakeRange(location, aString.length-location);
[result addObject:[NSValue valueWithRange:range]];
}
return result;
}
- (NSUInteger)_locationOfCharacter:(unichar)character inRange:(NSRange)range
{
NSString *characterString = [NSString stringWithCharacters:&character length:1];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:characterString];
NSRange result = [self.string rangeOfCharacterFromSet:characterSet options:0 range:range];
return result.location;
}
#pragma mark - Private Properties
- (NSRange)currentLineRange
{
return [self.lineRanges[self.rangeIndex] rangeValue];
}
@end