| // Copyright (c) 2017, 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. |
| |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'; |
| import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart' |
| show AbstractScanner; |
| import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart' as fasta; |
| import 'package:_fe_analyzer_shared/src/scanner/token.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(PrecedenceInfoTest); |
| }); |
| } |
| |
| /// Assert that fasta PrecedenceInfo implements analyzer TokenType. |
| @reflectiveTest |
| class PrecedenceInfoTest { |
| void assertInfo(check(String source, Token token)) { |
| void assertLexeme(String source) { |
| if (source == null || source.isEmpty) return; |
| var token = scanString(source, includeComments: true).tokens; |
| while (token is ErrorToken) { |
| token = token.next; |
| } |
| check(source, token); |
| } |
| |
| for (TokenType type in TokenType.all) { |
| assertLexeme(type.lexeme); |
| } |
| assertLexeme('1.0'); // DOUBLE |
| assertLexeme('0xA'); // HEXADECIMAL |
| assertLexeme('1'); // INT |
| assertLexeme('var'); // KEYWORD |
| assertLexeme('#!/'); // SCRIPT_TAG |
| assertLexeme('"foo"'); // STRING |
| assertLexeme('bar'); // IDENTIFIER |
| if (AbstractScanner.LAZY_ASSIGNMENT_ENABLED) { |
| assertLexeme('&&='); |
| assertLexeme('||='); |
| } |
| } |
| |
| void test_isOperator() { |
| var operatorLexemes = new Set<String>.from(const [ |
| '&', |
| '&&', |
| '&&=', |
| '&=', |
| '!', |
| '!=', |
| '|', |
| '||', |
| '||=', |
| '|=', |
| '^', |
| '^=', |
| '=', |
| '==', |
| '>', |
| '>=', |
| '>>', |
| '>>=', |
| '[]', |
| '[]=', |
| '<', |
| '<=', |
| '<<', |
| '<<=', |
| '-', |
| '-=', |
| '--', |
| '%', |
| '%=', |
| '..', |
| '+', |
| '+=', |
| '++', |
| '?', |
| '?.', |
| '??', |
| '??=', |
| '/', |
| '/=', |
| '*', |
| '*=', |
| '~', |
| '~/', |
| '~/=', |
| ]); |
| |
| assertInfo((String source, Token token) { |
| expect(token.isOperator, operatorLexemes.contains(source), |
| reason: source); |
| expect(token.type.isOperator, operatorLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isAdditiveOperator() { |
| var additiveLexemes = [ |
| '-', |
| '+', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isAdditiveOperator, additiveLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isAssignmentOperator() { |
| const assignmentLexemes = const [ |
| '&=', |
| '&&=', |
| '|=', |
| '||=', |
| '^=', |
| '=', |
| '>>=', |
| '<<=', |
| '-=', |
| '%=', |
| '+=', |
| '??=', |
| '/=', |
| '*=', |
| '~/=', |
| ]; |
| assertInfo((String source, Token token) { |
| expect( |
| token.type.isAssignmentOperator, assignmentLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isAssociativeOperator() { |
| const associativeLexemes = const [ |
| '&', |
| '&&', |
| '|', |
| '||', |
| '^', |
| '+', |
| '*', |
| ]; |
| assertInfo((String source, Token token) { |
| expect( |
| token.type.isAssociativeOperator, associativeLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isEqualityOperator() { |
| const equalityLexemes = const [ |
| '!=', |
| '==', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isEqualityOperator, equalityLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isIncrementOperator() { |
| const incrementLexemes = const [ |
| '--', |
| '++', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isIncrementOperator, incrementLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isMultiplicativeOperator() { |
| const multiplicativeLexemes = const [ |
| '%', |
| '/', |
| '*', |
| '~/', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isMultiplicativeOperator, |
| multiplicativeLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isRelationalOperator() { |
| const relationalLexemes = const [ |
| '>', |
| '>=', |
| '<', |
| '<=', |
| ]; |
| assertInfo((String source, Token token) { |
| expect( |
| token.type.isRelationalOperator, relationalLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isShiftOperator() { |
| const shiftLexemes = const [ |
| '>>', |
| '<<', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isShiftOperator, shiftLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isUnaryPostfixOperator() { |
| const unaryPostfixLexemes = const [ |
| '--', |
| '++', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isUnaryPostfixOperator, |
| unaryPostfixLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isUnaryPrefixOperator() { |
| const unaryPrefixLexemes = const [ |
| '!', |
| '-', |
| '--', |
| '++', |
| '~', |
| ]; |
| assertInfo((String source, Token token) { |
| expect( |
| token.type.isUnaryPrefixOperator, unaryPrefixLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isSelectorOperator() { |
| const selectorLexemes = const [ |
| '(', |
| '[', |
| '.', |
| '?.', |
| '[]', |
| ]; |
| assertInfo((String source, Token token) { |
| expect(token.type.isSelectorOperator, selectorLexemes.contains(source), |
| reason: source); |
| }); |
| } |
| |
| void test_isUserDefinableOperator() { |
| const userDefinableOperatorLexemes = const [ |
| '&', |
| '|', |
| '^', |
| '==', |
| '>', |
| '>=', |
| '>>', |
| '[]', |
| '[]=', |
| '<', |
| '<=', |
| '<<', |
| '-', |
| '%', |
| '+', |
| '/', |
| '*', |
| '~', |
| '~/', |
| ]; |
| assertInfo((String source, Token token) { |
| var userDefinable = userDefinableOperatorLexemes.contains(source); |
| expect(token.type.isUserDefinableOperator, userDefinable, reason: source); |
| expect(token.isUserDefinableOperator, userDefinable, reason: source); |
| expect(fasta.isUserDefinableOperator(token.lexeme), userDefinable, |
| reason: source); |
| }); |
| } |
| |
| void test_name() { |
| void assertName(String source, String name, {int offset: 0}) { |
| if (source == null || source.isEmpty) return; |
| var token = scanString(source, includeComments: true).tokens; |
| while (token is ErrorToken || token.offset < offset) { |
| token = token.next; |
| } |
| expect(token.type.name, name, |
| reason: 'source: $source\ntoken: ${token.lexeme}'); |
| } |
| |
| assertName('&', 'AMPERSAND'); |
| assertName('&&', 'AMPERSAND_AMPERSAND'); |
| assertName('&=', 'AMPERSAND_EQ'); |
| assertName('@', 'AT'); |
| assertName('!', 'BANG'); |
| assertName('!=', 'BANG_EQ'); |
| assertName('|', 'BAR'); |
| assertName('||', 'BAR_BAR'); |
| assertName('|=', 'BAR_EQ'); |
| assertName(':', 'COLON'); |
| assertName(',', 'COMMA'); |
| assertName('^', 'CARET'); |
| assertName('^=', 'CARET_EQ'); |
| assertName('}', 'CLOSE_CURLY_BRACKET'); |
| assertName(')', 'CLOSE_PAREN'); |
| assertName(']', 'CLOSE_SQUARE_BRACKET'); |
| assertName('=', 'EQ'); |
| assertName('==', 'EQ_EQ'); |
| assertName('=>', 'FUNCTION'); |
| assertName('>', 'GT'); |
| assertName('>=', 'GT_EQ'); |
| assertName('>>', 'GT_GT'); |
| assertName('>>=', 'GT_GT_EQ'); |
| assertName('#', 'HASH'); |
| assertName('[]', 'INDEX'); |
| assertName('[]=', 'INDEX_EQ'); |
| assertName('<', 'LT'); |
| assertName('<=', 'LT_EQ'); |
| assertName('<<', 'LT_LT'); |
| assertName('<<=', 'LT_LT_EQ'); |
| assertName('-', 'MINUS'); |
| assertName('-=', 'MINUS_EQ'); |
| assertName('--', 'MINUS_MINUS'); |
| assertName('{', 'OPEN_CURLY_BRACKET'); |
| assertName('(', 'OPEN_PAREN'); |
| assertName('[', 'OPEN_SQUARE_BRACKET'); |
| assertName('%', 'PERCENT'); |
| assertName('%=', 'PERCENT_EQ'); |
| assertName('.', 'PERIOD'); |
| assertName('..', 'PERIOD_PERIOD'); |
| assertName('+', 'PLUS'); |
| assertName('+=', 'PLUS_EQ'); |
| assertName('++', 'PLUS_PLUS'); |
| assertName('?', 'QUESTION'); |
| assertName('?.', 'QUESTION_PERIOD'); |
| assertName('??', 'QUESTION_QUESTION'); |
| assertName('??=', 'QUESTION_QUESTION_EQ'); |
| assertName(';', 'SEMICOLON'); |
| assertName('/', 'SLASH'); |
| assertName('/=', 'SLASH_EQ'); |
| assertName('*', 'STAR'); |
| assertName('*=', 'STAR_EQ'); |
| assertName('"\${', 'STRING_INTERPOLATION_EXPRESSION', offset: 1); |
| assertName('"\$', 'STRING_INTERPOLATION_IDENTIFIER', offset: 1); |
| assertName('~', 'TILDE'); |
| assertName('~/', 'TILDE_SLASH'); |
| assertName('~/=', 'TILDE_SLASH_EQ'); |
| assertName('`', 'BACKPING'); |
| assertName('\\', 'BACKSLASH'); |
| assertName('...', 'PERIOD_PERIOD_PERIOD'); |
| assertName('...?', 'PERIOD_PERIOD_PERIOD_QUESTION'); |
| } |
| |
| /// Assert precedence as per the Dart language spec |
| /// |
| /// Prefix "++" and "--" are excluded from the prefix (15) list |
| /// because they are interpreted as being in the postfix (16) list. |
| /// Leading "-" is excluded from the precedence 15 list |
| /// because it is interpreted as a minus token (precedence 13). |
| void test_precedence() { |
| const precedenceTable = const <int, List<String>>{ |
| 17: const <String>['.', '?.', '[', '('], |
| 16: const <String>['++', '--'], |
| 15: const <String>['!', '~'], // excluded '-', '++', '--' |
| 14: const <String>['*', '/', '~/', '%'], |
| 13: const <String>['+', '-'], |
| 12: const <String>['<<', '>>'], |
| 11: const <String>['&'], |
| 10: const <String>['^'], |
| 9: const <String>['|'], |
| 8: const <String>['<', '>', '<=', '>=', 'as', 'is', 'is!'], |
| 7: const <String>['==', '!='], |
| 6: const <String>['&&'], |
| 5: const <String>['||'], |
| 4: const <String>['??'], |
| 3: const <String>['? :'], |
| 2: const <String>['..'], |
| 1: const <String>['=', '*=', '/=', '+=', '-=', '&=', '^='], |
| }; |
| precedenceTable.forEach((precedence, lexemes) { |
| for (String source in lexemes) { |
| var token = scanString(source, includeComments: true).tokens; |
| while (token is ErrorToken) { |
| token = token.next; |
| } |
| expect(token.type.precedence, precedence, reason: source); |
| } |
| }); |
| } |
| |
| void test_type() { |
| void assertLexeme(String source, TokenType tt) { |
| var token = scanString(source, includeComments: true).tokens; |
| expect(token.type, same(tt), reason: source); |
| } |
| |
| assertLexeme('1.0', TokenType.DOUBLE); |
| assertLexeme('0xA', TokenType.HEXADECIMAL); |
| assertLexeme('1', TokenType.INT); |
| assertLexeme('var', Keyword.VAR); |
| assertLexeme('#!/', TokenType.SCRIPT_TAG); |
| assertLexeme('foo', TokenType.IDENTIFIER); |
| assertLexeme('"foo"', TokenType.STRING); |
| } |
| } |