[C, D] Портируем make.c на D (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
21-Июл-2020 00:30

Уолтер Брайт — «великодушный пожизненный диктатор» языка программирования D и основатель Digital Mars. За его плечами не один десяток лет опыта в разработке компиляторов и интерпретаторов для нескольких языков, в числе которых Zortech C++ — первый нативный компилятор C++. Он также создатель игры Empire, послужившей основным источником вдохновения для Sid Meier’s Civilization.

Цикл статей о Better C

SPL
Better C — это способ перенести существующие проекты на языке C на язык D в последовательной манере. В этой статье показан пошаговый процесс конвертации нетривиального проекта из C в D и показывает частые проблемы, которые при этом возникают.
Хотя фронтенд компилятора D dmd уже сконвертирован в D, это настолько большой проект, что его трудно целиком охватить. Мне был нужен более мелкий и скромный проект, который можно было бы полностью уяснить, но чтобы он не был умозрительным примером.
Мне пришла на ум старая make-программа, которую я написал для компилятора C Datalight в начале 1980-х. Это реальная имплементация классической программы make, которая постоянно использовалась с начала 80-х. Она написана на C ещё до его стандартизации, была портирована из одной системы в другую и укладывается всего в 1961 строчку кода, включая комментарии. Она до сих пор регулярно используется.
Вот документация и исходный код. Размер исполняемого файла make.exe — 49 692 байта, и последнее изменение было 19 августа 2012 г.
Наш Злобный план:
  • Свести к минимуму diff’ы между C- и D-версиями. Таким образом, если поведение программ будет различаться, проще будет найти источник различия.
  • Во время переноса не будет предпринято попыток исправить или улучшить код на C. Это делается во исполнение пункта № 1.
  • Не будет предпринято попыток рефакторинга кода. Опять же, см. пункт № 1.
  • Воспроизвести поведение программы на C насколько это возможно, со всеми багами.
  • Сделать всё, что необходимо ради исполнения пункта № 4.

Только когда мы закончим, можно будет приступить к исправления, рефакторингу, подчистке и т.д.
Спойлеры!
Законченный перенос с C на D. Размер исполняемого файла — 52 252 байт (сравнимо с оригиналом — 49 692 байт). Я не анализировал увеличение в размере, но вероятно, оно взялось из-за экземпляров шаблона NEWOBJ (в C-версии это макрос) и изменений в рантайме DMC после 2012 года.
Шаг за шагом
Вот отличия между версиями. Изменились 664 строки из 1961, примерно треть — кажется, что много, но надеюсь, я смогу вас убедить, что почти все отличия тривиальны.
Включение файлов через #include заменено на импортирование соответствующих модулей D: например, #include <stdio.h> заменено на import core.stdc.stdio;. К сожалению, некоторые из включаемых файлов специфичны для Digital Mars C, и для них не существует версий на D (это надо исправить). Чтобы не останавливаться на этом, я просто вставил соответствующие объявления с 29-й строки по 64-ю. (См. документацию по объявлению import).
Конструкция #if _WIN32 заменена на version (Windows). (См. документацию по условной компиляции версий и список предопределённых версий).
Объявление extern(C): помечает последующие объявления в файле как совместимые с C. (См. документацию по аттрибуту линковки).
При помощи глобального поиска и замены макросы debug1, debug2 и debug3 заменены на debug prinf. В целом, директивы препроцессора #ifdef DEBUG заменены на условную компиляцию при помощи debug. (См. документацию по выражению debug).
/* Delete these old C macro definitions...
#ifdef DEBUG
-#define debug1(a)       printf(a)
-#define debug2(a,b)     printf(a,b)
-#define debug3(a,b,c)   printf(a,b,c)
-#else
-#define debug1(a)
-#define debug2(a,b)
-#define debug3(a,b,c)
-#endif
*/
// And replace their usage with the debug statement
// debug2("Returning x%lx\n",datetime);
debug printf("Returning x%lx\n",datetime);

Макросы TRUE, FALSE и NULL при помощи поиска и замены заменены на true, false и null.
Макрос ESC заменён константой времени компиляции. (См. документацию по константам).
// #define ESC     '!'
enum ESC =      '!';

Макрос NEWOBJ заменён шаблонной функцией.
// #define NEWOBJ(type)    ((type *) mem_calloc(sizeof(type)))
type* NEWOBJ(type)() { return cast(type*) mem_calloc(type.sizeof); }

Макрос filenamecmp заменён функцией.
Убрана поддержка устаревших платформ.
Глобальные переменные в D по умолчанию помещаются в локальное хранилище потока (thread-local storage, TLS). Но поскольку make — однопоточная программа, их можно поместить в глобальное хранилище при помощи класса хранилища __gshared. (См. документацию по атрибуту __gshared).
// int CMDLINELEN;
__gshared int CMDLINELEN

В D нет отдельного пространства имён для структур, так что в typedef нет необходимости. Вместо этого можно использовать alias. (См. документацию по объявлению alias). Кроме того, из объявлений переменных убрано слово struct.
/*
typedef struct FILENODE
        {       char            *name,genext[EXTMAX+1];
                char            dblcln;
                char            expanding;
                time_t          time;
                filelist        *dep;
                struct RULE     *frule;
                struct FILENODE *next;
        } filenode;
*/
struct FILENODE
{
        char            *name;
        char[EXTMAX1]  genext;
        char            dblcln;
        char            expanding;
        time_t          time;
        filelist        *dep;
        RULE            *frule;
        FILENODE        *next;
}
alias filenode = FILENODE;

В языке D macro — это ключевое слово, поэтому вместо этого будем использовать MACRO.
В отличие от C, в языке D звёздочка в объявлении указателя является частью типа, поэтому при объявлении нескольких указателей звёздочка применяется к каждому символу:
// char *name,*text;
// In D, the * is part of the type and
// applies to each symbol in the declaration.
char* name, text;

Объявления массивов в стиле C преобразованы в объявления в стиле D. (См. документацию по синтаксису объявлений в D).
Слово static на уровне модуля в D ничего не значит. В C статические глобальные переменные эквивалентны приватным переменным уровня модуля в D, но это неважно, если модуль никогда не импортируется. Их всё ещё нужно обозначить как __gshared, и этот можно сделать целым блоком. (См. документацию по атрибуту static).
/*
static ignore_errors = FALSE;
static execute = TRUE;
static gag = FALSE;
static touchem = FALSE;
static debug = FALSE;
static list_lines = FALSE;
static usebuiltin = TRUE;
static print = FALSE;
...
*/
__gshared
{
    bool ignore_errors = false;
    bool execute = true;
    bool gag = false;
    bool touchem = false;
    bool xdebug = false;
    bool list_lines = false;
    bool usebuiltin = true;
    bool print = false;
    ...
}

Предварительные объявления функций в языке D не нужны. Функцию, определённую на уровне модуля, можно вызывать из любого места в этом модуле, даже до её определения.
В расширении символов подстановки в make-программе нет большого смысла.
Параметры функций, определённые с синтаксисом массивов, на самом деле являются указателями, и в D объявляются как указатели.
// int cdecl main(int argc,char *argv[])
int main(int argc,char** argv)

Макрос mem_init() ни во что не расширяется, и до этого мы его убрали.
В C можно грязно играть с аргументами, но D требует, чтобы они соответствовали прототипу функции.
void cmderr(const char* format, const char* arg) {...}
// cmderr("can't expand response file\n");
cmderr("can't expand response file\n", null);

При помощи глобального поиска и замены оператор-стрелка (->) языка C заменён на точку (.), поскольку в D доступ к членам осуществляется одинаково.
Директивы условной компиляции заменены на version.
/*
#if TERMCODE
    ...
#endif
*/
    version (TERMCODE)
    {
        ...
    }

Отсутствие прототипов функций свидетельствует о древности этого кода. D требует полноценных прототипов.
// doswitch(p)
// char *p;
void doswitch(char* p)

В языке D слово debug зарезервировано. Переименуем в xdebug.
Многострочные литералы в C требуют \n\ в конце каждой строки. В D этого не требуется.
Неиспользуемый код закомментирован при помощи вложенного блока комментариев /+ +/. (См. документацию по строчным, блочным и вложенным комментариям).
Выражение static if может во многих случаях заменить #if. (См. документацию по static if).
Массивы в D не сводятся к указателю автоматически, следует использовать .ptr.
// utime(name,timep);
utime(name,timep.ptr);

Использование const для строк в стиле C проистекает из строковых литералов в D, поскольку D не позволяет брать изменяемые указатели на строковые литералы. (См. документацию по const и immutable).
// linelist **readmakefile(char *makefile,linelist **rl)
linelist **readmakefile(const char *makefile,linelist **rl)

Преобразование void* в char* в D должно быть явным.
// buf = mem_realloc(buf,bufmax);
buf = cast(char*)mem_realloc(buf,bufmax);

Тип unsigned заменён на uint.
Атрибут inout можно использовать, чтобы передать «константность» аргумента функции на возвращаемый тип. Если параметр обозначен как const, то таким же будет возвращаемое значение, и наоборот. (См. документацию по inout-функциям).
// char *skipspace(p) {...}
inout(char) *skipspace(inout(char)* p) {...}

Макрос arraysize можно заменить на свойство .length. (См. документацию по свойствам массивов).
// useCOMMAND  |= inarray(p,builtin,arraysize(builtin));
useCOMMAND  |= inarray(p,builtin.ptr,builtin.length)

Строковые литералы неизменяемы (immutable), поэтому изменяемые строки необходимо заменить на массивы, выделенные на стеке. (См. документацию по строковым литералам).
// static char envname[] = "@_CMDLINE";
char[10] envname = "@_CMDLINE";

Свойство .sizeof служит заменой оператору sizeof() из C. (См. документацию по .sizeof).
// q = (char *) mem_calloc(sizeof(envname) + len);
q = cast(char *) mem_calloc(envname.sizeof + len)

Старые версии Windows нас не интересуют.
Доисторическое применение char * заменено на void*.
И вот и все изменения! Как видите, не так уж плохо. Я не выставлял таймер, но сомневаюсь, что всё это заняло у меня больше часа — включая исправление нескольких ошибок, которые я сделал в процессе.
У нас остаётся только файл man.c, который был нужен, чтобы открывать в браузере документацию по make при запуске с опцией -man. К счастью, он уже портирован на D, так что я могу просто скопировать код.
Собрать make так просто, что для этого даже не требуется make-файл:
\dmd2.079\windows\bin\dmd make.d dman.d -O -release -betterC -I. -I\dmd2.079\src\druntime\import\ shell32.lib

Резюме
Мы придерживались нашего Злобного плана по портированию нетривильной программы на олдскульном C на язык D, и смогли сделать это быстро и корректно. Мы получили эквивалентный исполняемый файл.
Проблемы, с которыми мы столкнулись, типичны и легко решаются следующими способами:
  • замена #include на import;
  • замена отсутствующих D-версий включаемых файлов;
  • глобальный поиск и замена вещей вроде ->;
  • замена макросов препроцессора на:
    • константы времени компиляции,
    • простые шаблоны,
    • функции,
    • спецификаторы версий,
    • спецификаторы отладки;
  • замена зарезервированных слов;
  • изменение объявлений массивов и указателей;
  • удаление ненужных прототипов функций;
  • более строгое соблюдение типов;
  • использование свойств массивов;
  • замена типов C типами D.

Не потребовалось ничего из следующего:
  • реорганизация кода,
  • изменения в структурах данных,
  • изменение хода выполнения программы,
  • изменения в работе программы,
  • изменение управления памятью.

Будущее
Теперь, когда у нас есть Better C, нам доступны многие современные возможности, которые позволят нам улучшить наш код:

К действию
Если вы знаете английский, заходите на форум D и расскажите нам, как продвигается ваш проект на Better C!
===========
Источник:
habr.com
===========

===========
Автор оригинала: Уолтер Брайт (Walter Bright)
===========
Похожие новости: Теги для поиска: #_c, #_d, #_dlang, #_betterc, #_c, #_d
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 07:20
Часовой пояс: UTC + 5