Данная статья представляет интерес для разработчиков, использующих или планирующих использовать библиотеку OpenC++ (OpenCxx). Автор рассказывает о своем опыте улучшения библиотеки OpenC++ и модификации библиотеки для решения специализированных задач.
В форумах часто можно услышать, что синтаксических анализаторов ("парсеров") языка Си++ в мире огромное количество. В том числе и бесплатных. Или, что можно взять, например, YACC и легко реализовать свой анализатор. Не верьте, все не так просто. Особенно, если вспомнить, что разобрать синтаксис – это меньше половины дела. Необходимо реализовать структуры для хранения дерева программы и семантических таблиц, содержащих информацию о различных объектах и областях их действия. Особенно это важно при разработке специализированных приложений, связанных с обработкой и статическим анализом Си++ кода. Для их реализации необходимо сохранение полного дерева программы, что могут предоставить не многие библиотеки. Одной из них является открытая библиотека OpenC++ (OpenCxx), о которой мы и поговорим в этой статье.
Хочется помочь разработчикам в освоении библиотеки OpenC++ и поделиться опытом ее модернизации и использования некоторых недочетов. Статья представляет собой сборник советов, каждый из которых посвящен исправлению какого-то дефекта или реализации усовершенствования.
Статья основывается на воспоминаниях об изменениях, которые были осуществлены в библиотеке VivaCore, основанной на базе OpenC++. Конечно, здесь отражена только малая часть этих изменений. Вспомнить и описать их все будет непростой задачей. Например, описание добавления в библиотеку OpenC++ поддержки языка Си займет много места. Но Вы всегда можете обратиться к исходным текстам библиотеки VivaCore и получить много интересной информации.
Последнее, о чем хочется сказать, что библиотека OpenC++, к сожалению, на данный момент устарела и нуждается в серьезной доработке для поддержания современного стандарта языка Си++. Поэтому, если Вы, например, собираетесь реализовать современный компилятор, то Вам лучше обратить внимание на GCC или посмотреть в сторону коммерческих библиотек. Но OpenC++ и сейчас остается хорошим и удобным инструментом для многих разработчиков в области систем специализированной обработки и модификации программного кода. C использованием OpenC++ разработаны многие интересные решения. Например: среда исполнения OpenTS для языка программирования T++ (разработка Института программных систем РАН), статический анализатор кода Viva64 или инструмент Synopsis для подготовки документации по исходному коду.
Цель данной статьи - показать на примерах, как можно модифицировать и улучшить код библиотеки OpenC++. Для этого в статье описано 15 модификаций библиотеки, связанных с исправлением ошибок или добавлением новой функциональности. Все они не только позволяют сделать библиотеку OpenC++ лучше, но и дают возможность глубже изучить принципы ее работы. Давайте познакомимся с ними.
Разрабатывая анализатор кода под конкретную среду разработки, Вы наверняка столкнетесь с ее специфическими языковыми конструкциями. Часто эти конструкции являются указаниями для конкретного компилятора и могут не представлять для Вас никакого практического интереса. Но такие конструкции не могут быть обработаны библиотекой OpenC++, так как не являются частью языка Си++. В этом случае одним из простых способов игнорировать их является добавление ключевых слов в таблицу rw_table table с ключом Ignore. Пример:
static rw_table table[] = {
...
{ "__ptr32", Ignore},
{ "__ptr64", Ignore},
{ "__unaligned", Ignore},
...
};
При добавлении следует учитывать, что слова в таблице rw_table table должны быть расположены в алфавитном порядке. Будьте аккуратны!
Если Вы хотите добавить ключевое слово, которое следует обрабатывать, то Вам необходимо создать новую лексему ("токен"). Рассмотрим пример добавления ключевого слова "__w64". В начале создайте идентификатор новой лексемы (смотрите файл token-name.h), например, так:
enum {
Identifier = 258,
Constant = 262,
...
W64 = 346, // New token name
...
};
Модернизируйте таблицу "table" в файле lex.cc:
static rw_table table[] = {
...
{ "__w64", W64 },
...
};
Наш следующий шаг - это создание класса для новой лексемы, который мы назовем LeafW64:
namespace Opencxx
{
class LeafW64 : public LeafReserved {
public:
LeafW64(Token& t) : LeafReserved(t) {}
LeafW64(char* str, ptrdiff_t len) :
LeafReserved(str, len) {}
ptrdiff_t What() { return W64; }
};
}
Для создания объекта нам понадобится модифицировать функцию optIntegralTypeOrClassSpec():
...
case UNSIGNED :
flag = 'U';
kw = new (GC) LeafUNSIGNED(tk);
break;
case W64 : // NEW!
flag = 'W';
kw = new (GC) LeafW64(tk);
break;
...
Обратите внимание, что поскольку мы решили отнести "__w64" к типам данных, то нам понадобился символ 'W' для кодирования этого типа. Более подробно с механизмом кодирования типов можно познакомиться в файле Encoding.cc.
Вводя новый тип, мы должны помнить о необходимости модернизации таких функций, как например Parser::isTypeSpecifier().
И, наконец, последний важный момент - это модификация функции Encoding::MakePtree:
Ptree* Encoding::MakePtree(unsigned char*& encoded, Ptree* decl)
{
...
case 'W' :
typespec = PtreeUtil::Snoc(typespec, w64_t);
break;
...
}
Естественно, это только пример, и добавление других лексем может потребовать гораздо больше работы. Хороший способ корректно добавить новую лексему - взять близкую к ней по смыслу, а затем найти и изучить все места в библиотеке OpenC++ где она используется.
Мы уже рассмотрели способ, как пропустить единичные ключевые слова, не имеющие для нашей программы смысловой нагрузки, но которые мешают разбору кода. К сожалению, иногда дело обстоит сложнее. Возьмем в качестве демонстрации такие конструкции как __pragma и __noop, которые можно встретить в заголовочных файлах VisualC++:
__forceinline DWORD HEAP_MAKE_TAG_FLAGS (
DWORD TagBase, DWORD Tag )
{
__pragma(warning(push)) __pragma(warning(disable : 4548)) do
{__noop(TagBase);} while((0,0) __pragma(warning(pop)) );
return ((DWORD)((TagBase) + ((Tag) << 18)));
}
Посмотреть описание конструкций __pragma и __noop можно в MSDN. Для нашей программы важно следующее: а) они нам не интересны; б) имеют параметры; в) мешают анализу кода.
Вначале добавим новые лексемы, как было рассказано ранее, но теперь для этой цели воспользуемся функцией InitializeOtherKeywords():
static void InitializeOtherKeywords(bool recognizeOccExtensions)
{
...
verify(Lex::RecordKeyword("__pragma", MSPRAGMA));
verify(Lex::RecordKeyword("__noop", MS__NOOP));
...
}
Решение заключается в модификации функции Lex::ReadToken таким образом, что, когда мы встречаем лексему DECLSPEC или MSPRAGMA, мы пропускаем ее. А затем пропускаем все лексемы, относящиеся к параметрам __pragma и __noop. Для пропуска всех лишних лексем мы используем функцию SkipDeclspecToken() таким образом, как показано далее.
ptrdiff_t Lex::ReadToken(char*& ptr, ptrdiff_t& len)
{
...
else if(t == DECLSPEC){
SkipDeclspecToken();
continue;
}
else if(t == MSPRAGMA) { // NEW
SkipDeclspecToken();
continue;
}
else if(t == MS__NOOP) { //NEW
SkipDeclspecToken();
continue;
}
...
}
В задачах анализа исходного кода большое количество функциональности связано с созданием диагностических сообщений, а также навигацией по исходным файлам. Неудобство заключается в том, что имена файлов, возвращаемые такими функциями как Program::LineNumber(), могут быть представлены в разнообразном виде. Вот несколько примеров:
C:\\Program Files\\MSVS 8\\VC\\atlmfc\\include\\afx.h
.\\drawing.cpp
c:\\src\\wxwindows-2.4.2\\samples\\drawing\\wx/defs.h
Boost\\boost-1_33_1\\boost/variant/recursive_variant.hpp
..\\FieldEdit2\\Src\\amsEdit.cpp
..\\..\\..\\src\\base\\ftbase.c
Путь может быть полным или относительным. Могут использоваться различные разделители. Все это делает использование таких путей неудобным для обработки или для вывода в информационных сообщениях. Поэтому мы предлагаем реализацию функции FixFileName(), приводящую пути к единообразному полному виду. Используемая вспомогательная функция GetInputFileDirectory() должна возвращать путь к каталогу, в котором находится обрабатываемый файл:
const string &GetInputFileDirectory() {
static string oldInputFileName;
static string fileDirectory;
string dir;
VivaConfiguration &cfg = VivaConfiguration::Instance();
string inputFileName;
cfg.GetInputFileName(inputFileName);
if (oldInputFileName == inputFileName)
return fileDirectory;
oldInputFileName = inputFileName;
filesystem::path inputFileNamePath(inputFileName,
filesystem::native);
fileDirectory = inputFileNamePath.branch_path().string();
if (fileDirectory.empty()) {
TCHAR curDir[MAX_PATH];
if (GetCurrentDirectory(MAX_PATH, curDir) != 0) {
fileDirectory = curDir;
} else {
assert(false);
}
}
algorithm::replace_all(fileDirectory, "/", "\\");
to_lower(fileDirectory);
return fileDirectory;
}
typedef map<string, string> StrStrMap;
typedef StrStrMap::iterator StrStrMapIt;
void FixFileName(string &fileName) {
static StrStrMap FileNamesMap;
StrStrMapIt it = FileNamesMap.find(fileName);
if (it != FileNamesMap.end()) {
fileName = it->second;
return;
}
string oldFileName = fileName;
algorithm::replace_all(fileName, "/", "\\");
algorithm::replace_all(fileName, "\\\\", "\\");
filesystem::path tmpPath(fileName, filesystem::native);
fileName = tmpPath.string();
algorithm::replace_all(fileName, "/", "\\");
to_lower(fileName);
if (fileName.length() < 2) {
assert(false);
FileNamesMap.insert(make_pair(oldFileName, fileName));
return;
}
if (fileName[0] == '.' && fileName[1] != '.') {
const string &dir = GetInputFileDirectory();
if (!dir.empty())
fileName.replace(0, 1, dir);
FileNamesMap.insert(make_pair(oldFileName, fileName));
return;
}
if (isalpha(fileName[0]) && fileName[1] == ':' ) {
FileNamesMap.insert(make_pair(oldFileName, fileName));
return;
}
const string &dir = GetInputFileDirectory();
if (dir.empty())
fileName.insert(0, ".\\");
else {
fileName.insert(0, "\\");
fileName.insert(0, dir);
}
FileNamesMap.insert(make_pair(oldFileName, fileName));
}
В системах построения документации по коду полезной может оказаться функция получения значения числового литерала. Например, с ее помощью можно узнать и использовать тот факт, что аргумент функции "void foo(a = 99)" равен 99.
Предлагаемая функция GetLiteralType() позволяет получить тип литерала и его значение, если он целочисленный. Функция GetLiteralType() создана для получения наиболее часто необходимой информации и не поддерживает редко используемые виды записи. Но если Вам, например, будет необходимо поддержать UCNs или получать значения типа double, то вы можете самостоятельно расширить функциональность приведенных ниже функций:
", 5) == 0) { retValue = 0; ; } ; }
IsHexLiteral( *from, size_t len)
{
(len < 3);
(from[0] != '0');
(from[1] != 'x' && from[1] != 'X');;
}
SimpleType GetTypeBySufix( *from, size_t len)
{
assert(from != NULL);
(len == 0)
ST_INT;
assert(!isdigit(*from));
suffix_8 = ;
suffix_16 = ;
suffix_32 = ;
suffix_64 = ;
suffix_i = ;
suffix_l = ;
suffix_u = ;
(len != 0)
{
--len;
c = *from++;
(c)
{
'8': suffix_8 = ; ;
'1': (len == 0 || *from++ != '6')
{
assert();
ST_UNKNOWN;
}
--len;
suffix_16 = ; ;
'3': (len == 0 || *from++ != '2')
{
assert();
ST_UNKNOWN;
}
--len;
suffix_32 = ; ;
'6': (len == 0 || *from++ != '4')
{
assert();
ST_UNKNOWN;
}
--len;
suffix_64 = ; ;
'I': 'i': suffix_i = ; ;
'U': 'u': suffix_u = ; ;
'L': 'l': suffix_l = ; ; : assert();
ST_UNKNOWN;
}
}
assert(suffix_8 + suffix_16 + suffix_32 + suffix_64 <= 1);
(suffix_8 || suffix_16)
ST_LESS_INT;
(suffix_32)
{
(suffix_u)
ST_UINT;
ST_INT;
}
(suffix_64)
{
(suffix_u)
ST_UINT64;
ST_INT64;
}
(suffix_l)
{
(suffix_u)
ST_ULONG;
ST_LONG;
}
(suffix_u)
ST_UINT;
assert(suffix_i);
ST_INT;
}
SimpleType GetHexLiteral( *from, size_t len, &retValue)
{
assert(len >= 3);
*p = from + 2;
(!GetHex(p, len, retValue))
{
ST_UNKNOWN;
}
ptrdiff_t newLen = len - (p - from);
assert(newLen >= 0 && newLen < <ptrdiff_t>(len));
GetTypeBySufix(p, newLen);
}
IsOctLiteral( *from, size_t len)
{
(len < 2);
(from[0] != '0');;
}
SimpleType GetOctLiteral( *from, size_t len,&retValue)
{
assert(len >= 2);
*p = from + 1; (
!GetOct(p, len, retValue))
{
ST_UNKNOWN;
}
ptrdiff_t newLen = len - (p - from); assert(newLen >= 0 && newLen
< <ptrdiff_t>(len));
GetTypeBySufix(p, newLen);
}
SimpleType GetDecLiteral( *from, size_t len, &retValue)
{
assert(len >= 1);
*limit = from + len;
n = 0;
(from < limit)
{
c = *from;
(c < '0' || c > '9');
from++;
n = n * 10 + (c - '0');
}
ptrdiff_t newLen = limit - from; (newLen == <ptrdiff_t>(len))
ST_UNKNOWN;
retValue = n;
assert(newLen >= 0 && newLen < <ptrdiff_t>(len));
GetTypeBySufix(from, newLen);
}
SimpleType GetLiteralType( *from, size_t len, &retValue)
{
(from == NULL || len == 0)
ST_UNKNOWN;
retValue = 1;
(from == NULL || len == 0)
ST_UNKNOWN;
(GetCharLiteral(from, len, retValue))
ST_LESS_INT;
(GetStringLiteral(from, len))
ST_POINTER;
(GetBoolLiteral(from, len, retValue))
ST_LESS_INT;
(IsRealLiteral(from, len))
GetRealLiteral(from, len);
(IsHexLiteral(from, len))
GetHexLiteral(from, len, retValue);
(IsOctLiteral(from, len))
GetOctLiteral(from, len, retValue);
GetDecLiteral(from, len, retValue);
}
unsigned __int64 GetHexValue(unsigned char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 0x0a;
if (c >= 'A' && c <= 'F')
return c - 'A' + 0x0a;
assert(false);
return 0;
}
bool GetHex(const char *&from, size_t len,
unsigned __int64 &retValue) {
unsigned __int64 c, n = 0, overflow = 0;
int digits_found = 0;
const char *limit = from + len;
while (from < limit)
{
c = *from;
if (!isxdigit(c))
break;
from++;
overflow |= n ^ (n << 4 >> 4);
n = (n << 4) + GetHexValue(c);
digits_found = 1;
}
if (!digits_found)
return false;
if (overflow) {
assert(false);
}
retValue = n;
return true;
}
bool GetOct(const char *&from, size_t len,
unsigned __int64 &retValue) {
unsigned __int64 c, n = 0;
bool overflow = false;
const char *limit = from + len;
while (from < limit)
{
c = *from;
if (c < '0' || c > '7')
break;
from++;
overflow |= static_cast<bool>(n ^ (n << 3 >> 3));
n = (n << 3) + c - '0';
}
retValue = n;
return true;
}
#define HOST_CHARSET_ASCII
bool GetEscape(const char *from, size_t len,
unsigned __int64 &retValue) {
/* Values of \a \b \e \f \n \r \t \v respectively. */
// HOST_CHARSET_ASCII
static const char charconsts[] =
{ 7, 8, 27, 12, 10, 13, 9, 11 };
// HOST_CHARSET_EBCDIC
//static const uchar charconsts[] =
{ 47, 22, 39, 12, 21, 13, 5, 11 };
unsigned char c;
c = from[0];
switch (c)
{
/* UCNs, hex escapes, and octal escapes
are processed separately. */
case 'u': case 'U':
// convert_ucn - not supported. Return: 65535.
retValue = 0xFFFFui64;
return true;
case 'x': {
const char *p = from + 1;
return GetHex(p, len, retValue);
}
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': {
const char *p = from + 1;
return GetOct(p, len, retValue);
}
case '\\': case '\'': case '"': case '?':
break;
case 'a': c = charconsts[0]; break;
case 'b': c = charconsts[1]; break;
case 'f': c = charconsts[3]; break;
case 'n': c = charconsts[4]; break;
case 'r': c = charconsts[5]; break;
case 't': c = charconsts[6]; break;
case 'v': c = charconsts[7]; break;
case 'e': case 'E': c = charconsts[2]; break;
default:
assert(false);
return false;
}
retValue = c;
return true;
}
//'A', '\t', L'A', '\xFE'
static bool GetCharLiteral(const char *from,
size_t len,
unsigned __int64 &retValue) {
if (len >= 3) {
if (from[0] == '\'' && from[len - 1] == '\'') {
unsigned char c = from[1];
if (c == '\\') {
verify(GetEscape(from + 2, len - 3, retValue));
} else {
retValue = c;
}
return true;
}
}
if (len >= 4) {
if (from[0] == 'L' &&
from[1] == '\'' &&
from[len - 1] == '\'') {
unsigned char c = from[2];
if (c == '\\') {
verify(GetEscape(from + 3, len - 4, retValue));
} else {
retValue = c;
}
return true;
}
}
return false;
}
// "string"
static bool GetStringLiteral(const char *from, size_t len) {
if (len >= 2) {
if (from[0] == '"' && from[len - 1] == '"')
return true;
}
if (len >= 3) {
if (from[0] == 'L' &&
from[1] == '"' &&
from[len - 1] == '"')
return true;
}
return false;
}
bool IsRealLiteral(const char *from, size_t len) {
if (len < 2)
return false;
bool isReal = false;
bool digitFound = false;
for (size_t i = 0; i != len; ++i) {
unsigned char c = from[i];
switch(c) {
case 'x': return false;
case 'X': return false;
case 'f': isReal = true; break;
case 'F': isReal = true; break;
case '.': isReal = true; break;
case 'e': isReal = true; break;
case 'E': isReal = true; break;
case 'l': break;
case '-': break;
case '+': break;
case 'L': break;
default:
if (!isdigit(c))
return false;
digitFound = true;
}
}
return isReal && digitFound;
}
SimpleType GetRealLiteral(const char *from, size_t len) {
assert(len > 1);
unsigned char rc1 = from[len - 1];
if (is_digit(rc1) || rc1 == '.' ||
rc1 == 'l' || rc1 == 'L' ||
rc1 == 'e' || rc1 == 'E')
return ST_DOUBLE;
if (rc1 == 'f' || rc1 == 'F')
return ST_FLOAT;
assert(false);
return ST_UNKNOWN;
}
bool GetBoolLiteral(const char *from, size_t len,
unsigned __int64 &retValue) {
if (len == 4 && strncmp(from, "true", 4) == 0) {
retValue = 1;
return true;
}
if (len == 5 && strncmp(from, "false", 5) == 0) {
retValue = 0;
return true;
}
return false;
}
bool IsHexLiteral(const char *from, size_t len) {
if (len < 3)
return false;
if (from[0] != '0')
return false;
if (from[1] != 'x' && from[1] != 'X')
return false;
return true;
}
SimpleType GetTypeBySufix(const char *from, size_t len) {
assert(from != NULL);
if (len == 0)
return ST_INT;
assert(!isdigit(*from));
bool suffix_8 = false;
bool suffix_16 = false;
bool suffix_32 = false;
bool suffix_64 = false;
bool suffix_i = false;
bool suffix_l = false;
bool suffix_u = false;
while (len != 0) {
--len;
const char c = *from++;
switch(c) {
case '8': suffix_8 = true; break;
case '1':
if (len == 0 || *from++ != '6') {
assert(false);
return ST_UNKNOWN;
}
--len;
suffix_16 = true;
break;
case '3':
if (len == 0 || *from++ != '2') {
assert(false);
return ST_UNKNOWN;
}
--len;
suffix_32 = true;
break;
case '6':
if (len == 0 || *from++ != '4') {
assert(false);
return ST_UNKNOWN;
}
--len;
suffix_64 = true;
break;
case 'I':
case 'i': suffix_i = true; break;
case 'U':
case 'u': suffix_u = true; break;
case 'L':
case 'l': suffix_l = true; break;
default:
assert(false);
return ST_UNKNOWN;
}
}
assert(suffix_8 + suffix_16 + suffix_32 + suffix_64 <= 1);
if (suffix_8 || suffix_16)
return ST_LESS_INT;
if (suffix_32) {
if (suffix_u)
return ST_UINT;
else
return ST_INT;
}
if (suffix_64) {
if (suffix_u)
return ST_UINT64;
else
return ST_INT64;
}
if (suffix_l) {
if (suffix_u)
return ST_ULONG;
else
return ST_LONG;
}
if (suffix_u)
return ST_UINT;
assert(suffix_i);
return ST_INT;
}
SimpleType GetHexLiteral(const char *from, size_t len,
unsigned __int64 &retValue) {
assert(len >= 3);
const char *p = from + 2;
if (!GetHex(p, len, retValue)) {
return ST_UNKNOWN;
}
ptrdiff_t newLen = len - (p - from);
assert(newLen >= 0 && newLen < static_cast<ptrdiff_t>(len));
return GetTypeBySufix(p, newLen);
}
bool IsOctLiteral(const char *from, size_t len) {
if (len < 2)
return false;
if (from[0] != '0')
return false;
return true;
}
SimpleType GetOctLiteral(const char *from, size_t len,
unsigned __int64 &retValue) {
assert(len >= 2);
const char *p = from + 1;
if (!GetOct(p, len, retValue)) {
return ST_UNKNOWN;
}
ptrdiff_t newLen = len - (p - from);
assert(newLen >= 0 && newLen < static_cast<ptrdiff_t>(len));
return GetTypeBySufix(p, newLen);
}
SimpleType GetDecLiteral(const char *from, size_t len,
unsigned __int64 &retValue) {
assert(len >= 1);
const char *limit = from + len;
unsigned __int64 n = 0;
while (from < limit) {
const char c = *from;
if (c < '0' || c > '9')
break;
from++;
n = n * 10 + (c - '0');
}
ptrdiff_t newLen = limit - from;
if (newLen == static_cast<ptrdiff_t>(len))
return ST_UNKNOWN;
retValue = n;
assert(newLen >= 0 && newLen < static_cast<ptrdiff_t>(len));
return GetTypeBySufix(from, newLen);
}
SimpleType GetLiteralType(const char *from, size_t len,
unsigned __int64 &retValue) {
if (from == NULL || len == 0)
return ST_UNKNOWN;
retValue = 1;
if (from == NULL || len == 0)
return ST_UNKNOWN;
if (GetCharLiteral(from, len, retValue))
return ST_LESS_INT;
if (GetStringLiteral(from, len))
return ST_POINTER;
if (GetBoolLiteral(from, len, retValue))
return ST_LESS_INT;
if (IsRealLiteral(from, len))
return GetRealLiteral(from, len);
if (IsHexLiteral(from, len))
return GetHexLiteral(from, len, retValue);
if (IsOctLiteral(from, len))
return GetOctLiteral(from, len, retValue);
return GetDecLiteral(from, len, retValue);
}
Мы предлагаем модифицировать функцию Lex::ReadStrConst() так, как показано ниже. Это позволит исправить две ошибки, связанные с обработкой разделенных строковых литералов. Первая ошибка возникает при обработке строк вида:
const char *name = "Viva\
Core";
Вторая - при обработке:
const wchar_t *str = L"begin"L"end".
Исправленный вариант функции:
bool Lex::ReadStrConst(size_t top, bool isWcharStr)
{
char c;
for(;;){
c = file->Get();
if(c == '\\'){
c = file->Get();
// Support: "\"
if (c == '\r') {
c = file->Get();
if (c != '\n')
return false;
} else if(c == '\0')
return false;
}
else if(c == '"</str>'){
size_t pos = file->GetCurPos() + 1;
ptrdiff_t nline = 0;
do{
c = file->Get();
if(c == '\n')
++nline;
} while(is_blank(c) || c == '\n');
if (isWcharStr && c == 'L') {
//Support: L"123" L"456" L "789".
c = file->Get();
if(c == '"')
/* line_number += nline; */ ;
else{
file->Unget();
return false;
}
} else {
if(c == '"')
/* line_number += nline; */ ;
else{
token_len = ptrdiff_t(pos - top);
file->Rewind(pos);
return true;
}
}
}
else if(c == '\n' || c == '\0')
return false;
}
}
В библиотеке OpenC++ существует ошибка, связанная с обработкой некоторых выражений, которые ошибочно воспринимаются как шаблоны. Например, в строке "bool r = a < 1 || b > (int) 2;" переменная "a" будет принята за имя шаблона, а дальше, дальше начнутся беды с синтаксическим анализом... Полноценное исправление данной ошибки требует существенных изменений и на данный момент не реализовано. Мы предлагаем промежуточное решение, исключающее основную часть ошибок. Ниже приведены функции, которые нужно добавить или модифицировать:
bool VivaParser::MaybeTypeNameOrClassTemplate(Token &token) {
if (m_env == NULL) {
return true;
}
const char *ptr = token.GetPtr();
ptrdiff_t len = token.GetLen();
Bind *bind;
bool isType = m_env->LookupType(ptr, len, bind);
return isType;
}
static bool isOperatorInTemplateArg(ptrdiff_t t) {
return t == AssignOp || t == EqualOp || t == LogOrOp ||
t == LogAndOp || t == IncOp || t == RelOp;
}
/*
template.args : '<' any* '>'
template.args must be followed by '(' or '::'
*/
bool Parser::isTemplateArgs()
{
ptrdiff_t i = 0;
ptrdiff_t t = lex->LookAhead(i++);
if(t == '<'){
ptrdiff_t n = 1;
while(n > 0){
ptrdiff_t u = lex->LookAhead(i++);
/*
TODO. :(
Fixing: bool r = a < 1 || b > (int) 2;
Исправим не все случаи, но все равно станет лучше.
Метод правки. Если нашли идентификатор рядом
с оператором, то это явно не шаблон, так как
внутри скобок может быть только тип или константное
выражение.
Пример, который все равно не работает:
r = a < fooi() || 1 > (int) b;
К сожалению, теперь неправильно обрабатывается
приведенное ниже выражение, но таких случаев
меньше, чем поправленных.
template <int z>
unsigned TFoo(unsigned a) {
return a + z;
}
enum EEnum { EE1, EE2 };
b = TFoo < EE1 && EE2 > (2);
*/
ptrdiff_t next = lex->LookAhead(i);
if (u == Identifier &&
isOperatorInTemplateArg(next))
return false;
if (isOperatorInTemplateArg(u) &&
next == Identifier)
return false;
if(u == '<')
++n;
else if(u == '>')
--n;
else if(u == '('){
ptrdiff_t m = 1;
while(m > 0){
ptrdiff_t v = lex->LookAhead(i++);
if(v == '(')
++m;
else if(v == ')')
--m;
else if(v == '\0' || v == ';' || v == '}')
return false;
}
}
else if(u == '\0' || u == ';' || u == '}')
return false;
}
t = lex->LookAhead(i);
return bool(t == Scope || t == '(');
}
return false;
}
К сожалению, реализованный в OpenC++ механизм обработки ошибок иногда приводит к аварийному завершению программы. Проблемным местом в OpenC++ является код, аналогичный этому:
if(!rDefinition(def)){
if(!SyntaxError())
return false;
SkipTo('}');
lex->GetToken(cp); // WARNING: crash in the same case.
body = PtreeUtil::List(new Leaf(op), 0, new Leaf(cp));
return true;
}
Следует уделить внимание местам, где происходит обработка ошибок, и скорректировать, как это показано на примере функций Parser::rLinkageBody() и Parser::SyntaxError(). Общий смысл исправлений заключается в том, что после возникновения ошибки не следует сразу извлекать следующую лексему, используя GetToken(), а нужно вначале проверить ее наличие, используя функцию CanLookAhead():
bool Parser::rLinkageBody(Ptree*& body)
{
Token op, cp;
Ptree* def;
if(lex->GetToken(op) != '{')
return false;
body = 0;
while(lex->LookAhead(0) != '}'){
if(!rDefinition(def)){
if(!SyntaxError())
return false; // too many errors
if (lex->CanLookAhead(1)) {
SkipTo('}');
lex->GetToken(cp);
if (!lex->CanLookAhead(0))
return false;
} else {
return false;
}
body =
PtreeUtil::List(new (GC) Leaf(op), 0,
new (GC) Leaf(cp));
return true; // error recovery
}
body = PtreeUtil::Snoc(body, def);
}
lex->GetToken(cp);
body = new (GC)
PtreeBrace(new (GC) Leaf(op), body, new (GC) Leaf(cp));
return true;
}
bool Parser::SyntaxError()
{
syntaxErrors_ = true;
Token t, t2;
if (lex->CanLookAhead(0)) {
lex->LookAhead(0, t);
} else {
lex->LookAhead(-1, t);
}
if (lex->CanLookAhead(1)) {
lex->LookAhead(1, t2);
} else {
t2 = t;
}
SourceLocation location(GetSourceLocation(*this, t.ptr));
string token(t2.ptr, t2.len);
errorLog_.Report(ParseErrorMsg(location, token));
return true;
}
Не вдаваясь в детали, предлагаем Вам заменить функцию rTemplateDecl2() на предложенный вариант. Это исключит некоторые ошибки при работе с шаблонными классами:
bool Parser::rTemplateDecl2(Ptree*& decl,
TemplateDeclKind &kind)
{
Token tk;
Ptree *args = 0;
if(lex->GetToken(tk) != TEMPLATE)
return false;
if(lex->LookAhead(0) != '<') {
if (lex->LookAhead(0) == CLASS) {
// template instantiation
decl = 0;
kind = tdk_instantiation;
return true; // ignore TEMPLATE
}
decl = new (GC)
PtreeTemplateDecl(new (GC) LeafReserved(tk));
} else {
decl = new (GC)
PtreeTemplateDecl(new (GC) LeafReserved(tk));
if(lex->GetToken(tk) != '<')
return false;
decl = PtreeUtil::Snoc(decl, new (GC) Leaf(tk));
if(!rTempArgList(args))
return false;
if(lex->GetToken(tk) != '>')
return false;
}
decl =
PtreeUtil::Nconc(decl,
PtreeUtil::List(args, new (GC) Leaf(tk)));
// ignore nested TEMPLATE
while (lex->LookAhead(0) == TEMPLATE) {
lex->GetToken(tk);
if(lex->LookAhead(0) != '<')
break;
lex->GetToken(tk);
if(!rTempArgList(args))
return false;
if(lex->GetToken(tk) != '>')
return false;
}
if (args == 0)
// template < > declaration
kind = tdk_specialization;
else
// template < ... > declaration
kind = tdk_decl;
return true;
}
В некоторых случаях бывает необходимо знать, где в тексте программы расположен код, из которого был построен определенный объект Ptree.
Ниже предлагается функция, возвращающая адрес начала и конца области памяти с текстом программы, из которой был создан указанный Ptree.
void GetPtreePos(const Ptree *p, const char *&begin,
const char *&end) {
if (p == NULL)
return;
if (p->IsLeaf()) {
const char *pos = p->GetLeafPosition();
if (begin == NULL) {
begin = pos;
} else {
begin = min(begin, pos);
}
end = max(end, pos);
}
else {
GetPtreePos(p->Car(), begin, end);
GetPtreePos(p->Cdr(), begin, end);
}
}
Библиотека OpenС++ не поддерживает объявление переменных вида "const A (a)". Для исправления этого недочета необходимо внутри функции Parser::rOtherDeclaration изменить участок кода:
if(!rDeclarators(decl, type_encode, false))
return false;
Вместо него следует использовать:
if(!rDeclarators(decl, type_encode, false)) {
// Support: const A (a);
Lex::TokenIndex after_rDeclarators = lex->Save();
lex->Restore(before_rDeclarators);
if (lex->CanLookAhead(3) && lex->CanLookAhead(-2)) {
ptrdiff_t c_2 = lex->LookAhead(-2);
ptrdiff_t c_1 = lex->LookAhead(-1);
ptrdiff_t c0 = lex->LookAhead(0);
ptrdiff_t c1 = lex->LookAhead(1);
ptrdiff_t c2 = lex->LookAhead(2);
ptrdiff_t c3 = lex->LookAhead(3);
if (c_2 == CONST && c_1 == Identifier &&
c0 == '(' && c1 == Identifier && c2 == ')' &&
(c3 == ';' || c3 == '='))
{
Lex::TokenContainer newEmptyContainer;
ptrdiff_t pos = before_rDeclarators;
lex->ReplaceTokens(pos + 2, pos + 3, newEmptyContainer);
lex->ReplaceTokens(pos + 0, pos + 1, newEmptyContainer);
lex->Restore(before_rDeclarators - 2);
bool res = rDeclaration(statement);
return res;
}
}
}
В приведенном коде используются некоторые вспомогательные функции, отсутствующие в этой статье. Но вы можете найти их в библиотеке VivaCore.
Иногда при программировании приходится использовать обходные пути для достижения результата. Например, широко известный макрос "max" часто приносит сложности при объявлении в классе метода вида "T max() {return m;}". В этом случае прибегают к хитростям и объявляют метод так: "T (max)() {return m;}". К сожалению, OpenC++ не понимает такие объявления внутри классов. Для исправления этого недочета следует модернизировать функцию Parser::isConstructorDecl() следующим образом:
bool Parser::isConstructorDecl()
{
if(lex->LookAhead(0) != '(')
return false;
else{
// Support: T (min)() { }
if (lex->LookAhead(1) == Identifier &&
lex->LookAhead(2) == ')' &&
lex->LookAhead(3) == '(')
return false;
ptrdiff_t t = lex->LookAhead(1);
if(t == '*' || t == '&' || t == '(')
return false; // declarator
else if(t == CONST || t == VOLATILE)
return true; // constructor or declarator
else if(isPtrToMember(1))
return false; // declarator (::*)
else
return true; // maybe constructor
}
}
Библиотека OpenC++ не "знает", что внутри функций можно использовать конструкции "using" и "namespace". Но это легко исправить, модернизируя функцию Parser::rStatement():
bool Parser::rStatement(Ptree*& st)
{
...
case USING :
return rUsing(st);
case NAMESPACE :
if (lex->LookAhead(2) == '=')
return rNamespaceAlias(st);
return rExprStatement(st);
...
}
Как известно, "this" является указателем. В OpenC++ это не так. Поэтому стоит исправить функцию Walker::TypeofThis(), чтобы исправить ошибку определения типа.
Замените код:
void Walker::TypeofThis(Ptree*, TypeInfo& t)
{
t.Set(env->LookupThis());
}
на:
void Walker::TypeofThis(Ptree*, TypeInfo& t)
{
t.Set(env->LookupThis());
t.Reference();
}
Мы уже упоминали о функции Program::LineNumber(), говоря, что она возвращает имена файлов в разных форматах. И затем предложили функцию FixFileName() для исправления этой ситуации. Но у функции LineNumber() есть еще один недостаток, связанный с медленной скоростью ее работы. Поэтому мы предлагаем оптимизированный вариант функции LineNumber():
/*
LineNumber() returns the line number of the line
pointed to by PTR.
*/
size_t Program::LineNumber(const char* ptr,
const char*& filename,
ptrdiff_t& filename_length,
const char *&beginLinePtr) const
{
beginLinePtr = NULL;
ptrdiff_t n;
size_t len;
size_t name;
ptrdiff_t nline = 0;
size_t pos = ptr - buf;
size_t startPos = pos;
if(pos > size){
// error?
assert(false);
filename = defaultname.c_str();
filename_length = defaultname.length();
beginLinePtr = buf;
return 0;
}
ptrdiff_t line_number = -1;
filename_length = 0;
while(pos > 0){
if (pos == oldLineNumberPos) {
line_number = oldLineNumber + nline;
assert(!oldFileName.empty());
filename = oldFileName.c_str();
filename_length = oldFileName.length();
assert(oldBeginLinePtr != NULL);
if (beginLinePtr == NULL)
beginLinePtr = oldBeginLinePtr;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldLineNumberPos = startPos;
return line_number;
}
switch(buf[--pos]) {
case '\n' :
if (beginLinePtr == NULL)
beginLinePtr = &(buf[pos]) + 1;
++nline;
break;
case '#' :
len = 0;
n = ReadLineDirective(pos, -1, name, len);
if(n >= 0){ // unless #pragma
if(line_number < 0) {
line_number = n + nline;
}
if(len > 0 && filename_length == 0){
filename = (char*)Read(name);
filename_length = len;
}
}
if(line_number >= 0 && filename_length > 0) {
oldLineNumberPos = pos;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldFileName = std::string(filename,
filename_length);
return line_number;
}
break;
}
}
if(filename_length == 0){
filename = defaultname.c_str();
filename_length = defaultname.length();
oldFileName = std::string(filename,
filename_length);
}
if (line_number < 0) {
line_number = nline + 1;
if (beginLinePtr == NULL)
beginLinePtr = buf;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldLineNumberPos = startPos;
}
return line_number;
}
В некоторых случаях функция Program::ReadLineDirective() дает сбой, принимая за директиву "#line" посторонний текст. Исправленный вариант функции выглядит следующим образом:
ptrdiff_t Program::ReadLineDirective(size_t i,
ptrdiff_t line_number,
size_t& filename, size_t& filename_length) const
{
char c;
do{
c = Ref(++i);
} while(is_blank(c));
#if defined(_MSC_VER) || defined(IRIX_CC)
if(i + 5 <= GetSize() &&
strncmp(Read(i), "line ", 5) == 0) {
i += 4;
do{
c = Ref(++i);
}while(is_blank(c));
} else {
return -1;
}
#endif
if(is_digit(c)){ /* # <line> <file> */
unsigned num = c - '0';
for(;;){
c = Ref(++i);
if(is_digit(c))
num = num * 10 + c - '0';
else
break;
}
/* line_number'll be incremented soon */
line_number = num - 1;
if(is_blank(c)){
do{
c = Ref(++i);
}while(is_blank(c));
if(c == '"'){
size_t fname_start = i;
do{
c = Ref(++i);
} while(c != '"');
if(i > fname_start + 2){
filename = fname_start;
filename_length = i - fname_start + 1;
}
}
}
}
return line_number;
}
Конечно, в этой статье описана малая часть возможных улучшений. Но хочется надеяться, что они будут полезны разработчикам при использовании библиотеки OpenC++ и станут примерами, демонстрирующими как можно специализировать библиотеку для своих задач.
Еще раз хочется напомнить, что показанные в этой статье и многие другие улучшения можно найти в коде библиотеки VivaCore. Для многих задач библиотека VivaCore может оказаться удобнее, чем OpenC++.
Если у Вас возникли вопросы, Вы хотите что-то добавить или прокомментировать, то наша команда Viva64.com всегда рада и открыта к общению. Мы готовы обсудить возникшие вопросы, дать рекомендации и помочь в использовании библиотеки OpenC++ или VivaCore. Пишите нам!