rusi/tokenize.c

762 lines
27 KiB
C
Raw Normal View History

#включить "руси.з"
// Текущий обрабатываемый файл
стат File *актайл;
// Список всех входных файлов
стат File **всехайлы;
стат цел ёмкостьхайлов = 0;
стат цел счётчикайлов = 0;
// Флаг: текущая позиция находится в начале строки
стат бул начало_строки;
// Флаг: перед текущей позицией был пробельный символ
стат бул был_пробел;
// =====================================================================
// Механизм отслеживания выделенной памяти для токенов и файлов
// =====================================================================
стат пусто **все_выделения = NULL;
стат цел счётчик_выделений = 0;
стат цел ёмкость_выделений = 0;
стат пусто отследить_память(пусто *указ) {
если (!указ) возврат;
если (счётчик_выделений == ёмкость_выделений) {
ёмкость_выделений = ёмкость_выделений ? ёмкость_выделений * 2 : 1024;
все_выделения = realloc(все_выделения, ёмкость_выделений * размер(пусто *));
}
все_выделения[счётчик_выделений++] = указ;
}
пусто free_all_tokens(пусто) {
для (цел ш = 0; ш < счётчик_выделений; ш++) {
free(все_выделения[ш]);
}
free(все_выделения);
все_выделения = NULL;
счётчик_выделений = 0;
ёмкость_выделений = 0;
free(всехайлы);
всехайлы = NULL;
ёмкостьхайлов = 0;
счётчикайлов = 0;
}
// =====================================================================
пусто error(символ *фмт, ...) {
va_list арг;
va_start(арг, фмт);
vfprintf(stderr, фмт, арг);
fprintf(stderr, "\n");
exit(1);
}
стат пусто формат_ошибки_в(символ *имяайла, символ *исходник, цел номер_строки,
символ *позиция, символ *фмт, va_list арг) {
символ *нач_строки = позиция;
пока (исходник < нач_строки && нач_строки[-1] != '\n')
нач_строки--;
символ *кон_строки = позиция;
пока (*кон_строки && *кон_строки != '\n')
кон_строки++;
цел отступ = fprintf(stderr, "%s:%d: ", имяайла, номер_строки);
fprintf(stderr, "%.*s\n", (цел)(кон_строки - нач_строки), нач_строки);
цел поз = display_width(нач_строки, позиция - нач_строки) + отступ;
fprintf(stderr, "%*s", поз, "");
fprintf(stderr, "^ ");
vfprintf(stderr, фмт, арг);
fprintf(stderr, "\n");
}
пусто error_at(символ *поз, символ *фмт, ...) {
цел номер_строки = 1;
для (символ *п = актайл->contents; п < поз; п++)
если (*п == '\n')
номер_строки++;
va_list арг;
va_start(арг, фмт);
формат_ошибки_в(актайл->name, актайл->contents, номер_строки, поз, фмт, арг);
exit(1);
}
пусто error_tok(Token *ток, символ *фмт, ...) {
va_list арг;
va_start(арг, фмт);
формат_ошибки_в(ток->file->name, ток->file->contents, ток->line_no, ток->loc, фмт, арг);
exit(1);
}
пусто warn_tok(Token *ток, символ *фмт, ...) {
va_list арг;
va_start(арг, фмт);
формат_ошибки_в(ток->file->name, ток->file->contents, ток->line_no, ток->loc, фмт, арг);
va_end(арг);
}
бул equal(Token *ток, символ *оп) {
возврат memcmp(ток->loc, оп, ток->len) == 0 && оп[ток->len] == '\0';
}
Token *skip(Token *ток, символ *оп) {
если (!equal(ток, оп))
error_tok(ток, "ожидался '%s'", оп);
возврат ток->next;
}
бул consume(Token **остаток, Token *ток, символ *стр) {
если (equal(ток, стр)) {
*остаток = ток->next;
возврат истина;
}
*остаток = ток;
возврат ложь;
}
стат Token *создатьокен(TokenKind вид, символ *начало, символ *конец) {
Token *ток = calloc(1, размер(Token));
отследить_память(ток);
ток->kind = вид;
ток->loc = начало;
ток->len = конец - начало;
ток->file = актайл;
ток->filename = актайл->display_name;
ток->at_bol = начало_строки;
ток->has_space = был_пробел;
начало_строки = был_пробел = ложь;
возврат ток;
}
стат бул есть_префикс(символ *стр, символ *преф) {
возврат strncmp(стр, преф, strlen(преф)) == 0;
}
стат цел разбор_идентификатора(символ *начало) {
символ *п = начало;
uint32_t зн = decode_utf8(&п, п);
если (!is_ident1(зн))
возврат 0;
для (;;) {
символ *след;
зн = decode_utf8(&след, п);
если (!is_ident2(зн))
возврат п - начало;
п = след;
}
}
стат цел значестн_цифры(символ зн) {
если ('0' <= зн && зн <= '9')
возврат зн - '0';
если ('a' <= зн && зн <= 'f')
возврат зн - 'a' + 10;
возврат зн - 'A' + 10;
}
стат цел разбор_пунктуатора(символ *п) {
стат символ *операторы[] = {
"<<=", ">>=", "...", "==", "!=", "<=", ">=", "->", "+=",
"-=", "*=", "/=", "++", "--", "%=", "&=", "|=", "^=", "&&",
"||", "<<", ">>", "##",
};
для (цел ш = 0; ш < размер(операторы) / размер(*операторы); ш++)
если (есть_префикс(п, операторы[ш]))
возврат strlen(операторы[ш]);
возврат ispunct(*п) ? 1 : 0;
}
стат бул зарезервированное_слово(Token *ток) {
стат ХешТаблица карта;
если (карта.вместимость == 0) {
стат символ *ключ_слова[] = {
"return", "if", "else", "for", "while", "int", "sizeof", "char",
"возврат", "если", "иначе", "для", "пока", "цел", "размер", "символ",
"struct", "union", "short", "long", "void", "typedef", "_Bool",
"структ", "объед", "корот", "длин", "пусто", "типдеф",
"enum", "static", "goto", "break", "continue", "switch", "case",
"переч", "стат", "перейти", "прерви", "продолжи", "выбор", "случай",
"default", "extern", "_Alignof", "_Выравнивание_типа", "_Alignas", "_Выравнивание", "do", "signed",
"_Generic", "_Обобщ", "умолч", "внеш", "делай", "знак",
"unsigned", "const", "volatile", "auto", "register", "restrict",
"беззнак", "конст", "летуч", "авто", "регистр", "огранич",
"__restrict", "__restrict__", "_Noreturn", "езвозврат", "float", "double",
"плав", "двойн", "typeof", "asm", "_Thread_local", "__thread", "_Atomic",
"типоф", "асм", "__attribute__",
};
для (цел ш = 0; ш < размер(ключ_слова) / размер(*ключ_слова); ш++)
хешставить(&карта, ключ_слова[ш], (пусто *)1);
}
возврат хеш_получить2(&карта, ток->loc, ток->len);
}
стат цел разборскейп_посл(символ **новая_поз, символ *п) {
если ('0' <= *п && *п <= '7') {
цел зн = *п++ - '0';
если ('0' <= *п && *п <= '7') {
зн = (зн << 3) + (*п++ - '0');
если ('0' <= *п && *п <= '7')
зн = (зн << 3) + (*п++ - '0');
}
*новая_поз = п;
возврат зн;
}
если (*п == 'x') {
п++;
если (!isxdigit(*п))
error_at(п, "недопустимая шестнадцатеричная escape-последовательность");
цел зн = 0;
для (; isxdigit(*п); п++)
зн = (зн << 4) + значестн_цифры(*п);
*новая_поз = п;
возврат зн;
}
*новая_поз = п + 1;
выбор (*п) {
случай 'a': возврат '\a';
случай 'b': возврат '\b';
случай 't': возврат '\t';
случай 'n': возврат '\n';
случай 'v': возврат '\v';
случай 'f': возврат '\f';
случай 'r': возврат '\r';
случай 'e': возврат 27;
умолч: возврат *п;
}
}
стат символ *найтионец_строки(символ *п) {
символ *начало = п;
для (; *п != '"'; п++) {
если (*п == '\n' || *п == '\0')
error_at(начало, "незакрытый строковый литерал");
если (*п == '\\')
п++;
}
возврат п;
}
стат Token *разбор_строкового_литерала(символ *начало, символ *кавычка) {
символ *конец = найтионец_строки(кавычка + 1);
символ *буф = calloc(1, конец - кавычка);
отследить_память(буф);
цел длина = 0;
для (символ *п = кавычка + 1; п < конец;) {
если (*п == '\\')
буф[длина++] = разборскейп_посл(&п, п + 1);
иначе
буф[длина++] = *п++;
}
Token *ток = создатьокен(TK_STR, начало, конец + 1);
ток->ty = array_of(ty_char, длина + 1);
ток->str = буф;
возврат ток;
}
стат Token *разбор_utf16_строки(символ *начало, символ *кавычка) {
символ *конец = найтионец_строки(кавычка + 1);
uint16_t *буф = calloc(2, конец - начало);
отследить_память(буф);
цел длина = 0;
для (символ *п = кавычка + 1; п < конец;) {
если (*п == '\\') {
буф[длина++] = разборскейп_посл(&п, п + 1);
продолжи;
}
uint32_t зн = decode_utf8(&п, п);
если (зн < 0x10000) {
буф[длина++] = зн;
} иначе {
зн -= 0x10000;
буф[длина++] = 0xd800 + ((зн >> 10) & 0x3ff);
буф[длина++] = 0xdc00 + (зн & 0x3ff);
}
}
Token *ток = создатьокен(TK_STR, начало, конец + 1);
ток->ty = array_of(ty_ushort, длина + 1);
ток->str = (символ *)буф;
возврат ток;
}
стат Token *разбор_utf32_строки(символ *начало, символ *кавычка, Type *тип) {
символ *конец = найтионец_строки(кавычка + 1);
uint32_t *буф = calloc(4, конец - кавычка);
отследить_память(буф);
цел длина = 0;
для (символ *п = кавычка + 1; п < конец;) {
если (*п == '\\')
буф[длина++] = разборскейп_посл(&п, п + 1);
иначе
буф[длина++] = decode_utf8(&п, п);
}
Token *ток = создатьокен(TK_STR, начало, конец + 1);
ток->ty = array_of(тип, длина + 1);
ток->str = (символ *)буф;
возврат ток;
}
стат Token *разбор_симв_литерала(символ *начало, символ *кавычка, Type *тип) {
символ *п = кавычка + 1;
если (*п == '\0')
error_at(начало, "незакрытый символьный литерал");
цел зн;
если (*п == '\\')
зн = разборскейп_посл(&п, п + 1);
иначе
зн = decode_utf8(&п, п);
символ *конец = strchr(п, '\'');
если (!конец)
error_at(п, "незакрытый символьный литерал");
Token *ток = создатьокен(TK_NUM, начало, конец + 1);
ток->val = зн;
ток->ty = тип;
возврат ток;
}
стат бул разборелого(Token *ток) {
символ *п = ток->loc;
цел основание = 10;
если (!strncasecmp(п, "0x", 2) && isxdigit(п[2])) {
п += 2;
основание = 16;
} иначе если (!strncasecmp(п, "0b", 2) && (п[2] == '0' || п[2] == '1')) {
п += 2;
основание = 2;
} иначе если (*п == '0') {
основание = 8;
}
int64_t знач = strtoul(п, &п, основание);
бул суф_l = ложь;
бул суф_u = ложь;
если (есть_префикс(п, "LLU") || есть_префикс(п, "LLu") ||
есть_префикс(п, "llU") || есть_префикс(п, "llu") ||
есть_префикс(п, "ULL") || есть_префикс(п, "Ull") ||
есть_префикс(п, "uLL") || есть_префикс(п, "ull")) {
п += 3;
суф_l = суф_u = истина;
} иначе если (!strncasecmp(п, "lu", 2) || !strncasecmp(п, "ul", 2)) {
п += 2;
суф_l = суф_u = истина;
} иначе если (есть_префикс(п, "LL") || есть_префикс(п, "ll")) {
п += 2;
суф_l = истина;
} иначе если (*п == 'L' || *п == 'l') {
п++;
суф_l = истина;
} иначе если (*п == 'U' || *п == 'u') {
п++;
суф_u = истина;
}
если (п != ток->loc + ток->len)
возврат ложь;
Type *тип;
если (основание == 10) {
если (суф_l && суф_u)
тип = ty_ulong;
иначе если (суф_l)
тип = ty_long;
иначе если (суф_u)
тип = (знач >> 32) ? ty_ulong : ty_uint;
иначе
тип = (знач >> 31) ? ty_long : ty_int;
} иначе {
если (суф_l && суф_u)
тип = ty_ulong;
иначе если (суф_l)
тип = (знач >> 63) ? ty_ulong : ty_long;
иначе если (суф_u)
тип = (знач >> 32) ? ty_ulong : ty_uint;
иначе если (знач >> 63)
тип = ty_ulong;
иначе если (знач >> 32)
тип = ty_long;
иначе если (знач >> 31)
тип = ty_uint;
иначе
тип = ty_int;
}
ток->kind = TK_NUM;
ток->val = знач;
ток->ty = тип;
возврат истина;
}
стат пусто преобразовать_пп_число(Token *ток) {
если (разборелого(ток))
возврат;
символ *конец;
длин двойн знач = strtold(ток->loc, &конец);
Type *тип;
если (*конец == 'f' || *конец == 'F') {
тип = ty_float;
конец++;
} иначе если (*конец == 'l' || *конец == 'L') {
тип = ty_ldouble;
конец++;
} иначе {
тип = ty_double;
}
если (ток->loc + ток->len != конец)
error_tok(ток, "недопустимая числовая константа");
ток->kind = TK_NUM;
ток->fval = знач;
ток->ty = тип;
}
пусто convert_pp_tokens(Token *ток) {
для (Token *т = ток; т->kind != TK_EOF; т = т->next) {
если (зарезервированное_слово(т))
т->kind = TK_KEYWORD;
иначе если (т->kind == TK_PP_NUM)
преобразовать_пп_число(т);
}
}
стат пусто назначитьомера_строк(Token *ток) {
символ *п = актайл->contents;
цел номер = 1;
делай {
если (п == ток->loc) {
ток->line_no = номер;
ток = ток->next;
}
если (*п == '\n')
номер++;
} пока (*п++);
}
Token *tokenize_string_literal(Token *ток, Type *базовый) {
Token *рез;
если (базовый->size == 2)
рез = разбор_utf16_строки(ток->loc, ток->loc);
иначе
рез = разбор_utf32_строки(ток->loc, ток->loc, базовый);
рез->next = ток->next;
возврат рез;
}
Token *tokenize(File *файл) {
актайл = файл;
символ *п = файл->contents;
Token голова = {};
Token *тек = &голова;
начало_строки = истина;
был_пробел = ложь;
пока (*п) {
если (есть_префикс(п, "//")) {
п += 2;
пока (*п != '\n')
п++;
был_пробел = истина;
продолжи;
}
если (есть_префикс(п, "/*")) {
символ *кон = strstr(п + 2, "*/");
если (!кон)
error_at(п, "незакрытый блочный комментарий");
п = кон + 2;
был_пробел = истина;
продолжи;
}
если (*п == '\n') {
п++;
начало_строки = истина;
был_пробел = ложь;
продолжи;
}
если (isspace(*п)) {
п++;
был_пробел = истина;
продолжи;
}
если (isdigit(*п) || (*п == '.' && isdigit(п[1]))) {
символ *нач = п++;
для (;;) {
если (п[0] && п[1] && strchr("eEpP", п[0]) && strchr("+-", п[1]))
п += 2;
иначе если (isalnum(*п) || *п == '.')
п++;
иначе
прерви;
}
тек = тек->next = создатьокен(TK_PP_NUM, нач, п);
продолжи;
}
если (*п == '"') {
тек = тек->next = разбор_строкового_литерала(п, п);
п += тек->len;
продолжи;
}
если (есть_префикс(п, "u8\"")) {
тек = тек->next = разбор_строкового_литерала(п, п + 2);
п += тек->len;
продолжи;
}
если (есть_префикс(п, "u\"")) {
тек = тек->next = разбор_utf16_строки(п, п + 1);
п += тек->len;
продолжи;
}
если (есть_префикс(п, "L\"")) {
тек = тек->next = разбор_utf32_строки(п, п + 1, ty_int);
п += тек->len;
продолжи;
}
если (есть_префикс(п, "U\"")) {
тек = тек->next = разбор_utf32_строки(п, п + 1, ty_uint);
п += тек->len;
продолжи;
}
если (*п == '\'') {
тек = тек->next = разбор_симв_литерала(п, п, ty_int);
тек->val = (символ)тек->val;
п += тек->len;
продолжи;
}
если (есть_префикс(п, "u'")) {
тек = тек->next = разбор_симв_литерала(п, п + 1, ty_ushort);
тек->val &= 0xffff;
п += тек->len;
продолжи;
}
если (есть_префикс(п, "L'")) {
тек = тек->next = разбор_симв_литерала(п, п + 1, ty_int);
п += тек->len;
продолжи;
}
если (есть_префикс(п, "U'")) {
тек = тек->next = разбор_симв_литерала(п, п + 1, ty_uint);
п += тек->len;
продолжи;
}
цел дл_ид = разбор_идентификатора(п);
если (дл_ид) {
тек = тек->next = создатьокен(TK_IDENT, п, п + дл_ид);
п += тек->len;
продолжи;
}
цел дл_пункт = разбор_пунктуатора(п);
если (дл_пункт) {
тек = тек->next = создатьокен(TK_PUNCT, п, п + дл_пункт);
п += тек->len;
продолжи;
}
error_at(п, "недопустимый токен");
}
тек = тек->next = создатьокен(TK_EOF, п, п);
назначитьомера_строк(голова.next);
возврат голова.next;
}
стат символ *загрузитьайл(символ *путь) {
FILE *фп;
если (strcmp(путь, "-") == 0) {
фп = stdin;
} иначе {
фп = fopen(путь, "r");
если (!фп)
возврат NULL;
}
символ *буф;
size_t разм_буф;
FILE *вых = open_memstream(&буф, &разм_буф);
для (;;) {
символ врем_буф[4096];
цел прочитано = fread(врем_буф, 1, размер(врем_буф), фп);
если (прочитано == 0)
прерви;
fwrite(врем_буф, 1, прочитано, вых);
}
если (фп != stdin)
fclose(фп);
fflush(вых);
если (разм_буф == 0 || буф[разм_буф - 1] != '\n')
fputc('\n', вых);
fputc('\0', вых);
fclose(вых);
отследить_память(буф);
возврат буф;
}
File **get_input_files(пусто) {
возврат всехайлы;
}
File *new_file(символ *имя, цел номер, символ *содержимое) {
File *файл = calloc(1, размер(File));
отследить_память(файл);
файл->name = имя;
файл->display_name = имя;
файл->file_no = номер;
файл->contents = содержимое;
возврат файл;
}
стат пусто нормализовать_переносы(символ *п) {
цел ш = 0, щ = 0;
пока (п[ш]) {
если (п[ш] == '\r' && п[ш + 1] == '\n') {
ш += 2;
п[щ++] = '\n';
} иначе если (п[ш] == '\r') {
ш++;
п[щ++] = '\n';
} иначе {
п[щ++] = п[ш++];
}
}
п[щ] = '\0';
}
стат пусто убрать_продолжения_строк(символ *п) {
цел ш = 0, щ = 0;
цел убрано = 0;
пока (п[ш]) {
если (п[ш] == '\\' && п[ш + 1] == '\n') {
ш += 2;
убрано++;
} иначе если (п[ш] == '\n') {
п[щ++] = п[ш++];
для (; убрано > 0; убрано--)
п[щ++] = '\n';
} иначе {
п[щ++] = п[ш++];
}
}
для (; убрано > 0; убрано--)
п[щ++] = '\n';
п[щ] = '\0';
}
стат uint32_t разбор_универсального(символ *п, цел длина) {
uint32_t зн = 0;
для (цел ш = 0; ш < длина; ш++) {
если (!isxdigit(п[ш]))
возврат 0;
зн = (зн << 4) | значестн_цифры(п[ш]);
}
возврат зн;
}
стат пусто раскрыть_универсальные(символ *п) {
символ *щ = п;
пока (*п) {
если (есть_префикс(п, "\\u")) {
uint32_t зн = разбор_универсального(п + 2, 4);
если (зн) {
п += 6;
щ += encode_utf8(щ, зн);
} иначе {
*щ++ = *п++;
}
} иначе если (есть_префикс(п, "\\U")) {
uint32_t зн = разбор_универсального(п + 2, 8);
если (зн) {
п += 10;
щ += encode_utf8(щ, зн);
} иначе {
*щ++ = *п++;
}
} иначе если (п[0] == '\\') {
*щ++ = *п++;
*щ++ = *п++;
} иначе {
*щ++ = *п++;
}
}
*щ = '\0';
}
Token *tokenize_file(символ *путь) {
символ *содержимое = загрузитьайл(путь);
если (!содержимое)
возврат NULL;
если (!memcmp(содержимое, "\xef\xbb\xbf", 3))
содержимое += 3;
нормализовать_переносы(содержимое);
убрать_продолжения_строк(содержимое);
раскрыть_универсальные(содержимое);
File *файл = new_file(путь, счётчикайлов + 1, содержимое);
если (счётчикайлов + 2 > ёмкостьхайлов) {
ёмкостьхайлов = ёмкостьхайлов ? ёмкостьхайлов * 2 : 16;
всехайлы = realloc(всехайлы, размер(File *) * ёмкостьхайлов);
}
всехайлы[счётчикайлов] = файл;
всехайлы[счётчикайлов + 1] = NULL;
счётчикайлов++;
возврат tokenize(файл);
}