Documentation updated + I wrote sobiralka script
This commit is contained in:
parent
e09238a8e7
commit
e1f2ff1824
@ -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<T>`, `Option<T>`,
|
||||
`Span<T>`, `MutSpan<T>`, `Result<O>Or<E>`,
|
||||
- `l1` -- самый первый уровень.
|
||||
- - Тут определены все утилиты, которые не
|
||||
потребовали кодгена.
|
||||
- - Исходный код `l` определяет типы и функции,
|
||||
полезные при любой генерации кода.
|
||||
- - В утилитах `l1` есть
|
||||
обёртки над базовыми обёртками над системными вызовами линукса.
|
||||
- - Шаблоны: `Vec<T>`, `Option<T>`, `Span<T>`, `MutSpan<T>`, `Result<O>Or<E>`.
|
||||
Ну а раз на нём эти шаблоны определены,
|
||||
то в этом же уровне код из папки `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<T>`, `BuffRBTree_Map<K, V>`
|
||||
|
||||
- `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_` +
|
||||
`<имя теста>`.
|
||||
|
||||
Определяются следуюущие трейты для типов
|
||||
280
src/l1/sobiralka.py
Normal file
280
src/l1/sobiralka.py
Normal file
@ -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()
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#define PROTOTYPE1_SRC_L1_SYSTEM_FSMANIP_H
|
||||
|
||||
/* For posix */
|
||||
#include "sys/stat.h"
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include "../core/util.h"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user