Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(159)

Unified Diff: pkg/compiler/lib/src/parser/parser.dart

Issue 1723443003: First step of support for parsing and ignoring generic methods. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Fixes bug with nested type arguments Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/compiler/lib/src/parser/element_listener.dart ('k') | pkg/compiler/lib/src/parser/parser_task.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« no previous file with comments | « pkg/compiler/lib/src/parser/element_listener.dart ('k') | pkg/compiler/lib/src/parser/parser_task.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698