Prechádzať zdrojové kódy

Add FLEXTypeEncodingParser

Tanner Bennett 6 rokov pred
rodič
commit
1647f7ab9f

+ 40 - 0
Classes/Utility/FLEXTypeEncodingParser.h

@@ -0,0 +1,40 @@
+//
+//  FLEXTypeEncodingParser.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/22/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// @return \c YES if the type is supported, \c NO otherwise
+BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger * _Nullable sizep, NSUInteger * _Nullable alignp);
+
+@interface FLEXTypeEncodingParser : NSObject
+
+/// @return whether the given type encoding can be passed to
+/// \c NSMethodSignature without it throwing an exception.
++ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding;
+
+/// @return The type encoding of an individual argument in a method's type encoding string.
+/// Pass 0 to get the type of the return value. 1 and 2 are `self` and `_cmd` respectively.
++ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
+
+/// @return The size in bytes of the typeof an individual argument in a method's type encoding string.
+/// Pass 0 to get the size of the return value. 1 and 2 are `self` and `_cmd` respectively.
++ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
+
+/// @param unaligned whether to compute the aligned or unaligned size.
+/// @return The size in bytes, or \c -1 if the type encoding is unsupported.
+/// Do not pass in the result of \c method_getTypeEncoding
++ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut unaligned:(BOOL)unaligned;
+
+/// Defaults to \C unaligned:NO
++ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 516 - 0
Classes/Utility/FLEXTypeEncodingParser.m

