Skip to content

Commit

Permalink
Refined descriptors handling for constants and properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaz committed May 8, 2012
1 parent 8d8e668 commit 185bb77
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 23 deletions.
100 changes: 100 additions & 0 deletions AppledocTests/Parsing/ObjectiveCConstantStateTests.mm
Expand Up @@ -336,6 +336,106 @@ static void runWithState(void(^handler)(ObjectiveCConstantState *state)) {
});
});
});

describe(@"edge cases / limitations for supporting descriptors", ^{
describe(@"requires at least one non-descriptor looking token before starting accepting destriptors", ^{
it(@"should detect types with double underscore prefix", ^{
runWithState(^(ObjectiveCConstantState *state) {
runWithString(@"__type1 __type2 __name;", ^(id parser, id tokens) {
// setup
id store = [OCMockObject mockForClass:[Store class]];
[[store expect] setCurrentSourceInfo:OCMOCK_ANY];
[[store expect] beginConstant];
[[store expect] beginConstantTypes];
[[store expect] appendType:@"__type1"];
[[store expect] appendType:@"__type2"];
[[store expect] endCurrentObject]; // types
[[store expect] appendConstantName:@"__name"];
[[store expect] endCurrentObject]; // constant
[[parser expect] popState];
ObjectiveCParseData *data = [ObjectiveCParseData dataWithStream:tokens parser:parser store:store];
// execute
[state parseWithData:data];
// verify
^{ [store verify]; } should_not raise_exception();
^{ [parser verify]; } should_not raise_exception();
});
});
});

it(@"should detect types with uppercase letters", ^{
runWithState(^(ObjectiveCConstantState *state) {
runWithString(@"TYPE1 TYPE2 NAME;", ^(id parser, id tokens) {
// setup
id store = [OCMockObject mockForClass:[Store class]];
[[store expect] setCurrentSourceInfo:OCMOCK_ANY];
[[store expect] beginConstant];
[[store expect] beginConstantTypes];
[[store expect] appendType:@"TYPE1"];
[[store expect] appendType:@"TYPE2"];
[[store expect] endCurrentObject]; // types
[[store expect] appendConstantName:@"NAME"];
[[store expect] endCurrentObject]; // constant
[[parser expect] popState];
ObjectiveCParseData *data = [ObjectiveCParseData dataWithStream:tokens parser:parser store:store];
// execute
[state parseWithData:data];
// verify
^{ [store verify]; } should_not raise_exception();
^{ [parser verify]; } should_not raise_exception();
});
});
});

it(@"should detect types with double and name with uppercase letters", ^{
runWithState(^(ObjectiveCConstantState *state) {
runWithString(@"__type1 __type2 NAME;", ^(id parser, id tokens) {
// setup
id store = [OCMockObject mockForClass:[Store class]];
[[store expect] setCurrentSourceInfo:OCMOCK_ANY];
[[store expect] beginConstant];
[[store expect] beginConstantTypes];
[[store expect] appendType:@"__type1"];
[[store expect] appendType:@"__type2"];
[[store expect] endCurrentObject]; // types
[[store expect] appendConstantName:@"NAME"];
[[store expect] endCurrentObject]; // constant
[[parser expect] popState];
ObjectiveCParseData *data = [ObjectiveCParseData dataWithStream:tokens parser:parser store:store];
// execute
[state parseWithData:data];
// verify
^{ [store verify]; } should_not raise_exception();
^{ [parser verify]; } should_not raise_exception();
});
});
});

it(@"should detect types with uppercase letters and name with double underscore prefix", ^{
runWithState(^(ObjectiveCConstantState *state) {
runWithString(@"TYPE1 TYPE2 __name;", ^(id parser, id tokens) {
// setup
id store = [OCMockObject mockForClass:[Store class]];
[[store expect] setCurrentSourceInfo:OCMOCK_ANY];
[[store expect] beginConstant];
[[store expect] beginConstantTypes];
[[store expect] appendType:@"TYPE1"];
[[store expect] appendType:@"TYPE2"];
[[store expect] endCurrentObject]; // types
[[store expect] appendConstantName:@"__name"];
[[store expect] endCurrentObject]; // constant
[[parser expect] popState];
ObjectiveCParseData *data = [ObjectiveCParseData dataWithStream:tokens parser:parser store:store];
// execute
[state parseWithData:data];
// verify
^{ [store verify]; } should_not raise_exception();
^{ [parser verify]; } should_not raise_exception();
});
});
});
});
});
});

