blob: a93dce73ba615fdc61edaaad20d7c7e761bf5da8 [file] [log] [blame]
// Copyright (c) 2013, 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.
library tokenizer_test;
import 'package:polymer_expressions/tokenizer.dart';
import 'package:unittest/unittest.dart';
main() {
group('tokenizer', () {
test('should tokenize an empty expression', () {
expectTokens('', []);
});
test('should tokenize an identifier', () {
expectTokens('abc', [t(IDENTIFIER_TOKEN, 'abc')]);
});
test('should tokenize a double quoted String', () {
expectTokens('"abc"', [t(STRING_TOKEN, 'abc')]);
});
test('should tokenize a single quoted String', () {
expectTokens("'abc'", [t(STRING_TOKEN, 'abc')]);
});
test('should tokenize a String with escaping', () {
expectTokens('"a\\b\\\\c\\\'\\""', [t(STRING_TOKEN, 'ab\\c\'"')]);
});
test('should tokenize a dot operator', () {
expectTokens('a.b', [
t(IDENTIFIER_TOKEN, 'a'),
t(DOT_TOKEN, '.'),
t(IDENTIFIER_TOKEN, 'b')]);
});
test('should tokenize a unary plus operator', () {
expectTokens('+a', [
t(OPERATOR_TOKEN, '+'),
t(IDENTIFIER_TOKEN, 'a')]);
});
test('should tokenize a binary plus operator', () {
expectTokens('a + b', [
t(IDENTIFIER_TOKEN, 'a'),
t(OPERATOR_TOKEN, '+'),
t(IDENTIFIER_TOKEN, 'b')]);
});
test('should tokenize a logical and operator', () {
expectTokens('a && b', [
t(IDENTIFIER_TOKEN, 'a'),
t(OPERATOR_TOKEN, '&&'),
t(IDENTIFIER_TOKEN, 'b')]);
});
test('should tokenize a ternary operator', () {
expectTokens('a ? b : c', [
t(IDENTIFIER_TOKEN, 'a'),
t(OPERATOR_TOKEN, '?'),
t(IDENTIFIER_TOKEN, 'b'),
t(COLON_TOKEN, ':'),
t(IDENTIFIER_TOKEN, 'c')]);
});
test('should tokenize an iterate expression with "in" keyword', () {
expectTokens('item in items', [
t(IDENTIFIER_TOKEN, 'item'),
t(KEYWORD_TOKEN, 'in'),
t(IDENTIFIER_TOKEN, 'items')]);
});
test('should tokenize keywords', () {
expectTokens('in', [t(KEYWORD_TOKEN, 'in')]);
expectTokens('this', [t(KEYWORD_TOKEN, 'this')]);
});
test('should tokenize groups', () {
expectTokens('a(b)[]{}', [
t(IDENTIFIER_TOKEN, 'a'),
t(GROUPER_TOKEN, '('),
t(IDENTIFIER_TOKEN, 'b'),
t(GROUPER_TOKEN, ')'),
t(GROUPER_TOKEN, '['),
t(GROUPER_TOKEN, ']'),
t(GROUPER_TOKEN, '{'),
t(GROUPER_TOKEN, '}')]);
});
test('should tokenize argument lists', () {
expectTokens('(a, b)', [
t(GROUPER_TOKEN, '('),
t(IDENTIFIER_TOKEN, 'a'),
t(COMMA_TOKEN, ','),
t(IDENTIFIER_TOKEN, 'b'),
t(GROUPER_TOKEN, ')')]);
});
test('should tokenize maps', () {
expectTokens("{'a': b}", [
t(GROUPER_TOKEN, '{'),
t(STRING_TOKEN, 'a'),
t(COLON_TOKEN, ':'),
t(IDENTIFIER_TOKEN, 'b'),
t(GROUPER_TOKEN, '}')]);
});
test('should tokenize lists', () {
expectTokens("[1, 'a', b]", [
t(GROUPER_TOKEN, '['),
t(INTEGER_TOKEN, '1'),
t(COMMA_TOKEN, ','),
t(STRING_TOKEN, 'a'),
t(COMMA_TOKEN, ','),
t(IDENTIFIER_TOKEN, 'b'),
t(GROUPER_TOKEN, ']')]);
});
test('should tokenize integers', () {
expectTokens('123', [t(INTEGER_TOKEN, '123')]);
expectTokens('+123', [t(OPERATOR_TOKEN, '+'), t(INTEGER_TOKEN, '123')]);
expectTokens('-123', [t(OPERATOR_TOKEN, '-'), t(INTEGER_TOKEN, '123')]);
});
test('should tokenize decimals', () {
expectTokens('1.23', [t(DECIMAL_TOKEN, '1.23')]);
expectTokens('+1.23', [t(OPERATOR_TOKEN, '+'), t(DECIMAL_TOKEN, '1.23')]);
expectTokens('-1.23', [t(OPERATOR_TOKEN, '-'), t(DECIMAL_TOKEN, '1.23')]);
});
test('should tokenize booleans as identifiers', () {
expectTokens('true', [t(IDENTIFIER_TOKEN, 'true')]);
expectTokens('false', [t(IDENTIFIER_TOKEN, 'false')]);
});
});
}
TokenMatcher isToken(int index, String text) => new TokenMatcher(index, text);
class TokenMatcher extends Matcher {
final int kind;
final String value;
TokenMatcher(this.kind, this.value);
bool matches(Token t, Map m) => t.kind == kind && t.value == value;
Description describe(Description d) => d.add('isToken($kind, $value) ');
}
expectTokens(String s, List<Token> expected) {
var tokens = new Tokenizer(s).tokenize();
var matchers = expected.map((t) => isToken(t.kind, t.value)).toList();
expect(tokens, matchList(matchers), reason: s);
}
Token t(int kind, String value) => new Token(kind, value);
MatcherList matchList(List matchers) => new MatcherList(matchers);
class MatcherList extends Matcher {
final List<Matcher> matchers;
MatcherList(this.matchers);
bool matches(List o, Map matchState) {
if (o.length != matchers.length) return false;
for (int i = 0; i < o.length; i++) {
var state = new Map();
if (!matchers[i].matches(o[i], state)) {
matchState.addAll({
'index': i,
'value': o[i],
'state': state,
});
return false;
}
}
return true;
}
Description describe(Description d) {
d.add('matches all: ');
matchers.forEach((m) => m.describe(d));
}
Description describeMismatch(item, Description mismatchDescription,
Map matchState, bool verbose) {
if (matchState != null) {
var index = matchState['index'];
var value = matchState['value'];
var state = matchState['state'];
var matcher = matchers[index];
mismatchDescription.add("Mismatch at index $index: ");
matcher.describeMismatch(value, mismatchDescription, state, verbose);
} else {
if (item.length != matchers.length) {
mismatchDescription.add('wrong lengths');
} else {
mismatchDescription.add('was ').addDescriptionOf(item);
}
}
}
}