| 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 42e432becd9afb93159aeec24e8c832ec9477ad6..76fde08b9ea1ee45d69b494469d05d675c1318ec 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,107 @@ class Parser {
|
| return false;
|
| }
|
|
|
| + /// Performs lookahead to see whether the next tokens can be type arguments.
|
| + ///
|
| + /// This method performs a lookahead (parsing without listener events)
|
| + /// using a near-regular language to determine a conservative approximation
|
| + /// of whether the next tokens can be a list of actual type arguments.
|
| + ///
|
| + /// It is conservative in the sense that it does not return true in cases
|
| + /// where the next tokens are a correct construct which is not an actual type
|
| + /// argument list (because we would then reject a correct program). But it
|
| + /// is OK to return either true or false when the next tokens are definitely
|
| + /// not correct (because this just causes a choice of one error message where
|
| + /// another one could also have been chosen). This allows for an imprecise
|
| + /// parsing approach, which is the reason why we can use a near-regular
|
| + /// language.
|
| + ///
|
| + /// The language which is recognized contains atoms consisting of a few
|
| + /// tokens:
|
| + ///
|
| + /// (identifier | (identifier '.' identifier) | ',' | '<' | '>' | '>>')*
|
| + ///
|
| + /// Moreover, it is enforced that it starts with '<', ends with '>' or '>>',
|
| + /// and that the angle brackets are balanced (where '>>' counts for two).
|
| + ///
|
| + /// Exceptions to this are controlled with the named arguments: If
|
| + /// [requireParenthesis] is true then it is required that the first token
|
| + /// after the successfully recognized type argument list is '(', and if
|
| + /// [allowImbalance] is true then one extra case is allowed: The recognized
|
| + /// type argument list may end in '>>' such that the final '>' creates an
|
| + /// imbalance (that is, it would have been balanced if we had had '>' rather
|
| + /// than '>>').
|
| + ///
|
| + /// The former is used to reduce the amount of recognized constructs when
|
| + /// it is known that the context requires the '(', and the latter is used to
|
| + /// allow for the '>>' balance violation when the context may be another type
|
| + /// argument list.
|
| + //
|
| + // 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.
|
| + bool isValidTypeArguments(Token token,
|
| + {bool requireParenthesis, bool allowImbalance}) {
|
| + // 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)?
|
| + //
|
| + // Clearly, any correct `typeArguments` construct will belong to the
|
| + // following regular language:
|
| + //
|
| + // (identifier | (identifier '.' identifier) | ',' | '<' | '>' | '>>')*
|
| + //
|
| + // So we check for that, and also for the context free language that just
|
| + // requires '<'/'>' to be balanced, and the first/last token to be '<'
|
| + // and '>' or '>>', respectively. The two named arguments are used to
|
| + // adjust the language slightly for context as described.
|
| + if (!optional('<', token)) return false;
|
| + 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 ((allowImbalance && angleBracketLevel > 0) ||
|
| + (!allowImbalance && angleBracketLevel != 0)) {
|
| + return false;
|
| + }
|
| + return requireParenthesis
|
| + ? token.kind == OPEN_PAREN_TOKEN
|
| + : true;
|
| + }
|
| +
|
| Token parseQualified(Token token) {
|
| token = parseIdentifier(token);
|
| while (optional('.', token)) {
|
| @@ -540,6 +662,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 +678,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 +815,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 +841,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 +899,18 @@ 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,
|
| + requireParenthesis: false, allowImbalance: true);
|
| + return token;
|
| + }
|
| +
|
| Token parseTypeArgumentsOpt(Token token) {
|
| return parseStuff(token,
|
| (t) => listener.beginTypeArguments(t),
|
| @@ -756,6 +919,22 @@ class Parser {
|
| (t) => listener.handleNoTypeArguments(t));
|
| }
|
|
|
| + Token parseAndIgnoreTypeArgumentsOpt(Token token,
|
| + {bool requireParenthesis, bool allowImbalance}) {
|
| + if (isValidTypeArguments(
|
| + token,
|
| + requireParenthesis: requireParenthesis,
|
| + allowImbalance: allowImbalance)) {
|
| + 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 +943,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) {
|
| @@ -1010,6 +1197,9 @@ class Parser {
|
| }
|
| Token token = parseIdentifier(name);
|
|
|
| + if (enableGenericMethods && getOrSet == null) {
|
| + token = parseAndIgnoreTypeParametersOpt(token);
|
| + }
|
| token = parseFormalParametersOpt(token);
|
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| token = parseAsyncModifier(token);
|
| @@ -1318,7 +1508,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, ';')) {
|
| @@ -1419,6 +1610,9 @@ class Parser {
|
| }
|
|
|
| token = parseQualifiedRestOpt(token);
|
| + if (enableGenericMethods && getOrSet == null) {
|
| + token = parseAndIgnoreTypeParametersOpt(token);
|
| + }
|
| token = parseFormalParametersOpt(token);
|
| token = parseInitializersOpt(token);
|
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| @@ -1505,6 +1699,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;
|
| @@ -2334,6 +2531,10 @@ class Parser {
|
| Token parseSend(Token token) {
|
| listener.beginSend(token);
|
| token = parseIdentifier(token);
|
| + if (enableGenericMethods) {
|
| + token = parseAndIgnoreTypeArgumentsOpt(token,
|
| + requireParenthesis: true, allowImbalance: false);
|
| + }
|
| token = parseArgumentsOpt(token);
|
| listener.endSend(token);
|
| return token;
|
|
|