Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

c2py — транслятор с подмножества языка C на язык Python

Транслятор построен на классической четырёхэтапной схеме: лексический анализ, синтаксический анализ, семантический анализ и генерация кода. Лексический анализатор порождается программой flex, синтаксический — программой bison; остальные этапы написаны на языке C.

Ключевое архитектурное решение: анализатор не печатает результат по ходу разбора, а строит абстрактное синтаксическое дерево. Между разбором и генерацией появляется место для семантического анализа, а генератор видит каждую конструкцию целиком и знает типы всех выражений. Именно это позволяет принимать решения, невозможные при построчном выводе.

Пример

Исходный текст на C Результат трансляции
#include <stdio.h>

int main(void) {
    int total = 0;
    for (int i = 1; i <= 5; i++) {
        total += i;
    }
    printf("total = %d\n", total);
    printf("half = %d\n", total / 2);
    printf("mean = %.1f\n", total / 5.0);
    return 0;
}
import sys


def main():
    total = 0
    for i in range(1, 5 + 1):
        total += i
    print("total = %d" % (total,))
    print("half = %d" % (_c_div(total, 2),))
    print("mean = %.1f" % (total / 5.0,))
    return 0


if __name__ == "__main__":
    sys.exit(main())

Обе программы печатают одно и то же:

total = 15
half = 7
mean = 3.0

Пример короткий, но три решения в нём уже нельзя принять без анализа. Цикл сведён к range() — это безопасно, потому что счётчик объявлен в заголовке и не используется после цикла. Деление total / 2 целое и потому вынесено в _c_div, а total / 5.0 вещественное и осталось обычным /: различить их можно только по типам операндов. Определение _c_div транслятор добавляет в начало файла; оно разобрано ниже, в разделе о целочисленном делении.

Сборка и запуск

Требуются flex, bison, gcc и python (версии 3.6 и выше).

python tools/build.py        собрать транслятор
python tools/build.py clean  очистить каталог build

В Windows то же самое делает build.bat с теми же аргументами.

Транслятор собирается в build/c2py.exe:

c2py                        input.c -> output.py
c2py program.c              program.c -> program.py
c2py program.c -o out.py    явное имя результата
c2py program.c --stdout     вывод на экран
c2py - < program.c          чтение со стандартного ввода

Есть и Makefile, повторяющий ту же последовательность. Обратите внимание: если в системе установлено несколько сборников gcc, make может выбрать не тот. Тогда компилятор указывается явно:

make CC=/путь/к/gcc

Основным и проверенным способом сборки считается tools/build.py.

Устройство

Файл Назначение
src/lexer.l лексический анализатор (flex)
src/parser.y грамматика и построение дерева (bison)
src/ast.c узлы абстрактного синтаксического дерева
src/types.c система типов и преобразования
src/symtab.c таблица символов, области видимости, имена Python
src/sema.c семантический анализ: типы, проверки, разметка дерева
src/codegen.c генерация кода Python
src/format.c разбор строки формата printf и scanf
src/escape.c перекодирование escape-последовательностей
src/diag.c диагностические сообщения с указанием места
src/comments.c таблица комментариев исходного файла
src/strbuf.c динамический строковый буфер

Поддерживаемое подмножество языка C

Типы. int, char, short, long, float, double, void, модификаторы signed, unsigned, const, одномерные массивы.

Объявления. Глобальные и локальные переменные, несколько описателей в одном объявлении, инициализаторы, списки инициализаторов для массивов (int a[] = {1, 2, 3}), объявления вперемешку с инструкциями (C99).

Функции. Определения с параметрами и возвращаемым значением, прототипы, рекурсия, вызов до определения.

Инструкции. if / else (включая цепочки), while, do-while, for (все три части необязательны, допустимо объявление в заголовке), switch / case / default, break, continue, return, блоки, пустая инструкция.

Выражения. Полная лестница приоритетов языка C: присваивание и его составные формы, тернарный оператор, ||, &&, |, ^, &, сравнения, сдвиги, аддитивные и мультипликативные операции, унарные + - ! ~, префиксные и постфиксные ++ и --, индексация, вызов, приведение типа, sizeof, оператор «запятая».

Литералы. Целые в десятичной, восьмеричной и шестнадцатеричной записи с суффиксами; вещественные, включая экспоненциальную форму; символьные и строковые со всеми escape-последовательностями, включая \xHH и восьмеричные; склейка соседних строковых литералов.

Библиотека. printf, scanf, puts, putchar, abs, fabs, sqrt, pow, floor, ceil, exit, strlen, rand, srand.

Решения, принимаемые при трансляции