@@ -0,0 +1,516 @@
+//
+//  FLEXTypeEncodingParser.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/22/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXTypeEncodingParser.h"
+#import "FLEXRuntimeUtility.h"
+
+#define S(ch) [NSString stringWithFormat:@"%c" , ch]
+
+BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) {
+    NSInteger size = 0, align = 0;
+    size = [FLEXTypeEncodingParser sizeForTypeEncoding:@(type) alignment:&align];
+    
+    if (size == -1) {
+        return NO;
+    }
+    
+    if (sizep) {
+        *sizep = (NSUInteger)size;
+    }
+    
+    if (alignp) {
+        *alignp = (NSUInteger)size;
+    }
+    
+    return YES;
+}
+
+@interface FLEXTypeEncodingParser ()
+@property (nonatomic, readonly) NSScanner *scan;
+@property (nonatomic, readonly) NSString *scanned;
+@property (nonatomic, readonly) NSString *unscanned;
+@end
+
+@implementation FLEXTypeEncodingParser
+
+- (NSString *)scanned {
+    return [self.scan.string substringToIndex:self.scan.scanLocation];
+}
+
+- (NSString *)unscanned {
+    return [self.scan.string substringFromIndex:self.scan.scanLocation];
+}
+
+#pragma mark Initialization
+
+- (id)initWithObjCTypes:(NSString *)typeEncoding {
+    self = [super init];
+    if (self) {
+        _scan = [NSScanner scannerWithString:typeEncoding];
+        _scan.caseSensitive = YES;
+    }
+
+    return self;
+}
+
+#pragma mark Public
+
++ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding {
+    FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
+    while (!parser.scan.isAtEnd) {
+        if ([parser scanAndGetSizeAndAlignForNextType:nil] == -1) {
+            return NO;
+        }
+    }
+    
+    return YES;
+}
+
++ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
+    FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
+
+    // Scan up to the argument we want
+    for (NSUInteger i = 0; i < idx; i++) {
+        if (![parser scanPastArg]) {
+            [NSException raise:NSRangeException
+                        format:@"Index %lu out of bounds for type encoding '%@'", idx, typeEncoding];
+        }
+    }
+
+    return [parser scanArg];
+}
+
++ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
+    return [self sizeForTypeEncoding:[self type:typeEncoding forMethodArgumentAtIndex:idx] alignment:nil];
+}
+
++ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut {
+    return [self sizeForTypeEncoding:type alignment:alignOut unaligned:NO];
+}
+
++ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned {
+    ssize_t align = 0;
+    ssize_t size = [[[self alloc] initWithObjCTypes:type] scanAndGetSizeAndAlignForNextType:&align];
+
+    if (size == -1) {
+        return size;
+    }
+    
+    if (alignOut) {
+        *alignOut = align;
+    }
+
+    if (unaligned) {
+        return size;
+    } else {
+        size += size % align;
+        return size;
+    }
+}
+
+#pragma mark Private
+
+/// Size in BYTES
+- (ssize_t)sizeForType:(FLEXTypeEncoding)type {
+    switch (type) {
+        case FLEXTypeEncodingChar: return sizeof(char);
+        case FLEXTypeEncodingInt: return sizeof(int);
+        case FLEXTypeEncodingShort: return sizeof(short);
+        case FLEXTypeEncodingLong: return sizeof(long);
+        case FLEXTypeEncodingLongLong: return sizeof(long long);
+        case FLEXTypeEncodingUnsignedChar: return sizeof(unsigned char);
+        case FLEXTypeEncodingUnsignedInt: return sizeof(unsigned int);
+        case FLEXTypeEncodingUnsignedShort: return sizeof(unsigned short);
+        case FLEXTypeEncodingUnsignedLong: return sizeof(unsigned long);
+        case FLEXTypeEncodingUnsignedLongLong: return sizeof(unsigned long long);
+        case FLEXTypeEncodingFloat: return sizeof(float);
+        case FLEXTypeEncodingDouble: return sizeof(double);
+        case FLEXTypeEncodingLongDouble: return sizeof(long double);
+        case FLEXTypeEncodingCBool: return sizeof(_Bool);
+        case FLEXTypeEncodingVoid: return 0;
+        case FLEXTypeEncodingCString: return sizeof(char *);
+        case FLEXTypeEncodingObjcObject:  return sizeof(id);
+        case FLEXTypeEncodingObjcClass:  return sizeof(Class);
+        case FLEXTypeEncodingSelector: return sizeof(SEL);
+        // Unknown / '?' is typically a pointer. In the rare case
+        // it isn't, such as in '{?=...}', it is never passed here.
+        case FLEXTypeEncodingUnknown:
+        case FLEXTypeEncodingPointer: return sizeof(uintptr_t);
+
+        default: return -1;
+    }
+}
+
+/// Size in bytes
+- (ssize_t)scanAndGetSizeAndAlignForNextType:(ssize_t *)alignment {
+    NSUInteger start = self.scan.scanLocation;
+
+    // Check for void first
+    if ([self scanChar:FLEXTypeEncodingVoid]) {
+        // Skip argument frame for method signatures
+        [self scanSize];
+        return 0;
+    }
+
+    // Scan optional const
+    [self scanChar:FLEXTypeEncodingConst];
+
+    // Check for pointer, then scan next
+    if ([self scanChar:FLEXTypeEncodingPointer]) {
+        // Recurse to scan something else
+        if ([self scanPastArg]) {
+            // Skip optional frame offset
+            [self scanSize];
+            
+            ssize_t size = [self sizeForType:FLEXTypeEncodingPointer];
+            if (alignment) {
+                *alignment = size;
+            }
+            return size;
+        } else {
+            // Scan failed, abort
+            self.scan.scanLocation = start;
+            return -1;
+        }
+    }
+
+    // Check for struct/union/array
+    if ([self canScanChar:FLEXTypeEncodingStructBegin] ||
+      [self canScanChar:FLEXTypeEncodingUnionBegin] ||
+      [self canScanChar:FLEXTypeEncodingArrayBegin]) {
+        NSUInteger backup = self.scan.scanLocation;
+
+        // Ensure we have a closing tag
+        if (![self scanPair:FLEXTypeEncodingStructBegin close:FLEXTypeEncodingStructEnd] &&
+          ![self scanPair:FLEXTypeEncodingUnionBegin close:FLEXTypeEncodingUnionEnd] &&
+          ![self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
+            // Scan failed, abort
+            self.scan.scanLocation = start;
+            return -1;
+        }
+
+        // Scan the next thing until we scan the closing tag
+        BOOL structOrUnion = NO;
+        NSInteger arrayCount = -1;
+        self.scan.scanLocation = backup;
+        FLEXTypeEncoding closing;
+        if ([self scanChar:FLEXTypeEncodingStructBegin]) {
+            closing = FLEXTypeEncodingStructEnd;
+            structOrUnion = YES;
+        } else if ([self scanChar:FLEXTypeEncodingUnionBegin]) {
+            closing = FLEXTypeEncodingUnionEnd;
+            structOrUnion = YES;
+        } else {
+            // Assert because code above did confirm a closing tag exists
+            assert([self scanChar:FLEXTypeEncodingArrayBegin]);
+            closing = FLEXTypeEncodingArrayEnd;
+            
+            arrayCount = [self scanSize];
+            if (!arrayCount) {
+                // Arrays must have a count after the opening brace
+                self.scan.scanLocation = start;
+                return -1;
+            }
+        }
+
+        if (structOrUnion) {
+            // If we encounter the ?= portion of something like {?=b8b4b1b1b18[8S]}
+            // then we skip over it, since it means nothing to us in this context.
+            // It is completely optional, and if it fails, we go right back where we were.
+            [self scanTypeName];
+        }
+
+        // Sum sizes of members together:
+        // Scan for bitfields before checking for other members
+        //
+        // Arrays will only have one "member," but
+        // this logic still works for them
+        ssize_t sizeSoFar = 0;
+        ssize_t maxAlign = 0;
+
+        while (![self scanChar:closing]) {
+            // Check for bitfields, which we cannot support because
+            // type encodings for bitfields do not include alignment info
+            if ([self scanChar:FLEXTypeEncodingBitField]) {
+                self.scan.scanLocation = start;
+                return -1;
+            }
+
+            // Structure fields could be named
+            [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
+
+            ssize_t align = 0;
+            ssize_t size = [self scanAndGetSizeAndAlignForNextType:&align];
+            if (size == -1) {
+                self.scan.scanLocation = start;
+                return -1;
+            }
+            
+            sizeSoFar += structOrUnion ? size : (size * arrayCount);
+            maxAlign = MAX(maxAlign, align);
+        }
+        
+        // Skip optional frame offset
+        [self scanSize];
+
+        if (alignment) {
+            *alignment = maxAlign;
+        }
+
+        return sizeSoFar;
+    }
+
+    // Scan single thing and possible size and return
+    ssize_t size = 0;
+    FLEXTypeEncoding t;
+    if ([self scanChar:FLEXTypeEncodingUnknown into:&t] ||
+      [self scanChar:FLEXTypeEncodingChar into:&t] ||
+      [self scanChar:FLEXTypeEncodingInt into:&t] ||
+      [self scanChar:FLEXTypeEncodingShort into:&t] ||
+      [self scanChar:FLEXTypeEncodingLong into:&t] ||
+      [self scanChar:FLEXTypeEncodingLongLong into:&t] ||
+      [self scanChar:FLEXTypeEncodingUnsignedChar into:&t] ||
+      [self scanChar:FLEXTypeEncodingUnsignedInt into:&t] ||
+      [self scanChar:FLEXTypeEncodingUnsignedShort into:&t] ||
+      [self scanChar:FLEXTypeEncodingUnsignedLong into:&t] ||
+      [self scanChar:FLEXTypeEncodingUnsignedLongLong into:&t] ||
+      [self scanChar:FLEXTypeEncodingFloat into:&t] ||
+      [self scanChar:FLEXTypeEncodingDouble into:&t] ||
+      [self scanChar:FLEXTypeEncodingLongDouble into:&t] ||
+      [self scanChar:FLEXTypeEncodingCBool into:&t] ||
+      [self scanChar:FLEXTypeEncodingCString into:&t] ||
+      [self scanChar:FLEXTypeEncodingSelector into:&t] ||
+      [self scanChar:FLEXTypeEncodingBitField into:&t]) {
+        // Skip optional frame offset
+        [self scanSize];
+        
+        if (t == FLEXTypeEncodingBitField) {
+            self.scan.scanLocation = start;
+            return -1;
+        } else {
+            // Compute size
+            size = [self sizeForType:t];
+        }
+    }
+
+    // These might have numbers OR quotes after them
+    else if ([self scanChar:FLEXTypeEncodingObjcObject] || [self scanChar:FLEXTypeEncodingObjcClass]) {
+        // Skip optional frame offset
+        [self scanSize];
+        [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
+        size = sizeof(id);
+    }
+
+    if (size) {
+        // Alignment of scalar types is its size
+        if (alignment) {
+            *alignment = size;
+        }
+
+        return size;
+    }
+
+    self.scan.scanLocation = start;
+    return -1;
+}
+
+- (BOOL)scanString:(NSString *)str {
+    return [self.scan scanString:str intoString:nil];
+}
+
+- (BOOL)canScanString:(NSString *)str {
+    if ([self scanString:str]) {
+        self.scan.scanLocation -= str.length;
+        return YES;
+    }
+
+    return NO;
+}
+
+- (BOOL)canScanChar:(char)c {
+    return [self canScanString:S(c)];
+}
+
+- (BOOL)scanChar:(char)c {
+    return [self scanString:S(c)];
+}
+
+- (BOOL)scanChar:(char)c into:(char *)ref {
+    if ([self scanString:S(c)]) {
+        *ref = c;
+        return YES;
+    }
+
+    return NO;
+}
+
+- (ssize_t)scanSize {
+    NSInteger size = 0;
+    if ([self.scan scanInteger:&size]) {
+        return size;
+    }
+
+    return 0;
+}
+
+- (NSString *)scanPair:(char)c1 close:(char)c2 {
+    // Starting position and string variables
+    NSUInteger start = self.scan.scanLocation;
+    NSString *s1 = S(c1);
+
+    // Scan opening tag
+    if (![self scanChar:c1]) {
+        self.scan.scanLocation = start;
+        return nil;
+    }
+
+    // Character set for scanning up to either symbol
+    NSCharacterSet *bothChars = ({
+        NSString *bothCharsStr = [NSString stringWithFormat:@"%c%c" , c1, c2];
+        [NSCharacterSet characterSetWithCharactersInString:bothCharsStr];
+    });
+
+    // Stack for finding pairs, starting with the opening symbol
+    NSMutableArray *stack = [NSMutableArray arrayWithObject:s1];
+
+    // Algorithm for scanning to the closing end of a pair of opening/closing symbols
+    // scanUpToCharactersFromSet:intoString: returns NO if you're already at one of the chars,
+    // so we need to check if we can actually scan one if it returns NO
+    while ([self.scan scanUpToCharactersFromSet:bothChars intoString:nil] ||
+           [self canScanChar:c1] || [self canScanChar:c2]) {
+        // Closing symbol found
+        if ([self scanChar:c2]) {
+            if (!stack.count) {
+                // Abort, no matching opening symbol
+                self.scan.scanLocation = start;
+                return nil;
+            }
+
+            // Pair found, pop opening symbol
+            [stack removeLastObject];
+            // Exit loop if we reached the closing brace we needed
+            if (!stack.count) {
+                break;
+            }
+        }
+        // Opening symbol found
+        if ([self scanChar:c1]) {
+            // Begin pair
+            [stack addObject:s1];
+        }
+    }
+
+    if (stack.count) {
+        // Abort, no matching closing symbol
+        self.scan.scanLocation = start;
+        return nil;
+    }
+
+    // Slice out the string we just scanned
+    return [self.scan.string
+        substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
+    ];
+}
+
+- (BOOL)scanPastArg {
+    NSUInteger start = self.scan.scanLocation;
+
+    // Check for void first
+    if ([self scanChar:FLEXTypeEncodingVoid]) {
+        return YES;
+    }
+
+    // Scan optional const
+    [self scanChar:FLEXTypeEncodingConst];
+
+    // Check for pointer, then scan next
+    if ([self scanChar:FLEXTypeEncodingPointer]) {
+        // Recurse to scan something else
+        if ([self scanPastArg]) {
+            return YES;
+        } else {
+            // Scan failed, abort
+            self.scan.scanLocation = start;
+            return NO;
+        }
+    }
+
+    // Check for struct/union/array, scan past it
+    if ([self scanPair:FLEXTypeEncodingStructBegin close:FLEXTypeEncodingStructEnd] ||
+      [self scanPair:FLEXTypeEncodingUnionBegin close:FLEXTypeEncodingUnionEnd] ||
+      [self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
+        return YES;
+    }
+
+    // Scan single thing and possible size and return
+    if ([self scanChar:FLEXTypeEncodingUnknown] ||
+      [self scanChar:FLEXTypeEncodingChar] ||
+      [self scanChar:FLEXTypeEncodingInt] ||
+      [self scanChar:FLEXTypeEncodingShort] ||
+      [self scanChar:FLEXTypeEncodingLong] ||
+      [self scanChar:FLEXTypeEncodingLongLong] ||
+      [self scanChar:FLEXTypeEncodingUnsignedChar] ||
+      [self scanChar:FLEXTypeEncodingUnsignedInt] ||
+      [self scanChar:FLEXTypeEncodingUnsignedShort] ||
+      [self scanChar:FLEXTypeEncodingUnsignedLong] ||
+      [self scanChar:FLEXTypeEncodingUnsignedLongLong] ||
+      [self scanChar:FLEXTypeEncodingFloat] ||
+      [self scanChar:FLEXTypeEncodingDouble] ||
+      [self scanChar:FLEXTypeEncodingLongDouble] ||
+      [self scanChar:FLEXTypeEncodingCBool] ||
+      [self scanChar:FLEXTypeEncodingCString] ||
+      [self scanChar:FLEXTypeEncodingSelector] ||
+      [self scanChar:FLEXTypeEncodingBitField]) {
+        // Size is optional
+        [self scanSize];
+        return YES;
+    }
+
+    // These might have numbers OR quotes after them
+    if ([self scanChar:FLEXTypeEncodingObjcObject] || [self scanChar:FLEXTypeEncodingObjcClass]) {
+        [self scanSize] || [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
+        return YES;
+    }
+
+    self.scan.scanLocation = start;
+    return NO;
+}
+
+- (NSString *)scanArg {
+    NSUInteger start = self.scan.scanLocation;
+    if (![self scanPastArg]) {
+        return nil;
+    }
+
+    return [self.scan.string
+        substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
+    ];
+}
+
+- (BOOL)scanTypeName {
+    NSUInteger start = self.scan.scanLocation;
+
+    // The ?= portion of something like {?=b8b4b1b1b18[8S]}
+    if ([self scanChar:FLEXTypeEncodingUnknown]) {
+        if (![self scanString:@"="]) {
+            // No size information available for strings like {?}
+            self.scan.scanLocation = start;
+            return NO;
+        }
+    } else if ([self.scan scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:nil]) {
+        if (![self scanString:@"="]) {
+            // No size information available for strings like {CGPoint}
+            self.scan.scanLocation = start;
+            return NO;
+        }
+    }
+
+    return YES;
+}
+
+@end

Classes/Utility/FLEXKeyboardHelpViewController.h → Classes/Utility/Keyboard/FLEXKeyboardHelpViewController.h


Classes/Utility/FLEXKeyboardHelpViewController.m → Classes/Utility/Keyboard/FLEXKeyboardHelpViewController.m


Classes/Utility/FLEXKeyboardShortcutManager.h → Classes/Utility/Keyboard/FLEXKeyboardShortcutManager.h


Classes/Utility/FLEXKeyboardShortcutManager.m → Classes/Utility/Keyboard/FLEXKeyboardShortcutManager.m


+ 23 - 5
FLEX.xcodeproj/project.pbxproj

@@ -224,6 +224,7 @@
 		C383C3C223B6B429007A321B /* NSTimer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C023B6B429007A321B /* NSTimer+Blocks.h */; };
 		C383C3C523B6BB81007A321B /* FLEXCodeFontCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */; };
 		C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */; };
+		C3854DF023F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */; };
 		C3878DBA23A749960038FDBE /* FLEXVariableEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3878DB823A749960038FDBE /* FLEXVariableEditorViewController.m */; };
 		C3878DBB23A749960038FDBE /* FLEXVariableEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3878DB923A749960038FDBE /* FLEXVariableEditorViewController.h */; };
 		C3878DBC23A749F70038FDBE /* FLEXFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */; };