describe(@"fail cases", ^{
Expand Down
18 changes: 11 additions & 7 deletions appledoc/Parsing/ObjectiveC/ObjectiveCConstantState.m
Expand Up @@ -49,12 +49,10 @@ - (BOOL)parseConstantDefinition:(ObjectiveCParseData *)data {
[data.store endCurrentObject]; // types
[data.store appendConstantName:token.stringValue];
if (hasDescriptors) [data.store beginConstantDescriptors];
return;
} else if (lookahead < indexOfEndToken) {
[data.store appendDescriptor:token.stringValue];
} else if ([token matches:@";"]) {
} else {
if (hasDescriptors) [data.store endCurrentObject]; // descriptors
return;
}
}];
if (found == NSNotFound) {
Expand All @@ -76,11 +74,17 @@ - (BOOL)finalizeConstant:(ObjectiveCParseData *)data {
#pragma mark - Helper methods

- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptorToken:(ObjectiveCParseData *)data {
// This one is a bit tricky: we want to leave at least 2 tokens before accepting descriptors: one for type and one for name. But if all tokens look like descriptor from start on, we should take them to be types - this would cover cases like (__unsafe_unretained etc). If all tokens look like descriptors, then just take the last as constant name and all prior as types.
LogParDebug(@"Scanning tokens for constant descriptors.");
__block NSInteger remainingTypeAndNameTokens = 2;
NSUInteger result = [data lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:@";" block:^(PKToken *token, BOOL *allowDescriptor) {
// require at least 2 tokens, one for type and one for name!
if (--remainingTypeAndNameTokens >= 0) *allowDescriptor = NO;
__block NSUInteger numberOfSuccessiveDescriptorLikeTokens = 0;
NSUInteger result = [data lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:@";" block:^(PKToken *token, NSUInteger lookahead, BOOL *isDescriptor) {
BOOL looksLikeDescriptor = [data doesStringLookLikeDescriptor:token.stringValue];
if (looksLikeDescriptor)
numberOfSuccessiveDescriptorLikeTokens++;
else
numberOfSuccessiveDescriptorLikeTokens = 0;
if (lookahead < 2) return; // require at least 2 tokens from the start
if (looksLikeDescriptor && numberOfSuccessiveDescriptorLikeTokens <= lookahead) *isDescriptor = YES;
}];
return result;
}
Expand Down
4 changes: 3 additions & 1 deletion appledoc/Parsing/ObjectiveC/ObjectiveCParseData.h
Expand Up @@ -11,6 +11,8 @@
@class TokensStream;
@class ObjectiveCParser;

typedef void(^GBDescriptorsLookaheadBlock)(PKToken *token, NSUInteger lookahead, BOOL *isDescriptor);

/** Provides data to ObjectiveCParserState objects.
The main reason for introducing this object is to reduce the number of parameters otherwise required for states. Instead of passing individual parameters around, we're now using parameter object.
Expand All @@ -20,7 +22,7 @@
+ (id)dataWithStream:(TokensStream *)stream parser:(ObjectiveCParser *)parser store:(Store *)store;

- (NSUInteger)lookaheadIndexOfFirstEndDelimiter:(id)end;
- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:(id)end block:(void(^)(PKToken *token, BOOL *allowDescriptor))handler;
- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:(id)end block:(GBDescriptorsLookaheadBlock)handler;
- (BOOL)doesStringLookLikeDescriptor:(NSString *)string;

@property (nonatomic, readonly, strong) Store *store;
Expand Down
12 changes: 4 additions & 8 deletions appledoc/Parsing/ObjectiveC/ObjectiveCParseData.m
Expand Up @@ -55,21 +55,17 @@ - (NSUInteger)lookaheadIndexOfFirstEndDelimiter:(id)end {
return result;
}

- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:(id)end block:(void(^)(PKToken *token, BOOL *allowDescriptor))handler {
- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:(id)end block:(GBDescriptorsLookaheadBlock)handler {
__block NSUInteger result = NSNotFound;
[self.stream lookAheadWithBlock:^(PKToken *token, NSUInteger lookahead, BOOL *stop) {
if ([token matches:end]) {
*stop = YES;
return;
}

// Give client a chance to skip tokens prior to start testing for descriptors.
BOOL canTokenBeTestedForDescriptor = YES;
handler(token, &canTokenBeTestedForDescriptor);
if (!canTokenBeTestedForDescriptor) return;

// If we encounter possible descriptor, use this index.
if ([self doesStringLookLikeDescriptor:token.stringValue]) {
BOOL isDescriptor = NO;
handler(token, lookahead, &isDescriptor);
if (isDescriptor) {
result = lookahead;
*stop = YES;
return;
Expand Down
15 changes: 8 additions & 7 deletions appledoc/Parsing/ObjectiveC/ObjectiveCPropertyState.m
Expand Up @@ -106,16 +106,17 @@ - (BOOL)finalizeProperty:(ObjectiveCParseData *)data {
}

- (NSUInteger)lookaheadIndexOfFirstPotentialDescriptor:(ObjectiveCParseData *)data {
// Require at least one token for type and one for name. Note that we should take all asterisks as types while here!
LogParDebug(@"Scanning tokens for property descriptors.");
__block NSInteger remainingTypeAndNameTokens = 2;
NSUInteger result = [data lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:@";" block:^(PKToken *token, BOOL *allowDescriptor) {
// Require at least one token for type and one for name. Note that we should take all asterisks as types while here!
if (remainingTypeAndNameTokens >= 0) {
*allowDescriptor = NO;
if ([token matches:@"*"]) return;
if (--remainingTypeAndNameTokens < 0) *allowDescriptor = YES;
__block BOOL wasPreviousTokenPossiblePropertyName = YES;
NSUInteger result = [data lookaheadIndexOfFirstPotentialDescriptorWithEndDelimiters:@";" block:^(PKToken *token, NSUInteger lookahead, BOOL *isDescriptor) {
if ([token matches:@"*"]) {
wasPreviousTokenPossiblePropertyName = NO;
return;
}
if (lookahead < 2) return;
if (wasPreviousTokenPossiblePropertyName && [data doesStringLookLikeDescriptor:token.stringValue]) *isDescriptor = YES;
wasPreviousTokenPossiblePropertyName = YES;
}];
return result;
}
Expand Down

0 comments on commit 185bb77

Please sign in to comment.