diff --git a/earth_doc/book_daria/book.typ b/earth_doc/book_daria/book.typ index 24d3cec..493c2ef 100644 --- a/earth_doc/book_daria/book.typ +++ b/earth_doc/book_daria/book.typ @@ -1,10 +1,13 @@ #import "../head.typ": * -#set page(footer: context {align(center, counter(page).display("1"))}) +#set page(margin: (top: 1.5cm, left: 2cm, right: 2cm), footer: context {align(center, counter(page).display("1"))}) #set text(size: 1.2em) #set par(first-line-indent: 16pt) -#align(center, heading(depth: 1)[Архитектура и стиль кода в prototype1 (Дарье)]) +#align(center, heading(depth: 1)[Архитектура и стиль +кода в Дарье (prototype1)]) + +#heading(depth: 2)[Структура репозитория] Для компиляции используется только сборочная система make. Генерировать makefile с помощью shell-скрипта разрешено, просто в данный момент это не @@ -72,18 +75,116 @@ haskell и rust. Самые высшие уровни Дарьи это те, ч Код с более высокого уровня может использовать код с более низкого уровня. Сгенерированный код с какого-то уровня может использовать исходный код с этого же уровня, но исходный код не может использовать генерированный -код своего уровня. Тем не менее код, +код своего уровня. Тем не менее код из `src/lX`, генерирующий другой код, может быть осведомлён о существовании и содердании более высоких слоёв, но только если он находится в специальной папке -`gen/l1vi` +`src/lX/anne`. Цель (функция `main`), что генерирует код `gen/lX` находится в +c-файле `src/lX/anne/codegen.c`. Вместе с ним в `src/lX/anne` помещены +h-файлы, которые содержат 'под-процедуры' от `src/lX/anne/codegen.c`, +каждый файл использует доступные в `src/lX` методы для нужд своего более высокого +уровня. В `src/lX/codegen` содержатся исключительно методы для генерации кода, +которые используются в `src/lX/anne`, но сам `src/lX/anne` не +осведомлён о высших уровнях. Больше нигде код, нужный непосредственно для генерации +кода, не помещается. + +Из этого правила есть ОДНО исключение: +Файл `src/l1/core/VecU8_as_str.h` при компиляции цели `src/l1/anne/codegen.h` +использует, как ему и положено, только исходный код с первого уровня, но +при компиляции любых другиц целей он инклюдит `gen/l1/VecAndSpan_U8.h`. +Казалось бы, это нарушение, ведь именно `src/l1` отвечает за генерацию `gen/l1`, +но это самый первый уровень, и если `gen/l1/VecAndSpan_U8.h` ещё не существует - значит +наше цель это и есть `src/l1/anne/codegen.c`. В этом случае вместо +`gen/l1/VecAndSpan_U8.h` инклюдится `src/l1/core/chicken_VecU8.h`. +Файл `src/l1/core/chicken_VecU8.h` содержит копию некоторых самых полезных методов из +`gen/l1/VecAndSpan_U8.h`, что позволяет писать и `l1` и все другие уровни с +одним и тем же определение строки. Это грязный трюк, но он сэкономил мне +сотни нервных клеток. + +_Не спрашивай как я написал `src/l1/core/chicken_VecU8.h` без +`gen/l1/VecAndSpan_U8.h`_... Разберём что делает каждый уровень, что есть в данный момент в Дарье: - `l_wl_protocols` -- из-за странного способа дистрибуции wayland-lib мы должны сами генерировать код для неё. -- `l1` -- самый первый уровень. Тут определены все утилиты, которые не -потребовали кодгена. Так же исходный код `l` определяет типы и функции, -полезные при любой генерации кода. В утилитах `l1` так же есть -обёртки над базовыми обёртками над системными вызовами линукса. -Первый уроведь определяет самые главные шаблоны: `Vec`, `Option`, -`Span`, `MutSpan`, `ResultOr`, \ No newline at end of file +- `l1` -- самый первый уровень. +- - Тут определены все утилиты, которые не + потребовали кодгена. +- - Исходный код `l` определяет типы и функции, + полезные при любой генерации кода. +- - В утилитах `l1` есть + обёртки над базовыми обёртками над системными вызовами линукса. +- - Шаблоны: `Vec`, `Option`, `Span`, `MutSpan`, `ResultOr`. + Ну а раз на нём эти шаблоны определены, + то в этом же уровне код из папки `src/l1/anne` инстанциирует их для + ВСЕХ будущих целей. +- - `gen/l1/geom.h` -- файл со структурами + векторов и матриц, их методами для операций из линейной алгебры. + +- - В `gen/l1/pixel_masses.h` инстанциированы матрицы пикселей, + или же проще - текстуры, + хранящиеся в памяти максимально тривиальным способом. + +#block(width: 100%, inset: 5pt, fill: luma(90%), radius: 4pt)[#par()[ + В `gen/l1` лежат +много много крутых заголовков, но `geom.h` и `pixel_masses.h` особенны тем, +что они не содержат в себе осведомление о высших уровнях, они пришли от самого `l1`, +а не просто зашли в `l1`, поскольку здесь впервые стали доступны методы для их +генерации. +]] + +- `l1_4` -- Тут просто лежат тесты для шаблонов `l1`. + +- `l1_5` +- - Инстанциация `Box`, `Ref`, `RefMut` для трейта (инстанциация + 'громадных' ссылок). +- - `gen/l1_5/marie/clipping.h` - дурацкий бесполезный файл для +пересечения треугольников. Даже не тестил его. Удалю его когда-нибудь. +- - Шаблоны `BuffRBTree_Set`, `BuffRBTree_Map` + +- `l2` +- - Тесты для `l1_5`. +- - `src/l2/liza` -- пространство имён для обёрток над `pipewire` и работы со звуком. +- - `src/l2/margaret/time_utils.h` -- утилиты для замерки времени с использованием + системных часов. +- - `src/l2/margaret/vulkan_utils.h` -- утилиты для использования Vulkan. +- - `src/l2/marie/graphics_geom.h`, `src/l2/marie/shape_geom.h` -- больше + алгебраических утилит, более специализированных. +- - `src/l2/marie/rasterization.h` -- Растеризация треугольников на процессоре. +- - `src/l2/marie/texture_processing.h` -- Утилиты для обработки текстур + на процессоре. Как и код, куча текстур генерируется автоматически, для этих целей + создан этот файл. +- - Тесты к `l1_5` +- - Особые тесты, названные `r` + `<число>`. Они не строгие их просто надо +запустить и проверить, что они запускаются. + +Этот список не полный. + +#heading(depth: 2)[Утилиты и классификация типов] + +Весь код в Дарье сильно отличается от привычного кода на C из-за многочисленных +утилит, использующихся повсеместно. Тот, кто не знает всех утилит, определяемых в +`l1`, не сможет дальше понять ничего. + +Нигде в Дарье я не принимаю во внимание случай Out of Memory. Когда `malloc` +возвращает `NULL` я немедленно завершаю программу. Поэтому я ввёл +функции `safe_malloc`, `safe_calloc`, `safe_realloc`. Они работают как и их +аналоги из стандартной библиотеки, но тебе не нужно беспокоиться о том что они вернут +ошибку. Хочешь понять код Дарьи - начни читать `src/l1/core/int_primitives.h`, там +определены слиасы для имён числовых типов. Вместо `uint32_t` надо писать +`U32`, дабы уподобиться `Holy C`. Дальше читай `src/l1/core/util.h` чтобы понять +все утилиты ничего незнакомого. Не всё здесь уникально для Дарьи, например, +```c #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) ``` +здесь определён как и во многих других проектах на C. +Дальше логически идёт `src/l1/core/VecU8_as_str.h`. Но у этого файла есть одна +"движущаяся" зависимость - откуда он берёт `VecU8`. Первоначально он берёт +часть методов `VecU8` из `src/l1/core/chicken_VecU8.h`. Прочитай его дабы понять какие +методы будут у вектора. `src/l1/core/VecU8_as_str.h` +инклюдит `src/l1/core/chicken_VecU8.h` когда определён "флаг" +`PROTOTYPE1_L1_CODEGEN_BOOTSTRAP_USE_CHICKEN_VECU8`. Поти всегда, когда что-то будет +иметь дело строками, он будет инклюдить `VecU8_as_str.h` +Теперь можешь заглянуть в `Makfile` и убедиться, что его структура совпадает с той, +что я описал. Для некоторых тестов есть своя отдельная Makefile-цель `run_` + +`<имя теста>`. + +Определяются следуюущие трейты для типов \ No newline at end of file diff --git a/src/l1/sobiralka.py b/src/l1/sobiralka.py new file mode 100644 index 0000000..5feecae --- /dev/null +++ b/src/l1/sobiralka.py @@ -0,0 +1,280 @@ +import sys +import os +from argparse import ArgumentParser +from typing import List, Tuple, Set +from dataclasses import dataclass + +token_type_if = 0 +token_type_elif = 1 +token_type_ifdef = 2 +token_type_elifdef = 3 +token_type_ifndef = 4 +token_type_elifndef = 5 +token_type_else = 6 +token_type_endif = 7 +token_type_regular = 8 +token_type_include_local = 9 +token_type_define = 10 + +@dataclass +class LineToken: + type: int = 5 + symbol: str = "" + filepath: str = "" + original: str = "" + +@dataclass +class IfDirBlock: + new_symbol_set: Set[str] + text: str = "" + +@dataclass +class IfDirCondAndBlock: + cond: LineToken + inside: IfDirBlock + +class ParserObj: + s: str + index = 0 + + def __init__(self, _s): + self.s = _s + + def skip_whitespace(self): + while self.index < len(self.s) and self.s[self.index].isspace(): + self.index += 1 + + def skip_word(self): + while self.index < len(self.s) and (self.s[self.index].isalnum() or self.s[self.index] == '_'): + self.index += 1 + +def parse_first_word(s): + p = ParserObj(s) + length = len(s) + p.skip_whitespace() + if p.index >= length or s[p.index] != '#': + return "" + p.index += 1 + + start = p.index + p.skip_word() + return s[start:p.index] + + +def parse_second_symbol(s): + p = ParserObj(s) + length = len(s) + + p.skip_whitespace() + if p.index >= length or s[p.index] != '#': + raise RuntimeError("How????") + p.index += 1 # Skip the '#' + + p.skip_word() + p.skip_whitespace() + + start = p.index + p.skip_word() + + if start == p.index: + raise ValueError("This is just fucking disappointing") + return s[start:p.index] + + +def parse_second_quoted_path(s): + p = ParserObj(s) + length = len(s) + + p.skip_whitespace() + if p.index >= length or s[p.index] != '#': + return None + p.index += 1 + + p.skip_word() + p.skip_whitespace() + + if p.index >= length or s[p.index] != '"': + return None + p.index += 1 + + start = p.index + + # Find the closing quote + while p.index < length and s[p.index] != '"': + p.index += 1 + + if p.index >= length: + return None + + return s[start:p.index] + +def analyze_file(lines_dirty) -> List[LineToken]: + lines = list(map(lambda x: x.rstrip(), lines_dirty)) + i = 0 + tokens = [] + while i < len(lines): + original_lines = "" + line = "" + while True: + original_lines = original_lines + lines[i] + "\n" + partial_line = lines[i] + i += 1 # i variable should not be used or changed in the rest of the cycle + if partial_line.endswith("\\"): + partial_line = partial_line[:-1] + line += partial_line + else: + line += partial_line + break + + directive = parse_first_word(line) + + if directive == 'include': + local_file = parse_second_quoted_path(line) + if local_file is not None: + tokens.append(LineToken(type = token_type_include_local, filepath=local_file, + original=original_lines)) + else: + tokens.append(LineToken(type = token_type_regular, original=original_lines)) + elif directive == 'define': + symbol = parse_second_symbol(line) + tokens.append(LineToken(type=token_type_define, symbol=symbol, original=original_lines)) + elif directive == 'ifdef': + symbol = parse_second_symbol(line) + tokens.append(LineToken(type=token_type_ifdef, symbol=symbol, original=original_lines)) + elif directive == 'ifndef': + symbol = parse_second_symbol(line) + tokens.append(LineToken(type=token_type_ifndef, symbol=symbol, original=original_lines)) + elif directive == 'elifdef': + symbol = parse_second_symbol(line) + tokens.append(LineToken(type=token_type_elifdef, symbol=symbol, original=original_lines)) + elif directive == 'elifndef': + symbol = parse_second_symbol(line) + tokens.append(LineToken(type=token_type_elifndef, symbol=symbol, original=original_lines)) + elif directive == 'else': + tokens.append(LineToken(type=token_type_else, original=original_lines)) + elif directive == 'endif': + tokens.append(LineToken(type=token_type_endif, original=original_lines)) + elif directive == 'if': + tokens.append(LineToken(type=token_type_if, original=original_lines)) + elif directive == 'elif': + tokens.append(LineToken(type=token_type_elif, original=original_lines)) + else: + tokens.append(LineToken(type=token_type_regular, original=original_lines)) + return tokens + + +# Returns new_i, block (text + new_symbol_set) +def process_part_of_file(tokens: List[LineToken], defined_symbols_outside: Set[str], i: int, input_dir, + take_seriously: bool) -> Tuple[int, IfDirBlock]: + output = "" + symbols_here = defined_symbols_outside.copy() + + in_if: List[IfDirCondAndBlock] = [] + saw_unevaluatable_condition = False + saw_else = False + saw_match = -1 + + while i < len(tokens): + token = tokens[i] + t = token.type + i += 1 + + if in_if and (t == token_type_if or t == token_type_ifdef or t == token_type_ifndef or + t == token_type_include_local or t == token_type_define or + t == token_type_regular): + raise ValueError("Okay, listen here, you little fuck") + + # When saw_else is true, we are definitely in_if + if saw_else and t != token_type_endif: + raise ValueError("Skill issue lol") + + if not in_if and t in [token_type_elif, token_type_elifdef, token_type_elifndef, + token_type_endif, token_type_else]: + i -= 1 + break + + if t == token_type_if or t == token_type_elif: + saw_unevaluatable_condition = True + + # If saw_unevaluatable_condition, nobody cares if we saw_match + if saw_match == -1: + if (t == token_type_ifdef or t == token_type_elifdef) and (token.symbol in symbols_here): + saw_match = len(in_if) + if (t == token_type_ifndef or t == token_type_elifndef) and (token.symbol not in symbols_here): + saw_match = len(in_if) + if t == token_type_else: + saw_match = len(in_if) + + if t == token_type_else: + saw_else = True + + if t in [token_type_if, token_type_ifdef, token_type_ifndef, + token_type_elif, token_type_elifdef, token_type_elifndef, token_type_else]: + i, block_in_front = process_part_of_file(tokens, symbols_here, i, input_dir, + (saw_match == len(in_if) or (t in [token_type_if, token_type_elif]) or + (t == token_type_else and saw_unevaluatable_condition)) and take_seriously) + in_if.append(IfDirCondAndBlock(cond=token, inside=block_in_front)) + + if t == token_type_endif: + assert in_if + if saw_unevaluatable_condition: + for if_block in in_if: + output += if_block.cond.original + output += if_block.inside.text + output += token.original + elif saw_match > -1: + output += in_if[saw_match].inside.text + symbols_here = in_if[saw_match].inside.new_symbol_set + + saw_unevaluatable_condition = False + saw_else = False + saw_match = -1 + in_if = [] + + if t == token_type_define: + symbols_here.add(token.symbol) + output += token.original + if t == token_type_include_local: + if take_seriously: + include_path = os.path.join(input_dir, token.filepath) + if not os.path.isfile(include_path): + raise ValueError(f"No such file {include_path}") + included_output = process_file(include_path, symbols_here) + output += included_output.text + symbols_here = included_output.new_symbol_set + else: + output += token.original + if t == token_type_regular: + output += token.original + + if in_if: + ValueError("You reached for the EOF too soon") + return i, IfDirBlock(text=output, new_symbol_set=symbols_here) + +# Returns block (text + new_symbols) +def process_file(file_path, symbols_here) -> IfDirBlock: + input_dir = os.path.dirname(os.path.abspath(file_path)) + with open(file_path, 'r') as f: + lines = f.readlines() + tokens = analyze_file(lines) + end_i, block = process_part_of_file(tokens, symbols_here, 0, input_dir, True) + assert end_i == len(tokens) + return block + + +def main(): + parser = ArgumentParser(description="Sobiralka") + parser.add_argument('input_file', type=str, help="Input C file path") + parser.add_argument('output_file', type=str, help="Output C file path") + parser.add_argument('-D', action='append', default=[], help="Define symbols") + + args = parser.parse_args() + defined_symbols = set(args.D) + + entire_program = process_file(args.input_file, defined_symbols) + with open(args.output_file, 'w') as f: + f.write(entire_program.text) + +if __name__ == "__main__": + main() + diff --git a/src/l1/system/fsmanip.h b/src/l1/system/fsmanip.h index 3dba631..9046041 100644 --- a/src/l1/system/fsmanip.h +++ b/src/l1/system/fsmanip.h @@ -2,7 +2,7 @@ #define PROTOTYPE1_SRC_L1_SYSTEM_FSMANIP_H /* For posix */ -#include "sys/stat.h" +#include #include #include "../core/util.h" diff --git a/src/l2/tests/tv0/tv0.c b/src/l2/tests/tv0/tv0.c deleted file mode 100644 index e69de29..0000000