@@ -268,6 +269,7 @@
 		C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */; };
 		C398682823AC36EC00E9E391 /* FLEXViewShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F527C4231891F6009CBA07 /* FLEXViewShortcuts.m */; };
 		C398682923AC370100E9E391 /* FLEXViewShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F527C3231891F6009CBA07 /* FLEXViewShortcuts.h */; };
+		C39EADC923F37B89005618BE /* FLEXTypeEncodingParser.m in Sources */ = {isa = PBXBuildFile; fileRef = C3854DF123F36C9E00FCD1E2 /* FLEXTypeEncodingParser.m */; };
 		C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */; };
 		C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */; };
 		C3A9422C23C3DA39006871A3 /* FHSView.h in Headers */ = {isa = PBXBuildFile; fileRef = C3A9422823C3DA39006871A3 /* FHSView.h */; };
@@ -559,6 +561,9 @@
 		C383C3C023B6B429007A321B /* NSTimer+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+Blocks.h"; sourceTree = "<group>"; };
 		C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCodeFontCell.h; sourceTree = "<group>"; };
 		C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCodeFontCell.m; sourceTree = "<group>"; };
+		C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTypeEncodingParserTests.m; sourceTree = "<group>"; };
+		C3854DF123F36C9E00FCD1E2 /* FLEXTypeEncodingParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTypeEncodingParser.m; sourceTree = "<group>"; };
+		C3854DF223F36C9E00FCD1E2 /* FLEXTypeEncodingParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTypeEncodingParser.h; sourceTree = "<group>"; };
 		C3878DB823A749960038FDBE /* FLEXVariableEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXVariableEditorViewController.m; sourceTree = "<group>"; };
 		C3878DB923A749960038FDBE /* FLEXVariableEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXVariableEditorViewController.h; sourceTree = "<group>"; };
 		C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCarouselCell.h; sourceTree = "<group>"; };
@@ -678,9 +683,10 @@
 		1C27A8B71F0E5A0400F0D02D /* FLEXTests */ = {
 			isa = PBXGroup;
 			children = (
+				C33C825A23159EAF00DD2451 /* FLEXTests.m */,
 				1C27A8B81F0E5A0400F0D02D /* FLEXTestsMethodsList.m */,
+				C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */,
 				1C27A8BA1F0E5A0400F0D02D /* Info.plist */,
-				C33C825A23159EAF00DD2451 /* FLEXTests.m */,
 			);
 			path = FLEXTests;
 			sourceTree = "<group>";
@@ -752,6 +758,7 @@
 			children = (
 				C36FBFB8230F3B52008D95D5 /* Runtime */,
 				C387C88022E0D22600750E58 /* Categories */,
+				C3854DF423F36CB300FCD1E2 /* Keyboard */,
 				3A4C94551B5B21410088C3F2 /* FLEXHeapEnumerator.h */,
 				3A4C94561B5B21410088C3F2 /* FLEXHeapEnumerator.m */,
 				3A4C94591B5B21410088C3F2 /* FLEXResources.h */,
@@ -764,10 +771,8 @@
 				7349FD6922B93CDF00051810 /* FLEXColor.m */,
 				C31D93E623E38E97005517BF /* FLEXBlockDescription.h */,
 				C31D93E723E38E97005517BF /* FLEXBlockDescription.m */,
-				942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */,
-				942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */,
-				94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */,
-				94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */,
+				C3854DF223F36C9E00FCD1E2 /* FLEXTypeEncodingParser.h */,
+				C3854DF123F36C9E00FCD1E2 /* FLEXTypeEncodingParser.m */,
 			);
 			path = Utility;
 			sourceTree = "<group>";
@@ -1115,6 +1120,17 @@
 			path = Runtime;
 			sourceTree = "<group>";
 		};
