9e77768c91
Add tests for incorrect literals, e.g. "+ 5". Added a temporary token kTIGHTADD that represents an ADD not terminated by a whitespace. It is temporary and eliminated in the parser (code generator ot AST do not see it). It is used to recognize correct literals. Review URL: https://chromiumcodereview.appspot.com//9264051 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@3745 260f80e4-7a28-3924-810f-c04153c831b5
371 lines
10 KiB
C++
371 lines
10 KiB
C++
// Copyright (c) 2012, 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.
|
|
|
|
#include "platform/assert.h"
|
|
#include "vm/os.h"
|
|
#include "vm/scanner.h"
|
|
#include "vm/token.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
void LogTokenDesc(Scanner::TokenDescriptor token) {
|
|
OS::Print("pos %2d:%d-%d token %s ",
|
|
token.position.line, token.position.column,
|
|
token.position.column + token.length,
|
|
Token::Name(token.kind));
|
|
if (token.literal != NULL) {
|
|
OS::Print("%s", token.literal->ToCString());
|
|
}
|
|
OS::Print("\n");
|
|
}
|
|
|
|
|
|
void LogTokenStream(const Scanner::GrowableTokenStream& token_stream) {
|
|
int token_index = 0;
|
|
EXPECT_GT(token_stream.length(), 0);
|
|
while (token_index < token_stream.length()) {
|
|
LogTokenDesc(token_stream[token_index]);
|
|
ASSERT(token_stream[token_index].kind != Token::kILLEGAL);
|
|
token_index++;
|
|
}
|
|
printf("%d tokens in stream.\n", token_index);
|
|
EXPECT_EQ(token_stream.Last().kind, Token::kEOS);
|
|
}
|
|
|
|
|
|
void CheckKind(const Scanner::GrowableTokenStream &token_stream,
|
|
int index,
|
|
Token::Kind kind) {
|
|
if (token_stream[index].kind != kind) {
|
|
OS::PrintErr("Token %d: expected kind %s but got %s\n", index,
|
|
Token::Name(token_stream[index].kind), Token::Name(kind));
|
|
}
|
|
EXPECT_EQ(kind, token_stream[index].kind);
|
|
}
|
|
|
|
|
|
void CheckLiteral(const Scanner::GrowableTokenStream& token_stream,
|
|
int index,
|
|
const char* literal) {
|
|
if (token_stream[index].literal == NULL) {
|
|
OS::PrintErr("Token %d: expected literal \"%s\" but got nothing\n",
|
|
index, literal);
|
|
} else if (strcmp(literal, token_stream[index].literal->ToCString())) {
|
|
OS::PrintErr("Token %d: expected literal \"%s\" but got \"%s\"\n",
|
|
index, literal, token_stream[index].literal->ToCString());
|
|
}
|
|
}
|
|
|
|
|
|
void CheckIdent(const Scanner::GrowableTokenStream& token_stream,
|
|
int index,
|
|
const char* literal) {
|
|
CheckKind(token_stream, index, Token::kIDENT);
|
|
CheckLiteral(token_stream, index, literal);
|
|
}
|
|
|
|
|
|
void CheckInteger(const Scanner::GrowableTokenStream& token_stream,
|
|
int index,
|
|
const char* literal) {
|
|
CheckKind(token_stream, index, Token::kINTEGER);
|
|
CheckLiteral(token_stream, index, literal);
|
|
}
|
|
|
|
|
|
void CheckLineNumber(const Scanner::GrowableTokenStream& token_stream,
|
|
int index,
|
|
int line_number) {
|
|
if (token_stream[index].position.line != line_number) {
|
|
OS::PrintErr("Token %d: expected line number %d but got %d\n",
|
|
index, line_number, token_stream[index].position.line);
|
|
}
|
|
}
|
|
|
|
|
|
void CheckNumTokens(const Scanner::GrowableTokenStream& token_stream,
|
|
int index) {
|
|
if (token_stream.length() != index) {
|
|
OS::PrintErr("Expected %d tokens but got only %d.\n",
|
|
index, token_stream.length());
|
|
}
|
|
}
|
|
|
|
|
|
const Scanner::GrowableTokenStream& Scan(const char* source) {
|
|
Scanner scanner(String::Handle(String::New(source)),
|
|
String::Handle(String::New("")));
|
|
|
|
OS::Print("\nScanning: <%s>\n", source);
|
|
const Scanner::GrowableTokenStream& tokens = scanner.GetStream();
|
|
LogTokenStream(tokens);
|
|
return tokens;
|
|
}
|
|
|
|
|
|
void BoringTest() {
|
|
const Scanner::GrowableTokenStream& tokens = Scan("x = iffy++;");
|
|
|
|
CheckNumTokens(tokens, 6);
|
|
CheckIdent(tokens, 0, "x");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckIdent(tokens, 2, "iffy");
|
|
CheckKind(tokens, 3, Token::kINCR);
|
|
CheckKind(tokens, 4, Token::kSEMICOLON);
|
|
}
|
|
|
|
|
|
void CommentTest() {
|
|
const Scanner::GrowableTokenStream& tokens =
|
|
Scan("Foo( /*block \n"
|
|
"comment*/ 0xff) // line comment;");
|
|
|
|
CheckNumTokens(tokens, 5);
|
|
CheckIdent(tokens, 0, "Foo");
|
|
CheckLineNumber(tokens, 0, 1);
|
|
CheckKind(tokens, 1, Token::kLPAREN);
|
|
CheckInteger(tokens, 2, "0xff");
|
|
CheckKind(tokens, 3, Token::kRPAREN);
|
|
CheckLineNumber(tokens, 3, 2);
|
|
}
|
|
|
|
|
|
void GreedIsGood() {
|
|
// means i++ + j
|
|
const Scanner::GrowableTokenStream& tokens = Scan("x=i+++j");
|
|
|
|
CheckNumTokens(tokens, 7);
|
|
CheckIdent(tokens, 0, "x");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckIdent(tokens, 2, "i");
|
|
CheckKind(tokens, 3, Token::kINCR);
|
|
CheckKind(tokens, 4, Token::kADD);
|
|
CheckIdent(tokens, 5, "j");
|
|
}
|
|
|
|
|
|
void StringEscapes() {
|
|
// sss = "\" \\ \n\r\t \'"
|
|
const Scanner::GrowableTokenStream& tokens =
|
|
Scan("sss = \"\\\" \\\\ \\n\\r\\t \\\'\"");
|
|
|
|
EXPECT_EQ(4, tokens.length());
|
|
CheckIdent(tokens, 0, "sss");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckKind(tokens, 2, Token::kSTRING);
|
|
CheckKind(tokens, 3, Token::kEOS);
|
|
CheckLineNumber(tokens, 2, 1);
|
|
const char* litchars = (tokens)[2].literal->ToCString();
|
|
EXPECT_EQ(9, (tokens)[2].literal->Length());
|
|
|
|
EXPECT_EQ('"', litchars[0]);
|
|
EXPECT_EQ(' ', litchars[1]);
|
|
EXPECT_EQ('\\', litchars[2]);
|
|
EXPECT_EQ('\n', litchars[4]);
|
|
EXPECT_EQ('\r', litchars[5]);
|
|
EXPECT_EQ('\t', litchars[6]);
|
|
EXPECT_EQ('\'', litchars[8]);
|
|
}
|
|
|
|
|
|
void RawString() {
|
|
// rs = @"\' \\"
|
|
const Scanner::GrowableTokenStream& tokens = Scan("rs = @\"\\\' \\\\\"");
|
|
|
|
EXPECT_EQ(4, tokens.length());
|
|
CheckIdent(tokens, 0, "rs");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckKind(tokens, 2, Token::kSTRING);
|
|
CheckKind(tokens, 3, Token::kEOS);
|
|
CheckLineNumber(tokens, 2, 1);
|
|
const char* litchars = (tokens)[2].literal->ToCString();
|
|
EXPECT_EQ(5, (tokens)[2].literal->Length());
|
|
|
|
EXPECT_EQ('\\', litchars[0]);
|
|
EXPECT_EQ('\'', litchars[1]);
|
|
EXPECT_EQ(' ', litchars[2]);
|
|
EXPECT_EQ('\\', litchars[3]);
|
|
EXPECT_EQ('\\', litchars[4]);
|
|
}
|
|
|
|
|
|
void MultilineString() {
|
|
// |mls = '''
|
|
// |1' x
|
|
// |2''';
|
|
const Scanner::GrowableTokenStream& tokens = Scan("mls = '''\n1' x\n2''';");
|
|
|
|
EXPECT_EQ(5, tokens.length());
|
|
CheckIdent(tokens, 0, "mls");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckKind(tokens, 2, Token::kSTRING);
|
|
CheckKind(tokens, 3, Token::kSEMICOLON);
|
|
CheckKind(tokens, 4, Token::kEOS);
|
|
CheckLineNumber(tokens, 0, 1);
|
|
CheckLineNumber(tokens, 4, 3); // Semicolon is on line 3.
|
|
const char* litchars = (tokens)[2].literal->ToCString();
|
|
EXPECT_EQ(6, (tokens)[2].literal->Length());
|
|
|
|
EXPECT_EQ('1', litchars[0]); // First newline is dropped.
|
|
EXPECT_EQ('\'', litchars[1]);
|
|
EXPECT_EQ(' ', litchars[2]);
|
|
EXPECT_EQ('x', litchars[3]);
|
|
EXPECT_EQ('\n', litchars[4]);
|
|
EXPECT_EQ('2', litchars[5]);
|
|
}
|
|
|
|
|
|
void EmptyString() {
|
|
// es = "";
|
|
const Scanner::GrowableTokenStream& tokens = Scan("es = \"\";");
|
|
|
|
EXPECT_EQ(5, tokens.length());
|
|
CheckIdent(tokens, 0, "es");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckKind(tokens, 2, Token::kSTRING);
|
|
CheckKind(tokens, 3, Token::kSEMICOLON);
|
|
CheckKind(tokens, 4, Token::kEOS);
|
|
EXPECT_EQ(0, (tokens)[2].literal->Length());
|
|
}
|
|
|
|
void EmptyMultilineString() {
|
|
// es = """""";
|
|
const Scanner::GrowableTokenStream& tokens = Scan("es = \"\"\"\"\"\";");
|
|
|
|
EXPECT_EQ(5, tokens.length());
|
|
CheckIdent(tokens, 0, "es");
|
|
CheckKind(tokens, 1, Token::kASSIGN);
|
|
CheckKind(tokens, 2, Token::kSTRING);
|
|
CheckKind(tokens, 3, Token::kSEMICOLON);
|
|
CheckKind(tokens, 4, Token::kEOS);
|
|
EXPECT_EQ(0, (tokens)[2].literal->Length());
|
|
}
|
|
|
|
|
|
void NumberLiteral() {
|
|
const Scanner::GrowableTokenStream& tokens =
|
|
Scan("5 0x5d 0.3 0.33 1E+12 .42 +5");
|
|
|
|
CheckKind(tokens, 0, Token::kINTEGER);
|
|
CheckKind(tokens, 1, Token::kINTEGER);
|
|
CheckKind(tokens, 2, Token::kDOUBLE);
|
|
CheckKind(tokens, 3, Token::kDOUBLE);
|
|
CheckKind(tokens, 4, Token::kDOUBLE);
|
|
CheckKind(tokens, 5, Token::kDOUBLE);
|
|
CheckKind(tokens, 6, Token::kTIGHTADD);
|
|
CheckKind(tokens, 7, Token::kINTEGER);
|
|
CheckKind(tokens, 8, Token::kEOS);
|
|
}
|
|
|
|
|
|
void ScanLargeText() {
|
|
const char* dart_source =
|
|
"// This source is not meant to be valid Dart code. The text is used to"
|
|
"// test the Dart scanner."
|
|
""
|
|
"// Cartesian point implemetation."
|
|
"class Point {"
|
|
""
|
|
" // Constructor"
|
|
" Point(Number x, Number y) : x(x), y(y) { }"
|
|
""
|
|
" // Addition for points."
|
|
" Point operator +(Point other) {"
|
|
" return new Point(x + other.x, y + other.y);"
|
|
" }"
|
|
""
|
|
" // Fields are const and they cannot be changed."
|
|
" const Number x;"
|
|
" const Number y;"
|
|
"}"
|
|
""
|
|
""
|
|
"// Polar point class that implements the Point interface."
|
|
"class PolarPoint implements Point {"
|
|
""
|
|
" PolarPoint(Number theta, Number radius)"
|
|
" : theta(theta), radius(radius) { }"
|
|
""
|
|
" Number get x() { return radius * Math.cos(theta); }"
|
|
" Number get y() { return radius * Math.sin(theta); }"
|
|
""
|
|
" const Number theta;"
|
|
" const Number radius;"
|
|
"}"
|
|
""
|
|
"interface Map<K extends Hashable,V> default HashMap<K, V> {"
|
|
" V operator [](K key);"
|
|
" void operator []=(K key, V value);"
|
|
" void forEach(function f(K key, V value));"
|
|
"}"
|
|
""
|
|
"class Foo {"
|
|
" static const Array kMyArray = [1,2,3,4,5,6];"
|
|
" static const Point kMyPoint = Point(1,2);"
|
|
"}"
|
|
""
|
|
"class DequeEntry<T> implements QueueEntry<T>{"
|
|
" DequeEntry<T> next;"
|
|
" DequeEntry<T> previous;"
|
|
" T value;"
|
|
"}"
|
|
""
|
|
"void forEach(void function f(T element)) {"
|
|
" for (int i = 0; i < this.length; i++) {"
|
|
" f(this[i]);"
|
|
" }"
|
|
"}"
|
|
""
|
|
""
|
|
"j!==!iffy // means j !== !iffy";
|
|
Scan(dart_source);
|
|
}
|
|
|
|
|
|
void InvalidText() {
|
|
const Scanner::GrowableTokenStream& tokens =
|
|
Scan("\\");
|
|
|
|
EXPECT_EQ(2, tokens.length());
|
|
CheckKind(tokens, 0, Token::kERROR);
|
|
CheckKind(tokens, 1, Token::kEOS);
|
|
}
|
|
|
|
|
|
void FindLineTest() {
|
|
const char* source =
|
|
"/*1*/ \n"
|
|
"/*2*/ class A {\n"
|
|
"/*3*/ void foo() { }\n"
|
|
"/*4*/ }\n";
|
|
|
|
Scanner scanner(String::Handle(String::New(source)),
|
|
String::Handle(String::New("")));
|
|
|
|
intptr_t token_index = scanner.TokenIndexAtLine(3);
|
|
EXPECT_EQ(3, token_index);
|
|
token_index = scanner.TokenIndexAtLine(100);
|
|
EXPECT(token_index < 0);
|
|
}
|
|
|
|
|
|
TEST_CASE(Scanner_Test) {
|
|
ScanLargeText();
|
|
|
|
BoringTest();
|
|
CommentTest();
|
|
GreedIsGood();
|
|
StringEscapes();
|
|
RawString();
|
|
MultilineString();
|
|
EmptyString();
|
|
EmptyMultilineString();
|
|
NumberLiteral();
|
|
InvalidText();
|
|
FindLineTest();
|
|
}
|
|
|
|
} // namespace dart
|