Index: third_party/pkg/angular/lib/core/parser/dynamic_parser_impl.dart |
diff --git a/third_party/pkg/angular/lib/core/parser/dynamic_parser_impl.dart b/third_party/pkg/angular/lib/core/parser/dynamic_parser_impl.dart |
index 0b893ddcf27389d2924011dc28232c30ba8f3d1e..60c6a004c498baf29b3feec0b0bf1f130e0aaf1e 100644 |
--- a/third_party/pkg/angular/lib/core/parser/dynamic_parser_impl.dart |
+++ b/third_party/pkg/angular/lib/core/parser/dynamic_parser_impl.dart |
@@ -3,9 +3,10 @@ library angular.core.parser.dynamic_parser_impl; |
import 'package:angular/core/parser/parser.dart' show ParserBackend; |
import 'package:angular/core/parser/lexer.dart'; |
import 'package:angular/core/parser/syntax.dart'; |
+import 'package:angular/core/parser/characters.dart'; |
+import 'package:angular/utils.dart' show isReservedWord; |
class DynamicParserImpl { |
- static Token EOF = new Token(-1, null); |
final ParserBackend backend; |
final String input; |
final List<Token> tokens; |
@@ -14,27 +15,33 @@ class DynamicParserImpl { |
DynamicParserImpl(Lexer lexer, this.backend, String input) |
: this.input = input, tokens = lexer.call(input); |
- Token get peek { |
- return (index < tokens.length) ? tokens[index] : EOF; |
- } |
+ Token get next => peek(0); |
+ Token peek(int offset) => (index + offset < tokens.length) |
+ ? tokens[index + offset] |
+ : Token.EOF; |
parseChain() { |
bool isChain = false; |
- while (optional(';')) { |
+ while (optionalCharacter($SEMICOLON)) { |
isChain = true; |
} |
List expressions = []; |
while (index < tokens.length) { |
- if (peek.text == ')' || peek.text == '}' || peek.text == ']') { |
- error('Unconsumed token ${peek.text}'); |
+ if (next.isCharacter($RPAREN) || |
+ next.isCharacter($RBRACE) || |
+ next.isCharacter($RBRACKET)) { |
+ error('Unconsumed token $next'); |
} |
var expr = parseFilter(); |
expressions.add(expr); |
- while (optional(';')) { |
+ while (optionalCharacter($SEMICOLON)) { |
isChain = true; |
} |
if (isChain && expr is Filter) { |
- error('cannot have a filter in a chain'); |
+ error('Cannot have a formatter in a chain'); |
+ } |
+ if (!isChain && index < tokens.length) { |
+ error("'${next}' is an unexpected token", index); |
} |
} |
return (expressions.length == 1) |
@@ -44,11 +51,10 @@ class DynamicParserImpl { |
parseFilter() { |
var result = parseExpression(); |
- while (optional('|')) { |
- String name = peek.text; // TODO(kasperl): Restrict to identifier? |
- advance(); |
+ while (optionalOperator('|')) { |
+ String name = expectIdentifierOrKeyword(); |
List arguments = []; |
- while (optional(':')) { |
+ while (optionalCharacter($COLON)) { |
// TODO(kasperl): Is this really supposed to be expressions? |
arguments.add(parseExpression()); |
} |
@@ -58,27 +64,27 @@ class DynamicParserImpl { |
} |
parseExpression() { |
- int start = peek.index; |
+ int start = next.index; |
var result = parseConditional(); |
- while (peek.text == '=') { |
+ while (next.isOperator('=')) { |
if (!backend.isAssignable(result)) { |
- int end = (index < tokens.length) ? peek.index : input.length; |
+ int end = (index < tokens.length) ? next.index : input.length; |
String expression = input.substring(start, end); |
error('Expression $expression is not assignable'); |
} |
- expect('='); |
+ expectOperator('='); |
result = backend.newAssign(result, parseConditional()); |
} |
return result; |
} |
parseConditional() { |
- int start = peek.index; |
+ int start = next.index; |
var result = parseLogicalOr(); |
- if (optional('?')) { |
+ if (optionalOperator('?')) { |
var yes = parseExpression(); |
- if (!optional(':')) { |
- int end = (index < tokens.length) ? peek.index : input.length; |
+ if (!optionalCharacter($COLON)) { |
+ int end = (index < tokens.length) ? next.index : input.length; |
String expression = input.substring(start, end); |
error('Conditional expression $expression requires all 3 expressions'); |
} |
@@ -91,7 +97,7 @@ class DynamicParserImpl { |
parseLogicalOr() { |
// '||' |
var result = parseLogicalAnd(); |
- while (optional('||')) { |
+ while (optionalOperator('||')) { |
result = backend.newBinaryLogicalOr(result, parseLogicalAnd()); |
} |
return result; |
@@ -100,7 +106,7 @@ class DynamicParserImpl { |
parseLogicalAnd() { |
// '&&' |
var result = parseEquality(); |
- while (optional('&&')) { |
+ while (optionalOperator('&&')) { |
result = backend.newBinaryLogicalAnd(result, parseEquality()); |
} |
return result; |
@@ -110,9 +116,9 @@ class DynamicParserImpl { |
// '==','!=' |
var result = parseRelational(); |
while (true) { |
- if (optional('==')) { |
+ if (optionalOperator('==')) { |
result = backend.newBinaryEqual(result, parseRelational()); |
- } else if (optional('!=')) { |
+ } else if (optionalOperator('!=')) { |
result = backend.newBinaryNotEqual(result, parseRelational()); |
} else { |
return result; |
@@ -124,13 +130,13 @@ class DynamicParserImpl { |
// '<', '>', '<=', '>=' |
var result = parseAdditive(); |
while (true) { |
- if (optional('<')) { |
+ if (optionalOperator('<')) { |
result = backend.newBinaryLessThan(result, parseAdditive()); |
- } else if (optional('>')) { |
+ } else if (optionalOperator('>')) { |
result = backend.newBinaryGreaterThan(result, parseAdditive()); |
- } else if (optional('<=')) { |
+ } else if (optionalOperator('<=')) { |
result = backend.newBinaryLessThanEqual(result, parseAdditive()); |
- } else if (optional('>=')) { |
+ } else if (optionalOperator('>=')) { |
result = backend.newBinaryGreaterThanEqual(result, parseAdditive()); |
} else { |
return result; |
@@ -142,9 +148,9 @@ class DynamicParserImpl { |
// '+', '-' |
var result = parseMultiplicative(); |
while (true) { |
- if (optional('+')) { |
+ if (optionalOperator('+')) { |
result = backend.newBinaryPlus(result, parseMultiplicative()); |
- } else if (optional('-')) { |
+ } else if (optionalOperator('-')) { |
result = backend.newBinaryMinus(result, parseMultiplicative()); |
} else { |
return result; |
@@ -156,13 +162,13 @@ class DynamicParserImpl { |
// '*', '%', '/', '~/' |
var result = parsePrefix(); |
while (true) { |
- if (optional('*')) { |
+ if (optionalOperator('*')) { |
result = backend.newBinaryMultiply(result, parsePrefix()); |
- } else if (optional('%')) { |
+ } else if (optionalOperator('%')) { |
result = backend.newBinaryModulo(result, parsePrefix()); |
- } else if (optional('/')) { |
+ } else if (optionalOperator('/')) { |
result = backend.newBinaryDivide(result, parsePrefix()); |
- } else if (optional('~/')) { |
+ } else if (optionalOperator('~/')) { |
result = backend.newBinaryTruncatingDivide(result, parsePrefix()); |
} else { |
return result; |
@@ -171,12 +177,12 @@ class DynamicParserImpl { |
} |
parsePrefix() { |
- if (optional('+')) { |
+ if (optionalOperator('+')) { |
// TODO(kasperl): This is different than the original parser. |
return backend.newPrefixPlus(parsePrefix()); |
- } else if (optional('-')) { |
+ } else if (optionalOperator('-')) { |
return backend.newPrefixMinus(parsePrefix()); |
- } else if (optional('!')) { |
+ } else if (optionalOperator('!')) { |
return backend.newPrefixNot(parsePrefix()); |
} else { |
return parseAccessOrCallMember(); |
@@ -186,24 +192,22 @@ class DynamicParserImpl { |
parseAccessOrCallMember() { |
var result = parsePrimary(); |
while (true) { |
- if (optional('.')) { |
- // TODO(kasperl): Check that this is an identifier. Are keywords okay? |
- String name = peek.text; |
- advance(); |
- if (optional('(')) { |
- List arguments = parseExpressionList(')'); |
- expect(')'); |
+ if (optionalCharacter($PERIOD)) { |
+ String name = expectIdentifierOrKeyword(); |
+ if (optionalCharacter($LPAREN)) { |
+ CallArguments arguments = parseCallArguments(); |
+ expectCharacter($RPAREN); |
result = backend.newCallMember(result, name, arguments); |
} else { |
result = backend.newAccessMember(result, name); |
} |
- } else if (optional('[')) { |
+ } else if (optionalCharacter($LBRACKET)) { |
var key = parseExpression(); |
- expect(']'); |
+ expectCharacter($RBRACKET); |
result = backend.newAccessKeyed(result, key); |
- } else if (optional('(')) { |
- List arguments = parseExpressionList(')'); |
- expect(')'); |
+ } else if (optionalCharacter($LPAREN)) { |
+ CallArguments arguments = parseCallArguments(); |
+ expectCharacter($RPAREN); |
result = backend.newCallFunction(result, arguments); |
} else { |
return result; |
@@ -212,77 +216,107 @@ class DynamicParserImpl { |
} |
parsePrimary() { |
- if (optional('(')) { |
- var result = parseExpression(); |
- expect(')'); |
+ if (optionalCharacter($LPAREN)) { |
+ var result = parseFilter(); |
+ expectCharacter($RPAREN); |
return result; |
- } else if (optional('null') || optional('undefined')) { |
+ } else if (next.isKeywordNull || next.isKeywordUndefined) { |
+ advance(); |
return backend.newLiteralNull(); |
- } else if (optional('true')) { |
+ } else if (next.isKeywordTrue) { |
+ advance(); |
return backend.newLiteralBoolean(true); |
- } else if (optional('false')) { |
+ } else if (next.isKeywordFalse) { |
+ advance(); |
return backend.newLiteralBoolean(false); |
- } else if (optional('[')) { |
- List elements = parseExpressionList(']'); |
- expect(']'); |
+ } else if (optionalCharacter($LBRACKET)) { |
+ List elements = parseExpressionList($RBRACKET); |
+ expectCharacter($RBRACKET); |
return backend.newLiteralArray(elements); |
- } else if (peek.text == '{') { |
+ } else if (next.isCharacter($LBRACE)) { |
return parseObject(); |
- } else if (peek.key != null) { |
+ } else if (next.isIdentifier) { |
return parseAccessOrCallScope(); |
- } else if (peek.value != null) { |
- var value = peek.value; |
+ } else if (next.isNumber) { |
+ num value = next.toNumber(); |
advance(); |
- return (value is num) |
- ? backend.newLiteralNumber(value) |
- : backend.newLiteralString(value); |
+ return backend.newLiteralNumber(value); |
+ } else if (next.isString) { |
+ String value = next.toString(); |
+ advance(); |
+ return backend.newLiteralString(value); |
} else if (index >= tokens.length) { |
throw 'Unexpected end of expression: $input'; |
} else { |
- error('Unexpected token ${peek.text}'); |
+ error('Unexpected token $next'); |
} |
} |
parseAccessOrCallScope() { |
- String name = peek.key; |
- advance(); |
- if (!optional('(')) return backend.newAccessScope(name); |
- List arguments = parseExpressionList(')'); |
- expect(')'); |
+ String name = expectIdentifierOrKeyword(); |
+ if (!optionalCharacter($LPAREN)) return backend.newAccessScope(name); |
+ CallArguments arguments = parseCallArguments(); |
+ expectCharacter($RPAREN); |
return backend.newCallScope(name, arguments); |
} |
parseObject() { |
List<String> keys = []; |
List values = []; |
- expect('{'); |
- if (peek.text != '}') { |
+ expectCharacter($LBRACE); |
+ if (!optionalCharacter($RBRACE)) { |
do { |
- // TODO(kasperl): Stricter checking. Only allow identifiers |
- // and strings as keys. Maybe also keywords? |
- var value = peek.value; |
- keys.add(value is String ? value : peek.text); |
- advance(); |
- expect(':'); |
+ String key = expectIdentifierOrKeywordOrString(); |
+ keys.add(key); |
+ expectCharacter($COLON); |
values.add(parseExpression()); |
- } while (optional(',')); |
+ } while (optionalCharacter($COMMA)); |
+ expectCharacter($RBRACE); |
} |
- expect('}'); |
return backend.newLiteralObject(keys, values); |
} |
- List parseExpressionList(String terminator) { |
+ List parseExpressionList(int terminator) { |
List result = []; |
- if (peek.text != terminator) { |
+ if (!next.isCharacter(terminator)) { |
do { |
result.add(parseExpression()); |
- } while (optional(',')); |
+ } while (optionalCharacter($COMMA)); |
} |
return result; |
} |
- bool optional(text) { |
- if (peek.text == text) { |
+ CallArguments parseCallArguments() { |
+ if (next.isCharacter($RPAREN)) { |
+ return const CallArguments(const [], const {}); |
+ } |
+ // Parse the positional arguments. |
+ List positionals = []; |
+ while (true) { |
+ if (peek(1).isCharacter($COLON)) break; |
+ positionals.add(parseExpression()); |
+ if (!optionalCharacter($COMMA)) { |
+ return new CallArguments(positionals, const {}); |
+ } |
+ } |
+ // Parse the named arguments. |
+ Map named = {}; |
+ do { |
+ int marker = index; |
+ String name = expectIdentifierOrKeyword(); |
+ if (isReservedWord(name)) { |
+ error("Cannot use Dart reserved word '$name' as named argument", marker); |
+ } else if (named.containsKey(name)) { |
+ error("Duplicate argument named '$name'", marker); |
+ } |
+ expectCharacter($COLON); |
+ named[name] = parseExpression(); |
+ } while (optionalCharacter($COMMA)); |
+ return new CallArguments(positionals, named); |
+ } |
+ |
+ bool optionalCharacter(int code) { |
+ if (next.isCharacter(code)) { |
advance(); |
return true; |
} else { |
@@ -290,19 +324,49 @@ class DynamicParserImpl { |
} |
} |
- void expect(text) { |
- if (peek.text == text) { |
+ bool optionalOperator(String operator) { |
+ if (next.isOperator(operator)) { |
advance(); |
+ return true; |
} else { |
- error('Missing expected $text'); |
+ return false; |
+ } |
+ } |
+ |
+ void expectCharacter(int code) { |
+ if (optionalCharacter(code)) return; |
+ error('Missing expected ${new String.fromCharCode(code)}'); |
+ } |
+ |
+ void expectOperator(String operator) { |
+ if (optionalOperator(operator)) return; |
+ error('Missing expected operator $operator'); |
+ } |
+ |
+ String expectIdentifierOrKeyword() { |
+ if (!next.isIdentifier && !next.isKeyword) { |
+ error('Unexpected token $next, expected identifier or keyword'); |
} |
+ String result = next.toString(); |
+ advance(); |
+ return result; |
+ } |
+ |
+ String expectIdentifierOrKeywordOrString() { |
+ if (!next.isIdentifier && !next.isKeyword && !next.isString) { |
+ error('Unexpected token $next, expected identifier, keyword, or string'); |
+ } |
+ String result = next.toString(); |
+ advance(); |
+ return result; |
} |
void advance() { |
index++; |
} |
- void error(message) { |
+ void error(message, [int index]) { |
+ if (index == null) index = this.index; |
String location = (index < tokens.length) |
? 'at column ${tokens[index].index + 1} in' |
: 'the end of the expression'; |