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; |