rusi/tokenize.c

762 lines
27 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#включить "руси.з"
// Текущий обрабатываемый файл
стат 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(файл);
}