Chromium Code Reviews| Index: pkg/compiler/lib/src/parser/parser.dart |
| diff --git a/pkg/compiler/lib/src/parser/parser.dart b/pkg/compiler/lib/src/parser/parser.dart |
| index f2e0577937e91946b398e1e8040ae978ff95a8ae..5180873c2d9c74a804953ad1c4ecdef08eed3916 100644 |
| --- a/pkg/compiler/lib/src/parser/parser.dart |
| +++ b/pkg/compiler/lib/src/parser/parser.dart |
| @@ -39,6 +39,8 @@ import '../tokens/token_constants.dart' show |
| EOF_TOKEN, |
| EQ_TOKEN, |
| FUNCTION_TOKEN, |
| + GT_TOKEN, |
| + GT_GT_TOKEN, |
| HASH_TOKEN, |
| HEXADECIMAL_TOKEN, |
| IDENTIFIER_TOKEN, |
| @@ -94,16 +96,35 @@ class FormalParameterType { |
| * matches, "star" means zero or more matches. For example, |
| * [parseMetadataStar] corresponds to this grammar snippet: [: |
| * metadata* :], and [parseTypeOpt] corresponds to: [: type? :]. |
| + * |
| + * On the extension to support generic methods: |
| + * |
| + * The syntactic conflict associated with relational expressions is resolved |
| + * in favor of generic invocations (for instance, with `foo(m<B, C>(3))` we |
| + * parse it as an invocation of `m` with type arguments `B, C` and value |
| + * argument `3`, not as two boolean arguments passed to `foo`. It is expected |
| + * that this situation is occurs rarely in practice, especially because a comma |
| + * or ')' will delimit the expression where we have `3` in the example, |
| + * which means that it won't need to be wrapped in parentheses. The work-around |
| + * is to add parentheses around the last relational expression (i.e., `(C>(3))` |
| + * in the example). |
| + * |
| + * Relative to the DEP #22 proposal, there is no support for currying |
| + * invocations (that is, we cannot evalate a type like `myMethod<T,S>` to |
| + * obtain a closure which is non-generic and which will pass the actual values |
| + * of `T` and `S` in the given context as type arguments). |
| */ |
| class Parser { |
| final Listener listener; |
| bool mayParseFunctionExpressions = true; |
| final bool enableConditionalDirectives; |
| + final bool enableGenericMethods; |
| bool asyncAwaitKeywordsEnabled; |
| Parser(this.listener, |
| {this.enableConditionalDirectives: false, |
| - this.asyncAwaitKeywordsEnabled: false}); |
| + this.asyncAwaitKeywordsEnabled: false, |
| + this.enableGenericMethods: false}); |
| Token parseUnit(Token token) { |
| listener.beginCompilationUnit(token); |
| @@ -524,6 +545,68 @@ class Parser { |
| return false; |
| } |
| + bool isValidTypeArguments(Token token, {bool requireParenthesis: false}) { |
| + // To avoid changing the token stream during an investigation of whether |
| + // the parser is looking at a `typeArguments` construct, we do not replace |
| + // one `>>` token by two `>` tokens (which would destroy a correct |
| + // expression like `foo(a < 0, b >> 2)`). Instead, we count levels for the |
| + // '<'/'>' brackets directly. This makes sense because the type argument |
| + // sublanguage is so tiny: |
| + // |
| + // typeArguments ::= '<' typeList '>' |
| + // typeList ::= type (',' type)* |
| + // type ::= typeName typeArguments? |
| + // typeName ::= qualified |
| + // qualified ::= identifier ('.' identifier)? |
| + // |
| + // The point is that we check for parenthesis correctness for '<'/'>' plus |
| + // membership of the intersection between the following regular languages: |
| + // |
| + // anything starting with '<' and ending with '>' |
| + // (identifier | (identifier '.' identifier) | ',' | '<' | '>' | '>>')* |
| + // |
| + // Obviously, every correct `typeArguments` will pass this test. However, |
| + // some incorrect ones will also pass, e.g., `<K,<<><>>,>`. This should be |
| + // safe, but the real solution is of course the following: |
| + // |
| + // TODO(eernst): Prove that if a token sequence is accepted by this test, |
| + // but it is not a correct `typeArguments` then it is a syntax error rather |
| + // than a correct continuation of the program that does not contain a |
| + // `typeArguments` construct at this point. |
| + // If that property does not hold then we will reject some correct programs. |
| + if (optional('<', token)) { |
|
Johnni Winther
2016/02/29 10:18:45
Negate this to avoid the else branch:
if (!option
eernst
2016/03/09 16:28:13
Done.
|
| + int angleBracketLevel = 1; |
| + token = token.next; |
| + while (!identical(token.kind, EOF_TOKEN) && angleBracketLevel > 0) { |
| + // Invariant: `angleBracketLevel` is #'<' - #'>' from initial token |
| + // to `token`, both included, where #'x' is the number of tokens with |
| + // `stringValue` 'x'; in this computation, we consider the one-element |
| + // token sequence '>>' equivalent to the two element sequence '>', '>'. |
| + final int kind = token.kind; |
| + switch (kind) { |
| + case IDENTIFIER_TOKEN: |
| + if (optional('.', token.next)) { |
| + token = token.next.next; |
| + if (token.kind != IDENTIFIER_TOKEN) return false; |
| + } |
| + break; |
| + case COMMA_TOKEN: break; |
| + case LT_TOKEN: angleBracketLevel++; break; |
| + case GT_TOKEN: angleBracketLevel--; break; |
| + case GT_GT_TOKEN: angleBracketLevel -= 2; break; |
| + default: return false; |
| + } |
| + token = token.next; |
| + } |
| + if (angleBracketLevel != 0) return false; |
| + return requireParenthesis |
| + ? token.kind == OPEN_PAREN_TOKEN |
| + : true; |
| + } else { |
| + return false; |
| + } |
| + } |
| + |
| Token parseQualified(Token token) { |
| token = parseIdentifier(token); |
| while (optional('.', token)) { |
| @@ -540,6 +623,14 @@ class Parser { |
| } |
| } |
| + Token parseAndIgnoreQualifiedRestOpt(Token token) { |
| + if (optional('.', token)) { |
| + return parseAndIgnoreQualifiedRest(token); |
| + } else { |
| + return token; |
| + } |
| + } |
| + |
| Token parseQualifiedRest(Token token) { |
| assert(optional('.', token)); |
| Token period = token; |
| @@ -548,6 +639,12 @@ class Parser { |
| return token; |
| } |
| + Token parseAndIgnoreQualifiedRest(Token token) { |
| + assert(optional('.', token)); |
| + token = parseAndIgnoreIdentifier(token.next); |
| + return token; |
| + } |
| + |
| Token skipBlock(Token token) { |
| if (!optional('{', token)) { |
| return listener.expectedBlockToSkip(token); |
| @@ -679,6 +776,13 @@ class Parser { |
| return token.next; |
| } |
| + Token parseAndIgnoreIdentifier(Token token) { |
| + if (!token.isIdentifier()) { |
| + token = listener.expectedIdentifier(token); |
| + } |
| + return token.next; |
| + } |
| + |
| Token expect(String string, Token token) { |
| if (!identical(string, token.stringValue)) { |
| return listener.expected(string, token); |
| @@ -698,6 +802,14 @@ class Parser { |
| return token; |
| } |
| + Token parseAndIgnoreTypeParameter(Token token) { |
| + token = parseAndIgnoreIdentifier(token); |
| + if (optional('extends', token)) { |
| + token = parseAndIgnoreType(token.next); |
| + } |
| + return token; |
| + } |
| + |
| /** |
| * Returns true if the stringValue of the [token] is [value]. |
| */ |
| @@ -748,6 +860,17 @@ class Parser { |
| return token; |
| } |
| + Token parseAndIgnoreType(Token token) { |
| + if (isValidTypeReference(token)) { |
| + token = parseAndIgnoreIdentifier(token); |
| + token = parseAndIgnoreQualifiedRestOpt(token); |
| + } else { |
| + token = listener.expectedType(token); |
| + } |
| + token = parseAndIgnoreTypeArgumentsOpt(token); |
| + return token; |
| + } |
| + |
| Token parseTypeArgumentsOpt(Token token) { |
| return parseStuff(token, |
| (t) => listener.beginTypeArguments(t), |
| @@ -756,6 +879,19 @@ class Parser { |
| (t) => listener.handleNoTypeArguments(t)); |
| } |
| + Token parseAndIgnoreTypeArgumentsOpt(Token token, |
| + {bool requireParenthesis: false}) { |
| + if (isValidTypeArguments(token, requireParenthesis: requireParenthesis)) { |
| + return parseStuff(token, |
| + (t) {}, |
| + (t) => parseAndIgnoreType(t), |
| + (c, bt, et) {}, |
| + (t) {}); |
| + } else { |
| + return token; |
| + } |
| + } |
| + |
| Token parseTypeVariablesOpt(Token token) { |
| return parseStuff(token, |
| (t) => listener.beginTypeVariables(t), |
| @@ -764,6 +900,14 @@ class Parser { |
| (t) => listener.handleNoTypeVariables(t)); |
| } |
| + Token parseAndIgnoreTypeParametersOpt(Token token) { |
| + return parseStuff(token, |
| + (t) {}, |
| + (t) => parseAndIgnoreTypeParameter(t), |
| + (c, bt, et) {}, |
| + (t) {}); |
| + } |
| + |
| // TODO(ahe): Clean this up. |
| Token parseStuff(Token token, Function beginStuff, Function stuffParser, |
| Function endStuff, Function handleNoStuff) { |
| @@ -780,10 +924,6 @@ class Parser { |
| token = new SymbolToken(GT_INFO, token.charOffset); |
| token.next = new SymbolToken(GT_INFO, token.charOffset + 1); |
| token.next.next = next; |
| - } else if (identical(token.stringValue, '>>>')) { |
| - token = new SymbolToken(GT_INFO, token.charOffset); |
| - token.next = new SymbolToken(GT_GT_INFO, token.charOffset + 1); |
| - token.next.next = next; |
| } |
| endStuff(count, begin, token); |
| return expect('>', token); |
| @@ -1014,6 +1154,9 @@ class Parser { |
| } |
| Token token = parseIdentifier(name); |
| + if (enableGenericMethods && getOrSet == null) { |
| + token = parseAndIgnoreTypeParametersOpt(token); |
| + } |
| token = parseFormalParametersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| @@ -1322,7 +1465,8 @@ class Parser { |
| // error handling. |
| final String value = token.stringValue; |
| if ((identical(value, '(')) || (identical(value, '.')) |
| - || (identical(value, '{')) || (identical(value, '=>'))) { |
| + || (identical(value, '{')) || (identical(value, '=>')) |
| + || (enableGenericMethods && identical(value, '<'))) { |
| isField = false; |
| break; |
| } else if (identical(value, ';')) { |
| @@ -1423,6 +1567,9 @@ class Parser { |
| } |
| token = parseQualifiedRestOpt(token); |
| + if (enableGenericMethods && getOrSet == null) { |
| + token = parseAndIgnoreTypeParametersOpt(token); |
| + } |
| token = parseFormalParametersOpt(token); |
| token = parseInitializersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| @@ -1509,6 +1656,9 @@ class Parser { |
| } |
| token = parseQualifiedRestOpt(token); |
| listener.endFunctionName(token); |
| + if (enableGenericMethods && getOrSet == null) { |
| + token = parseAndIgnoreTypeParametersOpt(token); |
| + } |
| token = parseFormalParametersOpt(token); |
| token = parseInitializersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| @@ -2338,6 +2488,9 @@ class Parser { |
| Token parseSend(Token token) { |
| listener.beginSend(token); |
| token = parseIdentifier(token); |
| + if (enableGenericMethods) { |
| + token = parseAndIgnoreTypeArgumentsOpt(token, requireParenthesis: true); |
| + } |
| token = parseArgumentsOpt(token); |
| listener.endSend(token); |
| return token; |