#ifndef BidiTestHarness_h
#define BidiTestHarness_h
#include <istream>
#include <map>
#include <stdio.h>
#include <string>
#include <vector>
namespace bidi_test {
enum ParagraphDirection {
DirectionAutoLTR = 1,
DirectionLTR = 2,
DirectionRTL = 4,
};
const int kMaxParagraphDirection = DirectionAutoLTR | DirectionLTR | DirectionRTL;
std::string nameFromParagraphDirection(ParagraphDirection paragraphDirection)
{
switch (paragraphDirection) {
case bidi_test::DirectionAutoLTR:
return "Auto-LTR";
case bidi_test::DirectionLTR:
return "LTR";
case bidi_test::DirectionRTL:
return "RTL";
}
return "";
}
template<class Runner>
class Harness {
public:
Harness(Runner& runner)
: m_runner(runner)
{
}
void parse(std::istream& bidiTestFile);
private:
Runner& m_runner;
};
inline void ltrim(std::string& s)
{
static const std::string separators(" \t");
s.erase(0, s.find_first_not_of(separators));
}
inline void rtrim(std::string& s)
{
static const std::string separators(" \t");
size_t lastNonSpace = s.find_last_not_of(separators);
if (lastNonSpace == std::string::npos) {
s.erase();
return;
}
size_t firstSpaceAtEndOfString = lastNonSpace + 1;
if (firstSpaceAtEndOfString >= s.size())
return;
s.erase(firstSpaceAtEndOfString, std::string::npos);
}
inline void trim(std::string& s)
{
rtrim(s);
ltrim(s);
}
static std::vector<std::string> parseStringList(const std::string& str)
{
std::vector<std::string> strings;
static const std::string separators(" \t");
size_t lastPos = str.find_first_not_of(separators);
size_t pos = str.find_first_of(separators, lastPos);
while (std::string::npos != pos || std::string::npos != lastPos) {
strings.push_back(str.substr(lastPos, pos - lastPos));
lastPos = str.find_first_not_of(separators, pos);
pos = str.find_first_of(separators, lastPos);
}
return strings;
}
static std::vector<int> parseIntList(const std::string& str)
{
std::vector<int> ints;
std::vector<std::string> strings = parseStringList(str);
for (size_t x = 0; x < strings.size(); x++) {
int i = atoi(strings[x].c_str());
ints.push_back(i);
}
return ints;
}
static std::vector<int> parseLevels(const std::string& line)
{
std::vector<int> levels;
std::vector<std::string> strings = parseStringList(line);
for (size_t x = 0; x < strings.size(); x++) {
const std::string& levelString = strings[x];
int i;
if (levelString == "x")
i = -1;
else
i = atoi(levelString.c_str());
levels.push_back(i);
}
return levels;
}
static std::basic_string<UChar> parseTestString(const std::string& line)
{
std::basic_string<UChar> testString;
static std::map<std::string, UChar> charClassExamples;
if (charClassExamples.empty()) {
charClassExamples.insert(std::make_pair("L", 0x6c));
charClassExamples.insert(std::make_pair("R", 0x05D0));
charClassExamples.insert(std::make_pair("EN", 0x33));
charClassExamples.insert(std::make_pair("ES", 0x2d));
charClassExamples.insert(std::make_pair("ET", 0x25));
charClassExamples.insert(std::make_pair("AN", 0x0660));
charClassExamples.insert(std::make_pair("CS", 0x2c));
charClassExamples.insert(std::make_pair("B", 0x0A));
charClassExamples.insert(std::make_pair("S", 0x09));
charClassExamples.insert(std::make_pair("WS", 0x20));
charClassExamples.insert(std::make_pair("ON", 0x3d));
charClassExamples.insert(std::make_pair("NSM", 0x05BF));
charClassExamples.insert(std::make_pair("AL", 0x0608));
charClassExamples.insert(std::make_pair("BN", 0x00AD));
charClassExamples.insert(std::make_pair("LRE", 0x202A));
charClassExamples.insert(std::make_pair("RLE", 0x202B));
charClassExamples.insert(std::make_pair("PDF", 0x202C));
charClassExamples.insert(std::make_pair("LRO", 0x202D));
charClassExamples.insert(std::make_pair("RLO", 0x202E));
charClassExamples.insert(std::make_pair("LRI", 0x2066));
charClassExamples.insert(std::make_pair("RLI", 0x2067));
charClassExamples.insert(std::make_pair("FSI", 0x2068));
charClassExamples.insert(std::make_pair("PDI", 0x2069));
}
std::vector<std::string> charClasses = parseStringList(line);
for (size_t i = 0; i < charClasses.size(); i++) {
testString.push_back(charClassExamples.find(charClasses[i])->second);
}
return testString;
}
static bool parseParagraphDirectionMask(const std::string& line, int& modeMask)
{
modeMask = atoi(line.c_str());
return modeMask >= 1 && modeMask <= kMaxParagraphDirection;
}
static void parseError(const std::string& line, size_t lineNumber)
{
printf("Parse error, line %zu : %s\n", lineNumber, line.c_str());
}
template<class Runner>
void Harness<Runner>::parse(std::istream& bidiTestFile)
{
static const std::string levelsPrefix("@Levels");
static const std::string reorderPrefix("@Reorder");
std::basic_string<UChar> testString;
std::vector<int> levels;
std::vector<int> reorder;
int paragraphDirectionMask;
std::string line;
size_t lineNumber = 0;
while (std::getline(bidiTestFile, line)) {
lineNumber++;
const std::string originalLine = line;
size_t commentStart = line.find_first_of('#');
if (commentStart != std::string::npos)
line = line.substr(0, commentStart);
trim(line);
if (line.empty())
continue;
if (line[0] == '@') {
if (!line.find(levelsPrefix)) {
levels = parseLevels(line.substr(levelsPrefix.length() + 1));
continue;
}
if (!line.find(reorderPrefix)) {
reorder = parseIntList(line.substr(reorderPrefix.length() + 1));
continue;
}
} else {
size_t seperatorIndex = line.find_first_of(';');
if (seperatorIndex == std::string::npos) {
parseError(originalLine, lineNumber);
continue;
}
testString = parseTestString(line.substr(0, seperatorIndex));
if (!parseParagraphDirectionMask(line.substr(seperatorIndex + 1), paragraphDirectionMask)) {
parseError(originalLine, lineNumber);
continue;
}
if (paragraphDirectionMask & DirectionAutoLTR)
m_runner.runTest(testString, reorder, levels, DirectionAutoLTR, originalLine, lineNumber);
if (paragraphDirectionMask & DirectionLTR)
m_runner.runTest(testString, reorder, levels, DirectionLTR, originalLine, lineNumber);
if (paragraphDirectionMask & DirectionRTL)
m_runner.runTest(testString, reorder, levels, DirectionRTL, originalLine, lineNumber);
}
}
}
}
#endif