+		C3854DF423F36CB300FCD1E2 /* Keyboard */ = {
+			isa = PBXGroup;
+			children = (
+				942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */,
+				942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */,
+				94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */,
+				94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */,
+			);
+			path = Keyboard;
+			sourceTree = "<group>";
+		};
 		C387C88022E0D22600750E58 /* Categories */ = {
 			isa = PBXGroup;
 			children = (
@@ -1565,6 +1581,7 @@
 			files = (
 				C33C825B23159EAF00DD2451 /* FLEXTests.m in Sources */,
 				1C27A8B91F0E5A0400F0D02D /* FLEXTestsMethodsList.m in Sources */,
+				C3854DF023F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1684,6 +1701,7 @@
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
 				C3531B9E23E69BB200A184AD /* FLEXManager+Networking.m in Sources */,
+				C39EADC923F37B89005618BE /* FLEXTypeEncodingParser.m in Sources */,
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,

+ 189 - 0
FLEXTests/FLEXTypeEncodingParserTests.m

@@ -0,0 +1,189 @@
+//
+//  FLEXTypeEncodingParserTests.m
+//  FLEXTests
+//
+//  Created by Tanner Bennett on 8/25/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+#import <UIKit/UIKit.h>
+#import "FLEXTypeEncodingParser.h"
+
+#define Type(t) @(@encode(t))
+#define TypeSizeAlignPair(t) Type(t): @[@(sizeof(t)), @(__alignof__(t))]
+
+@interface FLEXTypeEncodingParserTests : XCTestCase {
+    struct {
+        id __unsafe_unretained *bar;
+        void (*foo[5])(void);
+        SEL abc;
+        Class cls;
+        int baz;
+        NSString * __unsafe_unretained str;
+        id<NSCopying, NSCoding> __unsafe_unretained proto;
+        NSDictionary<NSCopying, NSCoding> * __unsafe_unretained another;
+    } _yikes;
+}
+
+@property (nonatomic, readonly) NSDictionary<NSString *, NSArray<NSNumber *> *> *typesToSizes;
+@end
+
+typedef struct Anon {
+    NSString<NSCopying,NSCoding> *bar;
+    int baz;
+} Anon;
+
+@implementation FLEXTypeEncodingParserTests
+
+- (void)setUp {
+    
+    memset(&_yikes.bar, 0x55, sizeof(id));
+    memset(&_yikes.foo, 0xff, sizeof(_yikes.foo));
+    memset(&_yikes.abc, 0x88, sizeof(SEL));
+    
+    _typesToSizes = @{
+        TypeSizeAlignPair(__typeof__(_yikes)),
+        @"{Anon=\"bar\"@\"NSString<NSCopying><NSCoding>\"\"baz\"i}" : @[@(sizeof(Anon)), @(__alignof__(Anon))],
+        Type(NSDecimal) : @[@-1, @0], // Real size is 16 on 64 bit machines
+        TypeSizeAlignPair(char),
+        TypeSizeAlignPair(short),
+        TypeSizeAlignPair(int),
+        TypeSizeAlignPair(long),
+        TypeSizeAlignPair(long long),
+        TypeSizeAlignPair(float),
+        TypeSizeAlignPair(double),
+        TypeSizeAlignPair(long double),
+        TypeSizeAlignPair(Class),
+        TypeSizeAlignPair(id),
+        TypeSizeAlignPair(CGPoint),
+        TypeSizeAlignPair(CGRect),
+        TypeSizeAlignPair(char *),
+        TypeSizeAlignPair(long *),
+        TypeSizeAlignPair(Class *),
+        TypeSizeAlignPair(CGRect *)
+    };
+}
+
+- (void)testTypeEncodingParser {
+    [self.typesToSizes enumerateKeysAndObjectsUsingBlock:^(NSString *typeString, NSArray<NSNumber *> *sa, BOOL *stop) {
+        if (sa[0].longLongValue >= 0) {
+            XCTAssertNoThrow(NSGetSizeAndAlignment(typeString.UTF8String, nil, nil));
+        } else {
+            XCTAssertThrows(NSGetSizeAndAlignment(typeString.UTF8String, nil, nil));
+        }
+        
+        ssize_t align = 0;
+        ssize_t size = [FLEXTypeEncodingParser sizeForTypeEncoding:typeString alignment:&align];
+        XCTAssertEqual(size, sa[0].longValue);
+        XCTAssertEqual(align, sa[1].longValue);
+    }];
+}
+
+- (void)testExpectedStructureSizes {
+    typedef struct _FooBytes {
+        uint8_t x: 3;
+        struct {
+            uint8_t a: 1;
+            uint8_t b: 2;
+        } y;
+        uint8_t z: 5;
+    } FooBytes;
+
+    typedef struct _FooInts {
+        unsigned int x: 3;
+        struct {
+            unsigned int a: 1;
+            unsigned int b: 2;
+        } y;
+        unsigned int z: 5;
+    } FooInts;
+
+    typedef struct _Bar {
+        unsigned int x: 3;
+        unsigned int z: 5;
+        struct {
+            unsigned int a: 1;
+            unsigned int b: 2;
+        } y;
+    } Bar;
+
+    typedef struct _ArrayInMiddle {
+        unsigned int x: 3;
+        unsigned char c[2];
+        unsigned int z: 5;
+    } ArrayInMiddle;
+    typedef struct _ArrayAtEnd {
+        unsigned int x: 3;
+        unsigned int z: 5;
+        unsigned char c[2];
+    } ArrayAtEnd;
+
+    typedef struct _OneBit {
+        uint8_t x: 1;
+    } OneBit;
+    typedef struct _OneByte {
+        uint8_t x;
+    } OneByte;
+    typedef struct _TwoBytes {
+        uint8_t x, y;
+    } TwoBytes;
+    typedef struct _TwoJoinedBytesAndOneByte {
+        uint16_t x;
+        uint8_t y;
+    } TwoJoinedBytesAndOneByte;
+
+    // Structs have the alignment of the size of their smallest member, recursively.
+    // That is, a struct has the alignment of the greater of the size of its
+    // largest direct member or the largest alignment of it's nested structs.
+    XCTAssertEqual(__alignof__(FooBytes), 1);
+    XCTAssertEqual(__alignof__(FooInts), 4);
+    XCTAssertEqual(__alignof__(ArrayInMiddle), 4);
+    XCTAssertEqual(__alignof__(ArrayAtEnd), 4);
+    XCTAssertEqual(__alignof__(OneBit), 1);
+    XCTAssertEqual(__alignof__(OneByte), 1);
+    XCTAssertEqual(__alignof__(TwoBytes), 1);
+    XCTAssertEqual(__alignof__(TwoJoinedBytesAndOneByte), 2);
+
+    // Nested structs are aligned before and after, if between bitfields
+    XCTAssertEqual(sizeof(FooBytes), 3);
+    XCTAssertEqual(sizeof(FooInts), 12);
+    // Bitfields are not aligned at all and they will pack if adjacent to one another
+    XCTAssertEqual(sizeof(Bar), 8);
+    // Structs are resized to match their alignment
+    XCTAssertEqual(sizeof(OneBit), 1);
+    XCTAssertEqual(sizeof(OneByte), 1);
+    XCTAssertEqual(sizeof(TwoJoinedBytesAndOneByte), 4);
+    // Arrays do not affect alignment like nested structs do
+    XCTAssertEqual(sizeof(ArrayInMiddle), 4);
+    XCTAssertEqual(sizeof(ArrayAtEnd), 4);
+
+    // Test my method of converting calculated sizes to actual sizes
+    // for FLEXTypeEncodingParser
+    #define RoundUpToMultipleOf4(num) ((num + 3) & ~0x03)
+    XCTAssertEqual(RoundUpToMultipleOf4(1), 4);
+    XCTAssertEqual(RoundUpToMultipleOf4(2), 4);
+    XCTAssertEqual(RoundUpToMultipleOf4(3), 4);
+    XCTAssertEqual(RoundUpToMultipleOf4(4), 4);
+    XCTAssertEqual(RoundUpToMultipleOf4(5), 8);
+    XCTAssertEqual(RoundUpToMultipleOf4(6), 8);
+    XCTAssertEqual(RoundUpToMultipleOf4(7), 8);
+    XCTAssertEqual(RoundUpToMultipleOf4(8), 8);
+    XCTAssertEqual(RoundUpToMultipleOf4(9), 12);
+    XCTAssertEqual(RoundUpToMultipleOf4(10), 12);
+    XCTAssertEqual(RoundUpToMultipleOf4(11), 12);
+    XCTAssertEqual(RoundUpToMultipleOf4(12), 12);
+    XCTAssertEqual(RoundUpToMultipleOf4(13), 16);
+}
+
+- (void)testMethodSignatures {
+    NSArray<NSString *> *signatures = @[
+        @"^{__CFBundle=}16@0:8", @"v24@0:8@16", @"@40@0:8@16{CGSize=dd}24"
+    ];
+    
+    for (NSString *signature in signatures) {
+        XCTAssertTrue([FLEXTypeEncodingParser methodTypeEncodingSupported:signature]);
+    }
+}
+
+@end