mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-06 22:44:22 +08:00
Replaced RegExp method parser with recursive descent
Summary: public This diff replaces the RegEx module method parser with a handwritten recursive descent parser that's faster and easier to maintain. The new parser is ~8 times faster when tested on the UIManager.managerChildren() method, and uses ~1/10 as much RAM. The new parser also supports lightweight generics, and is more tolerant of white space. (This means that you now can – and should – use types like `NSArray<NSString *> *` for your exported properties and method arguments, instead of `NSStringArray`). Reviewed By: jspahrsummers Differential Revision: D2736636 fb-gh-sync-id: f6a11431935fa8acc8ac36f3471032ec9a1c8490
This commit is contained in:
committed by
facebook-github-bot-4
parent
ce7c0b735f
commit
88ac40666c
@@ -15,6 +15,7 @@
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTParserUtils.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
|
||||
@@ -50,7 +51,7 @@ typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
|
||||
Class _moduleClass;
|
||||
NSInvocation *_invocation;
|
||||
NSArray<RCTArgumentBlock> *_argumentBlocks;
|
||||
NSString *_objCMethodName;
|
||||
NSString *_methodSignature;
|
||||
SEL _selector;
|
||||
NSDictionary *_profileArgs;
|
||||
}
|
||||
@@ -68,77 +69,106 @@ static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index,
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
void RCTParseObjCMethodName(NSString **, NSArray<RCTMethodArgument *> **);
|
||||
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument *> **arguments)
|
||||
// returns YES if the selector ends in a colon (indicating that there is at
|
||||
// least one argument, and maybe more selector parts) or NO if it doesn't.
|
||||
static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
|
||||
{
|
||||
static NSRegularExpression *typeNameRegex;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
|
||||
NSString *constPattern = @"(?:const)";
|
||||
NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))";
|
||||
NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))";
|
||||
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:(%@)|%@|(%@)|(%@))\\s*)",
|
||||
unusedPattern, constPattern, nullablePattern, nonnullPattern];
|
||||
NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
|
||||
annotationPattern];
|
||||
typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
|
||||
});
|
||||
|
||||
// Extract argument types
|
||||
NSString *methodName = *objCMethodName;
|
||||
NSRange methodRange = {0, methodName.length};
|
||||
NSMutableArray *args = [NSMutableArray array];
|
||||
[typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
|
||||
NSRange typeRange = [result rangeAtIndex:5];
|
||||
NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id";
|
||||
BOOL unused = ([result rangeAtIndex:2].length > 0);
|
||||
RCTNullability nullability = [result rangeAtIndex:3].length ? RCTNullable :
|
||||
[result rangeAtIndex:4].length ? RCTNonnullable : RCTNullabilityUnspecified;
|
||||
[args addObject:[[RCTMethodArgument alloc] initWithType:type
|
||||
nullability:nullability
|
||||
unused:unused]];
|
||||
}];
|
||||
*arguments = [args copy];
|
||||
|
||||
// Remove the parameter types and names
|
||||
methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
|
||||
range:methodRange
|
||||
withTemplate:@""];
|
||||
|
||||
// Remove whitespace
|
||||
methodName = [methodName stringByReplacingOccurrencesOfString:@"\n" withString:@""];
|
||||
methodName = [methodName stringByReplacingOccurrencesOfString:@" " withString:@""];
|
||||
|
||||
// Strip trailing semicolon
|
||||
if ([methodName hasSuffix:@";"]) {
|
||||
methodName = [methodName substringToIndex:methodName.length - 1];
|
||||
NSString *selectorPart;
|
||||
if (RCTParseIdentifier(input, &selectorPart)) {
|
||||
[selector appendString:selectorPart];
|
||||
}
|
||||
|
||||
*objCMethodName = methodName;
|
||||
RCTSkipWhitespace(input);
|
||||
if (RCTReadChar(input, ':')) {
|
||||
[selector appendString:@":"];
|
||||
RCTSkipWhitespace(input);
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
moduleClass:(Class)moduleClass
|
||||
static BOOL RCTParseUnused(const char **input)
|
||||
{
|
||||
return RCTReadString(input, "__unused") ||
|
||||
RCTReadString(input, "__attribute__((unused))");
|
||||
}
|
||||
|
||||
static RCTNullability RCTParseNullability(const char **input)
|
||||
{
|
||||
if (RCTReadString(input, "nullable")) {
|
||||
return RCTNullable;
|
||||
} else if (RCTReadString(input, "nonnull")) {
|
||||
return RCTNonnullable;
|
||||
}
|
||||
return RCTNullabilityUnspecified;
|
||||
}
|
||||
|
||||
SEL RCTParseMethodSignature(NSString *, NSArray<RCTMethodArgument *> **);
|
||||
SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument *> **arguments)
|
||||
{
|
||||
const char *input = methodSignature.UTF8String;
|
||||
RCTSkipWhitespace(&input);
|
||||
|
||||
NSMutableArray *args;
|
||||
NSMutableString *selector = [NSMutableString new];
|
||||
while (RCTParseSelectorPart(&input, selector)) {
|
||||
if (!args) {
|
||||
args = [NSMutableArray new];
|
||||
}
|
||||
|
||||
// Parse type
|
||||
if (RCTReadChar(&input, '(')) {
|
||||
RCTSkipWhitespace(&input);
|
||||
|
||||
BOOL unused = RCTParseUnused(&input);
|
||||
RCTSkipWhitespace(&input);
|
||||
|
||||
RCTNullability nullability = RCTParseNullability(&input);
|
||||
RCTSkipWhitespace(&input);
|
||||
|
||||
NSString *type = RCTParseType(&input);
|
||||
[args addObject:[[RCTMethodArgument alloc] initWithType:type
|
||||
nullability:nullability
|
||||
unused:unused]];
|
||||
RCTSkipWhitespace(&input);
|
||||
RCTReadChar(&input, ')');
|
||||
RCTSkipWhitespace(&input);
|
||||
} else {
|
||||
// Type defaults to id if unspecified
|
||||
[args addObject:[[RCTMethodArgument alloc] initWithType:@"id"
|
||||
nullability:RCTNullable
|
||||
unused:NO]];
|
||||
}
|
||||
|
||||
// Argument name
|
||||
RCTParseIdentifier(&input, NULL);
|
||||
RCTSkipWhitespace(&input);
|
||||
}
|
||||
|
||||
*arguments = [args copy];
|
||||
return NSSelectorFromString(selector);
|
||||
}
|
||||
|
||||
- (instancetype)initWithMethodSignature:(NSString *)methodSignature
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
moduleClass:(Class)moduleClass
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_moduleClass = moduleClass;
|
||||
_objCMethodName = [objCMethodName copy];
|
||||
_methodSignature = [methodSignature copy];
|
||||
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
|
||||
NSString *methodName = objCMethodName;
|
||||
NSString *methodName = methodSignature;
|
||||
NSRange colonRange = [methodName rangeOfString:@":"];
|
||||
if (colonRange.location != NSNotFound) {
|
||||
methodName = [methodName substringToIndex:colonRange.location];
|
||||
}
|
||||
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
|
||||
" supply an alternative using RCT_REMAP_METHOD()", objCMethodName);
|
||||
" supply an alternative using RCT_REMAP_METHOD()", methodSignature);
|
||||
methodName;
|
||||
});
|
||||
|
||||
if ([_objCMethodName rangeOfString:@"RCTPromise"].length) {
|
||||
if ([_methodSignature rangeOfString:@"RCTPromise"].length) {
|
||||
_functionType = RCTFunctionTypePromise;
|
||||
} else {
|
||||
_functionType = RCTFunctionTypeNormal;
|
||||
@@ -151,15 +181,12 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
|
||||
- (void)processMethodSignature
|
||||
{
|
||||
NSArray<RCTMethodArgument *> *arguments;
|
||||
NSString *objCMethodName = _objCMethodName;
|
||||
RCTParseObjCMethodName(&objCMethodName, &arguments);
|
||||
|
||||
_selector = NSSelectorFromString(objCMethodName);
|
||||
RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName);
|
||||
_selector = RCTParseMethodSignature(_methodSignature, &arguments);
|
||||
RCTAssert(_selector, @"%@ is not a valid selector", _methodSignature);
|
||||
|
||||
// Create method invocation
|
||||
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
|
||||
RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName);
|
||||
RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", _methodSignature);
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
invocation.selector = _selector;
|
||||
_invocation = invocation;
|
||||
@@ -203,7 +230,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
|
||||
BOOL isNullableType = NO;
|
||||
RCTMethodArgument *argument = arguments[i - 2];
|
||||
NSString *typeName = argument.type;
|
||||
SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]);
|
||||
SEL selector = RCTConvertSelectorForType(typeName);
|
||||
if ([RCTConvert respondsToSelector:selector]) {
|
||||
switch (objcType[0]) {
|
||||
|
||||
@@ -296,7 +323,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
|
||||
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 2,
|
||||
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
_moduleClass, _methodSignature);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
|
||||
@@ -310,7 +337,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
|
||||
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 1,
|
||||
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
_moduleClass, _methodSignature);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
|
||||
|
||||
Reference in New Issue
Block a user