Index: frog/parser.dart |
diff --git a/frog/parser.dart b/frog/parser.dart |
deleted file mode 100644 |
index 4cb22ab8250a72ba142f3976f8a566d59fb8fcef..0000000000000000000000000000000000000000 |
--- a/frog/parser.dart |
+++ /dev/null |
@@ -1,1748 +0,0 @@ |
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-// TODO(jimhug): Error recovery needs major work! |
-/** |
- * A simple recursive descent parser for the dart language. |
- * |
- * This parser is designed to be more permissive than the official |
- * Dart grammar. It is expected that many grammar errors would be |
- * reported by a later compiler phase. For example, a class is allowed |
- * to extend an arbitrary number of base classes - this can be |
- * very clearly detected and is reported in a later compiler phase. |
- */ |
-class Parser { |
- TokenSource tokenizer; |
- |
- final SourceFile source; |
- /** Enables diet parse, which skips function bodies. */ |
- final bool diet; |
- /** |
- * Throw an IncompleteSourceException if the parser encounters a premature end |
- * of file or an incomplete multiline string. |
- */ |
- final bool throwOnIncomplete; |
- |
- /** Allow semicolons to be omitted at the end of lines. */ |
- // TODO(nweiz): make this work for more than just end-of-file |
- final bool optionalSemicolons; |
- |
- /** |
- * Allow the await keyword, when the await transformation is available (see |
- * await/awaitc.dart). |
- */ |
- bool get enableAwait() => experimentalAwaitPhase != null; |
- |
- /** |
- * To resolve ambiguity in initializers between constructor body and lambda |
- * expression. |
- */ |
- bool _inhibitLambda = false; |
- |
- Token _previousToken; |
- Token _peekToken; |
- |
- // When we encounter '(' in a method body we need to find the ')' to know it |
- // we're parsing a lambda, paren-expr, or argument list. Closure formals are |
- // followed by '=>' or '{'. This list is used to cache the tokens after any |
- // nested parenthesis we find while peeking. |
- // TODO(jmesserly): it's simpler and faster to cache this on the Token itself, |
- // but that might add too much complexity for tools that need to invalidate. |
- List<Token> _afterParens; |
- int _afterParensIndex = 0; |
- |
- bool _recover = false; |
- |
- Parser(this.source, [this.diet = false, this.throwOnIncomplete = false, |
- this.optionalSemicolons = false, int startOffset = 0]) { |
- tokenizer = new Tokenizer(source, true, startOffset); |
- _peekToken = tokenizer.next(); |
- _afterParens = <Token>[]; |
- } |
- |
- /** Generate an error if [source] has not been completely consumed. */ |
- void checkEndOfFile() { |
- _eat(TokenKind.END_OF_FILE); |
- } |
- |
- /** Guard to break out of parser when an unexpected end of file is found. */ |
- bool isPrematureEndOfFile() { |
- if (throwOnIncomplete && _maybeEat(TokenKind.END_OF_FILE)) { |
- throw new IncompleteSourceException(_previousToken); |
- } else if (_maybeEat(TokenKind.END_OF_FILE)) { |
- _error('unexpected end of file', _peekToken.span); |
- return true; |
- } else { |
- return false; |
- } |
- } |
- |
- /** |
- * Recovers the parser after an error, by iterating until it finds one of |
- * the provide [TokenKind] values. |
- */ |
- bool _recoverTo(int kind1, [int kind2, int kind3]) { |
- assert(_recover); |
- while (!isPrematureEndOfFile()) { |
- int kind = _peek(); |
- if (kind == kind1 || kind == kind2 || kind == kind3) { |
- _recover = false; // Done recovering. Issue errors normally. |
- return true; |
- } |
- _next(); |
- } |
- // End of file without finding a match |
- return false; |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // Basic support methods |
- /////////////////////////////////////////////////////////////////// |
- int _peek() => _peekToken.kind; |
- |
- Token _next() { |
- _previousToken = _peekToken; |
- _peekToken = tokenizer.next(); |
- return _previousToken; |
- } |
- |
- bool _peekKind(int kind) => _peekToken.kind == kind; |
- |
- /* Is the next token a legal identifier? This includes pseudo-keywords. */ |
- bool _peekIdentifier() => _isIdentifier(_peekToken.kind); |
- |
- bool _isIdentifier(kind) { |
- return TokenKind.isIdentifier(kind) |
- // Note: 'await' is not a pseudo-keyword. When [enableAwait] is true, it |
- // is illegal to consider 'await' an identifier. |
- || (!enableAwait && kind == TokenKind.AWAIT); |
- } |
- |
- bool _maybeEat(int kind) { |
- if (_peekToken.kind == kind) { |
- _previousToken = _peekToken; |
- _peekToken = tokenizer.next(); |
- return true; |
- } else { |
- return false; |
- } |
- } |
- |
- void _eat(int kind) { |
- if (!_maybeEat(kind)) { |
- _errorExpected(TokenKind.kindToString(kind)); |
- } |
- } |
- |
- void _eatSemicolon() { |
- if (optionalSemicolons && _peekKind(TokenKind.END_OF_FILE)) return; |
- _eat(TokenKind.SEMICOLON); |
- } |
- |
- void _errorExpected(String expected) { |
- // Throw an IncompleteSourceException if that's the problem and |
- // throwOnIncomplete is true |
- if (throwOnIncomplete) isPrematureEndOfFile(); |
- var tok = _next(); |
- if (tok is ErrorToken && tok.message != null) { |
- // give priority to tokenizer errors |
- _error(tok.message, tok.span); |
- } else { |
- _error('expected $expected, but found $tok', tok.span); |
- } |
- } |
- |
- void _error(String message, [SourceSpan location=null]) { |
- // Suppress error messages while we're trying to recover. |
- if (_recover) return; |
- |
- if (location == null) { |
- location = _peekToken.span; |
- } |
- world.fatal(message, location); // syntax errors are fatal for now |
- _recover = true; // start error recovery |
- } |
- |
- /** Skips from an opening '{' to the syntactically matching '}'. */ |
- void _skipBlock() { |
- int depth = 1; |
- _eat(TokenKind.LBRACE); |
- while (true) { |
- var tok = _next(); |
- if (tok.kind == TokenKind.LBRACE) { |
- depth += 1; |
- } else if (tok.kind == TokenKind.RBRACE) { |
- depth -= 1; |
- if (depth == 0) return; |
- } else if (tok.kind == TokenKind.END_OF_FILE) { |
- _error('unexpected end of file during diet parse', tok.span); |
- return; |
- } |
- } |
- } |
- |
- SourceSpan _makeSpan(int start) { |
- return new SourceSpan(source, start, _previousToken.end); |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // Top level productions |
- /////////////////////////////////////////////////////////////////// |
- |
- /** Entry point to the parser for parsing a compilation unit (i.e. a file). */ |
- List<Definition> compilationUnit() { |
- var ret = []; |
- _maybeEat(TokenKind.HASHBANG); |
- |
- while (_peekKind(TokenKind.HASH)) { |
- ret.add(directive()); |
- } |
- _recover = false; |
- while (!_maybeEat(TokenKind.END_OF_FILE)) { |
- ret.add(topLevelDefinition()); |
- } |
- _recover = false; |
- return ret; |
- } |
- |
- directive() { |
- int start = _peekToken.start; |
- _eat(TokenKind.HASH); |
- var name = identifier(); |
- var args = arguments(); |
- _eatSemicolon(); |
- return new DirectiveDefinition(name, args, _makeSpan(start)); |
- } |
- |
- topLevelDefinition() { |
- switch (_peek()) { |
- case TokenKind.CLASS: |
- return classDefinition(TokenKind.CLASS); |
- case TokenKind.INTERFACE: |
- return classDefinition(TokenKind.INTERFACE); |
- case TokenKind.TYPEDEF: |
- return functionTypeAlias(); |
- default: |
- return declaration(); |
- } |
- } |
- |
- /** Entry point to the parser for an eval unit (i.e. a repl command). */ |
- evalUnit() { |
- switch (_peek()) { |
- case TokenKind.CLASS: |
- return classDefinition(TokenKind.CLASS); |
- case TokenKind.INTERFACE: |
- return classDefinition(TokenKind.INTERFACE); |
- case TokenKind.TYPEDEF: |
- return functionTypeAlias(); |
- default: |
- return statement(); |
- } |
- _recover = false; |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // Definition productions |
- /////////////////////////////////////////////////////////////////// |
- |
- classDefinition(int kind) { |
- int start = _peekToken.start; |
- _eat(kind); |
- var name = identifierForType(); |
- |
- var typeParams = null; |
- if (_peekKind(TokenKind.LT)) { |
- typeParams = typeParameters(); |
- } |
- |
- var _extends = null; |
- if (_maybeEat(TokenKind.EXTENDS)) { |
- _extends = typeList(); |
- } |
- |
- var _implements = null; |
- if (_maybeEat(TokenKind.IMPLEMENTS)) { |
- _implements = typeList(); |
- } |
- |
- var _native = null; |
- if (_maybeEat(TokenKind.NATIVE)) { |
- _native = maybeStringLiteral(); |
- if (_native != null) _native = new NativeType(_native); |
- } |
- |
- bool oldFactory = _maybeEat(TokenKind.FACTORY); |
- var defaultType = null; |
- if (oldFactory || _maybeEat(TokenKind.DEFAULT)) { |
- // TODO(jmesserly): keep old factory support for now. Remove soon. |
- if (oldFactory) { |
- world.warning('factory no longer supported, use "default" instead', |
- _previousToken.span); |
- } |
- |
- // Note: this can't be type() because it has type parameters not type |
- // arguments. |
- var baseType = nameTypeReference(); |
- var factTypeParams = null; |
- if (_peekKind(TokenKind.LT)) { |
- factTypeParams = typeParameters(); |
- } |
- defaultType = new DefaultTypeReference(oldFactory, |
- baseType, factTypeParams, _makeSpan(baseType.span.start)); |
- } |
- |
- var body = []; |
- if (_maybeEat(TokenKind.LBRACE)) { |
- while (!_maybeEat(TokenKind.RBRACE)) { |
- body.add(declaration()); |
- if (_recover) { |
- if (!_recoverTo(TokenKind.RBRACE, TokenKind.SEMICOLON)) break; |
- _maybeEat(TokenKind.SEMICOLON); |
- } |
- } |
- } else { |
- _errorExpected('block starting with "{" or ";"'); |
- } |
- return new TypeDefinition(kind == TokenKind.CLASS, name, typeParams, |
- _extends, _implements, _native, defaultType, body, _makeSpan(start)); |
- } |
- |
- functionTypeAlias() { |
- int start = _peekToken.start; |
- _eat(TokenKind.TYPEDEF); |
- |
- var di = declaredIdentifier(false); |
- var typeParams = null; |
- if (_peekKind(TokenKind.LT)) { |
- typeParams = typeParameters(); |
- } |
- var formals = formalParameterList(); |
- _eatSemicolon(); |
- |
- // TODO(jimhug): Validate that di.name is not a pseudo-keyword |
- var func = new FunctionDefinition(null, di.type, di.name, formals, |
- null, null, null, _makeSpan(start)); |
- |
- return new FunctionTypeDefinition(func, typeParams, _makeSpan(start)); |
- } |
- |
- initializers() { |
- _inhibitLambda = true; |
- var ret = []; |
- do { |
- ret.add(expression()); |
- } while (_maybeEat(TokenKind.COMMA)); |
- _inhibitLambda = false; |
- return ret; |
- } |
- |
- functionBody(bool inExpression) { |
- int start = _peekToken.start; |
- if (_maybeEat(TokenKind.ARROW)) { |
- var expr = expression(); |
- if (!inExpression) { |
- _eatSemicolon(); |
- } |
- return new ReturnStatement(expr, _makeSpan(start)); |
- } else if (_peekKind(TokenKind.LBRACE)) { |
- if (diet) { |
- _skipBlock(); |
- return new DietStatement(_makeSpan(start)); |
- } else { |
- return block(); |
- } |
- } else if (!inExpression) { |
- if (_maybeEat(TokenKind.SEMICOLON)) { |
- return null; |
- } |
- } |
- |
- _error('Expected function body (neither { nor => found)'); |
- } |
- |
- finishField(start, modifiers, type, name, value) { |
- var names = [name]; |
- var values = [value]; |
- |
- while (_maybeEat(TokenKind.COMMA)) { |
- names.add(identifier()); |
- if (_maybeEat(TokenKind.ASSIGN)) { |
- values.add(expression()); |
- } else { |
- values.add(null); |
- } |
- } |
- |
- _eatSemicolon(); |
- return new VariableDefinition(modifiers, type, names, values, |
- _makeSpan(start)); |
- } |
- |
- finishDefinition(int start, List<Token> modifiers, di) { |
- switch(_peek()) { |
- case TokenKind.LPAREN: |
- var formals = formalParameterList(); |
- var inits = null, native = null; |
- if (_maybeEat(TokenKind.COLON)) { |
- inits = initializers(); |
- } |
- if (_maybeEat(TokenKind.NATIVE)) { |
- native = maybeStringLiteral(); |
- if (native == null) native = ''; |
- } |
- var body = functionBody(/*inExpression:*/false); |
- if (di.name == null) { |
- // TODO(jimhug): Must be named constructor - verify how? |
- di.name = di.type.name; |
- } |
- return new FunctionDefinition(modifiers, di.type, di.name, formals, |
- inits, native, body, _makeSpan(start)); |
- |
- case TokenKind.ASSIGN: |
- _eat(TokenKind.ASSIGN); |
- var value = expression(); |
- return finishField(start, modifiers, di.type, di.name, value); |
- |
- case TokenKind.COMMA: |
- case TokenKind.SEMICOLON: |
- return finishField(start, modifiers, di.type, di.name, null); |
- |
- default: |
- // TODO(jimhug): This error message sucks. |
- _errorExpected('declaration'); |
- |
- return null; |
- } |
- } |
- |
- declaration([bool includeOperators=true]) { |
- int start = _peekToken.start; |
- if (_peekKind(TokenKind.FACTORY)) { |
- return factoryConstructorDeclaration(); |
- } |
- |
- var modifiers = _readModifiers(); |
- return finishDefinition(start, modifiers, |
- declaredIdentifier(includeOperators)); |
- } |
- |
- // TODO(jmesserly): do we still need this method? |
- // I left it here for now to support old-style factories |
- factoryConstructorDeclaration() { |
- int start = _peekToken.start; |
- var factoryToken = _next(); |
- |
- var names = [identifier()]; |
- while (_maybeEat(TokenKind.DOT)) { |
- names.add(identifier()); |
- } |
- if (_peekKind(TokenKind.LT)) { |
- var tp = typeParameters(); |
- world.warning('type parameters on factories are no longer supported, ' |
- + 'place them on the class instead', _makeSpan(tp[0].span.start)); |
- } |
- |
- var name = null; |
- var type = null; |
- if (_maybeEat(TokenKind.DOT)) { |
- name = identifier(); |
- } else { |
- if (names.length > 1) { |
- name = names.removeLast(); |
- } else { |
- name = new Identifier('', names[0].span); |
- } |
- } |
- |
- if (names.length > 1) { |
- // TODO(jimhug): This is nasty to support and currently unused. |
- _error('unsupported qualified name for factory', names[0].span); |
- } |
- type = new NameTypeReference(false, names[0], null, names[0].span); |
- var di = new DeclaredIdentifier(type, name, false, _makeSpan(start)); |
- return finishDefinition(start, [factoryToken], di); |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // Statement productions |
- /////////////////////////////////////////////////////////////////// |
- Statement statement() { |
- switch (_peek()) { |
- case TokenKind.BREAK: |
- return breakStatement(); |
- case TokenKind.CONTINUE: |
- return continueStatement(); |
- case TokenKind.RETURN: |
- return returnStatement(); |
- case TokenKind.THROW: |
- return throwStatement(); |
- case TokenKind.ASSERT: |
- return assertStatement(); |
- |
- case TokenKind.WHILE: |
- return whileStatement(); |
- case TokenKind.DO: |
- return doStatement(); |
- case TokenKind.FOR: |
- return forStatement(); |
- |
- case TokenKind.IF: |
- return ifStatement(); |
- case TokenKind.SWITCH: |
- return switchStatement(); |
- |
- case TokenKind.TRY: |
- return tryStatement(); |
- |
- case TokenKind.LBRACE: |
- return block(); |
- case TokenKind.SEMICOLON: |
- return emptyStatement(); |
- |
- case TokenKind.FINAL: |
- return declaration(false); |
- case TokenKind.VAR: |
- return declaration(false); |
- |
- default: |
- // Covers var decl, func decl, labeled stmt and real expressions. |
- return finishExpressionAsStatement(expression()); |
- } |
- } |
- |
- finishExpressionAsStatement(expr) { |
- // TODO(jimhug): This method looks very inefficient - bundle tests. |
- int start = expr.span.start; |
- |
- if (_maybeEat(TokenKind.COLON)) { |
- var label = _makeLabel(expr); |
- return new LabeledStatement(label, statement(), _makeSpan(start)); |
- } |
- |
- if (expr is LambdaExpression) { |
- if (expr.func.body is! BlockStatement) { |
- _eatSemicolon(); |
- expr.func.span = _makeSpan(start); |
- } |
- return expr.func; |
- } else if (expr is DeclaredIdentifier) { |
- var value = null; |
- if (_maybeEat(TokenKind.ASSIGN)) { |
- value = expression(); |
- } |
- return finishField(start, null, expr.type, expr.name, value); |
- } else if (_isBin(expr, TokenKind.ASSIGN) && |
- (expr.x is DeclaredIdentifier)) { |
- DeclaredIdentifier di = expr.x; // TODO(jimhug): inference should handle! |
- return finishField(start, null, di.type, di.name, expr.y); |
- } else if (_isBin(expr, TokenKind.LT) && _maybeEat(TokenKind.COMMA)) { |
- var baseType = _makeType(expr.x); |
- var typeArgs = [_makeType(expr.y)]; |
- var gt = _finishTypeArguments(baseType, 0, typeArgs); |
- var name = identifier(); |
- var value = null; |
- if (_maybeEat(TokenKind.ASSIGN)) { |
- value = expression(); |
- } |
- return finishField(expr.span.start, null, gt, name, value); |
- } else { |
- _eatSemicolon(); |
- return new ExpressionStatement(expr, _makeSpan(expr.span.start)); |
- } |
- } |
- |
- Expression testCondition() { |
- _eatLeftParen(); |
- var ret = expression(); |
- _eat(TokenKind.RPAREN); |
- return ret; |
- } |
- |
- /** Parses a block. Also is an entry point when parsing [DietStatement]. */ |
- BlockStatement block() { |
- int start = _peekToken.start; |
- _eat(TokenKind.LBRACE); |
- var stmts = []; |
- while (!_maybeEat(TokenKind.RBRACE)) { |
- stmts.add(statement()); |
- if (_recover && !_recoverTo(TokenKind.RBRACE, TokenKind.SEMICOLON)) break; |
- } |
- _recover = false; |
- return new BlockStatement(stmts, _makeSpan(start)); |
- } |
- |
- EmptyStatement emptyStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.SEMICOLON); |
- return new EmptyStatement(_makeSpan(start)); |
- } |
- |
- |
- IfStatement ifStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.IF); |
- var test = testCondition(); |
- var trueBranch = statement(); |
- var falseBranch = null; |
- if (_maybeEat(TokenKind.ELSE)) { |
- falseBranch = statement(); |
- } |
- return new IfStatement(test, trueBranch, falseBranch, _makeSpan(start)); |
- } |
- |
- WhileStatement whileStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.WHILE); |
- var test = testCondition(); |
- var body = statement(); |
- return new WhileStatement(test, body, _makeSpan(start)); |
- } |
- |
- DoStatement doStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.DO); |
- var body = statement(); |
- _eat(TokenKind.WHILE); |
- var test = testCondition(); |
- _eatSemicolon(); |
- return new DoStatement(body, test, _makeSpan(start)); |
- } |
- |
- forStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.FOR); |
- _eatLeftParen(); |
- |
- var init = forInitializerStatement(start); |
- if (init is ForInStatement) { |
- return init; |
- } |
- var test = null; |
- if (!_maybeEat(TokenKind.SEMICOLON)) { |
- test = expression(); |
- _eatSemicolon(); |
- } |
- var step = []; |
- if (!_maybeEat(TokenKind.RPAREN)) { |
- step.add(expression()); |
- while (_maybeEat(TokenKind.COMMA)) { |
- step.add(expression()); |
- } |
- _eat(TokenKind.RPAREN); |
- } |
- |
- var body = statement(); |
- |
- return new ForStatement(init, test, step, body, _makeSpan(start)); |
- } |
- |
- forInitializerStatement(int start) { |
- if (_maybeEat(TokenKind.SEMICOLON)) { |
- return null; |
- } else { |
- var init = expression(); |
- // Weird code here is needed to handle generic type and for in |
- // TODO(jmesserly): unify with block in finishExpressionAsStatement |
- if (_peekKind(TokenKind.COMMA) && _isBin(init, TokenKind.LT)) { |
- _eat(TokenKind.COMMA); |
- var baseType = _makeType(init.x); |
- var typeArgs = [_makeType(init.y)]; |
- var gt = _finishTypeArguments(baseType, 0, typeArgs); |
- var name = identifier(); |
- init = new DeclaredIdentifier(gt, name, false, _makeSpan(init.span.start)); |
- } |
- |
- if (_maybeEat(TokenKind.IN)) { |
- return _finishForIn(start, _makeDeclaredIdentifier(init)); |
- } else { |
- return finishExpressionAsStatement(init); |
- } |
- } |
- } |
- |
- _finishForIn(int start, DeclaredIdentifier di) { |
- var expr = expression(); |
- _eat(TokenKind.RPAREN); |
- var body = statement(); |
- return new ForInStatement(di, expr, body, |
- _makeSpan(start)); |
- } |
- |
- tryStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.TRY); |
- var body = block(); |
- var catches = []; |
- |
- while (_peekKind(TokenKind.CATCH)) { |
- catches.add(catchNode()); |
- } |
- |
- var finallyBlock = null; |
- if (_maybeEat(TokenKind.FINALLY)) { |
- finallyBlock = block(); |
- } |
- return new TryStatement(body, catches, finallyBlock, _makeSpan(start)); |
- } |
- |
- catchNode() { |
- int start = _peekToken.start; |
- _eat(TokenKind.CATCH); |
- _eatLeftParen(); |
- var exc = declaredIdentifier(); |
- var trace = null; |
- if (_maybeEat(TokenKind.COMMA)) { |
- trace = declaredIdentifier(); |
- } |
- _eat(TokenKind.RPAREN); |
- var body = block(); |
- return new CatchNode(exc, trace, body, _makeSpan(start)); |
- } |
- |
- switchStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.SWITCH); |
- var test = testCondition(); |
- var cases = []; |
- _eat(TokenKind.LBRACE); |
- while (!_maybeEat(TokenKind.RBRACE)) { |
- cases.add(caseNode()); |
- } |
- return new SwitchStatement(test, cases, _makeSpan(start)); |
- } |
- |
- _peekCaseEnd() { |
- var kind = _peek(); |
- //TODO(efortuna): also if the first is an identifier followed by a colon, we |
- //have a label for the case statement. |
- return kind == TokenKind.RBRACE || kind == TokenKind.CASE || |
- kind == TokenKind.DEFAULT; |
- } |
- |
- caseNode() { |
- int start = _peekToken.start; |
- var label = null; |
- if (_peekIdentifier()) { |
- label = identifier(); |
- _eat(TokenKind.COLON); |
- } |
- var cases = []; |
- while (true) { |
- if (_maybeEat(TokenKind.CASE)) { |
- cases.add(expression()); |
- _eat(TokenKind.COLON); |
- } else if (_maybeEat(TokenKind.DEFAULT)) { |
- cases.add(null); |
- _eat(TokenKind.COLON); |
- } else { |
- break; |
- } |
- } |
- if (cases.length == 0) { |
- _error('case or default'); |
- } |
- var stmts = []; |
- while (!_peekCaseEnd()) { |
- stmts.add(statement()); |
- if (_recover && !_recoverTo( |
- TokenKind.RBRACE, TokenKind.CASE, TokenKind.DEFAULT)) { |
- break; |
- } |
- } |
- return new CaseNode(label, cases, stmts, _makeSpan(start)); |
- } |
- |
- returnStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.RETURN); |
- var expr; |
- if (_maybeEat(TokenKind.SEMICOLON)) { |
- expr = null; |
- } else { |
- expr = expression(); |
- _eatSemicolon(); |
- } |
- return new ReturnStatement(expr, _makeSpan(start)); |
- } |
- |
- throwStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.THROW); |
- var expr; |
- if (_maybeEat(TokenKind.SEMICOLON)) { |
- expr = null; |
- } else { |
- expr = expression(); |
- _eatSemicolon(); |
- } |
- return new ThrowStatement(expr, _makeSpan(start)); |
- } |
- |
- assertStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.ASSERT); |
- _eatLeftParen(); |
- var expr = expression(); |
- _eat(TokenKind.RPAREN); |
- _eatSemicolon(); |
- return new AssertStatement(expr, _makeSpan(start)); |
- } |
- |
- breakStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.BREAK); |
- var name = null; |
- if (_peekIdentifier()) { |
- name = identifier(); |
- } |
- _eatSemicolon(); |
- return new BreakStatement(name, _makeSpan(start)); |
- } |
- |
- continueStatement() { |
- int start = _peekToken.start; |
- _eat(TokenKind.CONTINUE); |
- var name = null; |
- if (_peekIdentifier()) { |
- name = identifier(); |
- } |
- _eatSemicolon(); |
- return new ContinueStatement(name, _makeSpan(start)); |
- } |
- |
- |
- /////////////////////////////////////////////////////////////////// |
- // Expression productions |
- /////////////////////////////////////////////////////////////////// |
- expression() { |
- return infixExpression(0); |
- } |
- |
- _makeType(expr) { |
- if (expr is VarExpression) { |
- return new NameTypeReference(false, expr.name, null, expr.span); |
- } else if (expr is DotExpression) { |
- var type = _makeType(expr.self); |
- if (type.names == null) { |
- type.names = [expr.name]; |
- } else { |
- type.names.add(expr.name); |
- } |
- type.span = expr.span; |
- return type; |
- } else { |
- _error('expected type reference'); |
- return null; |
- } |
- } |
- |
- infixExpression(int precedence) { |
- return finishInfixExpression(unaryExpression(), precedence); |
- } |
- |
- _finishDeclaredId(type) { |
- var name = identifier(); |
- return finishPostfixExpression( |
- new DeclaredIdentifier(type, name, false, _makeSpan(type.span.start))); |
- } |
- |
- /** |
- * Takes an initial binary expression of A < B and turns it into a |
- * declared identifier included the A < B piece in the type. |
- */ |
- _fixAsType(BinaryExpression x) { |
- assert(_isBin(x, TokenKind.LT)); |
- // TODO(jimhug): good errors when expectations are violated |
- if (_maybeEat(TokenKind.GT)) { |
- // The simple case of A < B > just becomes a generic type |
- var base = _makeType(x.x); |
- var typeParam = _makeType(x.y); |
- var type = new GenericTypeReference(base, [typeParam], 0, |
- _makeSpan(x.span.start)); |
- return _finishDeclaredId(type); |
- } else { |
- // The case of A < B < kicks off a lot more parsing. |
- assert(_peekKind(TokenKind.LT)); |
- |
- var base = _makeType(x.x); |
- var paramBase = _makeType(x.y); |
- var firstParam = addTypeArguments(paramBase, 1); |
- |
- var type; |
- if (firstParam.depth <= 0) { |
- type = new GenericTypeReference(base, [firstParam], 0, |
- _makeSpan(x.span.start)); |
- } else if (_maybeEat(TokenKind.COMMA)) { |
- type = _finishTypeArguments(base, 0, [firstParam]); |
- } else { |
- _eat(TokenKind.GT); |
- type = new GenericTypeReference(base, [firstParam], 0, |
- _makeSpan(x.span.start)); |
- } |
- return _finishDeclaredId(type); |
- } |
- } |
- |
- finishInfixExpression(Expression x, int precedence) { |
- while (true) { |
- int kind = _peek(); |
- var prec = TokenKind.infixPrecedence(_peek()); |
- if (prec >= precedence) { |
- if (kind == TokenKind.LT || kind == TokenKind.GT) { |
- if (_isBin(x, TokenKind.LT)) { |
- // This must be a generic type according the the Dart grammar. |
- // This rule is in the grammar to forbid A < B < C and |
- // A < B > C as expressions both because they don't make sense |
- // and to make it easier to disambiguate the generic types. |
- // There are a number of other comparison operators that are |
- // also unallowed to nest in this way, but in the spirit of this |
- // "friendly" parser, those will be allowed until a later phase. |
- return _fixAsType(x); |
- } |
- } |
- var op = _next(); |
- if (op.kind == TokenKind.IS) { |
- var isTrue = !_maybeEat(TokenKind.NOT); |
- var typeRef = type(); |
- x = new IsExpression(isTrue, x, typeRef, _makeSpan(x.span.start)); |
- continue; |
- } |
- // Using prec + 1 ensures that a - b - c will group correctly. |
- // Using prec for ASSIGN ops ensures that a = b = c groups correctly. |
- var y = infixExpression(prec == 2 ? prec: prec+1); |
- if (op.kind == TokenKind.CONDITIONAL) { |
- _eat(TokenKind.COLON); |
- // Using prec for so "a ? b : c ? d : e" groups correctly as |
- // "a ? b : (c ? d : e)" |
- var z = infixExpression(prec); |
- x = new ConditionalExpression(x, y, z, _makeSpan(x.span.start)); |
- } else { |
- x = new BinaryExpression(op, x, y, _makeSpan(x.span.start)); |
- } |
- } else { |
- break; |
- } |
- } |
- return x; |
- } |
- |
- _isPrefixUnaryOperator(int kind) { |
- switch(kind) { |
- case TokenKind.ADD: |
- case TokenKind.SUB: |
- case TokenKind.NOT: |
- case TokenKind.BIT_NOT: |
- case TokenKind.INCR: |
- case TokenKind.DECR: |
- return true; |
- default: |
- return false; |
- } |
- } |
- |
- unaryExpression() { |
- int start = _peekToken.start; |
- // peek for prefixOperators and incrementOperators |
- if (_isPrefixUnaryOperator(_peek())) { |
- var tok = _next(); |
- var expr = unaryExpression(); |
- return new UnaryExpression(tok, expr, _makeSpan(start)); |
- } else if (enableAwait && _maybeEat(TokenKind.AWAIT)) { |
- var expr = unaryExpression(); |
- return new AwaitExpression(expr, _makeSpan(start)); |
- } |
- |
- return finishPostfixExpression(primary()); |
- } |
- |
- argument() { |
- int start = _peekToken.start; |
- var expr; |
- var label = null; |
- if (_maybeEat(TokenKind.ELLIPSIS)) { |
- label = new Identifier('...', _makeSpan(start)); |
- } |
- expr = expression(); |
- if (label == null && _maybeEat(TokenKind.COLON)) { |
- label = _makeLabel(expr); |
- expr = expression(); |
- } |
- return new ArgumentNode(label, expr, _makeSpan(start)); |
- } |
- |
- arguments() { |
- var args = []; |
- _eatLeftParen(); |
- var saved = _inhibitLambda; |
- _inhibitLambda = false; |
- if (!_maybeEat(TokenKind.RPAREN)) { |
- do { |
- args.add(argument()); |
- } while (_maybeEat(TokenKind.COMMA)); |
- _eat(TokenKind.RPAREN); |
- } |
- _inhibitLambda = saved; |
- return args; |
- } |
- |
- finishPostfixExpression(expr) { |
- switch(_peek()) { |
- case TokenKind.LPAREN: |
- return finishCallOrLambdaExpression(expr); |
- case TokenKind.LBRACK: |
- _eat(TokenKind.LBRACK); |
- var index = expression(); |
- _eat(TokenKind.RBRACK); |
- return finishPostfixExpression(new IndexExpression(expr, index, |
- _makeSpan(expr.span.start))); |
- case TokenKind.DOT: |
- _eat(TokenKind.DOT); |
- var name = identifier(); |
- var ret = new DotExpression(expr, name, _makeSpan(expr.span.start)); |
- return finishPostfixExpression(ret); |
- |
- case TokenKind.INCR: |
- case TokenKind.DECR: |
- var tok = _next(); |
- return new PostfixExpression(expr, tok, _makeSpan(expr.span.start)); |
- |
- // These are pseudo-expressions supported for cover grammar |
- // must be forbidden when parsing initializers. |
- // TODO(jmesserly): is this still needed? |
- case TokenKind.ARROW: |
- case TokenKind.LBRACE: |
- return expr; |
- |
- default: |
- if (_peekIdentifier()) { |
- return finishPostfixExpression( |
- new DeclaredIdentifier(_makeType(expr), identifier(), |
- false, _makeSpan(expr.span.start))); |
- } else { |
- return expr; |
- } |
- } |
- } |
- |
- finishCallOrLambdaExpression(expr) { |
- if (_atClosureParameters()) { |
- var formals = formalParameterList(); |
- var body = functionBody(true); |
- return _makeFunction(expr, formals, body); |
- } else { |
- if (expr is DeclaredIdentifier) { |
- _error('illegal target for call, did you mean to declare a function?', |
- expr.span); |
- } |
- var args = arguments(); |
- return finishPostfixExpression( |
- new CallExpression(expr, args, _makeSpan(expr.span.start))); |
- } |
- } |
- |
- /** Checks if the given expression is a binary op of the given kind. */ |
- _isBin(expr, kind) { |
- return expr is BinaryExpression && expr.op.kind == kind; |
- } |
- |
- _makeLiteral(Value value) { |
- return new LiteralExpression(value, value.span); |
- } |
- |
- primary() { |
- int start = _peekToken.start; |
- switch (_peek()) { |
- case TokenKind.THIS: |
- _eat(TokenKind.THIS); |
- return new ThisExpression(_makeSpan(start)); |
- |
- case TokenKind.SUPER: |
- _eat(TokenKind.SUPER); |
- return new SuperExpression(_makeSpan(start)); |
- |
- case TokenKind.CONST: |
- _eat(TokenKind.CONST); |
- if (_peekKind(TokenKind.LBRACK) || _peekKind(TokenKind.INDEX)) { |
- return finishListLiteral(start, true, null); |
- } else if (_peekKind(TokenKind.LBRACE)) { |
- return finishMapLiteral(start, true, null, null); |
- } else if (_peekKind(TokenKind.LT)) { |
- return finishTypedLiteral(start, true); |
- } else { |
- return finishNewExpression(start, true); |
- } |
- |
- case TokenKind.NEW: |
- _eat(TokenKind.NEW); |
- return finishNewExpression(start, false); |
- |
- case TokenKind.LPAREN: |
- return _parenOrLambda(); |
- |
- case TokenKind.LBRACK: |
- case TokenKind.INDEX: |
- return finishListLiteral(start, false, null); |
- case TokenKind.LBRACE: |
- return finishMapLiteral(start, false, null, null); |
- |
- // Literals |
- case TokenKind.NULL: |
- _eat(TokenKind.NULL); |
- return _makeLiteral(Value.fromNull(_makeSpan(start))); |
- |
- // TODO(jimhug): Make Literal creation less wasteful - no dup span/text. |
- case TokenKind.TRUE: |
- _eat(TokenKind.TRUE); |
- return _makeLiteral(Value.fromBool(true, _makeSpan(start))); |
- |
- case TokenKind.FALSE: |
- _eat(TokenKind.FALSE); |
- return _makeLiteral(Value.fromBool(false, _makeSpan(start))); |
- |
- case TokenKind.HEX_INTEGER: |
- var t = _next(); |
- return _makeLiteral(Value.fromInt(t.value, t.span)); |
- |
- case TokenKind.INTEGER: |
- var t = _next(); |
- return _makeLiteral(Value.fromInt(Math.parseInt(t.text), t.span)); |
- |
- case TokenKind.DOUBLE: |
- var t = _next(); |
- return _makeLiteral( |
- Value.fromDouble(Math.parseDouble(t.text), t.span)); |
- |
- case TokenKind.STRING: |
- case TokenKind.STRING_PART: |
- return adjacentStrings(); |
- |
- case TokenKind.LT: |
- return finishTypedLiteral(start, false); |
- |
- case TokenKind.VOID: |
- case TokenKind.VAR: |
- case TokenKind.FINAL: |
- return declaredIdentifier(false); |
- |
- default: |
- if (!_peekIdentifier()) { |
- // TODO(jimhug): Better error message. |
- _errorExpected('expression'); |
- } |
- return new VarExpression(identifier(), _makeSpan(start)); |
- } |
- } |
- |
- adjacentStrings() { |
- int start = _peekToken.start; |
- List<Expression> strings = []; |
- while (_peek() == TokenKind.STRING || _peek() == TokenKind.STRING_PART) { |
- Expression part = null; |
- if (_peek() == TokenKind.STRING) { |
- var t = _next(); |
- part = _makeLiteral(Value.fromString(t.value, t.span)); |
- } else { |
- part = stringInterpolation(); |
- } |
- strings.add(part); |
- } |
- if (strings.length == 1) { |
- return strings[0]; |
- } else { |
- assert(!strings.isEmpty()); |
- return new StringConcatExpression(strings, _makeSpan(start)); |
- } |
- } |
- |
- stringInterpolation() { |
- int start = _peekToken.start; |
- var pieces = new List<Expression>(); |
- var startQuote = null, endQuote = null; |
- while(_peekKind(TokenKind.STRING_PART)) { |
- var token = _next(); |
- pieces.add(_makeLiteral(Value.fromString(token.value, token.span))); |
- if (_maybeEat(TokenKind.LBRACE)) { |
- pieces.add(expression()); |
- _eat(TokenKind.RBRACE); |
- } else if (_maybeEat(TokenKind.THIS)) { |
- pieces.add(new ThisExpression(_previousToken.span)); |
- } else { |
- var id = identifier(); |
- pieces.add(new VarExpression(id, id.span)); |
- } |
- } |
- var tok = _next(); |
- if (tok.kind != TokenKind.STRING) { |
- _errorExpected('interpolated string'); |
- } |
- pieces.add(_makeLiteral(Value.fromString(tok.value, tok.span))); |
- var span = _makeSpan(start); |
- return new StringInterpExpression(pieces, span); |
- } |
- |
- String maybeStringLiteral() { |
- var kind = _peek(); |
- if (kind == TokenKind.STRING) { |
- var t = _next(); |
- return t.value; |
- } else if (kind == TokenKind.STRING_PART) { |
- _next(); |
- _errorExpected('string literal, but found interpolated string start'); |
- } |
- return null; |
- } |
- |
- _parenOrLambda() { |
- int start = _peekToken.start; |
- if (_atClosureParameters()) { |
- var formals = formalParameterList(); |
- var body = functionBody(true); |
- var func = new FunctionDefinition(null, null, null, formals, null, null, |
- body, _makeSpan(start)); |
- return new LambdaExpression(func, func.span); |
- } else { |
- _eatLeftParen(); |
- var saved = _inhibitLambda; |
- _inhibitLambda = false; |
- var expr = expression(); |
- _eat(TokenKind.RPAREN); |
- _inhibitLambda = saved; |
- return new ParenExpression(expr, _makeSpan(start)); |
- } |
- } |
- |
- bool _atClosureParameters() { |
- if (_inhibitLambda) return false; |
- Token after = _peekAfterCloseParen(); |
- return after.kind == TokenKind.ARROW || after.kind == TokenKind.LBRACE; |
- } |
- |
- /** Eats an LPAREN, and advances our after-RPAREN lookahead. */ |
- _eatLeftParen() { |
- _eat(TokenKind.LPAREN); |
- _afterParensIndex++; |
- } |
- |
- Token _peekAfterCloseParen() { |
- if (_afterParensIndex < _afterParens.length) { |
- return _afterParens[_afterParensIndex]; |
- } |
- |
- // Reset the queue |
- _afterParensIndex = 0; |
- _afterParens.clear(); |
- |
- // Start copying tokens as we lookahead |
- var tokens = <Token>[_next()]; // LPAREN |
- _lookaheadAfterParens(tokens); |
- |
- // Put all the lookahead tokens back into the parser's token stream. |
- var after = _peekToken; |
- tokens.add(after); |
- tokenizer = new DivertedTokenSource(tokens, this, tokenizer); |
- _next(); // Re-synchronize parser lookahead state. |
- return after; |
- } |
- |
- /** |
- * This scan for the matching RPAREN to the current LPAREN and saves this |
- * result for all nested parentheses so we don't need to look-head again. |
- */ |
- _lookaheadAfterParens(List<Token> tokens) { |
- // Save a slot in the array. This will hold the token after the parens. |
- int saved = _afterParens.length; |
- _afterParens.add(null); // save a slot |
- while (true) { |
- Token token = _next(); |
- tokens.add(token); |
- int kind = token.kind; |
- if (kind == TokenKind.RPAREN || kind == TokenKind.END_OF_FILE) { |
- _afterParens[saved] = _peekToken; |
- return; |
- } else if (kind == TokenKind.LPAREN) { |
- // Scan anything inside these nested parenthesis |
- _lookaheadAfterParens(tokens); |
- } |
- } |
- } |
- |
- _typeAsIdentifier(type) { |
- if (type.name.name == 'void') { |
- _errorExpected('identifer, but found "${type.name.name}"'); |
- } |
- |
- // TODO(jimhug): lots of errors to check for |
- return type.name; |
- } |
- |
- _specialIdentifier(bool includeOperators) { |
- int start = _peekToken.start; |
- String name; |
- |
- switch (_peek()) { |
- case TokenKind.ELLIPSIS: |
- _eat(TokenKind.ELLIPSIS); |
- _error('rest no longer supported', _previousToken.span); |
- name = identifier().name; |
- break; |
- case TokenKind.THIS: |
- _eat(TokenKind.THIS); |
- _eat(TokenKind.DOT); |
- name = 'this.${identifier().name}'; |
- break; |
- case TokenKind.GET: |
- if (!includeOperators) return null; |
- _eat(TokenKind.GET); |
- if (_peekIdentifier()) { |
- name = 'get:${identifier().name}'; |
- } else { |
- name = 'get'; |
- } |
- break; |
- case TokenKind.SET: |
- if (!includeOperators) return null; |
- _eat(TokenKind.SET); |
- if (_peekIdentifier()) { |
- name = 'set:${identifier().name}'; |
- } else { |
- name = 'set'; |
- } |
- break; |
- case TokenKind.OPERATOR: |
- if (!includeOperators) return null; |
- _eat(TokenKind.OPERATOR); |
- var kind = _peek(); |
- if (kind == TokenKind.NEGATE) { |
- name = ':negate'; |
- _next(); |
- } else { |
- name = TokenKind.binaryMethodName(kind); |
- if (name == null) { |
- // TODO(jimhug): This is a very useful error, but we have to |
- // lose it because operator is a pseudo-keyword... |
- //_errorExpected('legal operator name, but found: ${tok}'); |
- name = 'operator'; |
- } else { |
- _next(); |
- } |
- } |
- break; |
- default: |
- return null; |
- } |
- return new Identifier(name, _makeSpan(start)); |
- } |
- |
- // always includes this and ... as legal names to simplify other code. |
- declaredIdentifier([bool includeOperators=false]) { |
- int start = _peekToken.start; |
- var myType = null; |
- var name = _specialIdentifier(includeOperators); |
- bool isFinal = false; |
- if (name == null) { |
- myType = type(); |
- name = _specialIdentifier(includeOperators); |
- if (name == null) { |
- if (_peekIdentifier()) { |
- name = identifier(); |
- } else if (myType is NameTypeReference && myType.names == null) { |
- name = _typeAsIdentifier(myType); |
- isFinal = myType.isFinal; |
- myType = null; |
- } else { |
- // TODO(jimhug): Where do these errors get handled? |
- } |
- } |
- } |
- return new DeclaredIdentifier(myType, name, isFinal, _makeSpan(start)); |
- } |
- |
- finishNewExpression(int start, bool isConst) { |
- var type = type(); |
- var name = null; |
- if (_maybeEat(TokenKind.DOT)) { |
- name = identifier(); |
- } |
- var args = arguments(); |
- return new NewExpression(isConst, type, name, args, _makeSpan(start)); |
- } |
- |
- finishListLiteral(int start, bool isConst, TypeReference itemType) { |
- if (_maybeEat(TokenKind.INDEX)) { |
- // This is an empty array. |
- return new ListExpression(isConst, itemType, [], _makeSpan(start)); |
- } |
- |
- var values = []; |
- _eat(TokenKind.LBRACK); |
- while (!_maybeEat(TokenKind.RBRACK)) { |
- values.add(expression()); |
- if (_recover && !_recoverTo(TokenKind.RBRACK, TokenKind.COMMA)) break; |
- if (!_maybeEat(TokenKind.COMMA)) { |
- _eat(TokenKind.RBRACK); |
- break; |
- } |
- } |
- return new ListExpression(isConst, itemType, values, _makeSpan(start)); |
- } |
- |
- finishMapLiteral(int start, bool isConst, |
- TypeReference keyType, TypeReference valueType) { |
- var items = []; |
- _eat(TokenKind.LBRACE); |
- while (!_maybeEat(TokenKind.RBRACE)) { |
- // This is deliberately overly permissive - checked in later pass. |
- items.add(expression()); |
- _eat(TokenKind.COLON); |
- items.add(expression()); |
- if (_recover && !_recoverTo(TokenKind.RBRACE, TokenKind.COMMA)) break; |
- if (!_maybeEat(TokenKind.COMMA)) { |
- _eat(TokenKind.RBRACE); |
- break; |
- } |
- } |
- return new MapExpression(isConst, keyType, valueType, items, |
- _makeSpan(start)); |
- } |
- |
- finishTypedLiteral(int start, bool isConst) { |
- var span = _makeSpan(start); |
- |
- final typeToBeNamedLater = new NameTypeReference(false, null, null, span); |
- final genericType = addTypeArguments(typeToBeNamedLater, 0); |
- final typeArgs = genericType.typeArguments; |
- |
- if (_peekKind(TokenKind.LBRACK) || _peekKind(TokenKind.INDEX)) { |
- if (typeArgs.length != 1) { |
- world.error('exactly one type argument expected for list', |
- genericType.span); |
- } |
- return finishListLiteral(start, isConst, typeArgs[0]); |
- } else if (_peekKind(TokenKind.LBRACE)) { |
- var keyType, valueType; |
- if (typeArgs.length == 1) { |
- keyType = null; |
- valueType = typeArgs[0]; |
- } else if (typeArgs.length == 2) { |
- keyType = typeArgs[0]; |
- // making key explicit is just a warning. |
- world.warning( |
- 'a map literal takes one type argument specifying the value type', |
- keyType.span); |
- valueType = typeArgs[1]; |
- } // o.w. the type system will detect the mismatch in type arguments. |
- return finishMapLiteral(start, isConst, keyType, valueType); |
- } else { |
- _errorExpected('array or map literal'); |
- } |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // Some auxilary productions. |
- /////////////////////////////////////////////////////////////////// |
- _readModifiers() { |
- var modifiers = null; |
- while (true) { |
- switch(_peek()) { |
- case TokenKind.STATIC: |
- case TokenKind.FINAL: |
- case TokenKind.CONST: |
- case TokenKind.ABSTRACT: |
- case TokenKind.FACTORY: |
- if (modifiers == null) modifiers = []; |
- modifiers.add(_next()); |
- break; |
- default: |
- return modifiers; |
- } |
- } |
- |
- return null; |
- } |
- |
- ParameterType typeParameter() { |
- // non-recursive - so always starts from zero depth |
- int start = _peekToken.start; |
- var name = identifier(); |
- var myType = null; |
- if (_maybeEat(TokenKind.EXTENDS)) { |
- myType = type(1); |
- } |
- |
- var tp = new TypeParameter(name, myType, _makeSpan(start)); |
- return new ParameterType(name.name, tp); |
- } |
- |
- List<ParameterType> typeParameters() { |
- // always starts from zero depth |
- _eat(TokenKind.LT); |
- |
- bool closed = false; |
- var ret = []; |
- do { |
- var tp = typeParameter(); |
- ret.add(tp); |
- if (tp.typeParameter.extendsType is GenericTypeReference && |
- tp.typeParameter.extendsType.dynamic.depth == 0) { |
- closed = true; |
- break; |
- } |
- } while (_maybeEat(TokenKind.COMMA)); |
- if (!closed) { |
- _eat(TokenKind.GT); |
- } |
- return ret; |
- } |
- |
- int _eatClosingAngle(int depth) { |
- if (_maybeEat(TokenKind.GT)) { |
- return depth; |
- } else if (depth > 0 && _maybeEat(TokenKind.SAR)) { |
- return depth-1; |
- } else if (depth > 1 && _maybeEat(TokenKind.SHR)) { |
- return depth-2; |
- } else { |
- _errorExpected('>'); |
- return depth; |
- } |
- } |
- |
- addTypeArguments(TypeReference baseType, int depth) { |
- _eat(TokenKind.LT); |
- return _finishTypeArguments(baseType, depth, []); |
- } |
- |
- _finishTypeArguments(TypeReference baseType, int depth, types) { |
- var delta = -1; |
- do { |
- var myType = type(depth+1); |
- types.add(myType); |
- if (myType is GenericTypeReference && myType.depth <= depth) { |
- // TODO(jimhug): Friendly error if peek(COMMA). |
- delta = depth - myType.depth; |
- break; |
- } |
- } while (_maybeEat(TokenKind.COMMA)); |
- if (delta >= 0) { |
- depth -= delta; |
- } else { |
- depth = _eatClosingAngle(depth); |
- } |
- |
- var span = _makeSpan(baseType.span.start); |
- return new GenericTypeReference(baseType, types, depth, span); |
- } |
- |
- typeList() { |
- var types = []; |
- do { |
- types.add(type()); |
- } while (_maybeEat(TokenKind.COMMA)); |
- |
- return types; |
- } |
- |
- nameTypeReference() { |
- int start = _peekToken.start; |
- var name; |
- var names = null; |
- var typeArgs = null; |
- var isFinal = false; |
- |
- switch (_peek()) { |
- case TokenKind.VOID: |
- return new SimpleTypeReference(world.voidType, _next().span); |
- case TokenKind.VAR: |
- return new SimpleTypeReference(world.varType, _next().span); |
- case TokenKind.FINAL: |
- _eat(TokenKind.FINAL); |
- isFinal = true; |
- name = identifier(); |
- break; |
- default: |
- name = identifier(); |
- break; |
- } |
- |
- while (_maybeEat(TokenKind.DOT)) { |
- if (names == null) names = []; |
- names.add(identifier()); |
- } |
- |
- return new NameTypeReference(isFinal, name, names, _makeSpan(start)); |
- } |
- |
- type([int depth = 0]) { |
- var typeRef = nameTypeReference(); |
- |
- if (_peekKind(TokenKind.LT)) { |
- return addTypeArguments(typeRef, depth); |
- } else { |
- return typeRef; |
- } |
- } |
- |
- formalParameter(bool inOptionalBlock) { |
- int start = _peekToken.start; |
- var isThis = false; |
- var isRest = false; |
- var di = declaredIdentifier(false); |
- var type = di.type; |
- var name = di.name; |
- |
- if (name == null) { |
- _error('Formal parameter invalid', _makeSpan(start)); |
- } |
- |
- var value = null; |
- if (_maybeEat(TokenKind.ASSIGN)) { |
- if (!inOptionalBlock) { |
- _error('default values only allowed inside [optional] section'); |
- } |
- value = expression(); |
- } else if (_peekKind(TokenKind.LPAREN)) { |
- var formals = formalParameterList(); |
- var func = new FunctionDefinition(null, type, name, formals, |
- null, null, null, _makeSpan(start)); |
- type = new FunctionTypeReference(false, func, func.span); |
- } |
- if (inOptionalBlock && value == null) { |
- value = _makeLiteral(Value.fromNull(_makeSpan(start))); |
- } |
- |
- return new FormalNode(isThis, isRest, type, name, value, _makeSpan(start)); |
- } |
- |
- formalParameterList() { |
- _eatLeftParen(); |
- var formals = []; |
- var inOptionalBlock = false; |
- if (!_maybeEat(TokenKind.RPAREN)) { |
- if (_maybeEat(TokenKind.LBRACK)) { |
- inOptionalBlock = true; |
- } |
- formals.add(formalParameter(inOptionalBlock)); |
- while (_maybeEat(TokenKind.COMMA)) { |
- if (_maybeEat(TokenKind.LBRACK)) { |
- if (inOptionalBlock) { |
- _error('already inside an optional block', _previousToken.span); |
- } |
- inOptionalBlock = true; |
- } |
- formals.add(formalParameter(inOptionalBlock)); |
- } |
- if (inOptionalBlock) { |
- _eat(TokenKind.RBRACK); |
- } |
- _eat(TokenKind.RPAREN); |
- } |
- return formals; |
- } |
- |
- // Type names are not allowed to use pseudo keywords |
- identifierForType() { |
- var tok = _next(); |
- if (!_isIdentifier(tok.kind)) { |
- _error('expected identifier, but found $tok', tok.span); |
- } |
- if (tok.kind !== TokenKind.IDENTIFIER && tok.kind != TokenKind.NATIVE) { |
- _error('$tok may not be used as a type name', tok.span); |
- } |
- return new Identifier(tok.text, _makeSpan(tok.start)); |
- } |
- |
- identifier() { |
- var tok = _next(); |
- if (!_isIdentifier(tok.kind)) { |
- _error('expected identifier, but found $tok', tok.span); |
- } |
- |
- return new Identifier(tok.text, _makeSpan(tok.start)); |
- } |
- |
- /////////////////////////////////////////////////////////////////// |
- // These last productions handle most ambiguities in grammar |
- // They will convert expressions into other types. |
- /////////////////////////////////////////////////////////////////// |
- |
- /** |
- * Converts an [Expression], [Formals] and a [Statment] body into a |
- * [FunctionDefinition]. |
- */ |
- _makeFunction(expr, formals, body) { |
- var name, type; |
- if (expr is VarExpression) { |
- name = expr.name; |
- type = null; |
- } else if (expr is DeclaredIdentifier) { |
- name = expr.name; |
- type = expr.type; |
- if (name == null) { |
- _error('expected name and type', expr.span); |
- } |
- } else { |
- _error('bad function body', expr.span); |
- } |
- var span = new SourceSpan(expr.span.file, expr.span.start, body.span.end); |
- var func = new FunctionDefinition(null, type, name, formals, null, null, |
- body, span); |
- return new LambdaExpression(func, func.span); |
- } |
- |
- /** Converts an expression to a [DeclaredIdentifier]. */ |
- _makeDeclaredIdentifier(e) { |
- if (e is VarExpression) { |
- return new DeclaredIdentifier(null, e.name, false, e.span); |
- } else if (e is DeclaredIdentifier) { |
- return e; |
- } else { |
- _error('expected declared identifier'); |
- return new DeclaredIdentifier(null, null, false, e.span); |
- } |
- } |
- |
- /** Converts an expression into a label. */ |
- _makeLabel(expr) { |
- if (expr is VarExpression) { |
- return expr.name; |
- } else { |
- _errorExpected('label'); |
- return null; |
- } |
- } |
-} |
- |
-class IncompleteSourceException implements Exception { |
- final Token token; |
- |
- IncompleteSourceException(this.token); |
- |
- String toString() { |
- if (token.span == null) return 'Unexpected $token'; |
- return token.span.toMessageString('Unexpected $token'); |
- } |
-} |
- |
-/** |
- * Stores a token stream that will be used by the parser. Once the parser has |
- * reached the end of this [TokenSource], it switches back to the |
- * [previousTokenizer] |
- */ |
-class DivertedTokenSource implements TokenSource { |
- final List<Token> tokens; |
- final Parser parser; |
- final TokenSource previousTokenizer; |
- DivertedTokenSource(this.tokens, this.parser, this.previousTokenizer); |
- |
- int _pos = 0; |
- next() { |
- var token = tokens[_pos]; |
- ++_pos; |
- if (_pos == tokens.length) { |
- parser.tokenizer = previousTokenizer; |
- } |
- return token; |
- } |
-} |