Ниже перечислены места, где буквальный перевод дал бы неверный результат.

Целочисленное деление

В C деление и остаток усекают частное к нулю, а операции // и % в Python округляют вниз. На отрицательных значениях результаты расходятся: -7 / 2 равно -3 в C и -4 в Python.

Поэтому целочисленное деление переводится в вызов служебной функции _c_div (и _c_mod для остатка). Если оба операнда — заведомо неотрицательные константы, разницы нет, и выводится обычное //. Служебные функции добавляются в результат только тогда, когда действительно нужны.

Выбор между / и //

Какая операция нужна, определяется типом операндов, а не их написанием. Для этого и требуется семантический анализ: a / b при целых a и b и при вещественных — это разные операции.

Приоритеты операций

Приоритеты в Python отличаются от C. Сравнение там связывает слабее побитовых операций, а not — слабее сравнения. Поэтому a & b == c из C нельзя переписать теми же символами: получится другое дерево. Генератор расставляет скобки по приоритетам Python и ставит их только там, где они действительно нужны.

Логические операции

В C операции && и || дают 0 или 1, а and и or в Python возвращают сам операнд: 7 and 2 равно 2. Пока значение используется как условие, разница незаметна, поэтому в условиях выводятся обычные and и or. В остальных случаях результат приводится к числу.

Цикл for

Цикл сводится к range() только тогда, когда это безопасно: переменная объявлена в самом заголовке, шаг постоянен, а тело не меняет ни счётчик, ни границу. Последнее существенно: в C граница вычисляется заново на каждой итерации, а range() вычисляет её однажды.

Если переменная объявлена снаружи, используется цикл while. Причина в том, что после цикла значения различались бы: C оставляет значение после последнего приращения, а Python — последнее значение диапазона.

В форме while шаг цикла повторяется перед каждым continue — иначе цикл стал бы бесконечным.

Область видимости имён

В C областью видимости является блок, а в Python — функция целиком. Две разные переменные с одинаковым именем в соседних или вложенных блоках получают разные имена в результате. Отдельно отслеживаются имена, совпадающие с ключевыми словами Python (lambda, class, None и другие) и с именами служебных функций.

Функция, изменяющая глобальную переменную, получает объявление global; при чтении такое объявление не требуется и не добавляется.

Строки формата

Форматирование через оператор % в Python унаследовано от C, поэтому %d, %5.2f, %-10s, %x, %o, %e, %g работают одинаково. Строка формата не переписывается заново, а нормализуется: убираются модификаторы длины (l, ll, h, z), а %u и %i заменяются на %d. Так сохраняются ширина поля, точность и флаги.

Число аргументов сверяется со строкой формата на этапе семантического анализа. Если строка заканчивается переводом строки, он не печатается явно, а обеспечивается поведением print по умолчанию.

Прочее

do-while разворачивается в while True с проверкой условия в конце. switch превращается в цепочку if / elif; значение вычисляется один раз во временную переменную; подряд идущие метки объединяются через or; break, относящийся к switch, не переносится, а break цикла сохраняется. Комментарии исходного текста переносятся в результат, причём комментарий в конце строки остаётся в конце строки.

Сознательные ограничения

Перечисленные конструкции отвергаются с внятным сообщением, а не переводятся во что-то неверное:

  • указатели и адресная арифметика; оператор & допустим только в аргументах scanf;
  • структуры, объединения, перечисления, typedef, многомерные массивы;
  • ++ и -- внутри выражения (j = i++): в Python нет такой операции, а её перенос потребовал бы временных переменных и менял бы порядок вычислений; допустимы отдельная инструкция и третья часть for;
  • присваивание внутри другого выражения (if (a = b)); цепочка a = b = 0 поддерживается, поскольку выражается и в Python;
  • провал между ветвями switch без break;
  • break в середине ветви switch;
  • директивы препроцессора, кроме #include (он пропускается);
  • goto и метки.

Символьные массивы поддерживаются в двух видах: заданные строковым литералом (char msg[] = "...") представляются строкой Python, заданные списком — списком чисел.

Диагностика

Сообщения выводятся в формате, принятом в gcc, поэтому среды разработки разбирают их автоматически:

input.c:12:9: error: undeclared identifier 'total'

Транслятор сообщает о нескольких ошибках за один запуск: после ошибки разбор восстанавливается на ближайшей точке с запятой. При любой ошибке файл с результатом не создаётся.

Лицензия

MIT.

About

Транслятор подмножества языка C в Python на flex и bison с построением абстрактного синтаксического дерева и семантическим анализом типов и областей видимости

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages