Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52636a5d6e | |||
| 3bf50f7ff7 | |||
| 657c6d2955 | |||
| b26560c409 | |||
| 25cb7ba554 | |||
| dc37a14010 | |||
| ecb419efda | |||
| 5a48f7fafb | |||
| e761d21505 | |||
| 72466f7cad | |||
| d652f89240 | |||
| 46bd9ad0f1 | |||
| 5086c47f0f | |||
| 8a952b99eb | |||
| 5184415fc4 | |||
| a6fe50e247 | |||
| 110f69fb2e | |||
| 8531b25e75 | |||
| 7187752b29 | |||
| a4c8c79428 | |||
| 60373930fb | |||
| 057dd615ba | |||
| 48d552bf3a | |||
| 4a081501d8 | |||
| 46553f4e07 | |||
| ae23d2dea2 | |||
| e5866d6ba4 | |||
| adf667c087 | |||
| ffd179e064 | |||
| 961cfb786d | |||
| e1450ba7b4 | |||
| 3a33b30c07 | |||
| fabbc8129c | |||
| 977e2d3d4a | |||
| a43b4d6703 | |||
| e3342b63f3 | |||
| c88f127057 | |||
| 386837fc25 | |||
| 40ab5b9b48 | |||
| bc3483c2dd | |||
| 733746572c | |||
| 1b78dda125 | |||
| 05916a3cc6 | |||
| ee87ae1bd9 | |||
| 858f748e7a | |||
| c0dd1621e6 | |||
| 68d5be6e47 | |||
| 17639ed62d | |||
| 6d515e4b9a |
+2
-2
@@ -3,7 +3,7 @@
|
||||
# ===========================================================================
|
||||
|
||||
# `build/` directories anywhere in the tree
|
||||
# (top-level build/, lib/build/, toolchain/*/build/, ...)
|
||||
# (top-level build/, libc/build/, libbgi/build/, toolchain/*/build/, ...)
|
||||
build/
|
||||
|
||||
# sprinter-cc per-example intermediate directory
|
||||
@@ -40,7 +40,7 @@ tests/*/*.cdb
|
||||
tests/*/*.mem
|
||||
tests/*/*.rst
|
||||
|
||||
# libc archive (built from libc/, see lib/Makefile)
|
||||
# libc + libbgi archives (built by libc/Makefile + libbgi/Makefile)
|
||||
lib/*.lib
|
||||
|
||||
# Host-built mkexe binary + test outputs (input fixtures *.bin/*.ihx kept)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Sprinter C-Compiler — правила проекта
|
||||
|
||||
Target-слой SDCC 4.5 (z80) для компьютера Sprinter Sp2000: crt0,
|
||||
линковка, libc, mkexe. Общение и комментарии — на русском.
|
||||
|
||||
## Сборка и проверка
|
||||
|
||||
```
|
||||
make # tools + lib + libbgi + все тесты (45) + examples
|
||||
make -C libc # только libc → lib/sprinter.lib
|
||||
make -C libbgi # только BGI → lib/bgi256.lib (и bgi16.lib в Фазе 2)
|
||||
make floppy # упаковать все .exe в mame/v306/IMG/mc.img
|
||||
make size-check # размерный регресс: _CODE vs docs/size_baseline.tsv
|
||||
make size-baseline # принять текущие размеры эталоном
|
||||
```
|
||||
|
||||
Графика (BGI) — отдельная библиотека libbgi/ (см. ниже). Программа,
|
||||
использующая graphics.h, собирается с `--gfx 256` (или `--gfx 16` в
|
||||
Фазе 2): sprinter-cc подлинкует lib/bgi256.lib и добавит -I libbgi/include.
|
||||
|
||||
Одиночный тест: `cd tests/<имя> && make run` (пакует ТОЛЬКО этот exe
|
||||
+ EXTRA_DATA на дискету и запускает MAME). Тесты в MAME гоняет
|
||||
пользователь — готовь дискету и проси прогнать.
|
||||
|
||||
После правок libc/libbgi: пересборка от чистого листа (`make -C libc clean`
|
||||
/ `make -C libbgi clean`) не обязательна — stale .rel чистятся
|
||||
автоматически; `make size-check` обязателен (рост _CODE без причины —
|
||||
регрессия).
|
||||
|
||||
## Правила libc
|
||||
|
||||
- **1 публичная функция = 1 .c-модуль** (линкер тянет .rel целиком —
|
||||
гранулярность файлов = гранулярность DCE). Никакой группировки
|
||||
«используются вместе». Internal-хелперы — тоже по одному на модуль
|
||||
(`_`-префикс); общие статики — в отдельные data-модули
|
||||
(`_xxx_state.c`); internal-заголовки (`_file.h`, `_gfx.h`, …) —
|
||||
рядом с исходниками, НЕ в libc/include.
|
||||
- Имя файла = имя функции. libc/Makefile собирает wildcard'ом —
|
||||
ничего регистрировать не надо.
|
||||
- Комментарии — на русском; шапка модуля объясняет что/зачем + ABI.
|
||||
- File-scope переменные НЕ инициализировать `= 0` (crt0 зануляет
|
||||
_DATA; см. memory/sdcc_static_storage_gotcha).
|
||||
- asm-связки между модулями: `call/jp _global` — ок; `jr/djnz` через
|
||||
границу и fall-through — НЕЛЬЗЯ (docs/libc-split-asm-cases.md).
|
||||
- Заголовки: сначала пробовать include_next-паттерн; полная замена
|
||||
SDCC-заголовка обязана дублировать его контракт
|
||||
(docs/libc-headers.md).
|
||||
- Справочник API — docs/libc-reference.md (обновлять при добавлении
|
||||
функций).
|
||||
|
||||
## ABI и платформа (кратко; детали в memory/)
|
||||
|
||||
- SDCC `__sdcccall(1)`: arg1 → HL (8-бит → A), arg2 → DE, остальные
|
||||
на стеке (callee-pops в __naked); **возврат int/ptr в DE**, uint8 в A.
|
||||
IX callee-saved (в __naked с IX — push/pop обязательны).
|
||||
- ESTEX (rst #0x10): CF=1 — ошибка, код в A → `call __errno_set`;
|
||||
все регистры клобберятся (IX сохранять); стек обязан быть в W2.
|
||||
- BIOS (rst #0x08): строки/буферы в #4000-#BFFF.
|
||||
- Квирки: ESTEX WRITE возвращает DE=0 на успехе (судить по CF/A);
|
||||
лимит 8 файловых манипуляторов, 9-й OPEN ВЕШАЕТ DSS (_fd_guard);
|
||||
ENV $46: A=0 = NOT FOUND.
|
||||
- Перед обвинением компилятора/железа — подтвердить артефактом
|
||||
(сгенерированный .asm в libc/build/ или libbgi/build/, дамп, репро) — см.
|
||||
memory/defer_unexplained_quirks.
|
||||
|
||||
## Структура
|
||||
|
||||
- `libc/<area>/*.c` — модули libc (ядро, БЕЗ графики); `libc/include/` — публичные заголовки libc
|
||||
- `libbgi/` — графика BGI (отдельная библиотека): `common/` — mode-agnostic (один исходник, .rel в обеих driver-библиотеках), `bgi256/` + `bgi16/` — mode-specific leaf'ы (реальные реализации, без обёрток); `include/` — graphics.h + gfx.h; `_bgi.h` — внутренний заголовок. Собирает `lib/bgi256.lib` (и `bgi16.lib` в Фазе 2). Выбор режима линковкой: `--gfx 256` / `--gfx 16`.
|
||||
- `runtime/` — crt0-семейство, heap, bank (bank.s собирается per-build)
|
||||
- `bin/sprinter-cc` — обёртка компилятора; `toolchain/mkexe` — упаковщик
|
||||
- `tests/` — по одному API/фиче; `examples/` — реальные приложения
|
||||
- `docs/` — дизайн-доки; `docs/TODO.md` — roadmap
|
||||
- `third_party/solid-c/` — нативный Sprinter C (референс, CP866;
|
||||
их ABI несовместим — только как образец)
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# make build host tools, libc archive, all tests, all apps
|
||||
# make tools build only host tools (mkexe)
|
||||
# make lib build lib/sprinter.lib (libc archive used by sprinter-cc)
|
||||
# make lib build lib/sprinter.lib (libc) + lib/bgi256.lib (libbgi)
|
||||
# make tests build all libc feature tests under tests/
|
||||
# make examples build all real applications under examples/
|
||||
# make floppy package every .exe + test fixtures into mame/v306/IMG/mc.img
|
||||
@@ -13,12 +13,16 @@
|
||||
# Most heavy lifting is delegated to sub-Makefiles.
|
||||
|
||||
# Small libc-feature tests (one program per .c-language feature or libc API).
|
||||
TESTS := hello banked bankedbg strtest cat seek malloc mem_test argv errno \
|
||||
rt_test openenv ls conio attrprob timedir mouse banklocl stdlib \
|
||||
assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
||||
|
||||
TESTS := hello hello2 simple banked bankedbg banktest strtest cat seek \
|
||||
malloc mem_test argv errno rt_test openenv ls conio conio2 \
|
||||
attrprob timedir mouse banklocl stdlib assrtest ptime stattest \
|
||||
filetest fdmax fbench solidt irqtest cbltest cblwav dec_test gets stest2 winrest \
|
||||
bios_text text_palette \
|
||||
gfx_demo gfx_dbuf
|
||||
# gfx_d16 / gfx_text / gfx_mous — 16-цветные; убраны до Фазы 2 (bgi16.lib
|
||||
# ещё не собирается). Вернуть мигрированными на BGI --gfx 16.
|
||||
# Larger end-user applications under examples/.
|
||||
APPS := mdview
|
||||
APPS := mdview mdview2
|
||||
|
||||
MAME_DIR := mame/v306
|
||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
||||
@@ -31,9 +35,11 @@ ALL_EXES := $(TEST_EXES) $(APP_EXES)
|
||||
DATA_FILES := \
|
||||
tests/cat/test.txt \
|
||||
tests/seek/big.txt \
|
||||
tests/cblwav/speech.pcm \
|
||||
examples/mdview/SAMPLE.MD
|
||||
|
||||
.PHONY: all tools lib tests examples check clean sdcc floppy $(TESTS) $(APPS)
|
||||
.PHONY: all tools lib tests examples check clean sdcc floppy \
|
||||
size-check size-baseline $(TESTS) $(APPS)
|
||||
|
||||
all: tools lib tests examples
|
||||
|
||||
@@ -41,7 +47,8 @@ tools:
|
||||
$(MAKE) -C toolchain/mkexe
|
||||
|
||||
lib:
|
||||
$(MAKE) -C lib
|
||||
$(MAKE) -C libc
|
||||
$(MAKE) -C libbgi
|
||||
|
||||
check: tools
|
||||
$(MAKE) -C toolchain/mkexe check
|
||||
@@ -66,9 +73,18 @@ floppy: tests examples tests/seek/big.txt
|
||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||
|
||||
# Размерный регресс: сверить _CODE всех программ с docs/size_baseline.tsv.
|
||||
size-check:
|
||||
python3 toolchain/size_check.py
|
||||
|
||||
# Принять текущие размеры как эталон (после осознанных изменений).
|
||||
size-baseline:
|
||||
python3 toolchain/size_check.py --update
|
||||
|
||||
clean:
|
||||
$(MAKE) -C toolchain/mkexe clean
|
||||
$(MAKE) -C lib clean
|
||||
$(MAKE) -C libc clean
|
||||
$(MAKE) -C libbgi clean
|
||||
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
|
||||
@for a in $(APPS); do $(MAKE) -C examples/$$a clean; done
|
||||
|
||||
|
||||
@@ -51,14 +51,24 @@ CC_FLAGS += $(EXTRA_FLAGS)
|
||||
|
||||
all: $(EXAMPLE).exe
|
||||
|
||||
$(EXAMPLE).exe: $(SOURCES) $(MKEXE) $(LIB)
|
||||
# runtime/*.s (crt0-семейство, bank.s, heap.s) собираются per-build
|
||||
# внутри sprinter-cc — без этой зависимости их правка не перелинкует
|
||||
# уже собранный exe (кусало: фикс bank.s не подхватился).
|
||||
RUNTIME_DEPS := $(wildcard $(PROJ_ROOT)/runtime/*.s)
|
||||
|
||||
$(EXAMPLE).exe: $(SOURCES) $(MKEXE) $(LIB) $(RUNTIME_DEPS)
|
||||
$(SPRINTER_CC) $(CC_FLAGS) -o $@ $(SOURCES)
|
||||
|
||||
$(MKEXE):
|
||||
$(MAKE) -C $(PROJ_ROOT)/toolchain/mkexe
|
||||
|
||||
# $(LIB) = lib/sprinter.lib (libc). Графика (bgi256.lib) собирает
|
||||
# libbgi/Makefile; гоним и его — иначе standalone `make` в тесте с
|
||||
# --gfx 256 не найдёт bgi256.lib при линковке. Оба инкрементальные,
|
||||
# на повторном запуске ничего не пересобирают.
|
||||
$(LIB):
|
||||
$(MAKE) -C $(PROJ_ROOT)/lib
|
||||
$(MAKE) -C $(PROJ_ROOT)/libc
|
||||
$(MAKE) -C $(PROJ_ROOT)/libbgi
|
||||
|
||||
clean:
|
||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe
|
||||
|
||||
+23
-3
@@ -34,6 +34,9 @@
|
||||
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
||||
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
||||
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
||||
# --max-allocs N SDCC --max-allocs-per-node (default: SDCC's 3000).
|
||||
# Higher values give smaller/faster code at the cost of
|
||||
# compile time; 10000..100000 is a reasonable range.
|
||||
# --debug enable runtime diagnostics — defines DEBUG_RT for both
|
||||
# sdcc (-DDEBUG_RT) and the crt0 assembly (prepended
|
||||
# `DEBUG_RT = 1`). Exposes runtime introspection symbols
|
||||
@@ -61,6 +64,7 @@ CHECK_BANKS="$PROJ_ROOT/toolchain/check_banks.py"
|
||||
RUNTIME="$PROJ_ROOT/runtime"
|
||||
LIB_DIR="$PROJ_ROOT/lib"
|
||||
INC_DIR="$PROJ_ROOT/libc/include"
|
||||
BGI_INC_DIR="$PROJ_ROOT/libbgi/include" # gfx.h, graphics.h (BGI)
|
||||
|
||||
# ------- Defaults ------------------------------------------------------------
|
||||
OUT=""
|
||||
@@ -82,6 +86,8 @@ SOURCES=()
|
||||
LD_EXTRA=()
|
||||
MKEXE_EXTRA=()
|
||||
BANK_SPECS=() # entries like "1=engine.c"
|
||||
MAX_ALLOCS="" # if set, passed to sdcc as --max-allocs-per-node
|
||||
GFX_MODE="" # "256" → link BGI graphics.h driver sprinter_gfx256.lib
|
||||
|
||||
# ------- Parse args ----------------------------------------------------------
|
||||
usage() {
|
||||
@@ -105,6 +111,8 @@ while [[ $# -gt 0 ]]; do
|
||||
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
||||
--bank) BANK_SPECS+=("$2"); shift 2;;
|
||||
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
||||
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
||||
--gfx) GFX_MODE="$2"; shift 2;;
|
||||
--debug) DEBUG_RT=1; shift;;
|
||||
-v) VERBOSE=1; shift;;
|
||||
-h|--help) usage 0;;
|
||||
@@ -116,6 +124,17 @@ done
|
||||
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
|
||||
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
|
||||
|
||||
# ------- BGI graphics driver selection (--gfx) -------------------------------
|
||||
# graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver-
|
||||
# архив. Одновременно только один. drv16 пока не реализован.
|
||||
GFX_LD=()
|
||||
case "$GFX_MODE" in
|
||||
"") ;; # graphics.h не используется
|
||||
256) GFX_LD=("-lbgi256");; # libbgi/bgi256 → lib/bgi256.lib
|
||||
16) echo "sprinter-cc: --gfx 16 ещё не реализован (пока только 256)" >&2; exit 1;;
|
||||
*) echo "sprinter-cc: --gfx: ожидается 256 (или 16), дано: $GFX_MODE" >&2; exit 1;;
|
||||
esac
|
||||
|
||||
# ------- Resolve memory mode → CODE_LOC / DATA_LOC ---------------------------
|
||||
# tiny : CODE in W2 (0x8100), DATA auto after code (= W2)
|
||||
# small : CODE in W1 (0x4100), DATA in W2 (0x8000) — crt0 must alloc W2
|
||||
@@ -270,8 +289,9 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
|
||||
|
||||
# 2. user sources → .rel (HOME)
|
||||
USER_RELS=()
|
||||
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}")
|
||||
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}")
|
||||
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
|
||||
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
|
||||
for src in "${SOURCES[@]}"; do
|
||||
rel="$WORK/$(basename "$src" .c).rel"
|
||||
run "$SDCC" "${CC_FLAGS[@]}" -c -o "$rel" "$src"
|
||||
@@ -336,12 +356,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
|
||||
if [[ $VERBOSE -eq 1 ]]; then
|
||||
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
||||
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
|
||||
"-L$LIB_DIR" "-lsprinter"
|
||||
"-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter"
|
||||
else
|
||||
# Drop the warning line + its two follow-up "Library:" lines.
|
||||
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
||||
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
|
||||
"-L$LIB_DIR" "-lsprinter" 2>&1 \
|
||||
"-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter" 2>&1 \
|
||||
| awk '
|
||||
/^\?ASlink-Warning-Definition of public symbol/ { skip = 3 }
|
||||
skip > 0 { skip--; next }
|
||||
|
||||
+200
-434
@@ -1,436 +1,202 @@
|
||||
# TODO / Roadmap
|
||||
|
||||
Открытые задачи в порядке убывания приоритета. По мере появления реальных программ — приоритеты будут смещаться.
|
||||
|
||||
## Этап 5 — malloc / free + banking-aware page allocator ✅ ГОТОВО
|
||||
|
||||
- [x] SDCC's `malloc`/`free` + наш `runtime/heap.s` (полностью заменяет library heap.rel, 14000-байтный heap в окне 2)
|
||||
- [x] `libc/mem/mem_alloc.c` — page allocator: `mem_alloc_pages`/`mem_free_block`/`mem_get_page`/`mem_info` через ESTEX `$3C/$3D/$3E` + BIOS `$C4`
|
||||
- [x] `libc/mem/bank_io.c` — HOME-резидентные `bank_read`/`bank_write`/`bank_load_byte`/`bank_store_byte` со свопом W3 внутри
|
||||
- [x] `examples/malloc_test/` — проверка SDCC's malloc (~210 64-байтных allocations через всю heap)
|
||||
- [x] `examples/mem_test/` — проверка page allocator: 3 страницы, разные паттерны через bank_write, верификация через bank_read
|
||||
|
||||
## Этап 6 — argv parsing + sprinter-cc wrapper ✅ ГОТОВО
|
||||
|
||||
- [x] crt0 парсит ESTEX command-line из IX-prefix (inline asm в `runtime/crt0.s`)
|
||||
- [x] Strip leading CP/M-style space (DSS quirk)
|
||||
- [x] Передача `argc`/`argv` в main() через HL/DE (SDCC __sdcccall(1) ABI)
|
||||
- [x] argv[0] = basename .EXE через ESTEX APPINFO ($47 subfn 2)
|
||||
- [x] `runtime/crt0_minimal.s` — opt-out для очень маленьких программ
|
||||
- [x] `runtime/crt0_banked.s` — теперь тоже парсит argv (parse_argv + get_progname скопированы из crt0.s; будет factored в argv.s когда возьмёмся за libsprinter.lib)
|
||||
- [x] Bash-обёртка `bin/sprinter-cc`: `sprinter-cc -o foo.exe foo.c` одной строкой
|
||||
- [x] Поддержка опций: `--memory`, `--memory-manual`, `--stack-size`, `--crt0=`, `--bank N=FILE.c`, `--debug`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
|
||||
|
||||
## Этап 8 — графика (320×256×256 + 640×256×16 + accel + bitmap font) ✅ ГОТОВО
|
||||
|
||||
- [x] **8a** Graphics core: `gfx_init`/`gfx_done`/`gfx_clear`/`gfx_putpixel`/`gfx_pal_load`/`gfx_pal_set` (libc/gfx/gfx_core.c). Палитра через BIOS PIC_SET_PAL ($A4). Verified 320×256×256.
|
||||
- [x] **8b** Линии/прямоугольники/fill через accelerator (libc/gfx/gfx_lines.c): `gfx_hline`/`gfx_vline` через accel Fill (LD C,C / LD E,E + SMC block-size), `gfx_rect`/`gfx_fill_rect` с heuristic выбором ориентации (h/v bursts count), `gfx_line` с Bresenham для диагоналей. `gfx_clear` тоже переписан на column-major accel (~4× быстрее).
|
||||
- [x] **8c** 640×256×16 mode (libc/gfx/gfx_16.c): `gfx_*16` API, HIGH nibble = LEFT pixel (документация misleading), per-row RMW для vline (один байт = 2 горизонтальных пикселя).
|
||||
- [x] **8d** Bitmap font + gfx_text (libc/gfx/gfx_text.c): шрифт через BIOS WIN_GET_ZG ($B8), interleaved layout `font[row*256+char]`, `gfx_text`/`gfx_putchar` для 320 mode, `gfx_text16`/`gfx_putchar16` для 640 mode с pair-table lookup.
|
||||
|
||||
См. memory/sprinter_graphics.md, sprinter_accelerator.md, sprinter_graphics_16.md, sprinter_font_format.md.
|
||||
|
||||
Открытые мелочи (не блокируют):
|
||||
- [ ] Шрифт-quad для 640: per-cell палитра (mode 0x82 разрешает 1 из 4 палитр per 16×8 cell) — через прямой доступ к area-описания экрана 0x0300..0x039F
|
||||
|
||||
## Auto-banking (см. `memory/banking_roadmap.md` для деталей)
|
||||
|
||||
Phase 1 — file-level bin-packing — реализовывать когда проект перерастёт ~30 KB кода.
|
||||
|
||||
- [ ] `toolchain/auto_bank.py`:
|
||||
- Парсит размеры из `.rel`-файлов (или из .map после dry-run link'а)
|
||||
- First-fit-decreasing bin-packing
|
||||
- Уважает `#pragma codeseg BANKn` как manual override
|
||||
- Перелинковывает с новыми `-Wl-b_BANKn=...` параметрами
|
||||
- Печатает план распределения
|
||||
|
||||
Phase 2-5: incremental rebalance, declarative `banks.toml`, function-level, call-graph-aware. Только если/когда понадобится.
|
||||
|
||||
## Bank-local static data (mutable data в том же банке что и код) — ✅ ГОТОВО
|
||||
|
||||
- [x] Пример `examples/bank_local_data/` — функция в BANK1 со своим writable BSS array + const table + malloc-тест
|
||||
- [x] `mkexe -p 0` для нулевого padding банков (BSS-storage обнуляется при загрузке)
|
||||
- [x] Канонический рецепт: `--codeseg BANK1 --constseg BANK1 --dataseg BANK1` для bank1.c + `-Wl-b_BANK1=0x1C000` для линковки. **`--dataseg BANK1` РАБОТАЕТ** — раньше казалось обратное из-за trampoline bug который маскировал результат.
|
||||
- [x] **Критичный фикс trampoline'a в runtime/bank.s** — старый `pop af; out (n), a` клобберил A → все banked-функции возвращающие uint8_t тихо возвращали мусор. Новый `pop bc; out (c), b` сохраняет A.
|
||||
- [x] **malloc из banked-функции работает прозрачно** — heap живёт в W2 (HOME), W2 никогда не свапается trampoline'ом, pointer валиден из любого контекста. См. memory/bank_local_data_pattern.md.
|
||||
- [x] Документация в memory: `memory/bank_local_data_pattern.md` (полный рецепт + malloc + nuances), `memory/sdcc_banking.md` (trampoline fix)
|
||||
- [ ] Опционально — расширить `check_banks.py` чтобы показывать разбивку size = code + const + bss per bank (cosmetic)
|
||||
|
||||
Зачем: для модулей с большим private state (level loader, audio engine, scene data). Экономит W2 heap для динамики, а статика остаётся в бэке.
|
||||
|
||||
## Подсказки из solid-c (нативный Sprinter C — `third_party/solid-c/`)
|
||||
|
||||
После анализа solid-c'овской libc (см. `memory/solid_c_findings.md`) выявлены готовые паттерны для следующих недостающих функций. Приоритет от **высокого** к низкому:
|
||||
|
||||
### High-priority gaps (легко портировать, большая польза)
|
||||
- [x] **`errno` + `strerror`/`perror`** — табличка 32 ошибок (libc/io/errno.c)
|
||||
- [x] **Расширенный `open()`** для O_CREAT/O_TRUNC/O_APPEND/O_EXCL state machine
|
||||
- [x] **`atexit`** — 8-callback LIFO + `exit()` + `_exit()` (libc/io/atexit.c)
|
||||
- [x] **`setjmp`/`longjmp`** — 6-байт jmp_buf={sp,ix,pc} (libc/io/setjmp.c)
|
||||
- [x] **`sleep(seconds)`** — 50Hz halt-loop (libc/io/sleep.c)
|
||||
- [x] **ESTEX ENV API** ($46, getenv/putenv) — libc/io/env.c. Учли doc-bug: реально A=0 это NOT FOUND
|
||||
|
||||
### Medium-priority (нужно для shell-like утилит)
|
||||
- [ ] **Mouse driver** — `rst $30h`, 17 функций. **Сначала тест что работает в MAME**.
|
||||
- [x] **`ffirst`/`fnext` + ffblk_t struct** для directory listing — реализовано, demo: ls.exe
|
||||
- [x] **`getdatetime`/`setdatetime`** через ESTEX $21/$22 — libc/io/time.c, demo: time_dir_test
|
||||
- [x] **`chdir`/`getcwd`/`mkdir`/`rmdir`** — wrappers для ESTEX $1B-$1E — libc/io/fsdir.c
|
||||
- [x] **conio: `kbhit`/`getch`/`getche`/`cputs`/`clrscr`/`gotoxy`** — реализовано
|
||||
- [x] **conio extras**: `wherex`/`wherey` ($53), `wrchar`/`rdchar` ($58/$57), `textmode_get/set` ($50/$51), `clrscr_attr` ($56) + COLOR macros
|
||||
|
||||
### Low-priority — ✅ FILE* stack ГОТОВО
|
||||
|
||||
- [x] **Минимальный unbuffered FILE\*** — `libc/stdio/file.c` + `libc/include/stdio.h`. fopen/fclose/fputs/fgets/fread/fwrite/fseek/ftell/rewind/feof/ferror/clearerr/fflush + stdin/stdout/stderr как pseudo-streams. См. `memory/file_star_design.md` и `examples/filetest`.
|
||||
- [ ] fprintf / fscanf — нужна printf-через-callback machinery. Пока пользователь может `sprintf(buf, ...) + fputs(buf, fp)`.
|
||||
- [ ] Опциональный buffered mode (setvbuf, line/block buffering) — если когда-то понадобится.
|
||||
|
||||
### POSIX time API — ✅ ГОТОВО
|
||||
- [x] `libc/io/posix_time.c` — time/localtime/gmtime/mktime/asctime/ctime поверх getdatetime. SDCC's time.rel избегаем (нельзя override _RtcRead). См. `examples/ptime`.
|
||||
|
||||
### sys/stat — ✅ ГОТОВО
|
||||
- [x] `libc/io/stat.c` — POSIX stat/fstat. Гибрид open+fstat для файлов, ffirst+iter для папок (включая "."/".."). См. `examples/stattest` и `memory/estex_ffirst_dotdot.md`.
|
||||
|
||||
### assert — ✅ ГОТОВО (используем SDCC's __assert через fallback include path)
|
||||
|
||||
## libc/stdlib — ✅ не нужно делать (см. memory/sdcc_stdlib_works.md)
|
||||
|
||||
Проверено через `examples/stdlib_test/`: SDCC z80.lib содержит работающие реализации:
|
||||
- `atoi/atol/atof, strtol/strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv`
|
||||
- Полный `<string.h>` (memchr/cmp/set/cpy, strcat/cmp/cpy/len/chr/spn/etc.)
|
||||
- `<ctype.h>` (toupper/tolower)
|
||||
- `<math.h>` (sinf/cosf/sqrtf/etc.)
|
||||
|
||||
Линкер автоматически тянет из z80.lib когда нужно. **НЕ переписывать**.
|
||||
|
||||
Наши Sprinter-specific обязательные модули остаются: atexit, env, errno, setjmp, putchar/puts/getchar, conio, fsdir, time, mouse, open/read/lseek/close.
|
||||
|
||||
## Build-system: libsprinter.lib + sprinter-cc — ✅ ГОТОВО
|
||||
|
||||
- [x] `lib/Makefile` — собирает каждый libc/*.c в `.rel`, архивирует через sdar в `lib/sprinter.lib`
|
||||
- [x] Включает runtime/bank.s и runtime/heap.s (auto-pulled при __banked/malloc)
|
||||
- [x] `bin/sprinter-cc` — bash-wrapper: `sprinter-cc -o foo.exe foo.c` одной строкой
|
||||
- [x] Поддержка опций `--crt0=default|minimal|banked`, `--bank N=FILE.c`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
|
||||
- [x] `examples/hello_sccc/` — демо: `hello.c` собирается за один shell-вызов, размер совпадает с ручным Makefile (925 байт)
|
||||
- [x] Split `putchar.c` → `putchar.c` + `puts.c` для per-function granularity (puts override SDCC's z80.lib version)
|
||||
- [x] Включён в `make all` (зависимость `lib` перед `examples`)
|
||||
|
||||
Возможные улучшения (опционально):
|
||||
- [ ] Мигрировать остальные examples на sprinter-cc вместо ручных Makefile (косметика)
|
||||
- [ ] Дальнейшая декомпозиция libc/*.c per-function (но текущая granularity уже даёт нужный размер — линкер пакетует .rel целиком, и для большинства файлов это одна функция)
|
||||
|
||||
## Этап 9 — memory modes для sprinter-cc
|
||||
|
||||
DSS выделяет страницы памяти по размеру приложения: < 16 KB → одна страница, в остальные окна подключается «страница #FF» (read=0xFF, write игнорится). Из-за этого CODE-в-W1 + DATA-в-W2 для маленькой программы молча ломается. См. [memory/sprinter_memory_modes.md](../../.claude/projects/-Volumes-SAM8-Projects-DIY-Z80-Sprinter-C-Compiler/memory/sprinter_memory_modes.md).
|
||||
|
||||
- [x] **`tiny`**: всё (CODE+DATA+стек) в W2. Default. Verified hello/argv/conio/malloc/file/etc.
|
||||
- [x] **`--memory MODE` флаг в sprinter-cc**: parser + per-mode дефолты CODE_LOC/DATA_LOC, override через явные `--code-loc`/`--data-loc`. tiny работает; small/big/huge компилируются с warning'ом (runtime не готов). Реализовано 2026-05-30.
|
||||
- [x] **`--memory-manual SPEC`**: парсит `CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3`. Реализовано 2026-05-30.
|
||||
- [x] **`small` runtime**: `runtime/crt0_small.s` использует ESTEX `$3D GETMEM` + `$3A SETWIN2` чтобы выделить и замапить W2-страницу ДО gsinit. **НЕ** BIOS `$C4` — стек на этом этапе в W1 (boot_stack в HOME), а BIOS требует стек в W2. После маппинга SP переключается на 0xBFFE, дальше стандартный flow. Реализовано 2026-05-30, verified hello.exe.
|
||||
- [x] **`small` auto-detect для >16 KB программ**: `crt0_small.s` читает порт `0xC2` (текущая страница в W2 — не `0xA2`! это W1). Если 0xFF — выделяет page; иначе DSS уже сделала это (программа сама вылезла в W2). Один crt0 покрывает 0..30 KB. mkexe также разрешает HOME span W1+W2 (0x4000..0xBFFF). Verified hello: small (5 KB файл, SETWIN2 path) + 32 KB файл (auto-skip). Реализовано 2026-05-30.
|
||||
- [x] **`big` runtime** (tiny + banked code в W1): параметризовали `crt0_banked.s` + `bank.s` через `.ifdef BANK_W1` — другой banking port (0xA2 vs 0xE2), другой load-addr (0x4000 vs 0xC000). sprinter-cc prepend'ит `BANK_W1 = 1` при `--memory big`, передаёт `mkexe -B 0x4000`. Пример `examples/banked_big/`. Реализовано 2026-05-30.
|
||||
- [x] **`huge` runtime** (small + banked code в W3): merge W2-detect логики из `crt0_small.s` в `crt0_banked.s`. Существующий пример `examples/banked/` теперь использует MEMORY=huge. Реализовано 2026-05-30.
|
||||
- [x] **`--debug` флаг**: prepend `DEBUG_RT = 1` в crt0 + `-DDEBUG_RT` в sdcc. Открывает symbol `_w2_self_allocated` (uint8_t) — runtime diagnostic кто аллоцировал W2. Реализовано 2026-05-30.
|
||||
|
||||
### Дизайн-решения по libc и crt0
|
||||
|
||||
**Одна `sprinter.lib`** работает для всех memory mode — `.rel`-члены relocatable, SDLD делает dead-code elimination per-member (без графики не подтягивает `gfx_core.rel` и т.д.). Verified hello vs malloc_test через map-файлы.
|
||||
|
||||
**`gfx.lib` отдельно — НЕ нужен**: dead-code elimination уже работает.
|
||||
|
||||
**`libc_banked` (libc в bank вместо HOME)** — идея на потом, когда HOME (16 KB) забит user-кодом + libc в `huge` mode. Реализуется через `--codeseg BANK0` при компиляции libc; trade-off: trampoline ~30 циклов на каждый libc-вызов. Триггер: реальная программа упрётся в HOME budget.
|
||||
|
||||
**HW-зависимые модули — `sprinter_home.lib` отдельно.** Часть libc физически не может быть забанкована в W3, потому что она РАБОТАЕТ с W3:
|
||||
- `gfx_*` — пишет в видеопамять `0xC000+` после swap W3 на video page
|
||||
- `bank_io` (mem_alloc_pages/bank_read/bank_write) — swap'ит W3 через `OUT (0xE2)`
|
||||
- Будущие ISR — прерывание может прийти когда W3 на чём угодно
|
||||
|
||||
В huge mode эти модули ДОЛЖНЫ остаться в HOME (W1). Когда будем делать `libc_banked`, **одновременно** выделяем `sprinter_home.lib` (HOME-only) из `sprinter.lib` (bankable). Финальная схема:
|
||||
```
|
||||
sprinter_home.lib HOME-only: gfx, bank_io, ISR shims
|
||||
sprinter.lib bankable: printf, malloc, string, conio, stdio, env, ...
|
||||
sprinter_banked.lib тот же sprinter.lib но --codeseg BANK0 (для huge)
|
||||
```
|
||||
Триггер: реализация `--memory huge` runtime.
|
||||
|
||||
**crt0 — по одному на mode:**
|
||||
- `crt0.s` — текущий, для **tiny/big**: SP=0xBFFE, парсит argv (W2-ресурс уже выделен DSS).
|
||||
- `crt0_minimal.s` — текущий, для tiny без argv.
|
||||
- `crt0_small.s` — **новый, step 3**: для **small/huge**, аллоцирует W2 через `mem_alloc_pages` ДО gsinit, маппит в порт `0xA2`, потом стандартный flow.
|
||||
- `crt0_banked.s` — текущий, для **big**: trampoline-таблица для W3 банков, CODE в W2.
|
||||
- `crt0_banked_small.s` — **новый**: huge = small (W2-alloc) + banked (W3 trampolines).
|
||||
|
||||
sprinter-cc подбирает crt0 по `--memory` mode (сейчас `--crt0=` это override).
|
||||
- [x] **Настраиваемый размер стека**: флаг `sprinter-cc --stack-size BYTES`. Wrapper генерирует `heap_top.s` с `___sdcc_heap_end = stack_top + 1 - stack_size`, отдельный .rel линкуется per-program. Default ≈1278 байт (heap_top=0xBB00) из `runtime/heap_top.s`. Реализовано 2026-05-30.
|
||||
|
||||
Интерфейс: `sprinter-cc --memory [tiny|small|big|huge|manual] [--memory-manual SPEC] [--stack-size N] foo.c`. `--memory-manual` имеет смысл только с `--memory manual`.
|
||||
|
||||
## Known issues / quirks
|
||||
|
||||
- **ESTEX $46 ENV API**: ✅ работает. Док-ция в `DiskSyscalls.txt v1.6` ошибочно описывает return-status — A=0 это NOT FOUND, не FOUND. Зафиксировано в `memory/sprinter_platform.md`.
|
||||
|
||||
## ОБЯЗАТЕЛЬНЫЕ ЗАДАЧИ ДЛЯ V2 (после релиза v1)
|
||||
|
||||
### Turbo-C-style graphics API (BGI-like) — **MUST для v2**
|
||||
|
||||
Расширить наш `gfx_*` API до уровня **Turbo-C `<graphics.h>`** (BGI) для MS-DOS.
|
||||
Программисты привыкшие к Turbo-C должны переносить графический код 1-в-1.
|
||||
|
||||
**Что должно быть** (на основе Borland BGI):
|
||||
|
||||
Setup/teardown:
|
||||
- `initgraph()` / `closegraph()` — у нас сейчас `gfx_init`/`gfx_done`, добавить alias
|
||||
- `getmaxx()` / `getmaxy()` — макрос на GFX_WIDTH-1 / GFX_HEIGHT-1
|
||||
- `cleardevice()` — alias to gfx_clear
|
||||
- `getgraphmode()` / `setgraphmode()` — у нас get_videomode/set_videomode
|
||||
|
||||
Color/palette:
|
||||
- `setcolor(c)`, `getcolor()` — current draw color
|
||||
- `setbkcolor(c)`, `getbkcolor()` — background color
|
||||
- `setpalette(idx, c)` — палитра entry
|
||||
- `getpalette(&info)` — read all palette
|
||||
|
||||
Primitives (мы уже имеем эквиваленты — добавить BGI-имена как aliases):
|
||||
- `putpixel(x, y, c)` — есть как gfx_putpixel
|
||||
- `getpixel(x, y)` — нужно реализовать (RMW обратное — IN)
|
||||
- `moveto(x, y)`, `lineto(x, y)`, `linerel(dx, dy)` — current point + line drawing
|
||||
- `line(x1, y1, x2, y2)` — есть как gfx_line
|
||||
- `rectangle(x1, y1, x2, y2)` — есть как gfx_rect (но другой API: x1,y1,x2,y2 vs x,y,w,h!)
|
||||
- `bar(x1, y1, x2, y2)` — есть как gfx_fill_rect
|
||||
- `bar3d(x1, y1, x2, y2, depth, topflag)` — новое: rect + 3d edges
|
||||
- `circle(x, y, r)`, `arc(...)`, `ellipse(...)`, `pieslice(...)` — новые primitives
|
||||
- `fillpoly()`, `drawpoly()` — полигоны
|
||||
- `floodfill(x, y, border_color)` — заливка
|
||||
|
||||
Text on graphics screen:
|
||||
- `outtext(s)` / `outtextxy(x, y, s)` — есть как gfx_text (alias)
|
||||
- `settextstyle(font, dir, size)` — multiple bitmap fonts
|
||||
- `gettextsettings(&info)`
|
||||
- `textwidth(s)` / `textheight(s)` — measure
|
||||
|
||||
Image manipulation:
|
||||
- `imagesize(x1, y1, x2, y2)` — bytes needed for getimage
|
||||
- `getimage(x1, y1, x2, y2, buf)` — save rect to buffer
|
||||
- `putimage(x, y, buf, op)` — paste back with COPY_PUT/XOR_PUT/AND_PUT/OR_PUT/NOT_PUT
|
||||
|
||||
Clipping/viewport:
|
||||
- `setviewport(x1, y1, x2, y2, clip)` — drawing clip rect
|
||||
- `getviewsettings(&info)`
|
||||
- `clearviewport()`
|
||||
- `setactivepage(p)` / `setvisualpage(p)` — двойная буферизация (Sprinter имеет 2 screen)
|
||||
|
||||
Line style:
|
||||
- `setlinestyle(style, pattern, thickness)` — SOLID_LINE / DOTTED_LINE / etc.
|
||||
- `getlinesettings(&info)`
|
||||
|
||||
**Acceptance:** перенос типичной Turbo-C BGI программы (рисующей с использованием
|
||||
moveto/lineto/circle/bar/setcolor) должен работать без существенных правок.
|
||||
|
||||
**Notes:**
|
||||
- BGI fonts (TRIPLEX/SANS_SERIF/GOTHIC) — у нас один BIOS font, остальные нужно
|
||||
добавить (как bitmap data в lib)
|
||||
- imagesize/getimage/putimage — самые востребованные для game/animation
|
||||
- Active/visual page (двойная буферизация) — Sprinter поддерживает 2 graphics pages,
|
||||
нужен API switching
|
||||
|
||||
См. также `examples/` Turbo C 2.x BGIDEMO как reference что нужно.
|
||||
|
||||
### IM2 Interrupt Handlers — **MUST для v2**
|
||||
|
||||
User-задаваемые ISR через Z80 IM 2 mode. Нужны для:
|
||||
- Timer ticks (50 Hz frame counter, плавная анимация)
|
||||
- Music playback (AY, COVOX)
|
||||
- Real-time games (input + game logic + render в interrupt-driven)
|
||||
- Async keyboard / mouse handling
|
||||
|
||||
**Status:** ОТЛОЖЕНО до v2. Полный research + design в `docs/im2_isr_design.md`.
|
||||
|
||||
**Решение по архитектуре:** реализовать как отдельный memory mode `--memory im2`
|
||||
(вместо того чтобы лезть во все существующие crt0). Detail'и в design-doc.
|
||||
|
||||
**Резюме research'а** (полный текст в `docs/im2_isr_design.md`):
|
||||
- Vector 0xFF — frame + keyboard + CBL. Disambiguation по портам 0x19 / 0xFE
|
||||
- Mouse hardware-IRQ не приходит (на текущей плате)
|
||||
- Vector table / ISR / stack ОБЯЗАНЫ быть в W2 (0x8000..0xBFFF)
|
||||
- DSS имеет свой IM 2 handler — нужно chain'иться (иначе клавиатура / SYSTIME ломаются)
|
||||
|
||||
### Прочие крупные пункты для v2
|
||||
|
||||
- [ ] **FILE API rewrite — buffered streams** — текущая реализация в
|
||||
`libc/stdio/file.c` это provisional unbuffered shim (каждый fputc/fgetc
|
||||
= один read/write syscall). Нужна полноценная buffered семантика
|
||||
как в Solid-C:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint flags; // +0..1 file status flags
|
||||
int level; // +2..3 empty/fill level of buffer
|
||||
char *curp; // +4..5 current active pointer
|
||||
int fd; // +6..7 underlying low-level fd
|
||||
char *buffer; // +8..9 data transfer buffer
|
||||
char hold; // +10 ungetc byte if no buffer
|
||||
short token; // +11..12 reserved
|
||||
char dummy; // +13 reserved
|
||||
} FILE;
|
||||
```
|
||||
|
||||
stdin/stdout/stderr — fd-маркеры `0 / -1 / -2`. Отрицательные для
|
||||
stdout/stderr выбраны намеренно: ESTEX OPEN может вернуть positive
|
||||
small fd (1, 2, …) для обычного файла → если бы stdout=1, реальный
|
||||
fd=1 сталкивался бы с идентификатором. fd=0 для stdin безопасно
|
||||
(ESTEX 0 не возвращает). Сами fd не передаются в syscall'ы —
|
||||
диспетчеризация по флагам `_F_CONIN/_F_CONOUT`.
|
||||
|
||||
Принтер-потоки (stdaux/stdprn) НЕ реализуем — Sprinter принтерной
|
||||
API не имеет.
|
||||
|
||||
Альтернатива — взять реализацию из third_party/solid-c (sources в
|
||||
`SRC/CLIB/`); там есть готовый buffered FILE + fopen/fread/fwrite/
|
||||
fseek/setvbuf и т.д. Адаптировать к нашим open/read/write/lseek.
|
||||
|
||||
При rewrite заодно решить deferred issues stdio-review:
|
||||
- `fwrite` short-write должен ставить `_F_ERROR`
|
||||
- `fgets(buf, 1, fp)` — стандарт говорит "empty string", мы вернули NULL
|
||||
- `mode_to_flags` — break-out на '+' (cosmetic)
|
||||
|
||||
- [ ] **Audio API** — AY-3-8910 + COVOX через прерывания (требует IM2)
|
||||
- [ ] **ISA-8 slot support** — ZX-Bus карты (sound, network, etc.) — требует IM2 + чтения portов
|
||||
|
||||
## Прочие задачи (v1 backlog, не блокирующие)
|
||||
|
||||
- [x] **#9: text I/O split (Turbo-C style)** — stdio (puts/printf/putchar) теперь fast no-attr через PCHARS/PUTCHAR. conio (cputs/cprintf/putch) применяет attr через textcolor/textbackground/textattr. KEEP_EXIST_ATTR → conio fallback на fast path. Verified в hello.exe. См. `memory/text_output_api_split.md`. Реализовано 2026-05-31.
|
||||
- [x] **Mouse API полный** (резидентный driver, RST 30h) — все 14 функций обёрнуты (init/show/hide/refresh/read/goto/bounds/text_cursor/load_cursor/get_cursor/get/set_sensitivity/video_mode_changed). См. `memory/mouse_api.md`. Verified в MAME 2026-05-31. Sensitivity = divider (меньше = быстрее).
|
||||
- [ ] Interrupt handlers — IM 2 vector table в HOME для user ISR'ов
|
||||
- [ ] Поддержка `restore SP on EXIT` (паттерн из z88dk +pps) — проверить нужно ли
|
||||
- [ ] CI: автоматически запускать MAME с `-aviwrite` для screenshot-сравнения, чтобы тесты примеров проходили без человека
|
||||
|
||||
## Идеи на потом
|
||||
|
||||
- Поддержка `<setjmp.h>` (есть в SDCC stdlib — нужно протестировать что наш crt0 совместим)
|
||||
- `<time.h>` через ESTEX SYSTIME (`$21`) и CMOS BIOS-функции
|
||||
- ZX Spectrum-совместимый режим как отдельный target (для портирования спектрумовских программ)
|
||||
- Поддержка ZX-Bus карт (sound, network, etc.) — нужны драйверы
|
||||
- Profile-guided optimization tools (hot/cold detection) для крупных программ
|
||||
|
||||
## Linker duplicate-symbol warnings (благоприятные, отфильтрованы)
|
||||
|
||||
Когда мы сознательно overrides'им SDCC z80.lib функции собственной версией в `sprinter.lib`, `sdldz80` пишет `?ASlink-Warning-Definition of public symbol '...' found more than once`. Линкер берёт первое найденное определение (наше), поэтому поведение корректное — warning только noise.
|
||||
|
||||
Текущие overrides:
|
||||
- `_puts` — наша версия через PCHARS+\r\n vs SDCC posix puts
|
||||
- `___sdcc_heap` — наш heap в W2 vs SDCC's стандартный
|
||||
- `_asctime`, `_localtime` (и возможно другие из time) — наш `posix_time.c` через ESTEX SYSTIME vs SDCC's `time.rel` который зависит от `_RtcRead`
|
||||
|
||||
**Текущее решение:** `bin/sprinter-cc` отфильтровывает warning-блок (warning + 2 follow-up `Library:` строки) из вывода `sdc
|
||||
|
||||
c`. Через `-v` (verbose) всё показывается. Реализовано через awk-pipe.
|
||||
|
||||
**Возможные улучшения:**
|
||||
- Перейти на explicit `--nostdlib` + ручной список нужных модулей из z80.lib (string, math, stdlib без override'нутых) — убрать ИСТОЧНИК warning'ов, не маскировать
|
||||
- Или: переименовать наши `_puts` → `_puts_sprinter` + alias через linker flag (не уверен что SDCC поддерживает)
|
||||
- Или: оставить как сейчас (рабочее и benign) — приоритет низкий
|
||||
|
||||
## TODO: проверить на реальном железе
|
||||
|
||||
- [ ] **Port_Y banking trick** (`docs/part2/SprinterGraphics programming.txt`):
|
||||
доку утверждает что после `OUT (0x89), Y` адреса 0xC000+0x400*N в окне W3
|
||||
маппятся на строки Y..Y+15 (одно программирование → 16 строк).
|
||||
Empirical 2026-06-01 в MAME 0.283 этот trick **не работает** — пиксели
|
||||
по адресам выше 0xC000+row_width уходят в невидимую область. Канонический
|
||||
`docs/samples/plasma2.asm` тоже не использует banking, переустанавливает
|
||||
Port_Y per row.
|
||||
План:
|
||||
1. Получить доступ к реальному Sprinter
|
||||
2. Запустить тест dual-write (`_gfx_putpixel_raw` + второй write в `0xD000+x`)
|
||||
3. Если на железе видны двойные линии → бага MAME, открыть issue с
|
||||
минимальным репро
|
||||
4. Если на железе тоже одна линия → документ неверный, удалить упоминание
|
||||
из доки и просто оставить текущую реализацию (Port_Y per pixel)
|
||||
5. Если banking работает на железе → внедрить кэширование Port_Y в
|
||||
`_gfx_putpixel_raw` (sentinel out-of-range, см. memory/gfx_port_y_banking.md)
|
||||
|
||||
Связанный выигрыш для Bresenham (60-pixel диагональ) — около 8× меньше
|
||||
OUT (0x89) операций, для `gfx_fill_rect 320x256` — 16× меньше. Не блокирует
|
||||
release v1.
|
||||
|
||||
## GFX: расширения по `docs/part2/accelerator_doc.txt`
|
||||
|
||||
После прочтения детального accelerator doc выявлены незакрытые направления.
|
||||
Сейчас в коде используется только горизонтальный/вертикальный Fill mode.
|
||||
|
||||
### Quick wins для текущих primitives
|
||||
|
||||
- [ ] **Заменить SMC на `LD A, (var)` для block-size**. Документ явно
|
||||
разрешает `LD A, (HL)`, `LD A, (BC)`, `LD A, (DE)` (но не `LD A, r`).
|
||||
Это уберёт SMC complexity в `gfx_lines.c:hfill_chunk/vfill_chunk` и
|
||||
`gfx_16.c:g16_hfill_chunk`. Запрещено только register-to-register.
|
||||
- [ ] **Кэширование block-size**. Документ показывает что accel запоминает
|
||||
block size между bursts (см. `Horizontal_Line_Fill`: устанавливают
|
||||
size + `LD B,B` отключение, потом включают Fill mode и используют
|
||||
сохранённый size). Для `gfx_fill_rect` с 100 одинаковыми
|
||||
строками — установить size 1 раз, а не 100.
|
||||
|
||||
### Bank-prefix modes (port 0xE2 bits)
|
||||
|
||||
Документ показывает три варианта банка видеостраницы помимо стандартного 0x50:
|
||||
|
||||
| Bank byte | Effect |
|
||||
|---|---|
|
||||
| 0x50 | Normal write — пишется в shadow + видимый |
|
||||
| 0x54 | "no copy in main shadow RAM" |
|
||||
| 0x58 | **"FF is transparent"** — байт 0xFF при write оставляет background |
|
||||
| 0x5C | both |
|
||||
|
||||
Bank 0x58 объясняет почему mouse cursor рисуется с 0xFF-прозрачностью.
|
||||
Это путь к **sprite-blending через accel block copy**:
|
||||
|
||||
- [ ] **`gfx_set_bank_transparent(on)`** или флаг в `gfx_set_bank` для
|
||||
выбора 0x50/0x58 при отрисовке sprite'ов
|
||||
- [ ] Использовать в новом `gfx_blit()` чтобы по факту получать
|
||||
transparent sprites через accel-копию
|
||||
|
||||
### Block copy mode (sprite blit'ы)
|
||||
|
||||
`LD L,L` (horizontal) и `LD A,A` (vertical) — режим копирования блока через
|
||||
256-байтную accel memory. Это базис для blit'ов.
|
||||
|
||||
- [ ] **`gfx_blit(src_data, x, y, w, h)`** — копирование sprite'а
|
||||
(произвольный размер, через accel)
|
||||
- [ ] **`gfx_blit_transparent(src, x, y, w, h)`** — с использованием bank 0x58
|
||||
|
||||
См. `Draw_Restangle_Data` в accelerator_doc.txt как референс.
|
||||
|
||||
### AND / OR / XOR operations через accel
|
||||
|
||||
Документ показывает что accel поддерживает логические операции с блоками
|
||||
данных. Применения:
|
||||
- XOR — инверсия области (выделение selection в UI)
|
||||
- OR / AND — masking, alpha-style blending
|
||||
- См. пример в accelerator_doc.txt: "256 bytes block coding via XOR"
|
||||
|
||||
- [ ] **`gfx_xor_rect`** / **`gfx_or_rect`** / **`gfx_and_rect`** —
|
||||
примитивы логических операций над прямоугольником
|
||||
- [ ] **`gfx_invert_rect(x, y, w, h)`** — alias на xor с 0xFF
|
||||
|
||||
### Bitmap fonts разных размеров
|
||||
|
||||
Сейчас `gfx_text` / `gfx_putchar` хардкоженно работают с 8×8 шрифтом
|
||||
(BIOS WIN_GET_ZG возвращает 256×8 байт). Для будущих UI / титульников
|
||||
нужны:
|
||||
- [ ] **`gfx_set_font_size(w, h)`** — переключить ширину/высоту glyph'а
|
||||
- [ ] **`gfx_set_font_data(ptr, w, h, advance)`** — заменить указатель
|
||||
на пользовательский шрифт + размеры
|
||||
- [ ] Поддержка **proportional** (advance != w) шрифтов — добавить
|
||||
array advance[256] на ширину каждого glyph'а
|
||||
- [ ] **Big-font режимы**: 8×16, 16×16, 16×8 (для титульников)
|
||||
- [ ] Возможно отдельный API `gfx_text_ex(x, y, str, font_id)` где
|
||||
font_id выбирает один из загруженных шрифтов
|
||||
- [ ] **Anti-alias 2-bit шрифты** (бит фон / бит граница / 2-бит alpha?)
|
||||
— far future, для smooth UI
|
||||
|
||||
## Финальный этап оптимизаций (не сейчас)
|
||||
|
||||
- **`gfx_line` через accel для пологих диагоналей** — Bresenham для линии с |dy| << |dx| (или наоборот) выдаёт длинные runs одинакового Y (или X): пиксель, пиксель, пиксель, шаг Y, пиксель, пиксель... Каждый такой run — это готовый аргумент для `gfx_hline` (или `vline`).
|
||||
План исследования: посчитать длину runs как функцию от наклона; решить минимальный run length, при котором выгоднее accel hline чем N×putpixel (overhead accel ~20µs, putpixel ~5µs — accel выгоднее при run ≥ 4-5 px); для крутых диагоналей (dx ≈ dy) оставить Bresenham, для пологих — run-length-based fill.
|
||||
Сейчас `gfx_line` orthogonal cases уже через accel — оптимизировать только косые.
|
||||
|
||||
- **`gfx_fill_rect` с одним W3-swap на всю операцию** — сейчас каждый внутренний `gfx_hline`/`gfx_vline` делает свой DI/save-W3/restore-W3/EI. Можно сделать internal `_fill_rect_inner` который держит W3 замапленным и DI весь цикл; ~20µs × количество строк/столбцов экономии. Применимо ко всем композитным примитивам.
|
||||
Открытые задачи в порядке убывания приоритета; закрытые этапы — в
|
||||
«Истории» внизу. Текущий срез libc-работ: docs/libc-roadmap.md.
|
||||
|
||||
## Ближайшее
|
||||
|
||||
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
|
||||
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
|
||||
трогался asm акселератора); затем прогон на реальном Sprinter
|
||||
(mdview2 + FILE* v2 + fdmax — подтвердить лимит 8 манипуляторов
|
||||
и зависание DSS на 9-м OPEN)
|
||||
- [ ] Мигрировать оставшиеся examples на sprinter-cc вместо ручных
|
||||
Makefile (косметика)
|
||||
- [ ] check_banks.py: разбивка size = code + const + bss per bank
|
||||
|
||||
## Auto-banking (memory/banking_roadmap.md)
|
||||
|
||||
Phase 1 — file-level bin-packing (`toolchain/auto_bank.py`) — когда
|
||||
проект перерастёт ~30 KB кода: парсинг размеров из .rel/.map,
|
||||
first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинковка,
|
||||
печать плана. Phase 2-5 (rebalance, banks.toml, function-level) —
|
||||
по потребности.
|
||||
|
||||
## ОБЯЗАТЕЛЬНОЕ ДЛЯ V2
|
||||
|
||||
### Turbo-C-style graphics API (BGI-like) — Фаза 1 ГОТОВА (256, 2026-07-08)
|
||||
|
||||
Архитектура: `graphics.h` — mode-agnostic слой (libc/bgi/*.c в
|
||||
sprinter.lib), режим задаёт driver-архив; выбор линковкой через
|
||||
`sprinter-cc --gfx 256` (16 — позже, тем же leaf-split'ом; см.
|
||||
memory/bgi_two_lib_design).
|
||||
|
||||
**Фаза 1 (256, реализовано и проверено в MAME — tests/bgitest):**
|
||||
initgraph/closegraph/graphresult/cleardevice, setcolor/getcolor/
|
||||
setbkcolor/getbkcolor, getmaxx/getmaxy/getmaxcolor, putpixel/getpixel,
|
||||
moveto/moverel/getx/gety, line/lineto/linerel, rectangle, bar, circle,
|
||||
outtext/outtextxy. initgraph грузит EGA-палитру 0..15. Пакетные
|
||||
примитивы (circle) — одна W3-скобка на весь примитив (raw-плот), иначе
|
||||
на порядок медленнее.
|
||||
|
||||
**Фаза 2a/2b/2c ГОТОВЫ (2026-07-08, проверено в MAME tests/bgitest):**
|
||||
- 2a: arc, ellipse, drawpoly (Q7-тригонометрия, БЕЗ 32-бит).
|
||||
- 2b: setfillstyle/getfillsettings + 10 паттернов Borland, bar (с
|
||||
паттерном), bar3d, fillpoly (scanline min/max), fillellipse (isqrt).
|
||||
- 2c: floodfill (scanline span; медленный — self-bracket чтение, но
|
||||
корректный), pieslice, sector.
|
||||
|
||||
**Фаза 2d-1/2/3 ГОТОВЫ (2026-07-08, MAME tests/bgitest):**
|
||||
- 2d-1: getimage/putimage/imagesize (COPY/XOR/OR/AND/NOT_PUT), raw-блит
|
||||
в одной W3-скобке (_gfx_getpixel256_raw).
|
||||
- 2d-2: setlinestyle/getlinesettings (SOLID/DOTTED/CENTER/DASHED/USERBIT
|
||||
+ NORM/THICK) — line/lineto/linerel/rectangle/drawpoly.
|
||||
- 2d-3: settextstyle/gettextsettings/textwidth/textheight — масштаб
|
||||
1..10, HORIZ/VERT, прозрачный фон (свой scaled-рендер поверх 8×8).
|
||||
|
||||
**Фаза 2d (осталось):** setviewport/clearviewport + клиппинг (инвазивно —
|
||||
трогает все примитивы), settextjustify, setaspectratio (пиксели 320×256
|
||||
неквадратные — круги визуально эллиптичны), setfillpattern (USER_FILL),
|
||||
setactivepage/setvisualpage (2 страницы в gfx уже есть). Оптимизации:
|
||||
floodfill на raw-чтении; ellipse/arc в одной W3-сессии. Потом drv16 +
|
||||
sprinter_gfx16.lib (--gfx 16).
|
||||
Acceptance: типичная BGI-программа переносится без существенных
|
||||
правок. Референс: Turbo C 2.x BGIDEMO.
|
||||
|
||||
### IM2 Interrupt Handlers — Phase 1 ГОТОВ (2026-07-06, libc/irq, tests/irqtest; Phase 2: CBL/ISA/цепочки)
|
||||
|
||||
User-ISR через IM 2 — timer ticks, музыка (AY/COVOX), real-time
|
||||
игры, async input. Phase 1 реализован БЕЗ отдельного memory mode:
|
||||
libc/irq (<irq.h>: irq_install/irq_remove), таблица в BSS с runtime-
|
||||
выравниванием, jp-заглушка внутри таблицы, чейн к DSS всегда;
|
||||
работает в tiny/big, в small/huge — EINVAL. Детали и отличия от
|
||||
исходного плана: docs/im2_isr_design.md.
|
||||
|
||||
### Прочее v2
|
||||
|
||||
- [ ] **Audio API** — AY-3-8910 + COVOX (требует IM2)
|
||||
- [ ] **ISA-8 slot support** — ZX-Bus карты (требует IM2)
|
||||
|
||||
## GFX: расширения по accelerator_doc.txt
|
||||
|
||||
Quick wins:
|
||||
- [ ] block-size через `LD A,(nn)` вместо SMC (док разрешает LD A,(HL/BC/DE))
|
||||
- [ ] кэширование block-size между burst'ами (accel помнит размер)
|
||||
|
||||
Новые возможности:
|
||||
- [ ] `gfx_blit` / `gfx_blit_transparent` — block copy (LD L,L / LD A,A),
|
||||
прозрачность через bank 0x58 («FF is transparent»)
|
||||
- [ ] `gfx_xor_rect` / `gfx_or_rect` / `gfx_and_rect` / `gfx_invert_rect`
|
||||
- [ ] шрифты ≠ 8×8: gfx_set_font_data(ptr,w,h,advance), proportional,
|
||||
8×16/16×16, отдельный font_id API; font-quad для 640×256
|
||||
(per-cell палитра через дескрипторы 0x0300..0x039F)
|
||||
|
||||
Оптимизации (не сейчас):
|
||||
- [ ] gfx_line через accel для пологих диагоналей (runs ≥ 4-5 px)
|
||||
- [ ] композитные примитивы с одним W3-swap на операцию
|
||||
|
||||
## Прочий backlog
|
||||
|
||||
- [ ] factoring parse_argv из crt0/crt0_banked в общий argv.s
|
||||
- [ ] `restore SP on EXIT` (паттерн z88dk +pps) — проверить нужность
|
||||
- [x] ~~CI: MAME с -aviwrite для screenshot-сравнения без человека~~ —
|
||||
`toolchain/mame_interactive.py`: авто-запуск .exe вводом с
|
||||
эмуляцией клавиатуры + Lua-таймер для скриншотов/выхода; сравнение
|
||||
визуальное (Claude читает скриншот), не автоматический diff.
|
||||
Полный справочник: docs/mame-autotest.md.
|
||||
- [ ] linker duplicate-symbol warnings: сейчас фильтруются в
|
||||
sprinter-cc (наши overrides _puts/___sdcc_heap/_asctime/…);
|
||||
радикально — --nostdlib с ручным списком модулей z80.lib
|
||||
- [ ] ZX Spectrum-совместимый target; ZX-Bus драйверы; PGO-tools
|
||||
|
||||
## Проверить на реальном железе
|
||||
|
||||
- [ ] **Port_Y banking trick** (адреса 0xC000+0x400*N → строки
|
||||
Y..Y+15): в MAME 0.283 НЕ работает (2026-06-01). На железе:
|
||||
dual-write тест → если работает, кэшировать Port_Y в putpixel
|
||||
(~8× меньше OUT для Брезенхэма); если нет — вычистить из доков.
|
||||
- [ ] fdmax: лимит манипуляторов и зависание 9-го OPEN — MAME vs железо.
|
||||
- [ ] CBL: щелчок перед первым проигрыванием звука за сессию (tests/
|
||||
cbltest, tests/cblwav) — воспроизводится ТОЛЬКО на первом запуске
|
||||
программы за сессию MAME, не зависит от содержимого потока
|
||||
(тон/тишина/речь одинаково). Похоже на разовый прогрев звуковой
|
||||
подсистемы MAME при первой активации канала — на реальном железе
|
||||
скорее всего отсутствует. Проверить и закрыть либо описать как
|
||||
реальный аппаратный эффект.
|
||||
- [ ] **CBL: помехи в звуке при движении мыши** (tests/cblstream,
|
||||
2026-07-07) — при потоковом воспроизведении движение мыши даёт
|
||||
слышимые артефакты. Гипотеза (НЕ подтверждена): мышь может
|
||||
делить SIO-канал/детект-бит с клавиатурой (порт 0x19 бит 0,
|
||||
см. _irq_tramp.c), и трамплин классифицирует байты мыши как
|
||||
«клавиатура» → сразу chain на DSS (0x0038), пропуская проверку
|
||||
CBL-хука для ЭТОГО прерывания — при движении мыши часть тиков,
|
||||
которые должны были обслужить CBL-насос, уходят мимо, кольцо
|
||||
недоливается. Нужно исследование (MAME-дамп/лог трамплина).
|
||||
|
||||
## Known quirks (зафиксированы, обходы в libc)
|
||||
|
||||
- ESTEX $46 ENV: A=0 это NOT FOUND (док врёт) — memory/sprinter_platform
|
||||
- ESTEX WRITE $14: на успехе DE=0, не счётчик; успех = CF=0 & A=0 —
|
||||
memory/estex_write_de_quirk
|
||||
- DSS: 8 манипуляторов, 9-й OPEN вешает систему; _fd_guard в libc —
|
||||
memory/dss_fd_limit
|
||||
- SDCC z80 `if(n!=g)g=n;` пишет (n-g) — memory/sdcc_z80_cmp_store_a_bug
|
||||
|
||||
---
|
||||
|
||||
# История — закрытые этапы
|
||||
|
||||
## Этап 10 — libc: сплит + FILE v2 + Solid-C (2026-07-05/06) ✅
|
||||
|
||||
Полный план/итоги: docs/libc-roadmap.md. Кратко:
|
||||
- вся libc разложена «1 функция = 1 модуль» (~250 модулей, wildcard-
|
||||
сборка, DCE на уровне файлов): gfx_text −4.4 КБ, timedir/ls/stattest
|
||||
−3 КБ и т.д.; правила asm-связок: docs/libc-split-asm-cases.md
|
||||
- **FILE* v2 (B+)**: ленивый буфер 512 на чтение/запись с
|
||||
автопереключением, таблица OPEN_MAX=8, flush-on-exit, ungetc,
|
||||
fprintf/vfprintf, fdopen/freopen/fclosall/fgetpos/fsetpos; горячие
|
||||
пути fgetc/fputc/fgets на asm (fgets 100 КБ: 144с unbuffered-оценка
|
||||
→ ~1 с). Дизайн: docs/file-buffering-design.md
|
||||
- **scanf/fscanf/sscanf** — своё C-ядро (в SDCC z80 нет)
|
||||
- **Solid-C совместимость закрыта**: <dos.h> (даты/диски/absread),
|
||||
errno-алиасы, <sprinter_solid.h> — docs/solid_c_compatibility.md
|
||||
- гигиена: stale .rel чистка, все 43 теста в make all, размерный
|
||||
регресс (make size-check), контракт заголовков docs/libc-headers.md
|
||||
- справочник API: docs/libc-reference.md
|
||||
|
||||
## Этап 9 — memory modes (tiny/small/big/huge/manual) ✅ 2026-05-30
|
||||
|
||||
`--memory MODE` в sprinter-cc; crt0-семейство (crt0/minimal/small/
|
||||
banked); small: ESTEX GETMEM+SETWIN2 до gsinit, auto-detect W2 по
|
||||
порту 0xC2; big/huge: параметризация crt0_banked/bank.s через
|
||||
BANK_W1; --debug, --stack-size. Детали: memory/memory_modes_
|
||||
implemented, memory/sprinter_memory_modes.
|
||||
|
||||
Дизайн-решения: одна sprinter.lib на все режимы (DCE per-member);
|
||||
gfx.lib отдельно не нужен; libc_banked + sprinter_home.lib — идея
|
||||
на потом (триггер: HOME забит user-кодом).
|
||||
|
||||
## Этап 8 — графика ✅
|
||||
|
||||
320×256×256 + 640×256×16, акселератор (Fill h/v, SMC block-size),
|
||||
Брезенхэм, bitmap font (WIN_GET_ZG, interleaved), gfx_text.
|
||||
memory/sprinter_graphics*, sprinter_accelerator, sprinter_font_format.
|
||||
|
||||
## Bank-local data ✅
|
||||
|
||||
--codeseg/--constseg/--dataseg BANKn + mkexe -p 0; фикс трамплина
|
||||
(pop bc/out (c),b — сохраняет A); malloc из банка прозрачен (heap в
|
||||
W2). memory/bank_local_data_pattern.
|
||||
|
||||
## Этапы 5-7 и ранняя libc ✅
|
||||
|
||||
- malloc/free (SDCC + runtime/heap.s в W2), page allocator
|
||||
(mem_alloc_pages, ESTEX $3C-$3E + BIOS $C4), bank_read/bank_write
|
||||
- crt0 argv-парсинг (IX-prefix, CP/M-space quirk, APPINFO basename),
|
||||
sprinter-cc wrapper со всеми опциями
|
||||
- errno+strerror/perror, open state-machine, atexit, setjmp/longjmp,
|
||||
sleep, ENV API ($46), ffirst/fnext, getdatetime/setdatetime,
|
||||
chdir/getcwd/mkdir/rmdir, conio (полный), mouse (RST 30h, 14 ф-й),
|
||||
POSIX time API, sys/stat, assert
|
||||
- text I/O split (stdio fast / conio attr) — memory/text_output_api_split
|
||||
- SDCC stdlib НЕ переписываем — memory/sdcc_stdlib_works
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Fast RAM (Быстрое ОЗУ / «КЭШ-ОЗУ») на Sprinter
|
||||
|
||||
Сводка по результатам изучения документации платформы. Источники:
|
||||
|
||||
- `docs/converted/Architecture.txt` — официальное «Описание архитектуры» (раздел
|
||||
«Распределение основной памяти»).
|
||||
- `docs/converted/ARHITECT.txt` — ранняя редакция того же документа (про загрузку
|
||||
конфигураций ППЛМ).
|
||||
- `docs/converted/IvanMak.txt` / `docs/converted/Parinov.txt` / `docs/converted/Forum.txt`
|
||||
и `docs/part2/forum.txt` — форумные ответы Дениса Паринова (Sprinter Team) и
|
||||
руководство Ивана Мака (раздел «7 КЭШ-ОЗУ»).
|
||||
- `docs/part2/accelerator_doc.txt` — ограничение акселератора.
|
||||
- `docs/samples/sprinterIntLib.asm` — практический пример temporary-off / restore.
|
||||
|
||||
> **Терминология.** В документации одно и то же ОЗУ называется тремя именами:
|
||||
> **Fast RAM**, **Быстрое ОЗУ** и **«КЭШ-ОЗУ»**. Это *не* кэш в формальном смысле
|
||||
> (нет автоматического заполнения/вытеснения) — это отдельный массив статической
|
||||
> памяти, в котором процессор работает на полной частоте **без тактов ожидания**.
|
||||
> Имя «КЭШ» — историческое, по аналогии с кэшем на КР537РУ10 в Pentagon-128.
|
||||
|
||||
---
|
||||
|
||||
## 1. Что это и зачем
|
||||
|
||||
* **Объём:** 64 КБ статической памяти (SRAM), отдельной от основного DRAM-SIMM
|
||||
(4 МБ) и от видео-ОЗУ (256 КБ).
|
||||
* **Скорость:** процессор обращается к Fast RAM на полной тактовой частоте
|
||||
(21 МГц) **без wait-state'ов**. Основное ОЗУ (DRAM) требует тактов ожидания,
|
||||
поэтому код и данные в Fast RAM исполняются/читаются заметно быстрее.
|
||||
* **Назначение:** разместить «горячий» код или данные (внутренние циклы,
|
||||
таблицы, буферы), которые критичны по скорости.
|
||||
* **Системная роль:** Fast RAM также используется механизмом
|
||||
переконфигурирования ППЛМ — именно в неё BIOS грузит данные новой
|
||||
конфигурации и флаг `ACEX_30K_LOADING` (старое имя `FLEX_10K_LOADING`) перед
|
||||
программным сбросом. Поэтому к Fast RAM нельзя относиться как к «своей» памяти,
|
||||
которая всегда сохраняется (см. §5).
|
||||
|
||||
---
|
||||
|
||||
## 2. Карта физических страниц
|
||||
|
||||
Память делится на 16 КБ-блоки с однобайтовым физическим номером:
|
||||
|
||||
| Тип памяти | Физические номера страниц |
|
||||
|---------------|---------------------------|
|
||||
| Основное ОЗУ | `#00..#4F`, видео-область `#50..#5F`, ... |
|
||||
| ПЗУ (ROM) | `#E0..#EF` |
|
||||
| **Fast RAM** | `#F0..#FF` |
|
||||
|
||||
> Хотя диапазон номеров Fast RAM — `#F0..#FF` (16 значений), **реально
|
||||
> используются только биты 1 и 2** номера страницы. То есть адресуются 4
|
||||
> страницы × 16 КБ = **64 КБ**: `#F0`, `#F2`, `#F4`, `#F6`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Как включать Fast RAM
|
||||
|
||||
Есть **два способа** подключить Fast RAM в адресное пространство Z80.
|
||||
|
||||
### Способ A. Pentagon-style через порт `#FB` / `#7B` (в окно 0)
|
||||
|
||||
Включается «как кэш в Pentagon»: подключает 16 КБ Fast RAM в **окно 0**
|
||||
(`#0000..#3FFF`) вместо ПЗУ. Переключение — *побочный эффект чтения порта*
|
||||
(значение в `A` после `IN` — мусор, важен сам факт обращения):
|
||||
|
||||
```asm
|
||||
DI
|
||||
IN A,(#FB) ; включить Fast-RAM — 16 КБ в окно 0 (#0000..#3FFF)
|
||||
; ... ваш код / работа с Fast RAM ...
|
||||
IN A,(#7B) ; выключить Fast-RAM (вернуть ПЗУ в окно 0)
|
||||
EI
|
||||
```
|
||||
|
||||
* `IN A,(#FB)` — **включить**.
|
||||
* `IN A,(#7B)` — **выключить**.
|
||||
|
||||
> **Конфликт портов.** Порт `#FB` (и `#4F`) — это также порт COVOX/Blaster-а.
|
||||
> Вывод (`OUT`) в `#FB` управляет звуком, а *чтение* (`IN`) — переключает
|
||||
> Fast RAM. Не путать направления обращения.
|
||||
|
||||
### Способ B. Как ПЗУ — через PAGE0 (`#82`) + порт `#1FFD`
|
||||
|
||||
Fast RAM-страница (`#F0..#FF`) выбирается в PAGE0 и подключается на место ПЗУ
|
||||
в окно 0 через спец-порт `#1FFD`:
|
||||
|
||||
```asm
|
||||
; выбрать физическую страницу Fast RAM в PAGE0
|
||||
LD A, #F0 ; номер страницы Fast RAM
|
||||
OUT (#82), A ; PAGE0 = страница в окно 0
|
||||
|
||||
LD A,1 ; 1 → ОЗУ (выбранная страница) в #0000..#3FFF
|
||||
LD BC,#1FFD
|
||||
OUT (C),A
|
||||
; ...
|
||||
LD A,0 ; 0 → вернуть ПЗУ в #0000..#3FFF
|
||||
LD BC,#1FFD
|
||||
OUT (C),A
|
||||
```
|
||||
|
||||
* Порты PAGE: `PAGE0=#82`, `PAGE1=#A2`, `PAGE2=#C2`, `PAGE3=#E2`.
|
||||
**Чтение** порта PAGE возвращает текущий номер страницы.
|
||||
* Эти адреса портов формально могут отличаться в других конфигурациях ППЛМ —
|
||||
правильнее запрашивать их у BIOS и сверять (см. `docs/part2/bios_doc.txt`,
|
||||
~строка 1033).
|
||||
|
||||
---
|
||||
|
||||
## 4. Преимущества
|
||||
|
||||
1. **Скорость без wait-state.** Главное и единственное предназначение — код и
|
||||
данные исполняются на полной частоте 21 МГц без тактов ожидания, в отличие от
|
||||
основного DRAM.
|
||||
2. **Идеально для горячих участков.** Внутренние циклы, lookup-таблицы,
|
||||
временные буферы рендера — то, к чему обращаются интенсивно и многократно.
|
||||
3. **Отдельный массив.** Не отнимает страницы основного 4 МБ ОЗУ и не пересекается
|
||||
с видео-областью.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ограничения и подводные камни ⚠️
|
||||
|
||||
Это **самая важная часть** — Fast RAM небезопасна в обращении и легко даёт
|
||||
«молча не работает».
|
||||
|
||||
1. **Акселератор НЕ работает с Fast RAM.**
|
||||
Акселератор поддерживает пересылку блоков только для основного ОЗУ и
|
||||
видео-ОЗУ. Пересылку **ROM и FastRAM он не поддерживает**. То есть нельзя
|
||||
использовать accel-Fill/Copy для заполнения или копирования в/из Fast RAM —
|
||||
только обычные `LD`-циклы процессора.
|
||||
|
||||
2. **Содержимое не сохраняется между процессами.**
|
||||
Fast RAM может быть использована другими программами. При запуске любого
|
||||
процесса через DSS (а также самим механизмом переконфигурирования ППЛМ)
|
||||
**содержимое Fast RAM может быть затёрто**. Нельзя рассчитывать на
|
||||
персистентность данных между вызовами системы.
|
||||
|
||||
3. **Перед вызовами DSS и BIOS Fast RAM надо ОТКЛЮЧАТЬ.**
|
||||
Системные функции рассчитывают на стандартную карту памяти (ПЗУ в окне 0).
|
||||
Вызывать `RST 10h` (ESTEX/DSS) или `RST 8` (BIOS) при включённой Fast RAM в
|
||||
окне 0 — нельзя.
|
||||
|
||||
4. **Прерывания.**
|
||||
Fast RAM (способ A) подключается в окно 0, перекрывая ПЗУ и системный вектор.
|
||||
Если используются прерывания, программа **обязана установить свой обработчик
|
||||
по адресу `#0038`**. На практике работу с Fast RAM ведут с `DI`, а на время
|
||||
ожидания кадра/`halt` Fast RAM временно выключают и восстанавливают (см. §6).
|
||||
|
||||
5. **Окно 0 занято под DSS.**
|
||||
В нашем C-toolchain'е окно 0 (`#0000..#3FFF`) — это ESTEX/DSS система
|
||||
(см. `release_docs/ru/platform_reference.md`). Подключение Fast RAM в окно 0
|
||||
вытесняет именно её, что усиливает требование п.3.
|
||||
|
||||
6. **Конфликт `#FB` с COVOX.** См. §3, способ A.
|
||||
|
||||
---
|
||||
|
||||
## 6. Канонический паттерн temporary-off / restore
|
||||
|
||||
Из реального резидента (`docs/samples/sprinterIntLib.asm`): перед `ei: halt`
|
||||
(ожидание кадрового прерывания) Fast RAM временно выключается, после —
|
||||
восстанавливается прежнее состояние:
|
||||
|
||||
```asm
|
||||
_intWaitVsyncSys
|
||||
call memCacheOffTemporary ; временно выключаем Fast RAM
|
||||
ei
|
||||
halt
|
||||
jp memCacheRestoryState ; восстанавливаем прежнее состояние подключения
|
||||
```
|
||||
|
||||
Идея паттерна: библиотека хранит флаг «было ли Fast RAM включено», умеет
|
||||
безопасно его снять на время системных операций (прерывания, DSS/BIOS) и вернуть
|
||||
обратно. При интеграции в C-toolchain эту логику следует обернуть так же:
|
||||
сохранять состояние, отключать вокруг любого `RST`/`halt`, восстанавливать.
|
||||
|
||||
---
|
||||
|
||||
## 7. Выводы для нашего C-toolchain (SDCC + target-слой)
|
||||
|
||||
* **Из коробки сейчас не используется.** В `runtime/`, `lib/`, `libc/` обращений
|
||||
к Fast RAM нет (порт `#FB`/`#7B` нигде не задействован под эту задачу).
|
||||
* **Где могло бы пригодиться:** разместить «горячую» функцию или таблицу в
|
||||
Fast RAM для ускорения. Но 64 КБ перекрывают окно 0, конфликтуют с DSS и не
|
||||
переживают системные вызовы — это узкоспециализированный, ручной режим, не
|
||||
кандидат на общий механизм линковки.
|
||||
* **Реалистичный сценарий:** короткий самодостаточный inner-loop без вызовов
|
||||
системы, с `DI`, со своим вектором `#0038`, скопированный в Fast RAM обычным
|
||||
`LD`-циклом (не акселератором), исполняемый из окна 0, с гарантированным
|
||||
восстановлением карты памяти перед любым `RST`.
|
||||
* **Несовместимость с акселератором** означает, что для графики/блочных операций
|
||||
Fast RAM бесполезна — там выигрывает accel по основному/видео-ОЗУ.
|
||||
|
||||
Если будем добавлять поддержку — делать это отдельным opt-in механизмом
|
||||
(по аналогии с banked-режимами), с обязательной обёрткой off/restore вокруг всех
|
||||
точек входа в систему.
|
||||
@@ -0,0 +1,161 @@
|
||||
# FILE*: буферизация — анализ solid-c и варианты (2026-07-06)
|
||||
|
||||
Статус: **вариант B+ РЕАЛИЗОВАН 2026-07-06** (решение пользователя).
|
||||
Единый ленивый буфер BUFSIZ=512 на чтение/запись с автопереключением
|
||||
направления (_F_DIROUT), статическая таблица OPEN_MAX=8 слотов
|
||||
(без malloc для FILE), _fclosall через atexit, ungetc через hold,
|
||||
fprintf/vfprintf через vsprintf+fwrite, fflush(NULL) = все потоки.
|
||||
Внутренности: libc/file/_file.h (+_file_sync/_file_buf/_file_slots/
|
||||
_fclosall). Фактическая цена: filetest 7411→9929 Б _CODE (доля
|
||||
только-читающих потребителей ~+1.3 КБ, включая malloc); программы
|
||||
без FILE* не платят. Верификация: MAME filetest + fdmax + fbench.
|
||||
Ниже — исходный анализ, на основании которого принималось решение.
|
||||
|
||||
## Как сделано в solid-c (SRC/CLIB/STDIO.ASM)
|
||||
|
||||
Структура FILE — 14 байт, статический массив `_iob[8]` (без malloc
|
||||
для самих FILE; псевдопотоки stdout/stderr/stdaux/stdprn лежат ПЕРЕД
|
||||
массивом и адресуются отрицательными индексами — трюк, нам не нужен):
|
||||
|
||||
flags(2), level(2), curp(2), fd(2), buffer(2), hold(1), token(2), dummy(1)
|
||||
|
||||
Механика:
|
||||
|
||||
- **Буфер 512 Б, ленивый malloc** при первом буферизуемом fgetc/fputc
|
||||
(флаг `_F_BUF` = «буфер наш, free при fclose»). Программа без
|
||||
файлового I/O не платит ничего.
|
||||
- **Чтение** (`_fgetc`): `level == 0` → `read(fd, buffer, 512)`,
|
||||
`curp = buffer`; отдача — `*curp++`, `level--`; ставится `_F_IN`.
|
||||
- **Запись** (`_fputc`): `*curp++ = c`, `level++`; при `level == 512` —
|
||||
fflush (один `write` всего буфера); ставится `_F_OUT`.
|
||||
- **Полудуплекс**: fputc при взведённом `_F_IN` — ОШИБКА (не
|
||||
авто-flush); направление сбрасывает только fflush.
|
||||
- **fflush входного потока**: `lseek(fd, -level, SEEK_CUR)` — откат
|
||||
непрочитанного readahead, буфер инвалидируется. Выходного —
|
||||
`write(buffer, level)`.
|
||||
- **fseek/ftell** = fflush + голый lseek/ltell по fd (после flush
|
||||
позиция fd совпадает с логической позицией потока).
|
||||
- **ungetc**: буфер не пуст → `*--curp = c`; пуст/отсутствует → символ
|
||||
в поле `hold`, `curp` указывает на hold.
|
||||
- **fclosall через atexit** — сброс буферов при exit.
|
||||
- Консольные потоки минуют буфер (RST-вызовы напрямую).
|
||||
- **fread/fwrite — ПОБАЙТОВЫЙ цикл** через _fgetc/_fputc: большие блоки
|
||||
платят call+IY-доступ за каждый байт. Это слабое место порта.
|
||||
|
||||
## Варианты для нас
|
||||
|
||||
**A. Полный порт solid-c** (буфер на чтение и запись).
|
||||
Плюсы: ускоряются и писатели через fputc/fprintf. Минусы: полудуплекс
|
||||
(«запись после чтения без fflush — ошибка») — источник тонких багов;
|
||||
обязателен flush в exit (сцепка atexit+file); больше кода во всех
|
||||
модулях; наши блочные fread/fwrite пришлось бы защищать от деградации.
|
||||
|
||||
**B. Буферизовать ТОЛЬКО чтение (рекомендую).**
|
||||
FILE += `buf(2), level(2), curp(2), hold(2)`; буфер 512 Б лениво.
|
||||
|
||||
- fgetc: hold → буфер → refill. fgets остаётся циклом по fgetc
|
||||
(теперь дешёвым).
|
||||
- fread: сначала хвост буфера (memcpy), остаток ≥ 512 — прямой read()
|
||||
в ptr одним syscall (обходя буфер), мелкий остаток — refill.
|
||||
- Запись НЕ буферизуется — как сейчас: fputc = write(1 байт),
|
||||
fputs/fwrite = один write() на блок. Нечего терять при аварии,
|
||||
fflush остаётся no-op по данным, полудуплекса нет.
|
||||
- Согласование позиций: перед write/fseek/ftell на потоке с readahead —
|
||||
`lseek(fd, -(level), SEEK_CUR)` + инвалидация буфера (один общий
|
||||
хелпер `_file_sync`). ftell = lseek(0,CUR) − level (без syscall не
|
||||
выйдет — lseek и так syscall).
|
||||
- ungetc: через hold, работает и до первого заполнения буфера.
|
||||
|
||||
Плюсы: решает главную боль (парсеры), запись остаётся простой и
|
||||
надёжной, никакого flush-on-exit, r+ работает через _file_sync.
|
||||
Минусы: fputc-писатели остаются медленными (редкий паттерн — fputs/
|
||||
fwrite блочные и так быстрые).
|
||||
|
||||
**B+. Единый буфер на чтение И запись с АВТОпереключением направления
|
||||
(предложение 2026-07-06, кандидат в целевой дизайн).**
|
||||
Схема solid-c, но без ловушки: направление переключает сама библиотека.
|
||||
|
||||
- флаг направления в FILE: буфер сейчас «readahead» или «накопитель
|
||||
записи»;
|
||||
- fputc при направлении «чтение»: `_file_sync` (отмотка fd на -level,
|
||||
буфер пуст) → режим записи → накопление; сброс write() при
|
||||
заполнении;
|
||||
- fgetc при направлении «запись»: flush (write(buf, level)) → режим
|
||||
чтения → refill;
|
||||
- fwrite больших блоков: flush + прямой write мимо буфера; мелких —
|
||||
memcpy в буфер. fread симметрично;
|
||||
- fseek/ftell/fclose: flush-или-sync по направлению; ftell = позиция
|
||||
fd − level (чтение) / + level (запись);
|
||||
- **обязателен реестр открытых потоков**: поле next в FILE
|
||||
(регистрация в fopen, снятие в fclose) + _fclosall через atexit —
|
||||
стандарт требует flush всех потоков в exit(); без этого
|
||||
`fputs(...); exit(1);` теряет данные;
|
||||
- цена-семантика: ошибки записи становятся ОТЛОЖЕННЫМИ (вылезают при
|
||||
flush/fclose, не в момент fputc) — проверять результат fclose;
|
||||
- цена-код: ~+350–500 Б против ~+200–300 у B (тянется только
|
||||
использующими FILE*).
|
||||
|
||||
**C. Оставить небуферизованным** («большие файлы читаются целиком в
|
||||
EMM», паттерн mdview). Для приложений-парсеров среднего размера
|
||||
неудобно; отвергается самим существованием П2-пункта.
|
||||
|
||||
**D. Полная стандартная буферизация + setvbuf** — отвергнуто ранее
|
||||
решением file_star_design (минимальный FILE*).
|
||||
|
||||
## Что взять у solid-c при варианте B
|
||||
|
||||
- ленивый malloc 512 Б + флаг «буфер наш»;
|
||||
- откат readahead lseek'ом (механика их fflush-на-вход) — как
|
||||
`_file_sync` перед write/fseek/ftell;
|
||||
- ungetc с hold-байтом;
|
||||
- консольные потоки мимо буфера (у нас уже так).
|
||||
|
||||
Чего НЕ брать: побайтовые fread/fwrite, полудуплекс, статический
|
||||
`_iob[]` с отрицательными индексами, буферизацию записи.
|
||||
|
||||
## Лимит открытых файлов и статическая таблица FILE (2026-07-06)
|
||||
|
||||
Факты: solid-c — OPEN_MAX = 8, статический массив из 8 FILE-структур
|
||||
(+5 псевдопотоков перед ним), fdopen отвергает fd > 8. DSS-доки:
|
||||
FCB строятся «в рабочих областях ДОС», код ошибки 06h = «Too many
|
||||
open files» (наш EMFILE = 6 совпадает).
|
||||
|
||||
**ПОДТВЕРЖДЕНО тестом fdmax (MAME, DSS 1.71.57, 2026-07-06)**:
|
||||
пользователю доступно 8 манипуляторов, fd 2..9 (fd 1 держит шелл DSS
|
||||
под запущенный .exe). КРИТИЧНО: 9-й OPEN не возвращает 06h — он
|
||||
ВЕШАЕТ систему. Поэтому в libc/io добавлен предохранитель _fd_guard
|
||||
(счётчик в open/close, отказ EMFILE на 9-м open без захода в DSS) —
|
||||
таблица fopen и guard вместе закрывают и высокий, и низкий уровень.
|
||||
|
||||
Следствие для B+: вместо malloc-FILE + связного списка-реестра —
|
||||
**статическая таблица из 8 слотов** (свободный слот: flags == 0):
|
||||
- реестр для flush-on-exit бесплатен: _fclosall = цикл по таблице;
|
||||
- fopen без malloc — единственный отказ синхронен с отказом DSS
|
||||
(EMFILE), утечка «fclose без free» невозможна;
|
||||
- цена: ~112–128 Б BSS (в exe не входит), только у программ с fopen;
|
||||
- буферы НЕ статические — остаются ленивыми malloc 512 Б (потолок
|
||||
8×512 = 4 КБ heap в худшем случае);
|
||||
- объявить FOPEN_MAX 8 в stdio.h; если fdmax покажет лимит DSS < 8 —
|
||||
уменьшить таблицу.
|
||||
|
||||
## Семантика инвалидации (вариант B) — контрольный сценарий
|
||||
|
||||
Буфер — только кэш опережающего чтения. Правило: **любая запись и
|
||||
любой fseek обнуляют буфер; запись всегда идёт напрямую в файл после
|
||||
отмотки позиции** (`_file_sync`: `lseek(fd, -level, SEEK_CUR)` +
|
||||
`level = 0`). Буфер при записи НЕ патчится — write-through с правкой
|
||||
окна отвергнут как сложный ради редкого паттерна.
|
||||
|
||||
Сценарий «r+, чтение-запись-чтение» (обсуждено 2026-07-06):
|
||||
read 512 в буфер → 10×fgetc (логическая поз. 10, fd на 512) →
|
||||
первый fputc: sync отматывает fd на 10, буфер пуст, 10×write ложатся
|
||||
на 10..19 → fseek(0) → fgetc перечитывает буфер С ДИСКА и видит
|
||||
записанные байты. Протечка старой копии невозможна — она уничтожена
|
||||
в момент первой записи.
|
||||
|
||||
## Оценка/проверка
|
||||
|
||||
Бенчмарк до/после: цикл fgets по tests/seek/big.txt с замером ptime
|
||||
(тест tests/fbench), плюс filetest-регресс в MAME. Ожидание: чтение
|
||||
~512× меньше syscall'ов; код file-модулей +200–300 Б (тянется только
|
||||
использующими FILE*).
|
||||
+229
-2
@@ -1,6 +1,233 @@
|
||||
# IM2 Interrupt Handlers — Design Document
|
||||
|
||||
**Status:** РЕАЛИЗАЦИЯ ОТЛОЖЕНА (до пост-релизной версии). Обязательная фича для v2.
|
||||
**Status: Phase 1 (2026-07-06), Phase 2a CTC (2026-07-07), Phase 2b CBL
|
||||
(2026-07-07, редизайн на callback без кольца — см. ниже) РЕАЛИЗОВАНЫ**
|
||||
(libc/irq: irq_install/irq_remove + трамплин, irq_ctc_install/remove;
|
||||
libc/cbl: cbl_open/close/push_otir/push_accel + fill-callback; тесты
|
||||
tests/irqtest, tests/cbltest, tests/cblwav, tests/cblstream).
|
||||
Callback-редизайн **verified в MAME 2026-07-07**: все три CBL-теста
|
||||
(cbltest — матрица 64 комбинации; cblwav — banked-стрим, голос слышен;
|
||||
cblstream — собственное кольцо приложения) работают.
|
||||
|
||||
## Результаты verification (2026-07-06, по docs/samples и исходникам MAME)
|
||||
|
||||
Первая версия зависала на первом же прерывании после irq_install.
|
||||
Причины и проверенные факты:
|
||||
|
||||
- **DSS работает в IM 1** (обработчик на 0x0038). I=0x3F — наследие
|
||||
Spectrum ROM, НЕ признак IM 2-таблицы. Доказательства:
|
||||
`docs/samples/sprinterIntLib.asm` (intRestoreDefaultInterrupt: `ld i,a`
|
||||
+ `im 1` безусловно, «обязательно перед функциями дос и биос») и
|
||||
`docs/samples/SIO_CTC_KEY.asm` (выход: `LD I,A` + `IM 1`). Чтение
|
||||
«таблицы DSS» по [I<<8+0xFF] давало мусор (0x00BF) и было причиной
|
||||
зависания. Фикс: чейн из трамплина ВСЕГДА на 0x0038 (jp с
|
||||
interrupted-PC на стеке = имитация RST 38); irq_remove всегда
|
||||
восстанавливает IM 1 (I — только регистр).
|
||||
- **CBL-фильтр по биту 7 порта 0xFE убран из трамплина**: в MAME при
|
||||
выключенном CBL бит 7 всегда = 1 (sprinter.cpp kbd_fe_r:
|
||||
`data |= 0xE0`), и каждое кадровое прерывание ложно классифицировалось
|
||||
как CBL — user-handler не вызывался бы никогда. Признак «#fe.bit7=1»
|
||||
(sprinterIntLib.asm) имеет смысл только при активном CBL — вернуть в
|
||||
Phase 2 вместе с поддержкой CBL.
|
||||
- **Порт 0x19 = SIO-A RR0 (Z84C015), бит 0 = «Rx Character Available»** —
|
||||
семантика клавиатурного пробника верна (подтверждено
|
||||
sprinterIntLib.asm: `in a,(COM_A); bit 0,a; Z → кадровое`).
|
||||
- **Вектора встроенной периферии Z84C015**: SIO — 0x10..0x1E, CTC — 0x06
|
||||
(базовые вектора задаются записью в WR2 SIO-B / CTC ch0). Штатно их
|
||||
прерывания выключены (сэмплы включают/выключают их сами); наша
|
||||
заливка 257×H перехватывает любой вектор на трамплин, а чейн на
|
||||
0x0038 безопасен для любого источника.
|
||||
- Внешний вектор действительно 0xFF (MAME sprinter.cpp:
|
||||
`set_irq_acknowledge_callback` → 0xff).
|
||||
|
||||
Отличия реализации от плана ниже:
|
||||
|
||||
- **отдельный `--memory im2` НЕ понадобился**: таблица — статический
|
||||
буфер 513 Б в BSS с выравниванием в рантайме; т.к. внешний вектор —
|
||||
только 0xFF, значимы лишь байты [0xFF]/[0x100], и 3-байтовый
|
||||
`jp _irq_tramp` лежит ВНУТРИ таблицы по смещению H (H = старший
|
||||
байт её адреса, < 0xC0 — не пересекается). Никаких linker-областей
|
||||
и правок crt0;
|
||||
- работает в tiny/big (код и данные в W2); в small/huge irq_install
|
||||
возвращает EINVAL (проверка адресов трамплина/буфера);
|
||||
- **чейн к DSS — ВСЕГДА и всегда на 0x0038** (и клавиатура, и кадр):
|
||||
не нужно знать, что DSS делает в своём ISR — SYSTIME/клавиатура/мышь
|
||||
живут. User-handler зовётся только на кадровых (фильтр: бит 0
|
||||
порта 0x19 → мимо); финальный jp — SMC-операнд;
|
||||
- W3-порт в трамплине НЕ сохраняется: gfx держит DI на время свопов,
|
||||
а user-handler'у banking запрещён; DSS свои окна сохраняет сам;
|
||||
- irq_remove вешается на atexit (выход без снятия = IM 2/I указывают в
|
||||
память умершего процесса = крах шелла); восстановление — всегда IM 1.
|
||||
|
||||
## Phase 2a — CTC-таймер (РЕАЛИЗОВАН 2026-07-07)
|
||||
|
||||
`irq_ctc_install(handler, div2, div3)` / `irq_ctc_remove()` — вектор
|
||||
0x06, ОТДЕЛЬНЫЙ от кадрового 0xFF (решает проблему «кадр и клавиатура
|
||||
неразличимы»). Механика (по docs/samples/sprinterIntLib.asm и
|
||||
SIO_CTC_KEY.asm):
|
||||
|
||||
- CTC Z84C015: канал 2 тактируется видеотактом **875 кГц (1 тик =
|
||||
1 знакоместо)** и работает делителем; канал 3 считает от канала 2 и
|
||||
прерывает. f = 875000/(div2*div3), div 0 = 256;
|
||||
- пресет IRQ_CTC_VSYNC_DIV2/3 = 112×160 (2 пикс. линии × 160 = 320
|
||||
линий) — точное начало кадра ~48.8 Гц;
|
||||
- порты: CH0=0x10, CH2=0x12, CH3=0x13; control-слова 0x57 (ch2:
|
||||
counter, int off) / 0xD7 (ch3: counter, int on); базовый вектор
|
||||
блока пишется в CH0 (0 → вектор ch3 = 0x06); стоп = 0x03 (reset);
|
||||
- **RETI обязателен** в CTC-трамплине: daisy chain Z84C015 снимает IUS
|
||||
только по опкоду RETI — с RET следующее прерывание не придёт;
|
||||
- CTC-путь НЕ чейнится к DSS (личное прерывание); кадровые/клавиатурные
|
||||
0xFF идут своим путём параллельно;
|
||||
- общая IM2-таблица под счётчиком ссылок (_irq_table.c): кадровый и
|
||||
CTC-хендлеры ставятся/снимаются независимо, последний возвращает
|
||||
I/IM 1; atexit-уборка глушит CTC ОБЯЗАТЕЛЬНО (иначе после выхода
|
||||
прерывания кГц-частоты душат шелл).
|
||||
|
||||
## Phase 2b — CBL/COVOX audio (РЕАЛИЗОВАН 2026-07-07, редизайн без кольца)
|
||||
|
||||
`libc/include/cbl.h`: `cbl_open(freq_code, fmt, pump_mode,
|
||||
underrun_mode, fill)` / `cbl_close()` / `cbl_push_otir(src,n)` /
|
||||
`cbl_push_accel(src,n)` / `cbl_requests()` / `cbl_underruns()`. По
|
||||
`docs/samples/Пример для CBL.asm`, разделу «Звук через COVOX-Blaster»
|
||||
в `docs/converted/Forum.txt`, официальной документации "5.3
|
||||
COVOX-Blaster" (see below) и `docs/converted/accel_r.txt`:
|
||||
|
||||
**Архитектурный редизайн (2026-07-07)**: у CBL УЖЕ ЕСТЬ собственный
|
||||
аппаратный буфер 256 Б, разбитый на ДВЕ половины по 128 Б (double
|
||||
buffering целиком на стороне железа — см. официальную доку: "Блок ОЗУ
|
||||
256 байт условно разбит на две банки по 128 байт, и бит 7 порта #FE
|
||||
указывает какая из банок ОЗУ выводится в ЦАП... используется программой
|
||||
вывода для определения, нужно ли подгружать следующие 128 байт").
|
||||
Держать ЕЩЁ ОДНО кольцо в libc поверх этого — лишний второй буфер и
|
||||
лишняя копия. Библиотека теперь НЕ хранит кольца: при cbl_open()
|
||||
регистрируется callback `fill(n)`, который ISR вызывает НАПРЯМУЮ, а
|
||||
callback сам пропихивает n байт ИЗ ДАННЫХ ПРИЛОЖЕНИЯ (статический
|
||||
массив, банковая EMM-страница, файл — что угодно) прямо в CBL через
|
||||
`cbl_push_otir()`/`cbl_push_accel()` — без промежуточной копии в libc.
|
||||
Пример из офиц. документации делает ровно это: `OUTI` читает прямо из
|
||||
HL, указывающего в банковую страницу с WAV-данными, без всякого
|
||||
стейджинга.
|
||||
|
||||
`fill(n)` вызывается ИЗ ISR — ОБЯЗАН быть быстрым: никаких
|
||||
ESTEX/BIOS/gfx-вызовов (тот же констрейнт, что у `irq_install()`-
|
||||
хендлера). В частности, диск (read() — ESTEX) читать из fill() НЕЛЬЗЯ
|
||||
— см. tests/cblstream ниже, где под это заведено собственное кольцо
|
||||
уровня приложения. Возвращает ненулевое при успехе, 0 — недолив.
|
||||
|
||||
- **Control-порт 0x004E — 16-битный** (`ld bc,#0x004E` / `out (c),a`,
|
||||
не 8-битный `out (n),a`). Биты: 7 = CBL on, 6 = stereo, 5 = 16-bit,
|
||||
4 = interrupt enable, 3..0 = код частоты (8=7.8125 кГц…F=109.375 кГц,
|
||||
0/1 — legacy-режим без сэмплирования). `cbl_open` шлёт
|
||||
`0x90|fmt|freq` (on + int + формат + частота) под DI, `cbl_close`
|
||||
шлёт `0`.
|
||||
- **Формат — CBL_FMT_MONO8/MONO16/STEREO8/STEREO16** (2026-07-07):
|
||||
биты 5(16-бит)/6(stereo) напрямую соответствуют константам, можно
|
||||
OR'ить с freq. Блок запроса — 128 Б для 8-бит, 256 Б для 16-бит
|
||||
(НЕ зависит от моно/стерео — см. Forum.txt: «для каждых 128 байт
|
||||
(256 в режиме 16 бит)»); тишина — 0x80 (8-бит unsigned) или 0x0000
|
||||
(16-бит signed). 8-бит сэмплы центр 0x80, 16-бит центр 0x0000,
|
||||
stereo — чередование L/R. Runtime-размер блока — `_cbl_block`
|
||||
(128 или 256), заполняется в `cbl_open` по fmt.
|
||||
- **Два насоса, выбор — CBL_PUMP_OTIR/CBL_PUMP_ACCEL** (2026-07-07):
|
||||
- **OTIR** (`_cbl_pump_otir`) — блок в порт 0x4F через `otir`; B=младший
|
||||
байт `_cbl_block` (128 остаётся 128, 256 идёт как 0 — Z80 OTIR:
|
||||
B=0 значит 256 итераций). Проверено в MAME (звук слышен, 0
|
||||
underrun).
|
||||
- **ACCEL** (`_cbl_pump_accel`) — запись через акселератор в
|
||||
спец-страницу EMM 0xFD, замапленную в окно W3 на 0xC000 (Forum.txt:
|
||||
«запись данных в COVOX-Blaster... 128/256 байт с адреса 0xC000»).
|
||||
Последовательность (по accel_r.txt + рабочему прецеденту
|
||||
libc/gfx/_gfx_hfill256.c): `LD D,D` (режим размера блока) →
|
||||
immediate `LD A,n` (SMC-патч, 0 значит 256 — тот же трюк, что и в
|
||||
OTIR) → `LD L,L` (режим "копирование блока") → `LD A,(HL)` /
|
||||
`LD (DE),A` (блочное чтение источника → блочная запись в
|
||||
0xC000@стр.0xFD) → `LD B,B` (выкл). Акселератор НЕ продвигает
|
||||
HL/DE сам — advance после пересылки делается вручную (`add hl,
|
||||
(block)`). W3 сохраняется/восстанавливается вокруг переключения
|
||||
(как `bank_read`/`bank_write`); доп. DI/EI не нужны — весь насос
|
||||
целиком уже внутри ISR (прерывания замаскированы до EI/RETI
|
||||
трамплина). **Verified в MAME 2026-07-07** — tests/cbltest, все
|
||||
32 accel-комбинации матрицы прошли без ошибок/underrun.
|
||||
- Общий каприз с CBL-примером из docs/samples: там для установки
|
||||
размера блока акселератора используется `LD C,128` СРАЗУ ЗА
|
||||
`LD D,D` — это противоречит и accel_r.txt («далее следует команда
|
||||
типа LD A,dat»), и нашему же подтверждённому на gfx констрейнту
|
||||
(CLAUDE.md: «block-size ОБЯЗАН быть immediate операндом LD A,n»).
|
||||
Мы взяли ВЕРИФИЦИРОВАННЫЙ вариант (LD A,n), а не пример — вероятно,
|
||||
у автора размер уже был установлен раньше (заметка в accel_r.txt:
|
||||
«если размер блока был установлен ранее, его можно не
|
||||
устанавливать»), и `LD C,128` в примере готовит BC для последующего
|
||||
`ADD HL,BC`, а не для акселератора.
|
||||
- **Data-порт 0x4F** (для OTIR-насоса), блок 128/256 байт.
|
||||
- **Признак запроса блока — бит 7 порта 0xFE — валиден ТОЛЬКО когда
|
||||
CBL реально активен.** При выключенном CBL MAME (`kbd_fe_r`)
|
||||
подтягивает этот бит к 1 всегда (`data |= 0xE0`) — поэтому трамплин
|
||||
проверяет бит 0xFE.7 не напрямую, а через индирекцию
|
||||
`_irq_cbl_hook`: пока `cbl_open` не установил хук, кадровые
|
||||
прерывания даже не читают порт 0xFE (см. Phase 1 — по этой же
|
||||
причине первая версия фильтра была убрана). Официальная документация
|
||||
описывает тот же бит как "старший бит счётчика" адреса аппаратного
|
||||
буфера — по нему же программа определяет, какую половину доливать.
|
||||
- **Насос (`_cbl_pump_otir`/`_cbl_pump_accel`) теперь тривиален**:
|
||||
`_cbl_reqs++; ok = _cbl_fill ? _cbl_fill(_cbl_block) : 0; if (!ok) {
|
||||
_cbl_undr++; ...}`. Обычные (не `__naked`) Си-функции — можно, т.к.
|
||||
трамплин уже сохраняет ВЕСЬ контекст (оба регистровых набора + IX/IY)
|
||||
вокруг вызова хука, что бы функция ни наделала с регистрами.
|
||||
- **Поведение при недоливе — CBL_UNDERRUN_APP/SILENCE** (4-й параметр
|
||||
cbl_open, 2026-07-07): по умолчанию (`APP`, 0) недолив — не забота
|
||||
библиотеки, буфер тишины НЕ аллоцируется вовсе, в CBL доигрывает то,
|
||||
что уже лежало в его аппаратном буфере. `SILENCE` (1) — насос сам
|
||||
пропихивает тишину (`_cbl_silence`, malloc'ится В cbl_open() ТОЛЬКО
|
||||
в этом режиме, размером `_cbl_block`, залит 0x80/0x0000 по формату) —
|
||||
тот же приём, что раньше был жёстко вшит в насос, теперь опционален.
|
||||
`cbl_underruns()` считает недоливы в ОБОИХ режимах — это только
|
||||
диагностика.
|
||||
- **CBL-путь НЕ чейнится к DSS** — личное прерывание CBL, полный сейв
|
||||
контекста (основной набор + теневой AF/BC/DE/HL + IX/IY, т.к. `fill()`
|
||||
может клобберить что угодно) → `EI`/`RETI` напрямую, без 0x0038.
|
||||
- `cbl_open` держит те же анти-повторные гарантии, что и irq/ctc:
|
||||
занятый хук → EBUSY, `atexit(cbl_close)` регистрируется один раз,
|
||||
`_irq_table_ref()`/`_irq_table_unref()` для общей IM2-таблицы.
|
||||
- **tests/cbltest**: полная матрица (2 насоса × 8 форматов × 4 частоты
|
||||
= 64 комбинации) пилообразного тона, ~1 с каждая; `fill_tone()`
|
||||
всегда возвращает 1 (period-64 тон никогда не "кончается") —
|
||||
CBL_UNDERRUN_APP без буфера тишины достаточно. Main не поллит
|
||||
ничего — просто ждёт halt()'ом. OTIR+16-бит (16/64) пропускаются
|
||||
заранее (cbl_open вернул бы EINVAL).
|
||||
- **tests/cblwav**: потоковая речь (78 КБ) с ДИСКЕТЫ через banked EMM
|
||||
(диск слишком медленный для realtime — клип предзагружается в RAM
|
||||
ДО cbl_open). `fill_speech()` делает `bank_read()` из уже загруженной
|
||||
страницы в стейджинг и `cbl_push_otir()` — физические номера страниц
|
||||
кэшированы в массиве ЗАРАНЕЕ (на этапе загрузки, в main-контексте):
|
||||
`mem_get_page()` — BIOS-вызов, сам управляет EI/DI, и звать его ИЗ
|
||||
fill() (то есть из ISR) нельзя — его `ei` при возврате может
|
||||
преждевременно снять маску прерываний, пока мы ещё внутри ISR.
|
||||
- **tests/cblstream**: без banked-предзагрузки — чтение с диска
|
||||
ОДНОВРЕМЕННО с воспроизведением; рассчитан на быстрый носитель (HDD).
|
||||
Единственный из трёх тестов, где приложению НУЖНО собственное кольцо:
|
||||
`read()` — ESTEX-вызов, а `fill()` зовётся из ISR, где ESTEX/BIOS под
|
||||
запретом — поэтому диск читается ТОЛЬКО в main (в кольцо уровня
|
||||
приложения), а `fill_stream()` лишь копирует уже готовые байты и
|
||||
пропихивает `cbl_push_otir()`. НЕ входит в общую сборку (`make`/
|
||||
`make floppy`) — только `cd tests/cblstream && make run`, свой образ
|
||||
диска.
|
||||
|
||||
ISA-вектора, цепочки нескольких хендлеров на одном векторе — не
|
||||
реализовано (см. «Phase 2 (когда понадобится)» ниже).
|
||||
|
||||
**Щелчок перед первым звуком за сессию (2026-07-07, A/B/C-стенд)**:
|
||||
на РЕАЛЬНОМ файле (tests/cblwav, потоковая речь) перед началом
|
||||
воспроизведения был слышен щелчок/призвук. Диагностика через
|
||||
tests/cblwav (banked-стрим с пилой, затем с чистой тишиной вместо
|
||||
речи, в одном запуске) показала: щелчок слышен ТОЛЬКО на первом
|
||||
запуске программы после старта MAME и НЕ зависит от содержимого потока
|
||||
(тон / тишина / речь — одинаково). Проверенное на macOS (`afplay`)
|
||||
воспроизведение исходного speech.pcm — чистое, артефактов в самом
|
||||
файле нет. Разбивка `cbl_open()` на две записи в порт 0x004E (сначала
|
||||
код частоты, потом enable, с паузой) не повлияла — отменена. Вывод:
|
||||
это одноразовый прогрев звуковой подсистемы MAME при первой активации
|
||||
канала CBL за сессию эмулятора, не баг протокола/драйвера; на реальном
|
||||
железе, скорее всего, отсутствует (см. docs/TODO.md).
|
||||
|
||||
Этот документ собирает всё, что мы знаем о прерываниях Sprinter и план реализации user-задаваемых ISR через Z80 IM 2 mode. Когда возьмёмся за реализацию — читать этот файл, чтобы не повторять research.
|
||||
|
||||
@@ -168,7 +395,7 @@ User's ISR НЕ должен:
|
||||
|
||||
## Phase 2 (когда понадобится)
|
||||
|
||||
- CBL/COVOX prerequisite handler (для audio playback)
|
||||
- ~~CBL/COVOX prerequisite handler~~ — реализован, см. «Phase 2b» выше
|
||||
- ISA interrupt handler (для ZX-Bus карт)
|
||||
- Multiple user handler chain (e.g. tick + sound)
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Заголовки libc: контракт затенения SDCC (2026-07-06)
|
||||
|
||||
`libc/include` стоит в -I ПЕРЕД заголовками SDCC, поэтому наш файл с
|
||||
именем стандартного заголовка «затеняет» SDCC-шный. Два разрешённых
|
||||
паттерна:
|
||||
|
||||
## 1. Цепочка `#include_next` — только ДОБАВЛЯЕМ
|
||||
|
||||
Наш заголовок первым делом делает `#include_next <имя>` (берёт
|
||||
SDCC-версию) и дальше только добавляет Sprinter-расширения. Ничего
|
||||
из стандартной части не переобъявлять — malloc/strlen/… должны
|
||||
приходить из SDCC, иначе разъедутся прототипы с z80.lib (уже кусало:
|
||||
полный shadow stdlib.h терял malloc/free).
|
||||
|
||||
| Заголовок | Что добавляем |
|
||||
|---|---|
|
||||
| `stdlib.h` | min/max (функции, int16_t — как `int min()` в Solid-C) |
|
||||
| `string.h` | strlwr/strupr (CP866-регистры) |
|
||||
|
||||
## 2. Полная замена — обязаны продублировать контракт SDCC
|
||||
|
||||
Наш заголовок полностью замещает SDCC-шный. Он ОБЯЗАН объявить всё,
|
||||
что программы берут из z80.lib, с точными SDCC-сигнатурами:
|
||||
|
||||
| Заголовок | Обязан объявлять (из z80.lib) | Наше |
|
||||
|---|---|---|
|
||||
| `stdio.h` | printf, sprintf, vprintf, vsprintf | FILE* API (буферизованный B+), puts/putchar/getchar (наши, ESTEX), scanf-семейство, dec*/hex*, gets |
|
||||
| `time.h` | struct tm, time_t, time, mktime, gmtime, localtime, asctime, ctime — **раскладка struct tm и __TIME_UNSIGNED=1 должны совпадать с SDCC ABI** (см. шапку time.h) | datetime_t, getdatetime/setdatetime, DOW_* |
|
||||
|
||||
При апгрейде SDCC сверять сигнатуры этих двух заголовков с
|
||||
`third_party/sdcc/share/sdcc/include/`.
|
||||
|
||||
## 3. Свои заголовки (SDCC-аналога нет — затенения нет)
|
||||
|
||||
conio.h, dir.h, dos.h, errno.h, fcntl.h, mouse.h, palette.h,
|
||||
sprinter*.h, unistd.h, bios/*.
|
||||
|
||||
Графика вынесена из libc в отдельную библиотеку libbgi/: её публичные
|
||||
заголовки gfx.h (mode-agnostic BGI_GFX) и graphics.h (BGI API) живут в
|
||||
libbgi/include/ и пробрасываются через -I libbgi/include (sprinter-cc
|
||||
добавляет его автоматически). Внутренний заголовок графики —
|
||||
libbgi/_bgi.h (слияние старых libc/gfx/_gfx.h и libc/bgi/_bgi.h).
|
||||
|
||||
## Правила
|
||||
|
||||
- новый стандартный заголовок — сначала пробовать паттерн 1
|
||||
(include_next); паттерн 2 — только если надо переопределить
|
||||
реализацию (как puts/putchar на ESTEX);
|
||||
- в заголовках паттерна 2 — комментарий, какие декларации обслуживают
|
||||
z80.lib;
|
||||
- internal-заголовки libc (`_conio.h`, `_file.h`, …) живут
|
||||
РЯДОМ с исходниками в libc/<area>/, не в libc/include. Internal
|
||||
графики — в libbgi/_bgi.h (корень libbgi/, подключается из common/ и
|
||||
bgi256/bgi16/ как `#include "../_bgi.h"`).
|
||||
@@ -0,0 +1,371 @@
|
||||
# libc — справочник API (2026-07-06)
|
||||
|
||||
Сводка по заголовкам: сигнатура + одна строка + особенности ABI.
|
||||
Детали дизайна: docs/libc-headers.md (контракт затенения SDCC),
|
||||
docs/file-buffering-design.md (FILE*), docs/solid_c_compatibility.md.
|
||||
|
||||
Общие соглашения:
|
||||
- ошибки: возврат -1/NULL/EOF + `errno` (код DSS as-is, см. errno.h);
|
||||
- SDCC `__sdcccall(1)`: 1-й аргумент HL (8-битный — A), 2-й — DE,
|
||||
остальные на стеке; **int/указатель возвращается в DE**;
|
||||
- строки для BIOS-вызовов (rst 8) должны лежать в #4000–#BFFF;
|
||||
- стек при любых ESTEX/BIOS-вызовах — в W2 (обеспечено crt0).
|
||||
|
||||
## <stdio.h> — полная замена SDCC (контракт: printf-семейство из z80.lib)
|
||||
|
||||
Из SDCC z80.lib: `printf sprintf vprintf vsprintf`.
|
||||
|
||||
Консоль (ESTEX, без атрибутов — быстрый путь; цветной вывод — conio):
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `int putchar(int c)` | символ через PUTCHAR $5B; '\n'→CR LF |
|
||||
| `int getchar(void)` | блокирующий WAITKEY $30, ASCII |
|
||||
| `char puts(const char *s)` | строка + '\n' (посимвольно через putchar) |
|
||||
| `char *gets(char *buf)` | строка с консоли, без контроля длины |
|
||||
| `void dec8/dec16/dec32(v)` | десятичная печать без ведущих нулей |
|
||||
| `void hex8/hex16/hex32(v)` | hex-печать фиксированной ширины |
|
||||
|
||||
FILE* (буферизованный, вариант B+ — единый ленивый буфер BUFSIZ=512
|
||||
на чтение/запись с автопереключением; таблица `OPEN_MAX=8` слотов;
|
||||
exit() сбрасывает всё через atexit; **ошибки записи отложенные —
|
||||
проверять fclose**):
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `FILE *fopen(path, mode)` | "r/w/a" + '+', 'b/t' игнорируются |
|
||||
| `FILE *fdopen(fd, mode)` | завернуть готовый fd (закрывать fclose!) |
|
||||
| `FILE *freopen(path, mode, fp)` | переоткрыть тот же FILE* |
|
||||
| `int fclose(FILE*)` / `void fclosall(void)` | сброс+закрытие / все потоки |
|
||||
| `int fflush(FILE*)` | сброс записи / откат readahead; NULL = все |
|
||||
| `int fgetc/fputc(...)` | горячий путь на asm; getc/putc — макро-алиасы |
|
||||
| `char *fgets(buf, n, fp)` | до '\n' (сохраняется); блочный LDI-сканер |
|
||||
| `int fputs(s, fp)` | без '\n'; через fwrite |
|
||||
| `size_t fread/fwrite(p, sz, n, fp)` | блоки ≥ 512 идут мимо буфера |
|
||||
| `int ungetc(c, fp)` | 1 байт putback (и на stdin) |
|
||||
| `int fseek(fp, off, whence)` / `long ftell(fp)` | ftell без побочных эффектов |
|
||||
| `void rewind(fp)` | fseek(0) + сброс EOF/ERROR |
|
||||
| `int fgetpos/fsetpos(fp, &pos)` | fpos_t = long |
|
||||
| `int feof/ferror(fp)`, `void clearerr(fp)` | флаги потока |
|
||||
| `int fprintf/vfprintf(fp, fmt, ...)` | vsprintf в статический буфер 256 |
|
||||
| `int scanf/fscanf/sscanf(...)` | %d %u %x %o %c %s, `l`, ширина, %*, %% |
|
||||
| `int rename(old, new)` | ESTEX RENAME $10 |
|
||||
|
||||
`stdin/stdout/stderr` — консольные псевдопотоки (fd 0/-1/-2), не
|
||||
буферизуются; freopen на них меняет только FILE*-операции (printf
|
||||
идёт в ESTEX напрямую).
|
||||
|
||||
## <stdlib.h> — include_next + добавки
|
||||
|
||||
Из SDCC: malloc/free/calloc/realloc (heap в W2), atoi/atol/strtol/
|
||||
strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv, exit-типы.
|
||||
Наше: `int16_t min(a,b)`, `int16_t max(a,b)` (функции, как в Solid-C).
|
||||
|
||||
## <string.h> — include_next + добавки
|
||||
|
||||
Из SDCC: mem*/str* полностью. Наше: `char *strlwr/strupr(char *s)` —
|
||||
in-place регистр, латиница + кириллица CP866.
|
||||
|
||||
## <time.h> — полная замена (struct tm в SDCC-ABI, __TIME_UNSIGNED=1)
|
||||
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `void getdatetime(datetime_t*)` | RTC как есть (ESTEX SYSTIME $21) |
|
||||
| `int setdatetime(const datetime_t*)` | установка RTC ($22) |
|
||||
| `time_t time(time_t*)` | Unix-эпоха из RTC |
|
||||
| `mktime/gmtime/localtime/asctime/ctime` | POSIX поверх RTC (без TZ) |
|
||||
|
||||
`datetime_t`: day/month/year(полный)/hour/minute/second/dow (1=Вс).
|
||||
|
||||
## <unistd.h>, <fcntl.h> — fd-уровень (манипуляторы DSS)
|
||||
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `int open(path, flags)` | O_RDONLY/WRONLY/RDWR + O_CREAT/TRUNC/EXCL/APPEND (ESTEX $11/$0A/$0B) |
|
||||
| `int creat(path, mode)` | open(W|CREAT|TRUNC); mode игнорируется |
|
||||
| `int read/write(fd, buf, n)` | ESTEX $13/$14. **Квирк WRITE: DE-возврат ненадёжен, успех = CF=0&A=0** (см. memory/estex_write_de_quirk) |
|
||||
| `int close(fd)` | ESTEX $12 |
|
||||
| `long lseek(fd, off, whence)` | 32-битная позиция (MOVE_FP $15) |
|
||||
| `int unlink(path)` | удалить (DELETE $0E) |
|
||||
| `int isatty(fd)` | fd <= 0 (файловые манипуляторы DSS с 1) |
|
||||
| `int mkdir/rmdir/chdir(path)` | ESTEX $1B/$1C/$1D |
|
||||
| `char *getcwd(buf, size)` | буфер 256 байт, size игнорируется |
|
||||
| `void sleep(seconds)` | калиброванный busy-wait (см. ниже) |
|
||||
| `void delayms(uint16_t ms)` | то же, гранулярность — миллисекунды (см. ниже) |
|
||||
|
||||
**Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS —
|
||||
libc отказывает сама (EMFILE, предохранитель _fd_guard).
|
||||
|
||||
**`sleep()` (2026-07-07, редизайн)**: старая версия считала halt-
|
||||
пробуждения (50 = 1 c), предполагая, что КАЖДОЕ прерывание — кадровый
|
||||
тик; с CBL/клавиатурой на векторе 0xFF это уже не так (CBL прерывает
|
||||
намного чаще кадра — sleep() возвращался бы раньше срока). Теперь:
|
||||
лениво, один раз калибруется кратковременным `irq_install()` против
|
||||
РЕАЛЬНОГО кадрового тика (трамплин зовёт хук только на настоящих
|
||||
кадровых прерываниях — клавиатура/CBL уходят в свои ветки раньше),
|
||||
считая, сколько итераций тесного цикла умещается в один КАДР (не в
|
||||
секунду — калибровка на секунду переполняла `uint16_t`, был баг:
|
||||
`sleep(5)` отрабатывал быстрее секунды, найден пользователем на
|
||||
реальном прогоне и исправлен). `sleep(seconds)` — вложенный цикл:
|
||||
внешний по секундам, внутренний ровно 50 раз калиброванный busy-wait —
|
||||
без единого прерывания и БЕЗ умножения/32-битной арифметики (по духу
|
||||
`docs/samples/delayms.asm`, который тоже калибрует на 1 мс, а не на
|
||||
1 с). Погрешность ~5-10% (калибровочный и рабочий циклы не тактово-
|
||||
идентичны). Fallback на старое "50 halt" поведение, если фрейм-хук уже
|
||||
занят другим `irq_install()`-клиентом (EBUSY).
|
||||
**Cтоит дороже по размеру** (~+580 Б) — тянет весь модуль `irq`
|
||||
(install/remove/трамплин/IM2-таблицу), даже если программа больше
|
||||
ничего из irq не использует.
|
||||
|
||||
**`delayms(ms)`** — тот же движок (`libc/time/_sleep_calib.c`), общий с
|
||||
`sleep()`: калибровка одна на двоих (первый вызов ЛЮБОЙ из функций
|
||||
калибрует, вторая просто использует готовое). Производная величина
|
||||
"итераций на 1 мс" — одно 16-битное деление (`__divuint`, НЕ
|
||||
`__mullong`) на константу 20, вычисляется один раз при калибровке;
|
||||
`delayms(ms)` — простой цикл `ms` раз, без умножения вообще. Fallback
|
||||
при EBUSY — грубый (одно кадровое `halt`), точной альтернативы для
|
||||
миллисекундной гранулярности без калибровки нет.
|
||||
|
||||
**Квирк общий для sleep()/delayms()**: калибровка сама стоит ~20-40 мс
|
||||
(синхронизация на границу кадра + сам замер) — ПЕРВЫЙ вызов ЛЮБОЙ из
|
||||
двух функций в программе превысит запрошенное время на эту величину;
|
||||
для `delayms()` с маленьким `ms` это заметно (`delayms(5)` на первом
|
||||
вызове может растянуться на ~25-45 мс). Все последующие вызовы точны.
|
||||
Оставлено как есть по решению пользователя — не стали усложнять API
|
||||
отдельным calibrate()-примитивом.
|
||||
|
||||
## <errno.h>
|
||||
|
||||
`errno` (int), коды = коды DSS (EOK..EUNKERR) + POSIX-имена
|
||||
(ENOENT/EBADF/EMFILE/…) + алиасы Solid-C (EZERO/EINVFNC/ENOFILE/…).
|
||||
`const char *strerror(int)`, `void perror(const char*)`.
|
||||
|
||||
## <dos.h> — DOS-слой Solid-C
|
||||
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `void getdate/gettime(&d)` | struct date/time (Turbo-C; ti_hund=0) |
|
||||
| `int setdate/settime(&d)` | RMW полного datetime |
|
||||
| `uint8_t getdisk(void)` | текущий диск, 0=A (ESTEX $02) |
|
||||
| `int setdisk(uint8_t)` | смена диска; возврат = число дисков ($01) |
|
||||
| `int absread/abswrite(disk, sect, cnt, buf)` | секторы ЛОГИЧЕСКОГО диска (BIOS $55/$56); буфер в #4000–#BFFF; abswrite минует ФС! |
|
||||
|
||||
## <dir.h>
|
||||
|
||||
`int ffirst(pattern, ffblk_t*, attrib)` / `int fnext(ffblk_t*)` —
|
||||
поиск по шаблону (ESTEX $19/$1A). Квирк: "."/".." находятся только
|
||||
итерацией "*.*" (memory/estex_ffirst_dotdot). FA_*-атрибуты.
|
||||
|
||||
## <sys/stat.h>
|
||||
|
||||
`int stat(path, struct stat*)` / `int fstat(fd, ...)` — st_mode
|
||||
(S_ISREG/S_ISDIR), st_size, st_mtime (Unix-эпоха).
|
||||
|
||||
## <conio.h> — текстовый экран с атрибутами (Turbo-C стиль)
|
||||
|
||||
Клавиатура: `kbhit getch getche getkey` (+KEY_* коды позиций),
|
||||
`char *cgets(buf)`.
|
||||
Вывод с атрибутом: `putch cputs cprintf` (~10× медленнее stdio-пути;
|
||||
'\n' НЕ транслируется — писать "\r\n").
|
||||
Атрибуты: `textcolor textbackground textattr`, `set/get_text_attr`,
|
||||
COLOR_*-enum, `COLOR(fg,bg)`, COLOR_BLINK; `set/get_putch_raw_mode`.
|
||||
Экран: `clrscr clrscr_attr gotoxy home() wherex wherey wherexy scroll
|
||||
wrchar rdchar`; режимы `gettextmode/settextmode` (0x02=40×32,
|
||||
0x03=80×32).
|
||||
Порты/IRQ: `inp outp enable() disable()`.
|
||||
Текстовая палитра: `text_pal_load/set_color/get/get_color/reset`
|
||||
(план 0..3 → страница BIOS 4..7).
|
||||
|
||||
## <bios/text.h> — быстрый BIOS-вывод (rst 8, place-based)
|
||||
|
||||
`bios_set_place/get_place`, `bios_write[attr][_until|_stop]`,
|
||||
`bios_fillchar/fillattr/fillcharattr`, `bios_clearwin[_ch]`,
|
||||
`bios_scrollwin`. Строка s — в #4000–#BFFF; place продвигается.
|
||||
|
||||
## <gfx.h> — графика, mode-agnostic API (BGI_GFX); живёт в libbgi/include
|
||||
|
||||
Рисование (putpixel/line/bar/circle/…) вынесено в BGI — см. `<graphics.h>`
|
||||
и driver-библиотеки lib/bgi256.lib / lib/bgi16.lib (выбор режима линковкой:
|
||||
`sprinter-cc --gfx 256` / `--gfx 16`). В gfx.h остались только функции
|
||||
БЕЗ BGI-аналога (mode-agnostic, живут в libbgi/common/, .rel в обеих
|
||||
driver-библиотеках):
|
||||
|
||||
Setup: `gfx_init(mode,page)→prev`, `gfx_done(prev)`.
|
||||
Страницы/банк: `gfx_set/get_visible_page`, `gfx_set/get_draw_page`
|
||||
(double buffering), `gfx_set/get_bank` (0x50..0x5F, 0x58 = FF-
|
||||
прозрачность).
|
||||
|
||||
`gfx_wait_vsync()` (2026-07-07, редизайн): ждёт переход бита 5 порта
|
||||
0xFE из 1 в 0 — реальное аппаратное состояние луча (Y>256 → начало
|
||||
кадра, см. MAME sprinter.cpp kbd_fe_r), а не прерывание — поэтому не
|
||||
путается с клавиатурой/CBL/CTC, деляющими вектор 0xFF. Бит доступен
|
||||
только пока включён `cbl_mode()` (bit7 порта 0x004E) — если приложение
|
||||
уже играет через `cbl_open()`, бит достаётся бесплатно; иначе
|
||||
`gfx_wait_vsync()` лениво занимает bit7 "немым" кодом частоты через
|
||||
`_cbl_port_ref()`/`_cbl_port_unref()` (см. `<cbl.h>`, `_cbl_port.c`) —
|
||||
разделяемое владение портом 0x004E, безопасное при любом порядке
|
||||
использования с реальным CBL-звуком. Фолбэк на одно кадровое
|
||||
прерывание (`halt`), если бит не ведёт себя как ожидается за разумное
|
||||
число попыток.
|
||||
Шрифт: `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
|
||||
font[row*256+char]) — грузится лениво при первом использовании BGI-текста.
|
||||
Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3).
|
||||
|
||||
Константы режимов: `GFX_MODE_320x256x256` (0x81), `GFX_MODE_640x256x16`
|
||||
(0x82); размеры `GFX_WIDTH/HEIGHT` (320/256), `GFX_WIDTH_16/HEIGHT_16`
|
||||
(640/256). Рисование через `<graphics.h>` (BGI).
|
||||
|
||||
## <graphics.h> — Turbo-C BGI (функц. совместимость), Фаза 1, режим 256
|
||||
|
||||
Слой поверх `<gfx.h>` со «текущим» цветом/позицией. Режим задаётся
|
||||
driver-либой на линковке: `sprinter-cc --gfx 256` (→ 320×256×256; 16 —
|
||||
позже). API mode-agnostic: код не меняется при смене режима.
|
||||
Setup: `initgraph()` (без аргументов — режим фиксирован либой; грузит
|
||||
EGA-палитру 0..15, цвет=WHITE, фон=BLACK, CP=(0,0)),
|
||||
`closegraph()`, `graphresult()`, `cleardevice()`.
|
||||
Границы/цвет: `getmaxx/getmaxy` (319/255), `getmaxcolor` (255),
|
||||
`setcolor/getcolor`, `setbkcolor/getbkcolor`. Константы BLACK..WHITE.
|
||||
Точки: `putpixel(x,y,c)`, `getpixel(x,y)`.
|
||||
Позиция/линии: `moveto/moverel/getx/gety`, `lineto/linerel` (двигают
|
||||
CP), `line(x1,y1,x2,y2)` (не двигает).
|
||||
Фигуры: `rectangle` (контур), `bar` (заливка стилем), `circle`.
|
||||
Дуги (Ф2a): `arc`, `ellipse(x,y,st,end,xr,yr)`, `drawpoly(n,pts)`.
|
||||
Текст 8×8: `outtextxy(x,y,s)`, `outtext(s)` (двигает CP).
|
||||
Заливки (Ф2b): `setfillstyle(pattern,color)`/`getfillsettings` (10
|
||||
паттернов Borland: SOLID/EMPTY/LINE/…/HATCH/XHATCH/…), `bar3d`,
|
||||
`fillpoly(n,pts)`, `fillellipse(x,y,xr,yr)`.
|
||||
Заливка областей (Ф2c): `floodfill(x,y,border)` (медленно, но верно),
|
||||
`pieslice(x,y,st,end,r)`, `sector(x,y,st,end,xr,yr)`.
|
||||
Спрайты (Ф2d): `imagesize/getimage/putimage` (COPY/XOR/OR/AND/NOT_PUT;
|
||||
формат буфера: uint16 w,h + w*h байт).
|
||||
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
|
||||
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
|
||||
drawpoly.
|
||||
Стиль текста (Ф2d): `settextstyle(font,dir,size)`/`gettextsettings`,
|
||||
`textwidth`/`textheight` — масштаб 1..10, HORIZ/VERT, прозрачный фон
|
||||
(только DEFAULT_FONT 8×8).
|
||||
Реализация: libbgi/common/*.c (mode-agnostic math + BGI public API +
|
||||
BGI_GFX) + libbgi/bgi256/*.c (256-цветные leaf'ы) → lib/bgi256.lib
|
||||
(Фаза 2 добавит libbgi/bgi16/*.c → lib/bgi16.lib; common .rel одни и
|
||||
те же в обоих архивах). Leaf'ы — реальные реализации (БЕЗ обёрток:
|
||||
putpixel/getpixel полностью inline, _bgi_plot_raw/_bgi_hspan_raw/...
|
||||
поглощают акселераторный asm). Пакетные примитивы — одна W3-скобка
|
||||
на примитив; тригонометрия/эллипсы целочисленные (Q7/isqrt, БЕЗ 32-бит).
|
||||
Cross-lib: libbgi всегда линкуется с libc; _cbl_port_ref/unref
|
||||
объявлены extern в libbgi/_bgi.h (сверять с libc/cbl/_cbl.h).
|
||||
Ф2d (осталось): setviewport/клиппинг, settextjustify, setaspectratio —
|
||||
см. docs/TODO.md.
|
||||
|
||||
## <irq.h> — user-ISR кадрового прерывания (IM 2)
|
||||
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `int irq_install(isr_t h)` | h зовётся ~50 Гц на кадровых прерываниях; DSS-обработчик чейнится всегда (клавиатура/SYSTIME/мышь живы). 0 / -1+errno (EBUSY повтор, EINVAL — код не в W2: только tiny/big) |
|
||||
| `void irq_remove(void)` | вернуть таблицу DSS; идемпотентно; висит на atexit |
|
||||
| `int irq_ctc_install(h, div2, div3)` | периодический таймер CTC (вектор 0x06): f = 875000/(div2×div3), div 0=256; пресет кадра IRQ_CTC_VSYNC_DIV2/3 (112×160, ~48.8 Гц); независим от кадрового; трамплин завершает RETI |
|
||||
| `void irq_ctc_remove(void)` | глушит CTC (обязательно; atexit подстрахует) |
|
||||
| `IRQ_DISABLE()/IRQ_ENABLE()` | di/ei — скобки для чтения shared-переменных из main |
|
||||
|
||||
Handler'у нельзя: ESTEX/BIOS-вызовы, gfx_*/своп окон, акселератор,
|
||||
banked-функции; только volatile-глобалы и быстрая работа (<1 мс).
|
||||
|
||||
## <cbl.h> — потоковый звук CBL/COVOX (вектор 0xFF, свой ISR)
|
||||
|
||||
Без собственного кольца (2026-07-07): у CBL уже есть аппаратный буфер
|
||||
256 Б (2×128, двойная буферизация на стороне железа — см. официальную
|
||||
доку "5.3 COVOX-Blaster"); библиотека просто зовёт `fill()` приложения
|
||||
ИЗ ISR, а оно само пропихивает данные (откуда угодно) через
|
||||
`cbl_push_otir/accel` — без промежуточной копии.
|
||||
|
||||
| Сигнатура | Описание |
|
||||
|---|---|
|
||||
| `int cbl_open(freq_code, fmt, pump_mode, underrun_mode, fill)` | включить CBL (`0x90\|fmt\|freq`), зарегистрировать callback; 0 / -1+errno (EBUSY повтор, EINVAL — freq/fmt/pump_mode/underrun_mode плохие или OTIR+16-бит, ENOMEM — буфер тишины) |
|
||||
| `void cbl_close(void)` | выключить CBL, снять хук, освободить буфер тишины (если был); висит на atexit |
|
||||
| `void cbl_push_otir(const void *src, uint16_t n)` | пропихнуть n байт через `otir` в порт 0x4F; звать ИЗ fill() |
|
||||
| `void cbl_push_accel(const void *src, uint16_t n)` | то же акселератором (страница EMM 0xFD@0xC000); единственный путь для 16-бит |
|
||||
| `uint16_t cbl_requests(void)` | счётчик запросов блока ISR (~fs/(сэмплов в блоке) в секунду) |
|
||||
| `uint16_t cbl_underruns(void)` | счётчик недоливов (`fill` вернул 0 или не задан) |
|
||||
| `CBL_FREQ_7K8 .. CBL_FREQ_109K` | коды частоты (биты 3..0 control-порта) |
|
||||
| `CBL_FMT_MONO8/MONO16/STEREO8/STEREO16` | формат (биты 5/6 control-порта); блок 128 Б (8-бит) или 256 Б (16-бит), не зависит от моно/стерео; тишина 0x80 (8-бит) / 0x0000 (16-бит, знаковый) |
|
||||
| `CBL_PUMP_OTIR / CBL_PUMP_ACCEL` | способ выдачи — см. ниже |
|
||||
| `CBL_UNDERRUN_APP / CBL_UNDERRUN_SILENCE` | поведение при недоливе — см. ниже |
|
||||
|
||||
`typedef int (*cbl_fill_fn)(uint16_t n);` — callback, зовётся ИЗ ISR за
|
||||
очередным блоком (n = `_cbl_block`, 128/256). **Обязан быть быстрым**
|
||||
— никаких ESTEX/BIOS/gfx-вызовов (тот же констрейнт, что у
|
||||
`irq_install()`-хендлера); вернуть ненулевое, если реально пропихнул n
|
||||
байт. Диск (read()) читать из fill() НЕЛЬЗЯ — см. tests/cblstream,
|
||||
где под это заведено кольцо уровня приложения.
|
||||
|
||||
Два насоса (3-й параметр `cbl_open`):
|
||||
- **OTIR** (`_cbl_pump_otir`) — `cbl_push_otir()` в порт 0x4F; базовый.
|
||||
**НЕ умеет 16-бит** — `cbl_open(..., MONO16/STEREO16, CBL_PUMP_OTIR,
|
||||
...)` вернёт EINVAL: по исходнику MAME (sprinter.cpp) порт данных
|
||||
ВСЕГДА кладёт байт как есть в один слот, не собирая пару байт в
|
||||
16-бит значение и не сверяясь с 16-бит флагом вообще.
|
||||
- **ACCEL** (`_cbl_pump_accel`) — `cbl_push_accel()` через акселератор
|
||||
в спец-страницу EMM 0xFD, замапленную в окно W3 на 0xC000 (см.
|
||||
docs/converted/accel_r.txt, Forum.txt); размер блока патчится SMC
|
||||
(`LD D,D` + immediate `LD A,n` + `LD L,L` — тот же паттерн, что и в
|
||||
libc/gfx/_gfx_hfill256.c); единственный путь для 16-бит.
|
||||
**Verified в MAME 2026-07-07** (tests/cbltest, вся accel-половина
|
||||
матрицы прошла без ошибок/underrun).
|
||||
|
||||
Поведение при недоливе (4-й параметр `cbl_open`):
|
||||
- **CBL_UNDERRUN_APP** (по умолчанию, 0) — не забота библиотеки, буфер
|
||||
тишины НЕ аллоцируется, в CBL доигрывает то, что уже лежало в его
|
||||
аппаратном буфере.
|
||||
- **CBL_UNDERRUN_SILENCE** (1) — насос сам пропихивает тишину; буфер
|
||||
(128/256 Б по формату) аллоцируется malloc'ом ВНУТРИ `cbl_open()`
|
||||
только в этом режиме.
|
||||
`cbl_underruns()` считает недоливы в обоих режимах — диагностика,
|
||||
поведение не меняет.
|
||||
|
||||
Приватное прерывание, к DSS не чейнится; бит 7 порта 0xFE (запрос
|
||||
блока) читается только пока `cbl_open` не закрыт (при выключенном CBL
|
||||
бит всегда 1 — MAME-квирк).
|
||||
|
||||
**Разделяемое владение портом 0x004E** (2026-07-07): бит 5 порта 0xFE
|
||||
(позиция луча — см. `<gfx.h>` `gfx_wait_vsync()`) доступен только пока
|
||||
включён bit7 порта 0x004E, независимо от того, играет ли реальный
|
||||
звук. `_cbl_port_ref()`/`_cbl_port_unref()` (internal, `_cbl_port.c`)
|
||||
дают gfx-модулю занять bit7 "немым" кодом частоты (не заводящим таймер
|
||||
CBL — без звука/прерываний), не мешая реальной `cbl_open()`-сессии,
|
||||
если она уже идёт (и наоборот — `cbl_close()` возвращает "немой" режим
|
||||
вместо полного выключения порта, если gfx его ещё держит).
|
||||
|
||||
## <palette.h> — низкий уровень (BIOS $A4/$A6)
|
||||
|
||||
`pal_load pal_get pal_set_color pal_get_color` (страница 0..7,
|
||||
записи B,G,R,0), `pal_reset(type)` / `pal_reset_at(type,page,graph)`;
|
||||
PAL_GRAPH/PAL_SINCLAIR/PAL_CGA.
|
||||
|
||||
## <mouse.h> — драйвер RST 30h
|
||||
|
||||
`mouse_init show hide refresh read(mouse_state_t*) goto bounds_x/y
|
||||
text_cursor load_cursor/get_cursor(mouse_cursor_t*) set_sensitivity
|
||||
get_sensitivity_x/y video_mode_changed`. Sensitivity = делитель
|
||||
(меньше = быстрее). Solid-C алиасы ms_* включены.
|
||||
|
||||
## <sprinter.h> — платформа
|
||||
|
||||
Константы портов (PORT_PAGE_W0..W3, PORT_RGADR, PORT_RGMOD), номера
|
||||
всех ESTEX-функций (ESTEX_*), BIOS EMM ($C0..$C7); `__sfr`-доступ и
|
||||
inline `sprinter_page_w0..w3(page)`; ENV: `getenv putenv sysenv`.
|
||||
|
||||
## <sprinter_mem.h> — EMM-страницы и банковый I/O
|
||||
|
||||
`mem_alloc_pages(n)→blk_id, mem_free_block, mem_get_page(blk,idx),
|
||||
mem_info(&total,&free)` (реализации _bios/_estex; макро-выбор
|
||||
MEM_MANAGE_MODE_*). HOME-резидентный доступ к чужим страницам:
|
||||
`bank_load_byte/store_byte/read/write` (своп W3 внутри; *_w1 —
|
||||
вариант через окно W1).
|
||||
|
||||
## <sprinter_exit.h>
|
||||
|
||||
`atexit` (LIFO, 8 слотов), `exit` (хендлеры+сброс FILE), `_exit`.
|
||||
|
||||
## <sprinter_compat.h> / <sprinter_solid.h>
|
||||
|
||||
Типы (BYTE/BOOL/WORD/uint/FD/f_point), TRUE/FALSE/OK/ERROR,
|
||||
`setmem movmem` (порядок аргументов!), `strerr seek tell ltell
|
||||
remove _ffirst _setargv abort()`, isascii. `<sprinter_solid.h>` —
|
||||
зонтичный: один include для портирования Solid-C программ.
|
||||
@@ -0,0 +1,122 @@
|
||||
# libc — план работ (на рассмотрение, 2026-07-06)
|
||||
|
||||
Анализ после сплита четвёрки лидеров (time/stat/file/conio, итог −19.6 КБ
|
||||
суммарно по _CODE приложений). Ниже — что ещё стоит сделать, по приоритетам.
|
||||
|
||||
---
|
||||
|
||||
## П1. Досплит остальной libc — СДЕЛАНО 2026-07-06
|
||||
|
||||
Всё из таблицы ниже посплитано (кроме dec_print — осознанно оставлен).
|
||||
gfx: 40 модулей (внутренний заголовок `libc/gfx/_gfx.h`, скретчи в
|
||||
data-модулях `_gfx_state/_gfx_w3_state/_gfx_acc256/_gfx_g16_state/
|
||||
_gfx_font_state`, helpers `_gfx_hfill256/_gfx_hfill16/_gfx_rmw16/
|
||||
_gfx_text16`); video/palette → 7 модулей + `_palette.h`;
|
||||
conio/text_palette → 5 модулей. Эффект (_CODE, Б):
|
||||
gfx_demo 3769→3227, gfx_d16 3869→3327, gfx_text 6986→2568,
|
||||
gfx_mous 7287→5542. Не-gfx тесты не изменились.
|
||||
|
||||
Правило было: 1 публичная функция = 1 модуль, state/helpers — в отдельные
|
||||
internal-модули, комментарии на русском, без `= 0`.
|
||||
|
||||
| Файл | Ф-ий | _CODE | Замечания |
|
||||
|---|---|---|---|
|
||||
| gfx/gfx_256.c + gfx_16.c | 13+13 | 1277+1159 Б | самый жирный кусок; резать по примитивам (putpixel/line/hline/vline/rect/fill/clear/text). Учесть SMC-паттерны акселератора и кэш Port_Y — state в data-модули |
|
||||
| gfx/gfx_raw_16 / raw_256 / raw_common / core / palette / font / text_* | ~40 | ~2.7 КБ | вместе с предыдущим — весь gfx |
|
||||
| mouse/mouse.c | 16 | 308 Б | state mb_*/mc_* → data-модули |
|
||||
| bios/text.c | 14 | 264 Б | все `__naked`, сплит чистый |
|
||||
| io/open.c | 3+3 | 216 Б | open/creat/close; asm-хелперы `_estex_*_raw` у единственных потребителей |
|
||||
| errno/errno.c | 2 | 680 Б | strerror + таблица строк неразделимы (один модуль); perror — отдельно, зовёт strerror |
|
||||
| video/palette.c | 6 | 348 Б | |
|
||||
| conio/text_palette.c | 5 | 140 Б | |
|
||||
| io/read.c | 2 | 60 Б | read + write — обязательно врозь (write-only приложения) |
|
||||
| io/fsdir.c | 4 | 80 Б | mkdir/rmdir/chdir/getcwd |
|
||||
| env/env.c | 3 | 78 Б | getenv/putenv/sysenv + общий env_buf → data-модуль |
|
||||
| mem/mem_bios, mem_estex, bank_io_w1/w3 | 3–4 каждый | ~430 Б | |
|
||||
| sys/atexit.c | 3 | 106 Б | atexit/exit/_exit; общий стек хендлеров → data-модуль (exit тянется всегда из crt0, выигрыш небольшой но правильный) |
|
||||
| io/dir.c | 2 | 53 Б | ffirst/fnext |
|
||||
| stdlib/minmax.c | 2 | 27 Б | min/max врозь |
|
||||
| stdio/hex_print.c | 3 | 38 Б | сплит чистый (call/jp по именам) — см. docs/libc-split-asm-cases.md |
|
||||
| stdio/dec_print.c | 3 | 176 Б | НЕ резать (общее тело); открытое решение: вариант «3 независимых цикла» — п. отложен |
|
||||
|
||||
Ожидаемый эффект: графические приложения −1–2 КБ, mouse/BIOS-text — сотни байт.
|
||||
|
||||
## П2. FILE* — отложенные баги и недостающее
|
||||
|
||||
Из шапки бывшего file.c («PROVISIONAL», stdio-review issues 3/4/5):
|
||||
|
||||
- [x] fwrite: короткая запись ставит _F_ERROR (issue 3) — сделано 2026-07-06
|
||||
- [x] fgets(n=1): возвращает пустую строку по стандарту (issue 4) — сделано 2026-07-06
|
||||
- [x] mode_to_flags: проверено — парсер сканирует весь хвост режима, «rb+» работает (issue 5, уже был исправлен)
|
||||
- [x] **fprintf/vfprintf** — сделаны 2026-07-06 (vsprintf в статический 256-байтовый буфер + fwrite)
|
||||
- [x] ungetc — 1-байтный putback через поле hold; работает и на stdin — сделано 2026-07-06
|
||||
- [x] Буферизация FILE v2 — **реализован вариант B+** (2026-07-06): единый ленивый буфер BUFSIZ=512 на чтение и запись с автопереключением направления, статическая таблица OPEN_MAX=8 слотов, _fclosall через atexit, fflush(NULL) = все потоки. Дизайн: docs/file-buffering-design.md. Цена: filetest (использует всё) 7411→9929 Б _CODE; не-FILE программы не платят ничего. **Ждёт MAME-прогона: filetest, fdmax (лимит DSS), fbench (замер скорости)**
|
||||
- [ ] fdopen/freopen/fclosall/fgetpos/fsetpos — по мере надобности (Solid-C категория C)
|
||||
|
||||
## П3. Solid-C совместимость — ЗАКРЫТ 2026-07-06
|
||||
|
||||
Всё сделано (детали в docs/solid_c_compatibility.md): getdisk/setdisk,
|
||||
getdate/gettime/setdate/settime + <dos.h>, ltell/_setargv, errno-алиасы,
|
||||
<sprinter_solid.h>, div из SDCC (проверено), absread/abswrite (BIOS
|
||||
$55/$56 — номера найдены в solid-c DOS.ASM), **scanf/fscanf/sscanf**
|
||||
(своё C-ядро _scanf_core, 22 хост-теста), fdopen/freopen/fclosall/
|
||||
fgetpos/fsetpos (хвост П2). bdos/brk/ioctl — отказ решением.
|
||||
Тест tests/solidt ждёт MAME-прогона.
|
||||
|
||||
## П4. Недостающие POSIX-мелочи — ЗАКРЫТ 2026-07-06
|
||||
|
||||
- [x] rename() — ESTEX RENAME $10 (HL=старое, DE=новое), libc/io/rename.c
|
||||
- [x] isatty(fd) — fd < 2 (манипуляторы DSS с 2; консольные псевдо-fd 0/-1/-2)
|
||||
|
||||
## П5. Заголовки и гигиена сборки — ЗАКРЫТ 2026-07-06
|
||||
|
||||
- [x] Контракт затенения — **docs/libc-headers.md**: include_next
|
||||
(stdlib.h + новый string.h со strlwr/strupr) vs полная замена
|
||||
(stdio.h, time.h — обязаны дублировать сигнатуры z80.lib);
|
||||
из sprinter_compat.h убраны макросы min/max (конфликтовали с
|
||||
функциями из stdlib.h; в Solid-C это тоже функции)
|
||||
- [x] libc/Makefile: stale .rel чистятся сверкой списка перед упаковкой;
|
||||
штамп .modules триггерит перелинковку при смене состава (и сносит
|
||||
архив — mtime на exFAT грубый)
|
||||
- [x] Все extra-тесты в top-level TESTS (43 программы: + hello2, simple,
|
||||
banktest (переименован из banked.exe), conio2, dec_test, gets,
|
||||
stest2, winrest, bios_text, text_palette, gfx_dbuf) и mdview2 в APPS
|
||||
- [x] Размерный регресс: toolchain/size_check.py + docs/size_baseline.tsv;
|
||||
`make size-check` (выход 1 при росте) / `make size-baseline`
|
||||
|
||||
## П6. Верификация после сплита — ЗАКРЫТ 2026-07-06 (MAME; железо — отдельно)
|
||||
|
||||
- [ ] `make floppy` + прогон в MAME ключевых тестов (conio, filetest, ptime,
|
||||
stattest, mouse, gfx_demo) — линковка прошла, но поведение надо
|
||||
подтвердить на эмуляторе
|
||||
- [ ] Потом на железе (mdview2 и так ждёт проверки на железе — совместить)
|
||||
|
||||
## П7. Документация — ЗАКРЫТ 2026-07-06
|
||||
|
||||
- [x] **docs/libc-reference.md** — справочник API по всем заголовкам
|
||||
- [x] docs/TODO.md переписан: открытое наверху, закрытые этапы (5-10)
|
||||
в «Истории»; протухшие пункты (FILE rewrite «для v2») сняты
|
||||
- [x] **CLAUDE.md** создан: сборка/проверка, правила libc (1 ф-я =
|
||||
1 модуль, `_`-модули, русские комментарии, без `= 0`,
|
||||
asm-правила), ABI-шпаргалка, квирки, структура
|
||||
|
||||
## П8. Смежное (не libc, из TODO.md — чтобы не потерялось)
|
||||
|
||||
- auto-banking Phase 1 (toolchain/auto_bank.py) — когда проект перерастёт ~30 КБ
|
||||
- IM2 ISR v2 (docs/im2_isr_design.md) — отложено решением 2026-06-01
|
||||
- font-quad для 640×256 (per-cell палитра)
|
||||
- factoring parse_argv из crt0/crt0_banked в общий argv.s
|
||||
- check_banks.py: разбивка code/const/bss per bank (косметика)
|
||||
|
||||
---
|
||||
|
||||
## Предлагаемый порядок
|
||||
|
||||
1. **П1-лайт**: io/env/errno/atexit/minmax/mem/dir/fsdir (мелкие, час работы,
|
||||
выигрыш для всех CLI-приложений) + mouse + bios/text.
|
||||
2. **П6**: MAME-смоук — подтвердить, что сплит ничего не сломал в рантайме,
|
||||
до того как менять что-то ещё.
|
||||
3. **П1-gfx**: разбор графики (самый большой кусок, отдельный заход).
|
||||
4. **П2**: баги FILE* (3 шт.) + fprintf + ungetc.
|
||||
5. **П3/П4**: solid-c остатки + rename/isatty.
|
||||
6. **П5/П7**: гигиена сборки и документация — фоном, по кусочку.
|
||||
@@ -0,0 +1,52 @@
|
||||
# libc split: asm-связки между функциями
|
||||
|
||||
Журнал случаев, найденных при разбиении libc на «1 публичная функция = 1 модуль»
|
||||
(2026-07-05). Сюда записывается каждый обнаруженный переход `jr _func` или
|
||||
`jr/jp/call` на метку **внутри другой функции** — такие связки нельзя разрывать
|
||||
механически, разбираем каждую отдельно.
|
||||
|
||||
## Правила (справка)
|
||||
|
||||
| Паттерн | Через границу модулей |
|
||||
|---|---|
|
||||
| `call/jp _func` (публичная C-функция) | работает — метка глобальная (`::`) |
|
||||
| `call/jp _label` на метку в чужой функции | работает, только если метка объявлена `_label::` |
|
||||
| `jr` / `djnz` в другой модуль | **запрещено** — ±128 байт, разложение модулей не гарантировано |
|
||||
| fall-through (без перехода, в надежде на соседство) | **не работает никогда** |
|
||||
|
||||
## Случаи
|
||||
|
||||
### 1. stdio/dec_print.c — dec8/dec16/dec32: разделяемое тело (НЕ разрывать)
|
||||
|
||||
Статус: **оставлены в одном файле, решение отдельно.**
|
||||
|
||||
- `dec8` → `jp __dec_entry3` — прыжок в середину тела `dec32`;
|
||||
- `dec16` → `jp __dec_entry5` — то же;
|
||||
- метки уже глобальные (`__dec_entry3::`, `__dec_entry5::`) — линковаться будет,
|
||||
но выигрыша от сплита нет: dec8 всё равно притянет модуль с телом dec32;
|
||||
- внутри хвоста: `_dec_get_d16` **fall-through** в `_dec_emit_or_skip`,
|
||||
`_dec_get_d32` → `jr _dec_emit_or_skip`, общий флаг `_dec_flag`.
|
||||
|
||||
Это осознанный дизайн из solid-c: тройка делит per-digit код. Варианты на потом:
|
||||
(а) оставить как есть (176 Б тянутся целиком — терпимо);
|
||||
(б) развести на 3 независимых цикла — dec8 станет ~40 Б, но исходник длиннее
|
||||
и суммарно в exe, использующем dec8+dec32, станет хуже. Решение отложено.
|
||||
|
||||
### 2. stdio/hex_print.c — чист
|
||||
|
||||
`hex16` → `call _hex8` / `jp _hex8` (tail), `hex32` → `_hex16`: переходы по
|
||||
именам публичных функций. Разъезжается на hex8.c/hex16.c/hex32.c без правок.
|
||||
`_hex8_digit` — self-call внутри hex8, не мешает.
|
||||
|
||||
### 3. conio/conio.c — jp _clrscr_attr (чист)
|
||||
|
||||
`clrscr` → `jp _clrscr_attr` — tail-call публичной функции, работает через
|
||||
модули как есть.
|
||||
|
||||
### 4. mem/mem_bios.c, mem/mem_estex.c — jp __errno_set (чист)
|
||||
|
||||
Tail-call публичного internal-хелпера `_errno_set` — кросс-модульный уже сейчас.
|
||||
|
||||
---
|
||||
Все прочие `jr`-переходы в libc (проверены все `jr`, включая условные формы,
|
||||
2026-07-05) ведут на метки внутри своей же функции — сплиту не мешают.
|
||||
@@ -0,0 +1,242 @@
|
||||
# Автотестирование в MAME
|
||||
|
||||
Единый справочник: как запускать программы Sprinter в эмуляторе MAME
|
||||
**без участия человека**, вводить команды, снимать скриншоты, завершать
|
||||
сессию и анализировать результат. Если нужно что-то про автотесты в
|
||||
MAME — смотреть сюда.
|
||||
|
||||
Весь механизм собран в одном инструменте: **`toolchain/mame_interactive.py`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. TL;DR
|
||||
|
||||
```bash
|
||||
# собрать .exe (пример)
|
||||
make -C tests/bgitest
|
||||
|
||||
# запустить в MAME, снять экран, выйти по таймауту
|
||||
python3 toolchain/mame_interactive.py tests/bgitest/bgitest.exe \
|
||||
--snap 12,14 --timeout 16
|
||||
```
|
||||
|
||||
Инструмент сам:
|
||||
1. проверит, что нет висящих копий MAME;
|
||||
2. положит `.exe` на дискету A: (`mame/v306/IMG/mc.img`);
|
||||
3. запустит MAME с драйвером `sprinter`;
|
||||
4. дождётся приглашения `C:\>` и **напечатает `a:\bgitest.exe` + Enter**
|
||||
(эмулируя нажатия клавиш);
|
||||
5. снимет скриншоты в указанные секунды эмулированного времени;
|
||||
6. завершит сессию по таймауту;
|
||||
7. выведет пути к PNG-скриншотам.
|
||||
|
||||
Скриншоты лежат в `mame/v306/snap_auto/sprinter/` (`0000.png`, `0001.png`,
|
||||
…). Их читает Claude визуально (инструментом Read) — текст с экрана
|
||||
программно НЕ распознаётся.
|
||||
|
||||
---
|
||||
|
||||
## 2. Инструмент: `mame_interactive.py`
|
||||
|
||||
```
|
||||
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
|
||||
[--launch-at T] [--step "T:TEXT" ...] [--snap t1,t2,...] [--timeout N]
|
||||
```
|
||||
|
||||
| Аргумент | Назначение |
|
||||
|----------|-----------|
|
||||
| `exe` | `.exe` кладётся на A: и **авто-запускается** (печатается `a:\<exe>`+Enter в момент `--launch-at`). Без `exe` работаем на голой командной строке. |
|
||||
| `--data f ...` | доп. файлы на дискету A: (данные для теста). |
|
||||
| `--launch-at T` | секунда, когда печатается запуск `exe` (по умолчанию **8**). |
|
||||
| `--step "T:TEXT"` | в момент `T` сек напечатать `TEXT`. Можно много раз — диалог с уже запущенной программой. В `TEXT`: `\n`=Enter, `\t`=Tab; заглавные и символы через Shift — автоматически. |
|
||||
| `--snap t1,t2,...` | секунды эмуляции для скриншотов. По умолчанию: `launch_at+4` и `+6` (для `exe`), либо сразу после последнего ввода. |
|
||||
| `--timeout N` | секунд эмуляции до принудительного выхода. По умолчанию — чуть позже последнего скриншота. |
|
||||
|
||||
**Важно:** все времена — это **секунды эмулированного времени от старта
|
||||
машины** (не от нажатий, их «нет»). Загрузка DSS до `C:\>` занимает
|
||||
~7 секунд, поэтому `--launch-at 8` и скриншоты с ~12 с.
|
||||
|
||||
### Типовые рецепты
|
||||
|
||||
```bash
|
||||
# 1. Запустить тест и снять результат (самый частый случай)
|
||||
python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \
|
||||
--snap 12,14 --timeout 16
|
||||
|
||||
# 2. Набрать команду на голой командной строке (без exe)
|
||||
python3 toolchain/mame_interactive.py --step "8:dir\n" \
|
||||
--snap 10,11 --timeout 12
|
||||
|
||||
# 3. Запустить программу и ответить на её ввод (например, выбор пункта меню)
|
||||
python3 toolchain/mame_interactive.py tests/menu/menu.exe \
|
||||
--step "13:2\n" --snap 15 --timeout 17
|
||||
|
||||
# 4. Тест с файлом-данными на дискете
|
||||
python3 toolchain/mame_interactive.py examples/mdview2/mdview2.exe \
|
||||
--data doc.md --step "13:mdview2 doc.md\n" --snap 16 --timeout 18
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Как это работает внутри
|
||||
|
||||
### 3.1 Запуск MAME
|
||||
|
||||
Нативный `mame.arm` (arm64) в `mame/v306/`, драйвер `sprinter`, BIOS
|
||||
v3.06. Полный набор аргументов зашит в `COMMON_ARGS` внутри скрипта:
|
||||
две дискеты (A: наш `mc.img`, B: DSS 1.71u), два HDD-образа (система +
|
||||
медиа), CD-ROM, ZX-Bus карта NeoGS, `-video opengl -window`,
|
||||
`-snapshot_directory`, `-autoboot_script <сгенерированный .lua>`.
|
||||
|
||||
### 3.2 Ввод с клавиатуры — ключевой момент
|
||||
|
||||
У Sprinter в MAME **две** клавиатуры:
|
||||
|
||||
- `IO_LINE0..7` — легаси ZX-Spectrum-матрица (порт `0xFE`). DSS её для
|
||||
командной строки **НЕ читает**.
|
||||
- `root:kbd:ms_naturl` — **настоящая AT/PS-2 клавиатура**, подключённая
|
||||
последовательно к SIO Z84C015 (`sprinter.cpp:2037`). Именно её DSS
|
||||
читает как поток scancode'ов.
|
||||
|
||||
Поэтому **не работают** (проверено многократно): `natkeyboard:post`,
|
||||
`-autoboot_command`, а также `set_value` по полям `:IO_LINE*`. Всё это
|
||||
бьёт в ZX-матрицу, которую DSS игнорирует.
|
||||
|
||||
**Работает** — прямое управление полями AT-клавиатуры из Lua:
|
||||
```lua
|
||||
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(1) -- нажать
|
||||
... подождать ~0.06с ...
|
||||
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(0) -- отпустить
|
||||
```
|
||||
`at_keyboard` сам сгенерит make/break scancode'ы → SIO → DSS.
|
||||
|
||||
Скрипт хранит раскладку `char → (порт, битовая маска)` (словарь `PHYS` +
|
||||
`SHIFTED` для Shift-символов) и разворачивает строку в список
|
||||
timed-событий `(время, порт, маска, значение)`. Backslash `\` в
|
||||
AT-клавиатуре есть (поле `P2.1`/0x4) — путь `a:\name.exe` вводится
|
||||
полностью.
|
||||
|
||||
### 3.3 Тайминг (Lua)
|
||||
|
||||
Генерируется `_interactive_gen.lua`, который через
|
||||
`emu.register_periodic` на каждом кадре сверяет **эмулированное время** и
|
||||
проигрывает события ввода, снимает скриншоты и завершает сессию.
|
||||
|
||||
Время берётся как `t.seconds + t.attoseconds/1e18`, потому что
|
||||
`attotime.seconds` — **целое** (дробную часть отбрасывает); если считать
|
||||
по нему, все события схлопнутся в 1-секундную сетку.
|
||||
|
||||
Старт отсчёта — `emu.add_machine_reset_notifier` (НЕ `emu.register_start`
|
||||
— он deprecated).
|
||||
|
||||
### 3.4 Скриншоты
|
||||
|
||||
`manager.machine.video:snapshot()` пишет PNG в каталог из
|
||||
`-snapshot_directory` (`mame/v306/snap_auto/sprinter/`). Скрипт перед
|
||||
запуском чистит этот каталог, потом печатает пути к готовым файлам.
|
||||
Claude открывает их инструментом Read (визуальный анализ).
|
||||
|
||||
### 3.5 Завершение сессии
|
||||
|
||||
Два рубежа, чтобы MAME гарантированно не «завис»:
|
||||
- в Lua: при `elapsed >= timeout` → `manager.machine:exit()` (чистый
|
||||
выход);
|
||||
- снаружи: shell-обёртка `timeout <N+8> ./mame.arm …` как страховка.
|
||||
|
||||
---
|
||||
|
||||
## 4. Предпосылки (окружение)
|
||||
|
||||
- **MAME**: `mame/v306/mame.arm` + образы в `mame/v306/IMG/`
|
||||
(`mc.img` — наш перезаписываемый A:, `dss171u.img`, HDD `.chd`, CD
|
||||
`.iso`). Каталог `mame/` целиком в `.gitignore` — поэтому инструмент
|
||||
живёт в `toolchain/`, а не рядом с MAME.
|
||||
- **Загрузка должна доходить до `C:\>`.** `system.bat` на системном
|
||||
диске НЕ должен автоматически запускать Flex Navigator или приложение —
|
||||
иначе мы не попадём на командную строку и ввод уйдёт в чужую программу.
|
||||
(Это файл на HDD-образе, вне репозитория; правится один раз.)
|
||||
- **Нет висящих копий MAME.** Несколько одновременных инстансов пишут в
|
||||
один `mc.img` и дают недостоверный результат. Скрипт проверяет это сам
|
||||
(`pgrep`), но при ручных запусках MAME — проверяйте `ps aux | grep mame`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Как выбирать времена
|
||||
|
||||
- **Загрузка до `C:\>`:** ~7 секунд → `--launch-at 8` безопасно.
|
||||
- **Набор пути `a:\name.exe`:** ~13 символов × 0.14с ≈ 1.8с → команда
|
||||
уходит около 9.8с, программа стартует ~10с.
|
||||
- **Скриншот:** давайте программе дорисоваться. Быстрая программа —
|
||||
снимать с ~12с; если рисует долго/по частям, снимайте несколько кадров
|
||||
(`--snap 12,16,20`) и смотрите, где картинка «дособралась».
|
||||
- **Диалог с программой (`--step`):** времена шагов ставьте ПОСЛЕ старта
|
||||
программы (например, запуск на 8с, ответ на ввод на 13–15с).
|
||||
|
||||
---
|
||||
|
||||
## 6. Анализ результата
|
||||
|
||||
- Скриншоты — **единственный** способ проверки: программного чтения
|
||||
текстового/графического VRAM нет, OCR нет. Claude читает PNG глазами
|
||||
(инструмент Read с картинкой).
|
||||
- Лог MAME фильтруется по строкам `[interactive]` (моменты снимков и
|
||||
выхода) — видно, в какие секунды сделаны кадры.
|
||||
- Если картинка «не дособралась» — снять более поздний кадр (увеличить
|
||||
`--snap`/`--timeout`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Раскладка клавиатуры (справочно)
|
||||
|
||||
Раскладка снята дампом ioport-полей `:kbd:ms_naturl:*` живой машины.
|
||||
Она зашита в `PHYS`/`SHIFTED` внутри `mame_interactive.py`. Поддержаны:
|
||||
буквы (a–z, A–Z через Shift), цифры, пробел, Enter (`\n`), Tab (`\t`),
|
||||
и символы ``- = [ ] \ ; ' , . / ` `` плюс их Shift-версии
|
||||
`! @ # $ % ^ & * ( ) _ + { } | : " < > ? ~`.
|
||||
|
||||
Если понадобится клавиша вне списка — снять её поле дампом (пример
|
||||
Lua-пробы ниже) и добавить в `PHYS`:
|
||||
|
||||
```lua
|
||||
-- дамп всех полей клавиатуры в лог
|
||||
for tag, port in pairs(manager.machine.ioport.ports) do
|
||||
if tostring(tag):find("kbd") then
|
||||
for fname, field in pairs(port.fields) do
|
||||
print(string.format("%s mask=0x%x %q", tag, field.mask, fname))
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Квирки и грабли (все, на которые уже наступали)
|
||||
|
||||
- **`attotime.seconds` — целое.** Субсекундный тайминг только через
|
||||
`seconds + attoseconds/1e18`.
|
||||
- **Автоповтор (typematic).** Клавишу держать коротко (~0.06с). Если
|
||||
держать ~1с — `d` превратится в `dddddd`.
|
||||
- **Слипание scancode'ов.** Между символами ~0.14с.
|
||||
- **Не та клавиатура.** Ввод — только в `:kbd:ms_naturl`, НЕ в
|
||||
`:IO_LINE*`, НЕ через `natkeyboard`/`-autoboot_command`.
|
||||
- **Загрузка мимо `C:\>`.** Если `system.bat` что-то автозапускает —
|
||||
ввод уходит в чужую программу; вернуть чистую командную строку.
|
||||
- **Висящие копии MAME.** Всегда проверять перед запуском.
|
||||
- **macOS-специфика (справочно):** известный баг MAME
|
||||
(mamedev/mame#10612 — потеря ввода в fullscreen при движении мыши на
|
||||
старте) к нам НЕ относится: работаем в `-window`, ввод скриптовый.
|
||||
|
||||
---
|
||||
|
||||
## 9. На будущее (заметки, ещё не в инструменте)
|
||||
|
||||
- **Быстрый накопитель.** Тестам, которым важна скорость диска (напр.
|
||||
потоковое чтение), имеет смысл копировать файлы с медленной дискеты A:
|
||||
на HDD `C:\TEMP` перед запуском.
|
||||
- **Видео+звук.** MAME умеет писать AVI (`-aviwrite`) — для тестов с
|
||||
анимацией/звуком, где скриншотов мало. Пока не подключено к скрипту.
|
||||
- **Ручная отладка ввода.** Запуск MAME с `-console` даёт интерактивный
|
||||
Lua-REPL — удобно нащупывать поля/тайминги вживую перед скриптованием.
|
||||
- **Второй видеорежим/варианты BIOS** — при необходимости менять
|
||||
`COMMON_ARGS`.
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
|
||||
argv 3431
|
||||
assrtest 3847
|
||||
attrprob 4091
|
||||
banked 1056
|
||||
bankedbg 1067
|
||||
banklocl 4832
|
||||
banktest 3767
|
||||
bgi_img 6218
|
||||
bgitest 4196
|
||||
bios_text 4470
|
||||
cat 915
|
||||
cblstream 6277
|
||||
cbltest 6407
|
||||
cblwav 6486
|
||||
conio 4634
|
||||
conio2 3946
|
||||
dec_test 860
|
||||
errno 6216
|
||||
fbench 9040
|
||||
fdmax 6337
|
||||
filetest 11624
|
||||
gets 509
|
||||
gfx_d16 3319
|
||||
gfx_dbuf 2544
|
||||
gfx_demo 3219
|
||||
gfx_mous 5542
|
||||
gfx_text 2560
|
||||
hello 4176
|
||||
hello2 4251
|
||||
irqtest 5881
|
||||
ls 4919
|
||||
malloc 4462
|
||||
mdview 19122
|
||||
mdview2 27029
|
||||
mem_test 4558
|
||||
mouse 4394
|
||||
openenv 6127
|
||||
ptime 5896
|
||||
rt_test 4906
|
||||
seek 4177
|
||||
simple 963
|
||||
solidt 12381
|
||||
stattest 7700
|
||||
stdlib 6643
|
||||
stest2 3644
|
||||
strtest 1340
|
||||
text_palette 5039
|
||||
timedir 5471
|
||||
winrest 4481
|
||||
|
@@ -130,6 +130,31 @@ These will be in `docs/solid_c_diff.md`:
|
||||
|
||||
To make porting easier, add a single `<sprinter_solid.h>` that includes all the standard headers (`stdio.h`, `string.h`, `conio.h`, etc.) — Solid-C programs can `#include <sprinter_solid.h>` and have most functions available.
|
||||
|
||||
## Status 2026-07-06 — Phase 1/2/3 ЗАКРЫТЫ
|
||||
|
||||
Всё из категорий A/B/C реализовано или закрыто решением:
|
||||
|
||||
- **A (алиасы)**: все на месте в sprinter_compat.h (+ ltell, _setargv
|
||||
добавлены 2026-07-06); div/ldiv — из SDCC z80.lib (проверено).
|
||||
- **B**: getdisk/setdisk (ESTEX $02/$01, libc/io), getdate/gettime/
|
||||
setdate/settime (обёртки над getdatetime, структуры Turbo-C в
|
||||
<dos.h>), остальное было готово ранее.
|
||||
- **C**: fdopen/freopen/fclosall/fgetpos/fsetpos — реализованы поверх
|
||||
таблицы FILE v2 (libc/file); ungetc — есть (FILE v2);
|
||||
**absread/abswrite — BIOS $55/$56** (rst 8, A=диск, HL:IX=сектор,
|
||||
DE=буфер, B=счётчик; найдено в solid-c DOS.ASM) — реализованы в
|
||||
libc/io; **scanf/fscanf/sscanf — реализованы** (своё C-ядро
|
||||
_scanf_core с семантикой Solid-C: %d %u %x %o %c %s, l, ширина, %*;
|
||||
в SDCC z80 scanf нет); isatty — fd < 2 (см. memory/dss_fd_limit).
|
||||
bdos/bdosh/intdos — НЕ экспонируем (решение: типизированные
|
||||
обёртки); brk/sbrk — НЕ нужны (heap SDCC); ioctl — скип.
|
||||
- **errno**: Solid-C имена (EZERO/EINVFNC/ENOFILE/…) — алиасы в errno.h.
|
||||
- **Зонтичный заголовок**: <sprinter_solid.h>.
|
||||
- Тест: tests/solidt (MAME).
|
||||
|
||||
## History
|
||||
|
||||
- 2026-07-06 — Phase 1/2/3 закрыты: dos.h (даты/диски/сектора),
|
||||
scanf-семейство, fdopen/freopen/fclosall/fgetpos/fsetpos,
|
||||
rename/isatty (П4), errno-алиасы, sprinter_solid.h, тест solidt
|
||||
- 2026-06-01 — initial gap analysis vs Solid-C v2004
|
||||
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
Функция puts()
|
||||
Функция puts() записывает символьную строку в стандартный
|
||||
поток данных (т.е. выводит ее на экран). Функция puts()
|
||||
возвращает код символа «\п».
|
||||
int puts(const char *string);
|
||||
После выполнения функции puts() курсор переводится на
|
||||
новую строку.
|
||||
|
||||
|
||||
Функция putchar()
|
||||
Функция putchar() записывает символ в стандартный поток
|
||||
данных (т.е. выводит его на экран). Функция putchar() возвращает
|
||||
выведенный на экран символ.
|
||||
int putchar(int ch);
|
||||
|
||||
|
||||
Функция gets()
|
||||
Функция gets() считывает символьную строку стандартного
|
||||
входного потока и помещает ее по адресу, заданному указателем
|
||||
buffer; прием строки заканчивается, если функция обнаруживает
|
||||
символ конца строки «\п», данный символ удаляется и
|
||||
заменяется нуль-терминатором «\0».
|
||||
char *gets (char*buffer);
|
||||
Функция gets() возвращает указатель на считанную строку.
|
||||
|
||||
|
||||
Функция getchar()
|
||||
Функция getchar() считывает символ из стандартного
|
||||
входного потока.
|
||||
int getchar(void);
|
||||
Функция getchar() возвращает считанный символ.
|
||||
|
||||
|
||||
|
||||
=== Функции консольного ввода
|
||||
|
||||
char *cgets(char *str)
|
||||
- помещает в буфер, на начало которого
|
||||
указывает str, строку символов со стандартного ввода.
|
||||
Запись символов начинается с str[l]; str[0] должен содержать
|
||||
максимальное число символов, которое должно быть прочитано
|
||||
и записано в строку. Функция возвращает указатель на начало
|
||||
буфера str.
|
||||
|
||||
int getch(void)
|
||||
- выполняет ввод символа с клавиатуры.
|
||||
Turbo С не выполняет «эхо» ввода. В этой связи полезна для
|
||||
организации интерфейса с пользователем, при котором нажатие
|
||||
той или иной клавиши вызывает немедленную реакцию программы
|
||||
без отображения введённого символа на экране.
|
||||
|
||||
int getche(void)
|
||||
- выполняет небуферизуемый ввод символа
|
||||
с клавиатуры. Turbo С «эхоирует» ввод на экране. Перевод
|
||||
строки происходит при достижении правой вертикальной
|
||||
границы текущего активного окна.
|
||||
|
||||
int kbhit(void)
|
||||
- проверяет, пуст ли буфер клавиатуры.
|
||||
Если в буфере есть символы, функция возвращает ненулевое
|
||||
значение, в противном случае она возвращает О. Является
|
||||
удобным средством предотвращения «зацикливания» или «по-
|
||||
висания» при ожидании невозможного в данный момент события.
|
||||
Кроме того, осуществляется проверка нажатия комбинации
|
||||
клавиш «Ctrl-Break», что позволяет выполнить аварийное завершение
|
||||
программы.
|
||||
|
||||
int ungetch(int ch)
|
||||
- записывает непосредственно в буфер
|
||||
клавиатуры символ ch. Он будет доступен при выполнении следующей
|
||||
операции чтения с консоли (функциями файла
|
||||
«conio.h»). Разрешает помещать только один символ, который
|
||||
не должен совпадать с константой EOF, описанной в файле
|
||||
«stdio.h». В случае успеха функция возвращает ch; в противном
|
||||
случае возвращается -1.
|
||||
|
||||
|
||||
=== Функции консольного вывода
|
||||
|
||||
void textmode(int newmode)
|
||||
- изменяет текущий текстовый
|
||||
режим. Новый режим указывается единственным параметром
|
||||
newmode и может задаваться либо числом, либо с использованием
|
||||
символических констант, значения которых определяет
|
||||
перечислимый тип text_modes
|
||||
|
||||
Функции консольного вывода используют понятие активного
|
||||
окна экрана. Активное окно - это прямоугольная область
|
||||
экрана, в границах которой в данный момент работают функции.
|
||||
Описание активного окна (или, как часто говорят, фрейм)
|
||||
хранится во внутренней структурной переменной Turbo С. Установку
|
||||
параметров активного текстового окна выполняет функция
|
||||
window ().
|
||||
|
||||
void window(int l_t_col, int l_t_row, int r_b_col, int r_b_row)
|
||||
- описывает активное текстовое окно: первая пара
|
||||
аргументов задает столбец и строку левого верхнего угла, вторая
|
||||
пара - правого нижнего угла. Строки и столбцы нумеруются,
|
||||
начиная от 1. Поэтому, например, координаты левого верх-
|
||||
него и правого нижнего углов экрана в режимах «25 строк х 80
|
||||
столбцов» задаются парами (1,1) и (80,25). Ось X направлена
|
||||
слева направо, а ось Y направлена сверху вниз. Следует обратить
|
||||
внимание на то, как в Turbo С задаются координаты углов,
|
||||
сначала столбец, затем строка.
|
||||
|
||||
Фрейм окна Turbo С имеет следующую структуру:
|
||||
struct text_info {
|
||||
unsigned char winleft; /* столбец, строка */
|
||||
unsigned char wintop; /* левого верхнего угла */
|
||||
unsigned char winright; /* столбец, строка */
|
||||
unsigned char winbottom;/* правого верхнего угла */
|
||||
unsigned char attribute; /* атрибуты */
|
||||
unsigned char normattr; /* окна */
|
||||
unsigned char screenheight; /* полная высота экрана */
|
||||
unsigned char screenwidth; /* полная ширина экрана */
|
||||
unsigned char curx; /* строка, столбец */
|
||||
unsigned char сuгу /* текущей позиции курсора */
|
||||
}
|
||||
|
||||
|
||||
void gettextinfo(struct text_info *r)
|
||||
- заполняет поля
|
||||
структурной переменной по шаблону text_info, на которую
|
||||
ссылается. Шаблон структуры text_info, описывающей текущее
|
||||
окно экрана, содержится в заголовочном файле «conio.h».
|
||||
|
||||
Функция window() инициализирует поля координат фрейма
|
||||
окна. Функции textcolor(), textbackground(), textattr() и
|
||||
другие управляют цветом отображаемых символов окна.
|
||||
|
||||
void textattr(int newattr)
|
||||
- устанавливает атрибут для
|
||||
функций, работающих с текстовыми окнами. Атрибут хранится в
|
||||
поле attribute структурной переменной по шаблону text_info,
|
||||
доступной через функцию gettextinfo()
|
||||
|
||||
void textcolor(int newcolor)
|
||||
- задает цвет символов, не
|
||||
затрагивая установленный цвет фона. Цвет может быть или числом,
|
||||
или формироваться из символических констант, значения
|
||||
которых определяет перечисляемый тип COLORS.
|
||||
|
||||
void textbackground(int newcolor)
|
||||
- задает цвет фона
|
||||
символов, не затрагивая установленный цвет символа. Цвет может
|
||||
быть или числом, или формироваться из символических
|
||||
констант
|
||||
|
||||
void gotoxy(int х, int у)
|
||||
- устанавливает курсор в строку
|
||||
у и столбец х в текущем активном окне экрана. Верхний левый
|
||||
угол окна имеет координаты (1,1). При попытке позиционировать
|
||||
курсор за границы окна он останавливается на границе окна.
|
||||
Особенностью функции является то, что координаты х и у
|
||||
являются относительными, приведенными к левому верхнему
|
||||
углу. Например, если текущее окно было описано функцией
|
||||
window(1,8,80,25), обращение gotoxy(5,5); установит курсор
|
||||
в пятый относительный столбец окна (совпадает с абсолютным
|
||||
столбцом 4, отсчитываемым от О) в пятой относительной строке
|
||||
(так как верхняя строка окна задана равной 5, то абсолютная
|
||||
строка будет равна 5+8-1, если отсчет строк ведется от О)
|
||||
|
||||
int wherex(void),
|
||||
int wherey(void)
|
||||
- сообщают столбец и
|
||||
строку текущей позиции курсора; возвращают целое число в
|
||||
диапазоне
|
||||
|
||||
void clreol(void)
|
||||
- стирает в текстовом окне строку, на которую
|
||||
установлен курсор, начиная с текущей позиции курсора и
|
||||
до конца строки (до правой вертикальной границы окна).
|
||||
|
||||
void clrscr(void)
|
||||
- очищает все текстовое окно. Цвет «заливки»
|
||||
окна при очистке будет соответствовать значению, установленному
|
||||
символической переменной attribute в описании
|
||||
окна (структурная переменная по шаблону text_info).
|
||||
|
||||
void delline(void)
|
||||
- стирает в текстовом окне всю строку
|
||||
текста, на которую установлен курсор.
|
||||
|
||||
void insline(void)
|
||||
- вставляет пустую строку в текущей
|
||||
позиции курсора со сдвигом всех остальных строк окна на одну
|
||||
строку вниз. При этом самая нижняя строка текста окна теряется.
|
||||
|
||||
int cprintf(const char *format, ...)
|
||||
- выполняет вывод
|
||||
информации с преобразованием по заданной форматной строке,
|
||||
на которую указывает format. Является аналогом функции
|
||||
стандартной библиотеки printf(), но выполняет вывод в пределах
|
||||
заданного окна. В отличие от printf() функция cprintf()
|
||||
иначе реагирует на специальный символ '\п' - курсор переводится
|
||||
на новую строку, но не возвращается к левой границе окна.
|
||||
Поэтому для перевода курсора на начало новой строки текстового
|
||||
окна следует вывести последовательность символов CR-
|
||||
LF (OxOd,OxOa). Остальные специальные символы воздействуют
|
||||
на курсор так же, как и в случае функций стандартного ввода-
|
||||
вывода. Функция возвращает число выведенных байтов, а не
|
||||
число обработанных полей, как это делает функция printf().
|
||||
|
||||
int cputs(const char *str)
|
||||
- выводит строку символов в
|
||||
текстовое окно, начиная с текущей позиции курсора. На начало
|
||||
выводимой ASCIIZ-строки указывает str. Является аналогом
|
||||
функции стандартной библиотеки puts(), выполняет вывод в
|
||||
пределах заданного окна и при выводе не добавляет специальный
|
||||
символ '\п'. Реакция cputs() на специальный символ '\п'
|
||||
аналогична реакции cprintf(): курсор переводится на новую
|
||||
строку, но не возвращается к левой границе окна. Поэтому для
|
||||
перевода курсора на начало новой строки текстового окна следует
|
||||
вывести последовательность символов CR-LF (OxOd,OxOa).
|
||||
Остальные специальные символы воздействуют на курсор так
|
||||
же, как и в случае функций стандартного ввода/вывода. Функция
|
||||
возвращает ASCII-код последнего выведенного на экран
|
||||
символа. В отличие от puts() в функции отсутствует возврат
|
||||
символа EOF (вывод на экран происходит в любом случае).
|
||||
|
||||
int movetext(int left, int top, int right, int bottom, int destleft, int desttop)
|
||||
- переносит окно, заданное координатами
|
||||
левого верхнего (left, top) и правого нижнего (right, bottom)
|
||||
углов, в другое место на экране, заданное координатами левого
|
||||
верхнего угла нового положения окна. Размеры окна по горизонтали
|
||||
и вертикали сохраняются. Все координаты задаются относительно
|
||||
координат верхнего левого угла экрана (1,1). Функция
|
||||
возвращает ненулевое значение, если перенос заданного
|
||||
окна выполнен. В противном случае возвращается О. Функция
|
||||
корректно выполняет перекрывающиеся переносы, т.е. переносы,
|
||||
в которых прямоугольная область-источник и область, в которую
|
||||
окно переносится, частично покрывают друг друга.
|
||||
|
||||
int putch(int ch)
|
||||
- выводит символ в текущей позиции
|
||||
текстового окна экрана. Как и для функций cprintf(), cputs(),
|
||||
специальный символ '\п' вызывает только переход курсора на
|
||||
новую строку текстового окна без возврата к его левой вертикальной
|
||||
границе. Остальные специальные символы воздействуют
|
||||
на курсор так же, как и для функций стандартного ввода-
|
||||
вывода.
|
||||
|
||||
int puttext(int left, int top, int right, int bottom, void source)
|
||||
- выводит на экран текстовое окно, заданное координатами
|
||||
левого верхнего (left, top) и правого нижнего (right,
|
||||
bottom) углов. Символы и атрибуты располагаются в буфере,
|
||||
адрес начала которого задаёт указатель source (функция «открывает»
|
||||
или «восстанавливает» текстовое окно экрана). Обычно
|
||||
используется вместе с функцией gettext(), выполняющей
|
||||
обратную операцию - запись в source символов/атрибутов,
|
||||
полностью описывающих все знакоместа текстового окна. Функция
|
||||
проверяет по заданным координатам окна, можно ли разместить
|
||||
окно на экране для текущего режима видеоадаптера и
|
||||
корректны ли эти координаты. В случае, когда окно успешно
|
||||
выведено, возвращается ненулевое значение.
|
||||
|
||||
int gettext(int left, int top, int right, int bottom, void destin)
|
||||
- записывает в буфер destin символы и атрибуты текстового
|
||||
окна, заданного строкой и столбцом левого верхнего
|
||||
(left, top) и правого нижнего (right, bottom) углов. Первые два
|
||||
слова буфера занимают ширина и длина скопированного окна.
|
||||
Работает только в текстовых режимах видеоадаптера. Координаты
|
||||
задаются относительно верхнего левого угла экрана (1,1). В
|
||||
случае успеха возвращает ненулевое число.
|
||||
|
||||
|
||||
=== Файловый ввод/вывод
|
||||
|
||||
Прототипы функций ввода-вывода и используемые для этого
|
||||
типы данных описаны в стандартном заголовочном файле
|
||||
«stdio.h».
|
||||
Для файлового ввода/вывода в Си предусмотрены две основные
|
||||
группы функций:
|
||||
• функции низкоуровневого ввода/вывода, использующие
|
||||
для доступа к файлам целочисленные файловые дескрипторы;
|
||||
• функции более высокого уровня, осуществляющие буферизованный
|
||||
ввод/вывод с применением потоков.
|
||||
Поток в Си - это объект, служащий для доступа к файлам
|
||||
как к упорядоченной последовательности символов.
|
||||
Поток представляется структурой типа FILE, с которой ассоциирован
|
||||
некоторый открытый файл. При необходимости несколько
|
||||
потоков могут ссылаться на один и тот же файл.
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
MDVIEW Sample Document
|
||||
|
||||
This is a sample Markdown file for testing the Sprinter *mdview text
|
||||
viewer*. Phase 3 adds inline emphasis: bold, italic and underscore
|
||||
runs render with distinct background colours.
|
||||
@@ -1758,7 +1758,7 @@ static void clamp_top(void)
|
||||
}
|
||||
|
||||
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
|
||||
static void scroll_up(uint16_t n)
|
||||
static void md_scroll_up(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||||
if( new_top_line != top_line) {
|
||||
@@ -1773,7 +1773,7 @@ static void scroll_up(uint16_t n)
|
||||
}
|
||||
|
||||
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
|
||||
static void scroll_down(uint16_t n)
|
||||
static void md_scroll_down(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||||
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||||
@@ -1791,7 +1791,7 @@ static void scroll_down(uint16_t n)
|
||||
|
||||
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
|
||||
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране. */
|
||||
static void scroll_h(int8_t delta)
|
||||
static void md_scroll_horizon(int8_t delta)
|
||||
{
|
||||
uint16_t maxw = 0;
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
@@ -1999,12 +1999,12 @@ int main(int argc, char **argv)
|
||||
switch (scan) {
|
||||
case KEY_F1: show_help(); break;
|
||||
case KEY_F10: goto exit_loop;
|
||||
case KEY_UP: scroll_up(1); break;
|
||||
case KEY_DOWN: scroll_down(1); break;
|
||||
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); break;
|
||||
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); break;
|
||||
case KEY_PGUP: scroll_up(VIEW_H); break;
|
||||
case KEY_PGDN: scroll_down(VIEW_H); break;
|
||||
case KEY_UP: md_scroll_up(1); break;
|
||||
case KEY_DOWN: md_scroll_down(1); break;
|
||||
case KEY_LEFT: md_scroll_horizon(-(int8_t)HPAN_STEP); break;
|
||||
case KEY_RIGHT: md_scroll_horizon(+(int8_t)HPAN_STEP); break;
|
||||
case KEY_PGUP: md_scroll_up(VIEW_H); break;
|
||||
case KEY_PGDN: md_scroll_down(VIEW_H); break;
|
||||
case KEY_HOME: top_line = 0;
|
||||
viewport_x = 0;
|
||||
render_viewport(); break;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# MDView - CHANGELOG
|
||||
|
||||
”®à¬ â: ¢¥àᨨ ¨ ¤ âë ¯® git-¨áâ®à¨¨ ¯à®¥ªâ . ” ©« á®åà ñ ¢ CP866 -
|
||||
¥£® 㤮¡® ç¨â âì á ¬¨¬ MDView (§ ®¤® íâ® â¥á⠢⮤¥â¥ªâ ª®¤¨à®¢ª¨).
|
||||
|
||||
## v1.0 (b3) - 2026-07-05
|
||||
|
||||
- ”®®¢ ï ¯®¤£®â®¢ª ¢â®à®£® ¡®à ª®¤¨à®¢ª¨: ¯®ª ¯®«ì§®¢ ⥫ì ç¨â ¥â
|
||||
¤®ªã¬¥â, ¯ ã§ë ¬¥¦¤ã ª« ¢¨è ¬¨ âà âïâáï ¨¤¥ªá æ¨î ¢â®à®£® ¡®à
|
||||
(UTF-8 ¯à¨ 8-¡¨â®¬ ¯¥à¢¨ç®¬ ¨ ®¡®à®â). Š ¯¥à¢®¬ã F8 ¡®à ®¡ëç®
|
||||
㦥 £®â®¢ - ¯¥à¥ª«î票¥ ¬£®¢¥®. F8 ¤® £®â®¢®á⨠¥ áâந⠡®à
|
||||
§ ®¢®, ¤®ªàã稢 ¥â ç ⮥ ä®®¬ (¦¤ âì ®áâ ñâáï ¬¥ìè¥).
|
||||
- ”® ¯®«®áâìî ¥¢¨¤¨¬: íªà ¥ âண ¥âáï, ¯®á«¥ ª ¦¤®© ª« ¢¨è¨
|
||||
¢ë¤¥à¦¨¢ ¥âáï 宫¤ ã - ¯à¨ áªà®««¥ á ¢â®¯®¢â®à®¬ ä® ¥ ¢ª«¨¨¢ ¥âáï.
|
||||
- ˆ¤¥ªá â®à ¯¥à¥¢¥¤ñ १àã¥¬ë¥ è £¨ (index_begin/index_step);
|
||||
¯®¡®ç® § ªàëâ ªà ©: ®¡àë¢ UTF-ª®¢¥àâ æ¨¨ ᢥà夫¨®¬ (>4 Š�)
|
||||
¡§ æ¥ ¡®«ìè¥ ¥ ®¡à¥§ ¥â ®áâ ⮪ ¤®ªã¬¥â .
|
||||
- Š®¤¨à®¢ª ¢ áâ âãá-¡ ॠ¯®ª §ë¢ ¥âáï áà §ã: ¤¥â¥ªâ¨à®¢ ï - ¢ ¬®¬¥â
|
||||
ç « ¯¥à¢®© ¨¤¥ªá 樨, ®¢ ï ¯® F8 - ¢ ¬®¬¥â ¯¥à¥ª«î票ï, ¥
|
||||
¤®¦¨¤ ïáì ª®æ ᡮન ¡®à .
|
||||
|
||||
## v1.0 (b1) - 2026-07-05
|
||||
|
||||
- �®¢ë© HEX-०¨¬ (F4): ¤ ¬¯ Ž�ˆƒˆ�€‹œ�ŽƒŽ ä ©« ¢ ä®à¬ â¥
|
||||
"0x012340 : 16 ¡ ©â hex : 16 ¯¥ç âëå ᨬ¢®«®¢".
|
||||
�¥ç â ï ª®«®ª ãç¨âë¢ ¥â ⥪ãéãî ª®¤¨à®¢ªã; ¤«ï UTF-8 £«¨ä
|
||||
áâ ¢¨âáï ¯®§¨æ¨¨ «¨¤-¡ ©â , continuation-¡ ©âë - â®çª®©.
|
||||
‘¬¥ ª®¤¨à®¢ª¨ (F8) ¢ HEX ¬¥ï¥â ⮫쪮 ¯¥ç âãî ª®«®ªã.
|
||||
- �பàã⪠HEX ®¤ã áâபã - ¯¯ à âë© áªà®«« á ¯®¤à¨á®¢ª®©
|
||||
®¤®£® àï¤ (ª ª ¢ MD/RAW).
|
||||
- ‘¯à ¢ª : ¤®¡ ¢«¥ ª« ¢¨è F4, ¢¥àá¨ï v1.0 (b1).
|
||||
|
||||
## v1.0 (a4) - 2026-07-04
|
||||
|
||||
- …¤¨ ï ¯®§¨æ¨ï ¯à¨ ¯¥à¥ª«î票ïå ¢¨¤®¢ ¨ ª®¤¨à®¢®ª: F2 (MD/RAW),
|
||||
F8 (8-¡¨âë¥ / UTF-8) á®åà ïîâ ⥪ã饥 ¬¥áâ® ¢ ¤®ªã¬¥â¥.
|
||||
Œ¥¦¤ã ¡®à ¬¨ à §®£® à §¬¥à ¯®§¨æ¨ï ¯¥à¥®á¨âáï ¯à®¯®à樮 «ì®
|
||||
¡ ©â ¬ á â®ç®áâìî ¤® áâப¨.
|
||||
- ˆá¯à ¢«¥®: F8 ¢ RAW-०¨¬¥ à¨á®¢ « MD-¢¨¤ ¯®¢¥àå RAW.
|
||||
- Ž¯â¨¬¨§ æ¨ï à §¬¥à : exe 28215 -> 25100 ¡ ©â (-11%):
|
||||
£à¥áᨢ ï ««®ª æ¨ï ॣ¨áâ஢ SDCC (--max-allocs), § ã«¥¨¥
|
||||
_DATA ¢ crt0 (áïâë ¢á¥ "= 0" ã áâ ⨪®¢), ¤¥¤ã¯«¨ª æ¨ï ¯ àá¥à ,
|
||||
᫨逸 ¤¢ãå ᪠¥à®¢ inline-à §¬¥âª¨ ¢ ®¤¨ scan_stream.
|
||||
|
||||
## v1.0 (a3) - 2026-06-30
|
||||
|
||||
- ‹¨¬¨â ä ©« 256 Š�: ¡®«ì訩 ä ©« ®¡à¥§ ¥âáï á ¯à¥¤ã¯à¥¦¤¥¨¥¬,
|
||||
¯à®ç¨â ï ç áâì ¤®áâ㯠.
|
||||
- Ž¡àë¢ ¯® ¨áç¥à¯ ¨î ¨¤¥ªá /ªíè § ¢¥àè ¥âáï áâப®©-á®®¡é¥¨¥¬
|
||||
á ¯à¨ç¨®© (¢¬¥áâ® â¨å®© ¯®â¥à¨ áâப).
|
||||
- �®«¨à®¢ª áâ âãá-¡ à ¨ á¯à ¢ª¨; ¯à®æ¥â ¯à®ªàã⪨ 16-¡¨â®©
|
||||
à¨ä¬¥â¨ª¥ (¡¥§ 32-¡¨â®£® ¤¥«¥¨ï).
|
||||
- ’¥áâ®¢ë¥ ä ©«ë £à ¨çëå á«ãç ¥¢ (testfiles/, £¥¥à â®à).
|
||||
|
||||
## v1.0 (a2) - 2026-06-26
|
||||
|
||||
- Œ®®«¨â à á饯«ñ ¬®¤ã«¨: ï¤à® + index/md/raw/enc/table/status/help
|
||||
(à §¬¥à-¥©âà «ì®, ç¨â ¥¬®áâì).
|
||||
- ˆá¯à ¢«¥ ¤ã¡«¨ª â áâப¨ ¢ RAW ¯à¨ áªà®««¥ ã ª®æ ä ©« .
|
||||
|
||||
## v1.0 (a1) - 2026-06-25
|
||||
|
||||
- �®¤¤¥à¦ª ª®¤¨à®¢®ª CP866/CP1251/KOI8-R/UTF-8 (F8):
|
||||
¢â®®¯à¥¤¥«¥¨¥ ¯à¨ ®âªàë⨨, ¬£®¢¥ë© ६ ¯ 8-¡¨âëå,
|
||||
UTF-8 ¤¥ª®¤¨àã¥âáï ¢ ®â¤¥«ìë© ¡®à (¨ªà¥¬¥â «ì®).
|
||||
- RAW-¯à®á¬®âà ¨á室¨ª (F2): ¯¥à¥®á ªà â® 80 ¨«¨ ¯ ®à ¬ (F3).
|
||||
- Ž¯â¨¬¨§ æ¨ï à §¬¥à ª®¤ (-1.9 Š�).
|
||||
|
||||
## v1.0 (a0) - 2026-06-23..24
|
||||
|
||||
- �¥à¢ë© ¢ë¯ãáª: render-cache àå¨â¥ªâãà - ®¤¨ ¯à®å®¤ ¯ àá¥à ,
|
||||
£®â®¢ë¥ áâப¨ (ᨬ¢®«+ âਡãâ) ¢ EMM, ¬£®¢¥ ï ¯à®ªàã⪠.
|
||||
- Markdown: § £®«®¢ª¨, ᯨ᪨, æ¨â âë, à §¤¥«¨â¥«¨, fenced-ª®¤,
|
||||
â ¡«¨æë á à ¬ª®© ¨ ¢â®-è¨à¨®© ª®«®®ª, inline-à §¬¥âª
|
||||
(¦¨àë©/ªãàᨢ/¯®¤çñàªãâë©/§ çñàªãâë©/ª®¤, íªà ¨à®¢ ¨¥).
|
||||
- �ணà¥áᨢ ï § £à㧪 : ¯¥à¢ë© íªà áà §ã, ¨¤¥ªá æ¨ï ¢ 䮥,
|
||||
¢¨£ æ¨ï ¯® £®â®¢®© ç áâ¨, Esc/F10 ¯à¥àë¢ ¥â.
|
||||
- ƒ®à¨§®â «ìë© áªà®«« ª®¤®¢ëå ¡«®ª®¢ ¨ â ¡«¨æ.
|
||||
- “᪮२¥ ¯à¥¯à®æ¥áᨣ (22á -> 10á ¡®«ì讬 ä ©«¥).
|
||||
@@ -0,0 +1,840 @@
|
||||
# MDView — Просмотрщик Markdown для Sprinter
|
||||
|
||||
**MDView** — программа для просмотра документов в формате *Markdown* на компьютере
|
||||
Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает
|
||||
основную RAM программы.
|
||||
|
||||
## Возможности
|
||||
|
||||
- Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая)
|
||||
- До **16 384** экранных строк в индексе
|
||||
- Автоматический перенос слов по ширине экрана (80 столбцов)
|
||||
- Горизонтальный сдвиг для широких строк (блоки кода, таблицы)
|
||||
- Статус-бар: имя файла, диапазон строк, процент прокрутки
|
||||
- Спиннер в строке состояния во время загрузки и индексации
|
||||
- Поддержка «мягкого» склеивания строк в абзацах и цитатах
|
||||
|
||||
## Запуск
|
||||
|
||||
```
|
||||
mdview [имя_файла.md]
|
||||
```
|
||||
|
||||
Если имя файла не задано, загружается `README.MD`.
|
||||
|
||||
## Управление
|
||||
|
||||
```
|
||||
Клавиша Действие
|
||||
───────────── ────────────────────────────────────────
|
||||
Up Down Прокрутка на одну строку вверх / вниз
|
||||
PgUp PgDn Прокрутка на страницу (30 строк)
|
||||
Home Начало документа
|
||||
End Конец документа
|
||||
Left Right Горизонтальный сдвиг (только nowrap-строки)
|
||||
F1 Окно справки
|
||||
F8 Кодировка: CP866 → CP1251 → KOI8-R → UTF-8 → ...
|
||||
F10 / Esc Выход из программы
|
||||
```
|
||||
|
||||
Кодировка определяется автоматически при открытии (BOM + эвристика по
|
||||
первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась.
|
||||
8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на
|
||||
отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво — при
|
||||
первом переключении в него (короткая пауза со спиннером), дальше мгновенно.
|
||||
|
||||
## Синтаксис Markdown
|
||||
|
||||
### Заголовки
|
||||
|
||||
Поддерживаются уровни H1–H4. Уровни H5 и H6 отображаются как H4.
|
||||
|
||||
# Заголовок первого уровня
|
||||
## Заголовок второго уровня
|
||||
### Заголовок третьего уровня
|
||||
#### Заголовок четвёртого уровня
|
||||
|
||||
### Текстовое форматирование
|
||||
|
||||
**Жирный текст** выделяется двойными звёздочками: `**текст**`
|
||||
|
||||
*Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_`
|
||||
|
||||
`Встроенный код` обозначается обратными кавычками
|
||||
|
||||
~~Зачёркнутый текст~~ — двойные тильды: `~~текст~~`
|
||||
|
||||
### Ненумерованный список
|
||||
|
||||
Маркеры `-`, `*` или `+`:
|
||||
|
||||
- Первый пункт списка
|
||||
- Второй пункт списка
|
||||
- Третий пункт с достаточно длинным текстом, который при необходимости
|
||||
будет перенесён на следующую строку с сохранением отступа
|
||||
|
||||
### Нумерованный список
|
||||
|
||||
1. Первый элемент
|
||||
2. Второй элемент
|
||||
3. Третий элемент
|
||||
|
||||
### Цитата
|
||||
|
||||
> Блок цитаты начинается с символа `>`. Несколько последовательных
|
||||
> строк одной цитаты склеиваются в единый абзац с автоматическим
|
||||
> переносом слов.
|
||||
|
||||
### Блок кода (verbatim)
|
||||
|
||||
Блок кода заключается в тройные обратные кавычки. Внутри блока
|
||||
текст отображается «как есть» без разбора Markdown:
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
int main(void) {
|
||||
puts("Hello, Sprinter!");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Горизонтальная линия
|
||||
|
||||
Три или более символов `---`, `***` или `___` на отдельной строке:
|
||||
|
||||
---
|
||||
|
||||
## Технические характеристики
|
||||
|
||||
- **Платформа:** Sprinter, процессор Z80 @ 21 МГц
|
||||
- **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`)
|
||||
- **Максимальный размер файла:** 128 КБ
|
||||
- **Максимальное число строк в индексе:** 16 384
|
||||
- **Максимальная длина строки в рендер-кэше:** 255 ячеек (см. ограничение ниже)
|
||||
- **Режим памяти:** small
|
||||
- Код программы, cтек, данные, куча — окнa W1-W2 (32 КБ, адреса 0x4000–0xBFFF).
|
||||
- Буфер файла — страницы EMM, отображаемые в W3 (0xC000–0xFFFF)
|
||||
|
||||
## Известные ограничения
|
||||
|
||||
### Длина строки в MD-режиме — 255 ячеек
|
||||
|
||||
Рендер-кэш хранит каждую логическую строку как **не более 255 пар (символ, атрибут)**
|
||||
— константа `MAX_CACHE_LINE_LEN`. Лимит задан типами: `g_ncells` и
|
||||
`cache_rec_t.len` — `uint8_t`. Касается всех строк, но заметнее всего на
|
||||
**горизонтально скроллируемых** строках (блоки кода и строки таблиц, флаг
|
||||
`IF_HSCROLL`), которые в MD-режиме можно листать вправо.
|
||||
|
||||
**Что происходит с более длинной строкой:** при индексации `gc_put()` молча
|
||||
отбрасывает каждую ячейку после 255-й (`if (g_ncells < MAX_CACHE_LINE_LEN)`).
|
||||
В кэш попадают только первые 255 ячеек, остаток **теряется** — до него нельзя
|
||||
доскроллить и **нет маркера обрезки** на 255-й позиции (маркер `>` означает лишь
|
||||
«есть ещё в пределах кэша»). Переполнения буфера нет — `gc_put` проверяет границу.
|
||||
|
||||
Важно: «255 ячеек» — это **отрендеренная ширина**, не байты исходника. Табы в
|
||||
коде разворачиваются в пробелы (до `TAB_STOP`), а ячейки таблицы добиваются
|
||||
пробелами до ширины колонки + рамки `│` — поэтому кап достигается раньше, чем
|
||||
255 «полезных» символов.
|
||||
|
||||
> **RAW-режим (`F2`) этого лимита не имеет** — он рисует прямо из файла
|
||||
> побайтово, длинные строки видны целиком (через wrap `F3` или гориз. скролл).
|
||||
|
||||
**Идея снятия лимита** (оценка, не реализовано) — расширить длину до `uint16_t`:
|
||||
- `cache_rec_t.len` `uint8_t→uint16_t` — структура остаётся **ровно 8 байт**
|
||||
(len съедает один pad-байт), адресация `idx<<3` не меняется. Бесплатно.
|
||||
- `g_ncells` / `g_ncells_at_space` → `uint16_t` — главная цена по **коду/скорости**:
|
||||
16-битная арифметика на Z80 в горячем `gc_put` (вызов на каждую ячейку) и в
|
||||
scan-циклах. Ориентир: **+0.2…0.4 КБ кода** + замедление индексации.
|
||||
- Буфер `g_cells[MAX_CACHE_LINE_LEN*2]` в near-RAM (W2) — главная цена по
|
||||
**памяти**: `2 × кап` байт. Сейчас 510 Б; кап 512 → +0.5 КБ, 1024 → +1.5 КБ.
|
||||
EMM-кэш контента (1 МБ) длинные строки тянет легко — узкое место именно near.
|
||||
- `viewport_x` (и копия в `docset_t`) + потолок `max_vx` (сейчас 248) → `uint16_t`,
|
||||
иначе хранить >255 можно, а доскроллить нельзя. Плюс `widths[]`/`ccx` в таблицах/коде.
|
||||
|
||||
Реалистичный компромисс — кап 512–1023: хватит почти всем листингам/таблицам,
|
||||
цена ~+0.3 КБ кода и +0.5…1.5 КБ near-RAM. **Дешёвая полумера без `uint16_t`** —
|
||||
ставить честный маркер обрезки на 255-й позиции, чтобы потеря была видна.
|
||||
|
||||
## TODO
|
||||
|
||||
1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно
|
||||
разрешить работать с большим кол-вом страниц памяти, пока оттестированно
|
||||
на работе с 8-мю страницами по 16Кб.
|
||||
|
||||
Сделано: форматированные таблицы с рамкой; поддержка кодировок
|
||||
CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8`
|
||||
(второй набор строится лениво, по первому переключению).
|
||||
|
||||
4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода
|
||||
символов через BIOS WRCHAR (пакетный вывод, DMA).
|
||||
|
||||
---
|
||||
|
||||
*MDView v0.2 · (c) 2026 Петров А.Г.*
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Sprinter C Compiler — v1.0
|
||||
|
||||
C toolchain for **Sprinter** — the Z80-based home computer by Peters Plus, running
|
||||
ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format.
|
||||
|
||||
Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds
|
||||
everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX,
|
||||
banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the
|
||||
`mkexe` utility for producing SprintEXE images.
|
||||
|
||||
## What you get
|
||||
|
||||
* **`bin/sprinter-cc`** — one-line driver: `sprinter-cc -o foo.exe foo.c`
|
||||
* **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` — see below.
|
||||
* **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch.
|
||||
* **Graphics**: 320×256×256 and 640×256×16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator.
|
||||
* **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`.
|
||||
* **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs.
|
||||
* **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps).
|
||||
* **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`.
|
||||
* **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`.
|
||||
* **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0.
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
git clone <this repo> sprinter-c
|
||||
cd sprinter-c
|
||||
make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB)
|
||||
make all # build mkexe + libsprinter.lib + 27 examples
|
||||
make floppy # pack everything into mame/v306/IMG/mc.img
|
||||
cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME
|
||||
```
|
||||
|
||||
Compile a single program:
|
||||
|
||||
```sh
|
||||
cat > hello.c <<EOF
|
||||
#include <stdio.h>
|
||||
int main(void) { puts("Hello, Sprinter!"); return 0; }
|
||||
EOF
|
||||
|
||||
bin/sprinter-cc -o hello.exe hello.c
|
||||
```
|
||||
|
||||
That's it — `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell.
|
||||
|
||||
## Memory modes
|
||||
|
||||
Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates
|
||||
pages by program size — small programs get only one page. Pick a memory mode based
|
||||
on what your program needs:
|
||||
|
||||
| Mode | Code lives in | Banking | Use when | Note |
|
||||
|---|---|---|---|---|
|
||||
| `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | |
|
||||
| `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | |
|
||||
| `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | |
|
||||
| `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | |
|
||||
| `manual` | user-specified | optional | special layouts | Not implemented |
|
||||
|
||||
```sh
|
||||
sprinter-cc --memory small -o big.exe bigprog.c
|
||||
sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c
|
||||
```
|
||||
|
||||
Banked functions are declared with `__banked`:
|
||||
|
||||
```c
|
||||
void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped
|
||||
```
|
||||
|
||||
## Examples (27 total)
|
||||
|
||||
| Example | What it demonstrates |
|
||||
|---|---|
|
||||
| `hello` | Hello world with stdio + conio Turbo-C-style colors |
|
||||
| `argv` | argv parsing in crt0 |
|
||||
| `cat` | File I/O — read & print TEST.TXT |
|
||||
| `seek` | 32-bit lseek over a 100 KB file |
|
||||
| `ls` | Directory listing via ffirst/fnext |
|
||||
| `filetest` | FILE\* streams (fopen/fread/...) |
|
||||
| `stattest` | `stat`/`fstat` on files and directories |
|
||||
| `errno` | errno / strerror / perror |
|
||||
| `mem_test` | Page allocator + bank\_read/bank\_write |
|
||||
| `malloc` | Heap stress test (200+ allocations) |
|
||||
| `banked` | Banked code in W3 (huge mode) |
|
||||
| `bankedbg` | Banked code in W1 (big mode) |
|
||||
| `banklocl` | Bank-local static data and BSS |
|
||||
| `mouse` | Mouse driver in text mode |
|
||||
| `gfx_mous` | Mouse with custom bitmap cursor in graphics mode |
|
||||
| `gfx_demo` | 320×256×256 lines / rects / accelerator |
|
||||
| `gfx_d16` | 640×256×16 same primitives |
|
||||
| `gfx_text` | Bitmap-font text on graphics screen |
|
||||
| `timedir` | Date/time + directory listing |
|
||||
| `ptime` | POSIX time API |
|
||||
| `openenv` | open() flags + environment vars |
|
||||
| `conio` | conio API smoke test |
|
||||
| `attrprob` | Probe Sprinter text attribute byte layout |
|
||||
| `strtest` | string.h test (from SDCC's z80.lib) |
|
||||
| `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) |
|
||||
| `assrtest` | assert() |
|
||||
| `rt_test` | Runtime helpers (sleep, setjmp, atexit) |
|
||||
|
||||
## Headers
|
||||
|
||||
Standard:
|
||||
* `<stdio.h>` — puts / printf / FILE\* + Sprinter-specific dec/hex helpers
|
||||
* `<stdlib.h>` — atoi / atof / malloc / qsort / ... (from SDCC z80.lib)
|
||||
* `<string.h>` / `<ctype.h>` / `<math.h>` — from SDCC z80.lib
|
||||
* `<unistd.h>` — read / write / close / lseek / unlink
|
||||
* `<fcntl.h>` — open / creat + O\_RDONLY / O\_CREAT / ...
|
||||
* `<errno.h>` — errno + error names + strerror
|
||||
* `<sys/stat.h>` — stat / fstat
|
||||
* `<setjmp.h>` / `<assert.h>` — from SDCC
|
||||
|
||||
Sprinter-specific:
|
||||
* `<conio.h>` — putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y
|
||||
* `<gfx.h>` — gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text — for both 320 and 640 modes (gfx_\*16 variants)
|
||||
* `<mouse.h>` — full 14-function driver wrapper + mouse_cursor_t with bitmap support
|
||||
* `<dir.h>` — chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk
|
||||
* `<time.h>` — getdatetime / setdatetime + POSIX time / localtime / etc.
|
||||
* `<sprinter.h>` — raw ports, ESTEX/BIOS function numbers, env API
|
||||
* `<sprinter_exit.h>` — exit / \_exit / atexit
|
||||
* `<sprinter_mem.h>` — mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write
|
||||
* `<sprinter_compat.h>` — Solid-C compatibility layer (aliases + BOOL/WORD/uint types)
|
||||
|
||||
## Toolchain commands
|
||||
|
||||
```sh
|
||||
make all # build mkexe + lib + every example
|
||||
make floppy # repack mame/v306/IMG/mc.img with all .exe files
|
||||
make check # 17 mkexe unit-tests
|
||||
make clean # remove all build artefacts
|
||||
make sdcc # one-time: fetch SDCC 4.5 binary
|
||||
```
|
||||
|
||||
### sprinter-cc options
|
||||
|
||||
```
|
||||
sprinter-cc -o foo.exe foo.c [more.c ...] [options]
|
||||
|
||||
--memory MODE tiny | small | big | huge | manual (default: tiny)
|
||||
--memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3)
|
||||
--stack-size N bytes reserved for the stack (default ~1278)
|
||||
--crt0=TYPE default | minimal | banked | small
|
||||
--bank N=FILE.c compile FILE.c into bank N (repeatable, max 15)
|
||||
--debug enable runtime diagnostics (defines DEBUG_RT)
|
||||
-I PATH extra include path
|
||||
-L 0xADDR / -E / -S override load / entry / stack addresses
|
||||
-Wl FLAG pass FLAG to sdldz80
|
||||
--mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding)
|
||||
-v verbose
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
What works in v1.0:
|
||||
* Compile / link / pack to SprintEXE — verified on all 27 examples
|
||||
* Four memory modes (tiny / small / big / huge)
|
||||
* Graphics (both modes) with accelerator
|
||||
* Mouse (text + graphics cursor)
|
||||
* File I/O, directories, environment, time
|
||||
* All headers listed above
|
||||
|
||||
Deferred to v2.0 (see `docs/TODO.md`):
|
||||
* **Turbo-C-style BGI graphics API** — `initgraph` / `setcolor` / `circle` /
|
||||
`getimage` / `putimage` / etc. on top of our `gfx_*` primitives
|
||||
* Remaining Solid-C compatibility gaps (Phase 2/3) — see `docs/solid_c_compatibility.md`
|
||||
* Manual memory mode
|
||||
* Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers)
|
||||
|
||||
Deferred to v3.0:
|
||||
* **IM2 interrupt handlers** — research complete (`docs/im2_isr_design.md`),
|
||||
implementation scheduled for v3
|
||||
* **Audio API** (AY-3-8910 + COVOX) — requires IM2
|
||||
* **ISA-8 slot drivers** — requires IM2 (???)
|
||||
|
||||
## Documentation
|
||||
|
||||
* `docs/TODO.md` — roadmap and open work items
|
||||
* `docs/solid_c_compatibility.md` — gap analysis vs Solid-C 2004
|
||||
* `docs/im2_isr_design.md` — interrupt handler design (v2)
|
||||
* `docs/converted/` — source documentation (ESTEX, BIOS, architecture)
|
||||
converted to plain text for `grep`
|
||||
* `docs/reference/`, `docs/samples/`, `docs/memory management/` — original
|
||||
Russian docs and code samples from Peters Plus
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
bin/sprinter-cc one-line compiler driver (bash)
|
||||
toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE
|
||||
toolchain/check_banks.py post-link bank size enforcer
|
||||
runtime/ crt0 variants (default, minimal, small, banked)
|
||||
bank trampolines, heap, heap_top
|
||||
libc/include/ headers
|
||||
libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib
|
||||
lib/ Makefile that archives libsprinter.lib via sdar
|
||||
examples/ 27 example programs
|
||||
mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script
|
||||
third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`)
|
||||
third_party/solid-c/ reference: original Sprinter native C (for compat target)
|
||||
docs/ documentation
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This repository contains:
|
||||
* Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` —
|
||||
MIT-licensed.
|
||||
* `third_party/sdcc/` — SDCC 4.5 under GPLv2 with linking exception
|
||||
(see `third_party/sdcc/COPYING.txt`)
|
||||
* `third_party/solid-c/` — original Sprinter Solid C, used only as a reference
|
||||
|
||||
## Credits
|
||||
|
||||
* **Sprinter / Peters Plus** — Иван Мак, Дмитрий Паринов and the original team
|
||||
* **SDCC** — for the underlying Z80 compiler
|
||||
* **MAME** — for the Sprinter Sp2000 emulation
|
||||
|
||||
---
|
||||
|
||||
For questions / patches: see CONTRIBUTING.md (TBD) or open an issue.
|
||||
|
||||
---
|
||||
|
||||
# TODO / Roadmap
|
||||
|
||||
Открытые задачи в порядке убывания приоритета. По мере появления реальных программ — приоритеты будут смещаться.
|
||||
|
||||
## Этап 5 — malloc / free + banking-aware page allocator ✅ ГОТОВО
|
||||
|
||||
- [x] SDCC's `malloc`/`free` + наш `runtime/heap.s` (полностью заменяет library heap.rel, 14000-байтный heap в окне 2)
|
||||
- [x] `libc/mem/mem_alloc.c` — page allocator: `mem_alloc_pages`/`mem_free_block`/`mem_get_page`/`mem_info` через ESTEX `$3C/$3D/$3E` + BIOS `$C4`
|
||||
- [x] `libc/mem/bank_io.c` — HOME-резидентные `bank_read`/`bank_write`/`bank_load_byte`/`bank_store_byte` со свопом W3 внутри
|
||||
- [x] `examples/malloc_test/` — проверка SDCC's malloc (~210 64-байтных allocations через всю heap)
|
||||
- [x] `examples/mem_test/` — проверка page allocator: 3 страницы, разные паттерны через bank_write, верификация через bank_read
|
||||
|
||||
## Этап 6 — argv parsing + sprinter-cc wrapper ✅ ГОТОВО
|
||||
|
||||
- [x] crt0 парсит ESTEX command-line из IX-prefix (inline asm в `runtime/crt0.s`)
|
||||
- [x] Strip leading CP/M-style space (DSS quirk)
|
||||
- [x] Передача `argc`/`argv` в main() через HL/DE (SDCC __sdcccall(1) ABI)
|
||||
- [x] argv[0] = basename .EXE через ESTEX APPINFO ($47 subfn 2)
|
||||
- [x] `runtime/crt0_minimal.s` — opt-out для очень маленьких программ
|
||||
- [x] `runtime/crt0_banked.s` — теперь тоже парсит argv (parse_argv + get_progname скопированы из crt0.s; будет factored в argv.s когда возьмёмся за libsprinter.lib)
|
||||
- [x] Bash-обёртка `bin/sprinter-cc`: `sprinter-cc -o foo.exe foo.c` одной строкой
|
||||
- [x] Поддержка опций: `--memory`, `--memory-manual`, `--stack-size`, `--crt0=`, `--bank N=FILE.c`, `--debug`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
|
||||
|
||||
## Этап 8 — графика (320×256×256 + 640×256×16 + accel + bitmap font) ✅ ГОТОВО
|
||||
|
||||
- [x] **8a** Graphics core: `gfx_init`/`gfx_done`/`gfx_clear`/`gfx_putpixel`/`gfx_pal_load`/`gfx_pal_set` (libc/gfx/gfx_core.c). Палитра через BIOS PIC_SET_PAL ($A4). Verified 320×256×256.
|
||||
- [x] **8b** Линии/прямоугольники/fill через accelerator (libc/gfx/gfx_lines.c): `gfx_hline`/`gfx_vline` через accel Fill (LD C,C / LD E,E + SMC block-size), `gfx_rect`/`gfx_fill_rect` с heuristic выбором ориентации (h/v bursts count), `gfx_line` с Bresenham для диагоналей. `gfx_clear` тоже переписан на column-major accel (~4× быстрее).
|
||||
- [x] **8c** 640×256×16 mode (libc/gfx/gfx_16.c): `gfx_*16` API, HIGH nibble = LEFT pixel (документация misleading), per-row RMW для vline (один байт = 2 горизонтальных пикселя).
|
||||
- [x] **8d** Bitmap font + gfx_text (libc/gfx/gfx_text.c): шрифт через BIOS WIN_GET_ZG ($B8), interleaved layout `font[row*256+char]`, `gfx_text`/`gfx_putchar` для 320 mode, `gfx_text16`/`gfx_putchar16` для 640 mode с pair-table lookup.
|
||||
|
||||
См. memory/sprinter_graphics.md, sprinter_accelerator.md, sprinter_graphics_16.md, sprinter_font_format.md.
|
||||
|
||||
Открытые мелочи (не блокируют):
|
||||
- [ ] Шрифт-quad для 640: per-cell палитра (mode 0x82 разрешает 1 из 4 палитр per 16×8 cell) — через прямой доступ к area-описания экрана 0x0300..0x039F
|
||||
|
||||
## Auto-banking (см. `memory/banking_roadmap.md` для деталей)
|
||||
|
||||
Phase 1 — file-level bin-packing — реализовывать когда проект перерастёт ~30 KB кода.
|
||||
|
||||
- [ ] `toolchain/auto_bank.py`:
|
||||
- Парсит размеры из `.rel`-файлов (или из .map после dry-run link'а)
|
||||
- First-fit-decreasing bin-packing
|
||||
- Уважает `#pragma codeseg BANKn` как manual override
|
||||
- Перелинковывает с новыми `-Wl-b_BANKn=...` параметрами
|
||||
- Печатает план распределения
|
||||
|
||||
Phase 2-5: incremental rebalance, declarative `banks.toml`, function-level, call-graph-aware. Только если/когда понадобится.
|
||||
|
||||
## Bank-local static data (mutable data в том же банке что и код) — ✅ ГОТОВО
|
||||
|
||||
- [x] Пример `examples/bank_local_data/` — функция в BANK1 со своим writable BSS array + const table + malloc-тест
|
||||
- [x] `mkexe -p 0` для нулевого padding банков (BSS-storage обнуляется при загрузке)
|
||||
- [x] Канонический рецепт: `--codeseg BANK1 --constseg BANK1 --dataseg BANK1` для bank1.c + `-Wl-b_BANK1=0x1C000` для линковки. **`--dataseg BANK1` РАБОТАЕТ** — раньше казалось обратное из-за trampoline bug который маскировал результат.
|
||||
- [x] **Критичный фикс trampoline'a в runtime/bank.s** — старый `pop af; out (n), a` клобберил A → все banked-функции возвращающие uint8_t тихо возвращали мусор. Новый `pop bc; out (c), b` сохраняет A.
|
||||
- [x] **malloc из banked-функции работает прозрачно** — heap живёт в W2 (HOME), W2 никогда не свапается trampoline'ом, pointer валиден из любого контекста. См. memory/bank_local_data_pattern.md.
|
||||
- [x] Документация в memory: `memory/bank_local_data_pattern.md` (полный рецепт + malloc + nuances), `memory/sdcc_banking.md` (trampoline fix)
|
||||
- [ ] Опционально — расширить `check_banks.py` чтобы показывать разбивку size = code + const + bss per bank (cosmetic)
|
||||
|
||||
Зачем: для модулей с большим private state (level loader, audio engine, scene data). Экономит W2 heap для динамики, а статика остаётся в бэке.
|
||||
|
||||
## Подсказки из solid-c (нативный Sprinter C — `third_party/solid-c/`)
|
||||
|
||||
После анализа solid-c'овской libc (см. `memory/solid_c_findings.md`) выявлены готовые паттерны для следующих недостающих функций. Приоритет от **высокого** к низкому:
|
||||
|
||||
### High-priority gaps (легко портировать, большая польза)
|
||||
- [x] **`errno` + `strerror`/`perror`** — табличка 32 ошибок (libc/io/errno.c)
|
||||
- [x] **Расширенный `open()`** для O_CREAT/O_TRUNC/O_APPEND/O_EXCL state machine
|
||||
- [x] **`atexit`** — 8-callback LIFO + `exit()` + `_exit()` (libc/io/atexit.c)
|
||||
- [x] **`setjmp`/`longjmp`** — 6-байт jmp_buf={sp,ix,pc} (libc/io/setjmp.c)
|
||||
- [x] **`sleep(seconds)`** — 50Hz halt-loop (libc/io/sleep.c)
|
||||
- [x] **ESTEX ENV API** ($46, getenv/putenv) — libc/io/env.c. Учли doc-bug: реально A=0 это NOT FOUND
|
||||
|
||||
### Medium-priority (нужно для shell-like утилит)
|
||||
- [ ] **Mouse driver** — `rst $30h`, 17 функций. **Сначала тест что работает в MAME**.
|
||||
- [x] **`ffirst`/`fnext` + ffblk_t struct** для directory listing — реализовано, demo: ls.exe
|
||||
- [x] **`getdatetime`/`setdatetime`** через ESTEX $21/$22 — libc/io/time.c, demo: time_dir_test
|
||||
- [x] **`chdir`/`getcwd`/`mkdir`/`rmdir`** — wrappers для ESTEX $1B-$1E — libc/io/fsdir.c
|
||||
- [x] **conio: `kbhit`/`getch`/`getche`/`cputs`/`clrscr`/`gotoxy`** — реализовано
|
||||
- [x] **conio extras**: `wherex`/`wherey` ($53), `wrchar`/`rdchar` ($58/$57), `textmode_get/set` ($50/$51), `clrscr_attr` ($56) + COLOR macros
|
||||
|
||||
### Low-priority — ✅ FILE* stack ГОТОВО
|
||||
|
||||
- [x] **Минимальный unbuffered FILE\*** — `libc/stdio/file.c` + `libc/include/stdio.h`. fopen/fclose/fputs/fgets/fread/fwrite/fseek/ftell/rewind/feof/ferror/clearerr/fflush + stdin/stdout/stderr как pseudo-streams. См. `memory/file_star_design.md` и `examples/filetest`.
|
||||
- [ ] fprintf / fscanf — нужна printf-через-callback machinery. Пока пользователь может `sprintf(buf, ...) + fputs(buf, fp)`.
|
||||
- [ ] Опциональный buffered mode (setvbuf, line/block buffering) — если когда-то понадобится.
|
||||
|
||||
### POSIX time API — ✅ ГОТОВО
|
||||
- [x] `libc/io/posix_time.c` — time/localtime/gmtime/mktime/asctime/ctime поверх getdatetime. SDCC's time.rel избегаем (нельзя override _RtcRead). См. `examples/ptime`.
|
||||
|
||||
### sys/stat — ✅ ГОТОВО
|
||||
- [x] `libc/io/stat.c` — POSIX stat/fstat. Гибрид open+fstat для файлов, ffirst+iter для папок (включая "."/".."). См. `examples/stattest` и `memory/estex_ffirst_dotdot.md`.
|
||||
|
||||
### assert — ✅ ГОТОВО (используем SDCC's __assert через fallback include path)
|
||||
|
||||
## libc/stdlib — ✅ не нужно делать (см. memory/sdcc_stdlib_works.md)
|
||||
|
||||
Проверено через `examples/stdlib_test/`: SDCC z80.lib содержит работающие реализации:
|
||||
- `atoi/atol/atof, strtol/strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv`
|
||||
- Полный `<string.h>` (memchr/cmp/set/cpy, strcat/cmp/cpy/len/chr/spn/etc.)
|
||||
- `<ctype.h>` (toupper/tolower)
|
||||
- `<math.h>` (sinf/cosf/sqrtf/etc.)
|
||||
|
||||
Линкер автоматически тянет из z80.lib когда нужно. **НЕ переписывать**.
|
||||
|
||||
Наши Sprinter-specific обязательные модули остаются: atexit, env, errno, setjmp, putchar/puts/getchar, conio, fsdir, time, mouse, open/read/lseek/close.
|
||||
|
||||
## Build-system: libsprinter.lib + sprinter-cc — ✅ ГОТОВО
|
||||
|
||||
- [x] `lib/Makefile` — собирает каждый libc/*.c в `.rel`, архивирует через sdar в `lib/sprinter.lib`
|
||||
- [x] Включает runtime/bank.s и runtime/heap.s (auto-pulled при __banked/malloc)
|
||||
- [x] `bin/sprinter-cc` — bash-wrapper: `sprinter-cc -o foo.exe foo.c` одной строкой
|
||||
- [x] Поддержка опций `--crt0=default|minimal|banked`, `--bank N=FILE.c`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
|
||||
- [x] `examples/hello_sccc/` — демо: `hello.c` собирается за один shell-вызов, размер совпадает с ручным Makefile (925 байт)
|
||||
- [x] Split `putchar.c` → `putchar.c` + `puts.c` для per-function granularity (puts override SDCC's z80.lib version)
|
||||
- [x] Включён в `make all` (зависимость `lib` перед `examples`)
|
||||
|
||||
Возможные улучшения (опционально):
|
||||
- [ ] Мигрировать остальные examples на sprinter-cc вместо ручных Makefile (косметика)
|
||||
- [ ] Дальнейшая декомпозиция libc/*.c per-function (но текущая granularity уже даёт нужный размер — линкер пакетует .rel целиком, и для большинства файлов это одна функция)
|
||||
|
||||
## Этап 9 — memory modes для sprinter-cc
|
||||
|
||||
DSS выделяет страницы памяти по размеру приложения: < 16 KB → одна страница, в остальные окна подключается «страница #FF» (read=0xFF, write игнорится). Из-за этого CODE-в-W1 + DATA-в-W2 для маленькой программы молча ломается. См. [memory/sprinter_memory_modes.md](../../.claude/projects/-Volumes-SAM8-Projects-DIY-Z80-Sprinter-C-Compiler/memory/sprinter_memory_modes.md).
|
||||
|
||||
- [x] **`tiny`**: всё (CODE+DATA+стек) в W2. Default. Verified hello/argv/conio/malloc/file/etc.
|
||||
- [x] **`--memory MODE` флаг в sprinter-cc**: parser + per-mode дефолты CODE_LOC/DATA_LOC, override через явные `--code-loc`/`--data-loc`. tiny работает; small/big/huge компилируются с warning'ом (runtime не готов). Реализовано 2026-05-30.
|
||||
- [x] **`--memory-manual SPEC`**: парсит `CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3`. Реализовано 2026-05-30.
|
||||
- [x] **`small` runtime**: `runtime/crt0_small.s` использует ESTEX `$3D GETMEM` + `$3A SETWIN2` чтобы выделить и замапить W2-страницу ДО gsinit. **НЕ** BIOS `$C4` — стек на этом этапе в W1 (boot_stack в HOME), а BIOS требует стек в W2. После маппинга SP переключается на 0xBFFE, дальше стандартный flow. Реализовано 2026-05-30, verified hello.exe.
|
||||
- [x] **`small` auto-detect для >16 KB программ**: `crt0_small.s` читает порт `0xC2` (текущая страница в W2 — не `0xA2`! это W1). Если 0xFF — выделяет page; иначе DSS уже сделала это (программа сама вылезла в W2). Один crt0 покрывает 0..30 KB. mkexe также разрешает HOME span W1+W2 (0x4000..0xBFFF). Verified hello: small (5 KB файл, SETWIN2 path) + 32 KB файл (auto-skip). Реализовано 2026-05-30.
|
||||
- [x] **`big` runtime** (tiny + banked code в W1): параметризовали `crt0_banked.s` + `bank.s` через `.ifdef BANK_W1` — другой banking port (0xA2 vs 0xE2), другой load-addr (0x4000 vs 0xC000). sprinter-cc prepend'ит `BANK_W1 = 1` при `--memory big`, передаёт `mkexe -B 0x4000`. Пример `examples/banked_big/`. Реализовано 2026-05-30.
|
||||
- [x] **`huge` runtime** (small + banked code в W3): merge W2-detect логики из `crt0_small.s` в `crt0_banked.s`. Существующий пример `examples/banked/` теперь использует MEMORY=huge. Реализовано 2026-05-30.
|
||||
- [x] **`--debug` флаг**: prepend `DEBUG_RT = 1` в crt0 + `-DDEBUG_RT` в sdcc. Открывает symbol `_w2_self_allocated` (uint8_t) — runtime diagnostic кто аллоцировал W2. Реализовано 2026-05-30.
|
||||
|
||||
### Дизайн-решения по libc и crt0
|
||||
|
||||
**Одна `sprinter.lib`** работает для всех memory mode — `.rel`-члены relocatable, SDLD делает dead-code elimination per-member (без графики не подтягивает `gfx_core.rel` и т.д.). Verified hello vs malloc_test через map-файлы.
|
||||
|
||||
**`gfx.lib` отдельно — НЕ нужен**: dead-code elimination уже работает.
|
||||
|
||||
**`libc_banked` (libc в bank вместо HOME)** — идея на потом, когда HOME (16 KB) забит user-кодом + libc в `huge` mode. Реализуется через `--codeseg BANK0` при компиляции libc; trade-off: trampoline ~30 циклов на каждый libc-вызов. Триггер: реальная программа упрётся в HOME budget.
|
||||
|
||||
**HW-зависимые модули — `sprinter_home.lib` отдельно.** Часть libc физически не может быть забанкована в W3, потому что она РАБОТАЕТ с W3:
|
||||
- `gfx_*` — пишет в видеопамять `0xC000+` после swap W3 на video page
|
||||
- `bank_io` (mem_alloc_pages/bank_read/bank_write) — swap'ит W3 через `OUT (0xE2)`
|
||||
- Будущие ISR — прерывание может прийти когда W3 на чём угодно
|
||||
|
||||
В huge mode эти модули ДОЛЖНЫ остаться в HOME (W1). Когда будем делать `libc_banked`, **одновременно** выделяем `sprinter_home.lib` (HOME-only) из `sprinter.lib` (bankable). Финальная схема:
|
||||
```
|
||||
sprinter_home.lib HOME-only: gfx, bank_io, ISR shims
|
||||
sprinter.lib bankable: printf, malloc, string, conio, stdio, env, ...
|
||||
sprinter_banked.lib тот же sprinter.lib но --codeseg BANK0 (для huge)
|
||||
```
|
||||
Триггер: реализация `--memory huge` runtime.
|
||||
|
||||
**crt0 — по одному на mode:**
|
||||
- `crt0.s` — текущий, для **tiny/big**: SP=0xBFFE, парсит argv (W2-ресурс уже выделен DSS).
|
||||
- `crt0_minimal.s` — текущий, для tiny без argv.
|
||||
- `crt0_small.s` — **новый, step 3**: для **small/huge**, аллоцирует W2 через `mem_alloc_pages` ДО gsinit, маппит в порт `0xA2`, потом стандартный flow.
|
||||
- `crt0_banked.s` — текущий, для **big**: trampoline-таблица для W3 банков, CODE в W2.
|
||||
- `crt0_banked_small.s` — **новый**: huge = small (W2-alloc) + banked (W3 trampolines).
|
||||
|
||||
sprinter-cc подбирает crt0 по `--memory` mode (сейчас `--crt0=` это override).
|
||||
- [x] **Настраиваемый размер стека**: флаг `sprinter-cc --stack-size BYTES`. Wrapper генерирует `heap_top.s` с `___sdcc_heap_end = stack_top + 1 - stack_size`, отдельный .rel линкуется per-program. Default ≈1278 байт (heap_top=0xBB00) из `runtime/heap_top.s`. Реализовано 2026-05-30.
|
||||
|
||||
Интерфейс: `sprinter-cc --memory [tiny|small|big|huge|manual] [--memory-manual SPEC] [--stack-size N] foo.c`. `--memory-manual` имеет смысл только с `--memory manual`.
|
||||
|
||||
## Known issues / quirks
|
||||
|
||||
- **ESTEX $46 ENV API**: ✅ работает. Док-ция в `DiskSyscalls.txt v1.6` ошибочно описывает return-status — A=0 это NOT FOUND, не FOUND. Зафиксировано в `memory/sprinter_platform.md`.
|
||||
|
||||
## ОБЯЗАТЕЛЬНЫЕ ЗАДАЧИ ДЛЯ V2 (после релиза v1)
|
||||
|
||||
### Turbo-C-style graphics API (BGI-like) — **MUST для v2**
|
||||
|
||||
Расширить наш `gfx_*` API до уровня **Turbo-C `<graphics.h>`** (BGI) для MS-DOS.
|
||||
Программисты привыкшие к Turbo-C должны переносить графический код 1-в-1.
|
||||
|
||||
**Что должно быть** (на основе Borland BGI):
|
||||
|
||||
Setup/teardown:
|
||||
- `initgraph()` / `closegraph()` — у нас сейчас `gfx_init`/`gfx_done`, добавить alias
|
||||
- `getmaxx()` / `getmaxy()` — макрос на GFX_WIDTH-1 / GFX_HEIGHT-1
|
||||
- `cleardevice()` — alias to gfx_clear
|
||||
- `getgraphmode()` / `setgraphmode()` — у нас get_videomode/set_videomode
|
||||
|
||||
Color/palette:
|
||||
- `setcolor(c)`, `getcolor()` — current draw color
|
||||
- `setbkcolor(c)`, `getbkcolor()` — background color
|
||||
- `setpalette(idx, c)` — палитра entry
|
||||
- `getpalette(&info)` — read all palette
|
||||
|
||||
Primitives (мы уже имеем эквиваленты — добавить BGI-имена как aliases):
|
||||
- `putpixel(x, y, c)` — есть как gfx_putpixel
|
||||
- `getpixel(x, y)` — нужно реализовать (RMW обратное — IN)
|
||||
- `moveto(x, y)`, `lineto(x, y)`, `linerel(dx, dy)` — current point + line drawing
|
||||
- `line(x1, y1, x2, y2)` — есть как gfx_line
|
||||
- `rectangle(x1, y1, x2, y2)` — есть как gfx_rect (но другой API: x1,y1,x2,y2 vs x,y,w,h!)
|
||||
- `bar(x1, y1, x2, y2)` — есть как gfx_fill_rect
|
||||
- `bar3d(x1, y1, x2, y2, depth, topflag)` — новое: rect + 3d edges
|
||||
- `circle(x, y, r)`, `arc(...)`, `ellipse(...)`, `pieslice(...)` — новые primitives
|
||||
- `fillpoly()`, `drawpoly()` — полигоны
|
||||
- `floodfill(x, y, border_color)` — заливка
|
||||
|
||||
Text on graphics screen:
|
||||
- `outtext(s)` / `outtextxy(x, y, s)` — есть как gfx_text (alias)
|
||||
- `settextstyle(font, dir, size)` — multiple bitmap fonts
|
||||
- `gettextsettings(&info)`
|
||||
- `textwidth(s)` / `textheight(s)` — measure
|
||||
|
||||
Image manipulation:
|
||||
- `imagesize(x1, y1, x2, y2)` — bytes needed for getimage
|
||||
- `getimage(x1, y1, x2, y2, buf)` — save rect to buffer
|
||||
- `putimage(x, y, buf, op)` — paste back with COPY_PUT/XOR_PUT/AND_PUT/OR_PUT/NOT_PUT
|
||||
|
||||
Clipping/viewport:
|
||||
- `setviewport(x1, y1, x2, y2, clip)` — drawing clip rect
|
||||
- `getviewsettings(&info)`
|
||||
- `clearviewport()`
|
||||
- `setactivepage(p)` / `setvisualpage(p)` — двойная буферизация (Sprinter имеет 2 screen)
|
||||
|
||||
Line style:
|
||||
- `setlinestyle(style, pattern, thickness)` — SOLID_LINE / DOTTED_LINE / etc.
|
||||
- `getlinesettings(&info)`
|
||||
|
||||
**Acceptance:** перенос типичной Turbo-C BGI программы (рисующей с использованием
|
||||
moveto/lineto/circle/bar/setcolor) должен работать без существенных правок.
|
||||
|
||||
**Notes:**
|
||||
- BGI fonts (TRIPLEX/SANS_SERIF/GOTHIC) — у нас один BIOS font, остальные нужно
|
||||
добавить (как bitmap data в lib)
|
||||
- imagesize/getimage/putimage — самые востребованные для game/animation
|
||||
- Active/visual page (двойная буферизация) — Sprinter поддерживает 2 graphics pages,
|
||||
нужен API switching
|
||||
|
||||
См. также `examples/` Turbo C 2.x BGIDEMO как reference что нужно.
|
||||
|
||||
### IM2 Interrupt Handlers — **MUST для v2**
|
||||
|
||||
User-задаваемые ISR через Z80 IM 2 mode. Нужны для:
|
||||
- Timer ticks (50 Hz frame counter, плавная анимация)
|
||||
- Music playback (AY, COVOX)
|
||||
- Real-time games (input + game logic + render в interrupt-driven)
|
||||
- Async keyboard / mouse handling
|
||||
|
||||
**Status:** ОТЛОЖЕНО до v2. Полный research + design в `docs/im2_isr_design.md`.
|
||||
|
||||
**Решение по архитектуре:** реализовать как отдельный memory mode `--memory im2`
|
||||
(вместо того чтобы лезть во все существующие crt0). Detail'и в design-doc.
|
||||
|
||||
**Резюме research'а** (полный текст в `docs/im2_isr_design.md`):
|
||||
- Vector 0xFF — frame + keyboard + CBL. Disambiguation по портам 0x19 / 0xFE
|
||||
- Mouse hardware-IRQ не приходит (на текущей плате)
|
||||
- Vector table / ISR / stack ОБЯЗАНЫ быть в W2 (0x8000..0xBFFF)
|
||||
- DSS имеет свой IM 2 handler — нужно chain'иться (иначе клавиатура / SYSTIME ломаются)
|
||||
|
||||
### Прочие крупные пункты для v2
|
||||
|
||||
- [ ] **FILE API rewrite — buffered streams** — текущая реализация в
|
||||
`libc/stdio/file.c` это provisional unbuffered shim (каждый fputc/fgetc
|
||||
= один read/write syscall). Нужна полноценная buffered семантика
|
||||
как в Solid-C:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint flags; // +0..1 file status flags
|
||||
int level; // +2..3 empty/fill level of buffer
|
||||
char *curp; // +4..5 current active pointer
|
||||
int fd; // +6..7 underlying low-level fd
|
||||
char *buffer; // +8..9 data transfer buffer
|
||||
char hold; // +10 ungetc byte if no buffer
|
||||
short token; // +11..12 reserved
|
||||
char dummy; // +13 reserved
|
||||
} FILE;
|
||||
```
|
||||
|
||||
stdin/stdout/stderr — fd-маркеры `0 / -1 / -2`. Отрицательные для
|
||||
stdout/stderr выбраны намеренно: ESTEX OPEN может вернуть positive
|
||||
small fd (1, 2, …) для обычного файла → если бы stdout=1, реальный
|
||||
fd=1 сталкивался бы с идентификатором. fd=0 для stdin безопасно
|
||||
(ESTEX 0 не возвращает). Сами fd не передаются в syscall'ы —
|
||||
диспетчеризация по флагам `_F_CONIN/_F_CONOUT`.
|
||||
|
||||
Принтер-потоки (stdaux/stdprn) НЕ реализуем — Sprinter принтерной
|
||||
API не имеет.
|
||||
|
||||
Альтернатива — взять реализацию из third_party/solid-c (sources в
|
||||
`SRC/CLIB/`); там есть готовый buffered FILE + fopen/fread/fwrite/
|
||||
fseek/setvbuf и т.д. Адаптировать к нашим open/read/write/lseek.
|
||||
|
||||
При rewrite заодно решить deferred issues stdio-review:
|
||||
- `fwrite` short-write должен ставить `_F_ERROR`
|
||||
- `fgets(buf, 1, fp)` — стандарт говорит "empty string", мы вернули NULL
|
||||
- `mode_to_flags` — break-out на '+' (cosmetic)
|
||||
|
||||
- [ ] **Audio API** — AY-3-8910 + COVOX через прерывания (требует IM2)
|
||||
- [ ] **ISA-8 slot support** — ZX-Bus карты (sound, network, etc.) — требует IM2 + чтения portов
|
||||
|
||||
## Прочие задачи (v1 backlog, не блокирующие)
|
||||
|
||||
- [x] **#9: text I/O split (Turbo-C style)** — stdio (puts/printf/putchar) теперь fast no-attr через PCHARS/PUTCHAR. conio (cputs/cprintf/putch) применяет attr через textcolor/textbackground/textattr. KEEP_EXIST_ATTR → conio fallback на fast path. Verified в hello.exe. См. `memory/text_output_api_split.md`. Реализовано 2026-05-31.
|
||||
- [x] **Mouse API полный** (резидентный driver, RST 30h) — все 14 функций обёрнуты (init/show/hide/refresh/read/goto/bounds/text_cursor/load_cursor/get_cursor/get/set_sensitivity/video_mode_changed). См. `memory/mouse_api.md`. Verified в MAME 2026-05-31. Sensitivity = divider (меньше = быстрее).
|
||||
- [ ] Interrupt handlers — IM 2 vector table в HOME для user ISR'ов
|
||||
- [ ] Поддержка `restore SP on EXIT` (паттерн из z88dk +pps) — проверить нужно ли
|
||||
- [ ] CI: автоматически запускать MAME с `-aviwrite` для screenshot-сравнения, чтобы тесты примеров проходили без человека
|
||||
|
||||
## Идеи на потом
|
||||
|
||||
- Поддержка `<setjmp.h>` (есть в SDCC stdlib — нужно протестировать что наш crt0 совместим)
|
||||
- `<time.h>` через ESTEX SYSTIME (`$21`) и CMOS BIOS-функции
|
||||
- ZX Spectrum-совместимый режим как отдельный target (для портирования спектрумовских программ)
|
||||
- Поддержка ZX-Bus карт (sound, network, etc.) — нужны драйверы
|
||||
- Profile-guided optimization tools (hot/cold detection) для крупных программ
|
||||
|
||||
## Linker duplicate-symbol warnings (благоприятные, отфильтрованы)
|
||||
|
||||
Когда мы сознательно overrides'им SDCC z80.lib функции собственной версией в `sprinter.lib`, `sdldz80` пишет `?ASlink-Warning-Definition of public symbol '...' found more than once`. Линкер берёт первое найденное определение (наше), поэтому поведение корректное — warning только noise.
|
||||
|
||||
Текущие overrides:
|
||||
- `_puts` — наша версия через PCHARS+\r\n vs SDCC posix puts
|
||||
- `___sdcc_heap` — наш heap в W2 vs SDCC's стандартный
|
||||
- `_asctime`, `_localtime` (и возможно другие из time) — наш `posix_time.c` через ESTEX SYSTIME vs SDCC's `time.rel` который зависит от `_RtcRead`
|
||||
|
||||
**Текущее решение:** `bin/sprinter-cc` отфильтровывает warning-блок (warning + 2 follow-up `Library:` строки) из вывода `sdcc`. Через `-v` (verbose) всё показывается. Реализовано через awk-pipe.
|
||||
|
||||
**Возможные улучшения:**
|
||||
- Перейти на explicit `--nostdlib` + ручной список нужных модулей из z80.lib (string, math, stdlib без override'нутых) — убрать ИСТОЧНИК warning'ов, не маскировать
|
||||
- Или: переименовать наши `_puts` → `_puts_sprinter` + alias через linker flag (не уверен что SDCC поддерживает)
|
||||
- Или: оставить как сейчас (рабочее и benign) — приоритет низкий
|
||||
|
||||
## TODO: проверить на реальном железе
|
||||
|
||||
- [ ] **Port_Y banking trick** (`docs/part2/SprinterGraphics programming.txt`):
|
||||
доку утверждает что после `OUT (0x89), Y` адреса 0xC000+0x400*N в окне W3
|
||||
маппятся на строки Y..Y+15 (одно программирование → 16 строк).
|
||||
Empirical 2026-06-01 в MAME 0.283 этот trick **не работает** — пиксели
|
||||
по адресам выше 0xC000+row_width уходят в невидимую область. Канонический
|
||||
`docs/samples/plasma2.asm` тоже не использует banking, переустанавливает
|
||||
Port_Y per row.
|
||||
План:
|
||||
1. Получить доступ к реальному Sprinter
|
||||
2. Запустить тест dual-write (`_gfx_putpixel_raw` + второй write в `0xD000+x`)
|
||||
3. Если на железе видны двойные линии → бага MAME, открыть issue с
|
||||
минимальным репро
|
||||
4. Если на железе тоже одна линия → документ неверный, удалить упоминание
|
||||
из доки и просто оставить текущую реализацию (Port_Y per pixel)
|
||||
5. Если banking работает на железе → внедрить кэширование Port_Y в
|
||||
`_gfx_putpixel_raw` (sentinel out-of-range, см. memory/gfx_port_y_banking.md)
|
||||
|
||||
Связанный выигрыш для Bresenham (60-pixel диагональ) — около 8× меньше
|
||||
OUT (0x89) операций, для `gfx_fill_rect 320x256` — 16× меньше. Не блокирует
|
||||
release v1.
|
||||
|
||||
## GFX: расширения по `docs/part2/accelerator_doc.txt`
|
||||
|
||||
После прочтения детального accelerator doc выявлены незакрытые направления.
|
||||
Сейчас в коде используется только горизонтальный/вертикальный Fill mode.
|
||||
|
||||
### Quick wins для текущих primitives
|
||||
|
||||
- [ ] **Заменить SMC на `LD A, (var)` для block-size**. Документ явно
|
||||
разрешает `LD A, (HL)`, `LD A, (BC)`, `LD A, (DE)` (но не `LD A, r`).
|
||||
Это уберёт SMC complexity в `gfx_lines.c:hfill_chunk/vfill_chunk` и
|
||||
`gfx_16.c:g16_hfill_chunk`. Запрещено только register-to-register.
|
||||
- [ ] **Кэширование block-size**. Документ показывает что accel запоминает
|
||||
block size между bursts (см. `Horizontal_Line_Fill`: устанавливают
|
||||
size + `LD B,B` отключение, потом включают Fill mode и используют
|
||||
сохранённый size). Для `gfx_fill_rect` с 100 одинаковыми
|
||||
строками — установить size 1 раз, а не 100.
|
||||
|
||||
### Bank-prefix modes (port 0xE2 bits)
|
||||
|
||||
Документ показывает три варианта банка видеостраницы помимо стандартного 0x50:
|
||||
|
||||
| Bank byte | Effect |
|
||||
|---|---|
|
||||
| 0x50 | Normal write — пишется в shadow + видимый |
|
||||
| 0x54 | "no copy in main shadow RAM" |
|
||||
| 0x58 | **"FF is transparent"** — байт 0xFF при write оставляет background |
|
||||
| 0x5C | both |
|
||||
|
||||
Bank 0x58 объясняет почему mouse cursor рисуется с 0xFF-прозрачностью.
|
||||
Это путь к **sprite-blending через accel block copy**:
|
||||
|
||||
- [ ] **`gfx_set_bank_transparent(on)`** или флаг в `gfx_set_bank` для
|
||||
выбора 0x50/0x58 при отрисовке sprite'ов
|
||||
- [ ] Использовать в новом `gfx_blit()` чтобы по факту получать
|
||||
transparent sprites через accel-копию
|
||||
|
||||
### Block copy mode (sprite blit'ы)
|
||||
|
||||
`LD L,L` (horizontal) и `LD A,A` (vertical) — режим копирования блока через
|
||||
256-байтную accel memory. Это базис для blit'ов.
|
||||
|
||||
- [ ] **`gfx_blit(src_data, x, y, w, h)`** — копирование sprite'а
|
||||
(произвольный размер, через accel)
|
||||
- [ ] **`gfx_blit_transparent(src, x, y, w, h)`** — с использованием bank 0x58
|
||||
|
||||
См. `Draw_Restangle_Data` в accelerator_doc.txt как референс.
|
||||
|
||||
### AND / OR / XOR operations через accel
|
||||
|
||||
Документ показывает что accel поддерживает логические операции с блоками
|
||||
данных. Применения:
|
||||
- XOR — инверсия области (выделение selection в UI)
|
||||
- OR / AND — masking, alpha-style blending
|
||||
- См. пример в accelerator_doc.txt: "256 bytes block coding via XOR"
|
||||
|
||||
- [ ] **`gfx_xor_rect`** / **`gfx_or_rect`** / **`gfx_and_rect`** —
|
||||
примитивы логических операций над прямоугольником
|
||||
- [ ] **`gfx_invert_rect(x, y, w, h)`** — alias на xor с 0xFF
|
||||
|
||||
### Bitmap fonts разных размеров
|
||||
|
||||
Сейчас `gfx_text` / `gfx_putchar` хардкоженно работают с 8×8 шрифтом
|
||||
(BIOS WIN_GET_ZG возвращает 256×8 байт). Для будущих UI / титульников
|
||||
нужны:
|
||||
- [ ] **`gfx_set_font_size(w, h)`** — переключить ширину/высоту glyph'а
|
||||
- [ ] **`gfx_set_font_data(ptr, w, h, advance)`** — заменить указатель
|
||||
на пользовательский шрифт + размеры
|
||||
- [ ] Поддержка **proportional** (advance != w) шрифтов — добавить
|
||||
array advance[256] на ширину каждого glyph'а
|
||||
- [ ] **Big-font режимы**: 8×16, 16×16, 16×8 (для титульников)
|
||||
- [ ] Возможно отдельный API `gfx_text_ex(x, y, str, font_id)` где
|
||||
font_id выбирает один из загруженных шрифтов
|
||||
- [ ] **Anti-alias 2-bit шрифты** (бит фон / бит граница / 2-бит alpha?)
|
||||
— far future, для smooth UI
|
||||
|
||||
## Финальный этап оптимизаций (не сейчас)
|
||||
|
||||
- **`gfx_line` через accel для пологих диагоналей** — Bresenham для линии с |dy| << |dx| (или наоборот) выдаёт длинные runs одинакового Y (или X): пиксель, пиксель, пиксель, шаг Y, пиксель, пиксель... Каждый такой run — это готовый аргумент для `gfx_hline` (или `vline`).
|
||||
План исследования: посчитать длину runs как функцию от наклона; решить минимальный run length, при котором выгоднее accel hline чем N×putpixel (overhead accel ~20µs, putpixel ~5µs — accel выгоднее при run ≥ 4-5 px); для крутых диагоналей (dx ≈ dy) оставить Bresenham, для пологих — run-length-based fill.
|
||||
Сейчас `gfx_line` orthogonal cases уже через accel — оптимизировать только косые.
|
||||
|
||||
- **`gfx_fill_rect` с одним W3-swap на всю операцию** — сейчас каждый внутренний `gfx_hline`/`gfx_vline` делает свой DI/save-W3/restore-W3/EI. Можно сделать internal `_fill_rect_inner` который держит W3 замапленным и DI весь цикл; ~20µs × количество строк/столбцов экономии. Применимо ко всем композитным примитивам.
|
||||
+24
-16
@@ -8,37 +8,45 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mdview2
|
||||
EXTRA_SRCS := mdview2_raw.c mdview2_hex.c mdview2_help.c mdview2_table.c mdview2_enc.c mdview2_md.c mdview2_status.c mdview2_index.c
|
||||
MEMORY := small
|
||||
# --max-allocs 100000: агрессивнее аллокация регистров SDCC — минус ~850 байт
|
||||
# кода на всём приложении (замер 2026-07-04) ценой более долгой компиляции.
|
||||
EXTRA_FLAGS := --max-allocs 100000
|
||||
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Образ дискеты: только mdview2.exe + README.MD (перекодированный
|
||||
# из UTF-8 в CP866 — рабочую кодировку Sprinter).
|
||||
#
|
||||
# README.MD хранится в репозитории в UTF-8; iconv -c конвертирует
|
||||
# его в CP866, отбрасывая символы без аналога в целевой кодировке.
|
||||
# Результат кладётся в .disk_tmp/README.MD, чтобы make_disk.py
|
||||
# использовал правильное имя файла на диске.
|
||||
# Образ дискеты: mdview2.exe + документация. Каждый документ — в СВОЕЙ
|
||||
# кодировке, чтобы дискета заодно покрывала все пути автодетекта:
|
||||
# README.MD — документация, ОРИГИНАЛЬНЫЙ UTF-8 (как в репозитории);
|
||||
# CHANGES.MD — история версий, CP866 (хранится так, кладётся как есть;
|
||||
# имя укорочено: "CHANGELOG" не влезает в FAT 8.3);
|
||||
# DEMO.MD — демонстрация разметки, конвертируется UTF-8 → CP1251.
|
||||
#
|
||||
# iconv -c возвращает ненулевой код, если хоть один символ отброшен
|
||||
# (даже с -c) — это ОЖИДАЕМО при потере символов без аналога в CP866,
|
||||
# не ошибка конвертации; вывод при этом всё равно корректно записан.
|
||||
# (даже с -c) — это ОЖИДАЕМО при потере символов без аналога в целевой
|
||||
# кодировке, не ошибка конвертации; вывод при этом корректно записан.
|
||||
# Поэтому код возврата iconv игнорируется (|| true).
|
||||
#
|
||||
# Тестовые файлы лимита 256 КБ лежат в testfiles/ (генератор gen_testfiles.py);
|
||||
# на диск НЕ кладутся — при необходимости добавить вручную в список ниже.
|
||||
# ------------------------------------------------------------------
|
||||
DISK_TMP := .disk_tmp
|
||||
README_DISK := $(DISK_TMP)/README.MD
|
||||
DEMO_DISK := $(DISK_TMP)/DEMO.MD
|
||||
CHLOG_DISK := $(DISK_TMP)/CHANGES.MD
|
||||
|
||||
$(DISK_TMP):
|
||||
mkdir -p $@
|
||||
|
||||
$(README_DISK): README.MD | $(DISK_TMP)
|
||||
iconv -c -f UTF-8 -t CP866 README.MD > $@ || true
|
||||
$(CHLOG_DISK): CHANGELOG.md | $(DISK_TMP)
|
||||
cp CHANGELOG.md $@
|
||||
|
||||
# UTF8TEST.MD кладётся на диск КАК ЕСТЬ (в UTF-8, без перекодировки) —
|
||||
# это тестовый вход для проверки UTF-8 рендеринга (Фаза 2 кодировок).
|
||||
floppy: $(EXAMPLE).exe $(README_DISK)
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK) UTF8TEST.MD
|
||||
$(DEMO_DISK): DEMO.MD | $(DISK_TMP)
|
||||
iconv -c -f UTF-8 -t CP1251 DEMO.MD > $@ || true
|
||||
|
||||
floppy: $(EXAMPLE).exe $(DEMO_DISK) $(CHLOG_DISK)
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe README.md $(DEMO_DISK) $(CHLOG_DISK)
|
||||
@echo
|
||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# MDView v1.0 (b3) — просмотрщик Markdown для Sprinter
|
||||
|
||||
**MDView** — программа просмотра документов Markdown для компьютера
|
||||
**Sprinter-2000** (Z80, ОС ESTEX). Файл загружается в расширенную память (EMM)
|
||||
и один раз «прогоняется» через парсер: готовые к показу строки (пары
|
||||
символ+атрибут) складываются в **рендер-кэш** в EMM, после чего прокрутка в
|
||||
любую сторону — это просто копирование готовых строк на экран, без повторного
|
||||
парсинга. Даже на файлах в сотни килобайт листание остаётся мгновенным.
|
||||
|
||||
Текстовый режим 80×32, цветное оформление элементов разметки, три режима
|
||||
просмотра (MD / RAW / HEX) и четыре кодировки с автоопределением.
|
||||
|
||||
---
|
||||
|
||||
## Возможности
|
||||
|
||||
* **Markdown-рендеринг** с цветовым оформлением:
|
||||
* заголовки `#`…`######` (H5/H6 отображаются как H4);
|
||||
* **жирный** (`**текст**`), *курсив* (`*текст*`), подчёркнутый (`_текст_`),
|
||||
~~зачёркнутый~~ (`~~текст~~`), `встроенный код` (`` `текст` ``);
|
||||
* экранирование `\*`, `\_`, `` \` `` и любой ASCII-пунктуации;
|
||||
* чекбоксы `[x]` / `[ ]` в списках;
|
||||
* ненумерованные (`-`, `*`, `+`) и нумерованные (`1.`, `1)`) списки
|
||||
с базовой вложенностью по отступам;
|
||||
* цитаты `>` (склейка многострочных, маркер │ на переносах);
|
||||
* fenced-блоки кода ` ``` ` (без переносов, горизонтальный скролл);
|
||||
* таблицы `| … | … |` — рисуются псевдографической рамкой, ширины колонок
|
||||
вычисляются по содержимому (до 16 колонок);
|
||||
* горизонтальные разделители `---` / `***` / `___`;
|
||||
* жёсткие переносы (два пробела или `\` в конце строки);
|
||||
* мягкая склейка абзацев с переносом по словам под ширину экрана.
|
||||
* **Кодировки: CP866, CP1251, KOI8-R, UTF-8.**
|
||||
* автоопределение при открытии (BOM → UTF-8; валидность multibyte-структуры;
|
||||
частотный анализ ходовых русских букв для 8-битных);
|
||||
* переключение по кругу клавишей **F8** в любой момент;
|
||||
* 8-битные кодировки отличаются только перекодировкой глифов на отрисовке —
|
||||
переключение мгновенно;
|
||||
* UTF-8 декодируется в CP866 в **отдельный набор** (файл + индекс + кэш);
|
||||
второй набор готовится **в фоне**, пока вы читаете документ, — обычно
|
||||
к первому нажатию F8 он уже построен и переключение мгновенно,
|
||||
с сохранением позиции. Если фон не успел, F8 докручивает начатую
|
||||
сборку (со спиннером), а не начинает её заново.
|
||||
* **Три режима просмотра:**
|
||||
* **MD** — форматированный Markdown (по умолчанию);
|
||||
* **RAW** (**F2**) — исходный текст без разметки: перенос строк кратно 80
|
||||
(**F3** — режим панорамы с горизонтальным скроллом);
|
||||
* **HEX** (**F4**) — дамп *оригинального* файла:
|
||||
`0x012340 │ 16 байт hex │ 16 печатных символов`. Печатная колонка
|
||||
интерпретируется текущей кодировкой; для UTF-8 глиф ставится на позиции
|
||||
лид-байта, continuation-байты показываются точкой.
|
||||
* **Единая позиция** при любых переключениях: MD ↔ RAW ↔ HEX и смена
|
||||
кодировки сохраняют текущее место в документе (между наборами разного
|
||||
размера — пропорционально, с точностью до строки).
|
||||
* **Прогрессивная загрузка**: первый экран показывается сразу, индексация
|
||||
продолжается в фоне; по готовой части документа уже можно листать,
|
||||
**Esc**/**F10** прерывают загрузку.
|
||||
* **Фоновая работа незаметна**: второй набор кодировки строится только в
|
||||
паузах между клавишами (после нажатия выдерживается пауза), поэтому
|
||||
скролл — в том числе с автоповтором — не теряет плавности.
|
||||
|
||||
---
|
||||
|
||||
## Запуск
|
||||
|
||||
```
|
||||
MDVIEW2.EXE <файл.md>
|
||||
```
|
||||
|
||||
Без аргумента открывается `README.MD` из текущего каталога.
|
||||
|
||||
## Клавиши
|
||||
|
||||
| Клавиша | Действие |
|
||||
|--------------|-------------------------------------------------------------|
|
||||
| ↑ / ↓ | прокрутка на одну строку |
|
||||
| PgUp / PgDn | прокрутка на экран (30 строк) |
|
||||
| Home / End | в начало / в конец документа |
|
||||
| ← / → | горизонтальный сдвиг: код/таблицы в MD, панорама в RAW |
|
||||
| F1 | справка |
|
||||
| F2 | RAW-режим ↔ MD |
|
||||
| F3 | в RAW: перенос строк ↔ панорама |
|
||||
| F4 | HEX-режим ↔ прежний вид |
|
||||
| F8 | кодировка: CP866 → CP1251 → KOI8-R → UTF-8 → … |
|
||||
| Esc / F10 | выход (во время загрузки — прервать её) |
|
||||
|
||||
Статус-бар (верхняя строка): имя файла, кодировка, диапазон видимых строк
|
||||
и процент прокрутки. Нижняя строка — меню доступных F-клавиш.
|
||||
|
||||
---
|
||||
|
||||
## Ограничения
|
||||
|
||||
| Параметр | Значение |
|
||||
|---------------------------------|-------------------------------------------|
|
||||
| Размер файла | до 256 КБ (больший — обрезается с предупреждением) |
|
||||
| Логических строк (после переносов) | до 18 432 |
|
||||
| Длина строки в рендер-кэше | 255 ячеек (RAW/HEX ограничения не имеют) |
|
||||
| Колонок в таблице | до 16 |
|
||||
| Шаг табуляции | 4 |
|
||||
|
||||
При исчерпании любого лимита документ завершается строкой-сообщением
|
||||
с указанием причины обрыва; всё, что вошло, доступно для просмотра.
|
||||
|
||||
## Требования
|
||||
|
||||
* Sprinter-2000 с ОС ESTEX;
|
||||
* расширенная память (EMM): в худшем случае (файл 256 КБ + оба набора
|
||||
кодировок) — до ~150 страниц по 16 КБ (~2,4 МБ). Для типичных файлов
|
||||
в десятки килобайт достаточно нескольких десятков страниц.
|
||||
|
||||
Код, данные, стек и куча программы занимают окна W1+W2 (32 КБ, режим
|
||||
памяти `small`); окно W3 используется только для доступа к EMM-страницам.
|
||||
|
||||
---
|
||||
|
||||
## Сборка
|
||||
|
||||
Требуется тулчейн этого репозитория (обёртка `sprinter-cc` над SDCC 4.5).
|
||||
Из каталога `examples/mdview2`:
|
||||
|
||||
```
|
||||
make # собрать mdview2.exe
|
||||
make floppy # собрать и упаковать дискету для MAME (mc.img)
|
||||
make run # floppy + запуск MAME
|
||||
```
|
||||
|
||||
На дискету кладутся: `MDVIEW2.EXE` и три документа, каждый в своей кодировке
|
||||
(заодно покрывают все пути автодетекта): `README.MD` — этот файл, как есть
|
||||
(UTF-8); `DEMO.MD` — демонстрация всех элементов разметки (CP1251);
|
||||
`CHANGES.MD` — история версий (CP866).
|
||||
|
||||
## Структура исходников
|
||||
|
||||
| Файл | Назначение |
|
||||
|------------------|--------------------------------------------------------------|
|
||||
| `mdview2.c` | ядро: EMM-аллокации, рендер-кэш, наборы кодировок, загрузка файла, главный цикл |
|
||||
| `mdview2_index.c`| парсер/индексатор Markdown — единственный проход по файлу |
|
||||
| `mdview2_md.c` | MD-вид: отрисовка из кэша, прокрутка |
|
||||
| `mdview2_raw.c` | RAW-вид (F2/F3) |
|
||||
| `mdview2_hex.c` | HEX-вид (F4) |
|
||||
| `mdview2_enc.c` | кодировки: детект, ремап-таблицы, конвертер UTF-8 → CP866 |
|
||||
| `mdview2_table.c`| отрисовка таблиц |
|
||||
| `mdview2_status.c`| статус-бар, меню, спиннер |
|
||||
| `mdview2_help.c` | справка (F1) |
|
||||
| `mdview2_conf.h` | конфигурация: `WITH_RAW` / `WITH_HEX` (модули отключаемы) |
|
||||
| `mdview2.h` | общие константы, атрибуты, межмодульный API |
|
||||
|
||||
Подробности архитектуры — в `docs/mdview2-plan.md`.
|
||||
|
||||
## Лицензия и авторы
|
||||
|
||||
© 2026 Петров А.Г. Часть проекта Sprinter C Compiler
|
||||
(см. LICENSE в корне репозитория).
|
||||
+634
-2060
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* mdview2.h — общие определения и интерфейс между модулями mdview2.
|
||||
*
|
||||
* Монолит mdview2.c расщеплён на ядро + модули: index (парсер), md/raw (виды),
|
||||
* enc (кодировки), table, help. Здесь — разделяемые константы экрана/атрибутов,
|
||||
* словари флагов/стилей, тип cache_rec_t, extern-объявления разделяемого
|
||||
* состояния ядра и API каждого модуля (сгруппированы по секциям ниже).
|
||||
*/
|
||||
#ifndef MDVIEW2_H
|
||||
#define MDVIEW2_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <palette.h> /* COLOR(), COLOR_* */
|
||||
#include "mdview2_conf.h" /* WITH_RAW / WITH_HEX — выбор опциональных модулей */
|
||||
|
||||
/* ---- Геометрия экрана -------------------------------------------- */
|
||||
#define SCREEN_W 80
|
||||
#define SCREEN_H 32
|
||||
#define VIEW_TOP_ROW 1
|
||||
#define VIEW_H 30 /* видимая область: строки 1..30 включительно */
|
||||
#define MENU_ROW 31
|
||||
#define TAB_STOP 4
|
||||
#define HPAN_STEP 8u /* шаг горизонтального сдвига (←/→) */
|
||||
|
||||
/* ---- Геометрия статус-бара (строка 0): фиксированные поля и разделители ---- */
|
||||
#define SPINNER_COL 8 /* слот спиннера загрузки */
|
||||
#define DIV1_X 45 /* разделитель │ перед числами диапазона строк */
|
||||
#define DIV2_X 71 /* разделитель │ перед процентом */
|
||||
/* Числа/метка пишутся с DIVn_X+2; область между разделителями — [DIV1_X+1 .. DIV2_X-1]. */
|
||||
|
||||
/* ---- Параметры файла и памяти ------------------------------------ */
|
||||
#define PAGE_BITS 14u
|
||||
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
|
||||
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
|
||||
#define MAX_PAGES 16 /* 16 страниц × 16 КБ = 256 КБ */
|
||||
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE)
|
||||
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
|
||||
|
||||
/* ---- Палитра атрибутов ------------------------------------------- */
|
||||
#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE)
|
||||
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
||||
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
||||
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||
#define ATTR_WARN COLOR(COLOR_YELLOW, COLOR_RED) /* строка-обрыв (исчерпан кэш/лимит) */
|
||||
|
||||
/* ---- Атрибуты диалога справки ------------------------------------ */
|
||||
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||
#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||
#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||
#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||
|
||||
/* ---- Режимы просмотра -------------------------------------------- */
|
||||
#define VIEW_MD 0 /* markdown с форматированием (по умолчанию) */
|
||||
#define VIEW_RAW_WRAP 1 /* RAW: длинные строки переносятся кратно 80 */
|
||||
#define VIEW_RAW_HSCROLL 2 /* RAW: длинные строки в одну, гориз. скролл */
|
||||
#define VIEW_HEX 3 /* HEX-дамп оригинального файла */
|
||||
#define VIEW_IS_RAW(v) ((v) == VIEW_RAW_WRAP || (v) == VIEW_RAW_HSCROLL)
|
||||
|
||||
/* ---- Кодировки --------------------------------------------------- */
|
||||
/* 8-битные (CP866/CP1251/KOI8R) различаются только ремапом глифов [128-255]
|
||||
* на отрисовке; UTF-8 — отдельный декодированный набор. */
|
||||
#define ENC_CP866 0
|
||||
#define ENC_CP1251 1
|
||||
#define ENC_KOI8R 2
|
||||
#define ENC_UTF8 3
|
||||
#define ENC_UNSUPPORTED -1 /* UTF16 / UTF32 */
|
||||
#define CONV_MARGIN 4096u /* на сколько байт держать UTF-конвертацию впереди индексатора */
|
||||
|
||||
/* ---- Флаги сегмента индекса (IF_*) — общие для индексатора/кэша/вью ---- */
|
||||
#define IF_CONT 0x01u /* сегмент является продолжением перенесённой строки */
|
||||
#define IF_NOWRAP 0x02u /* строка не переносится (кодовый блок / HR / таблица) */
|
||||
#define IF_BLANK 0x04u /* визуально пустая строка */
|
||||
#define IF_CODE 0x08u /* тело fenced code-блока (verbatim-режим) */
|
||||
#define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица),
|
||||
* включая строки короче 80 — двигаются как единый блок.
|
||||
* НЕ ставится на HR и границы fence (им нечего прятать). */
|
||||
#define IF_TRUNC_MSG 0x20u /* строка-обрыв: рисуется ВЖИВУЮ фикс. текстом (без контент-кэша);
|
||||
* cache_rec.reserved несёт код причины (TRUNC_*). */
|
||||
|
||||
/* ---- Причина обрыва индексации (cache_rec.reserved при IF_TRUNC_MSG) ---- */
|
||||
#define TRUNC_CONTENT 1 /* исчерпан контент-кэш (MAX_CACHE_CONTENT_PAGES) */
|
||||
#define TRUNC_LINES 2 /* исчерпана ёмкость индекса (max_lines) */
|
||||
#define TRUNC_FILE 3 /* файл больше MAX_FILE — прочитаны первые 256 КБ */
|
||||
|
||||
/* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */
|
||||
/* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */
|
||||
#define CK_PLAIN 0
|
||||
#define CK_QUOTE 1
|
||||
#define CK_LIST 2
|
||||
#define CK_OTHER 3
|
||||
|
||||
/* Начальный inline-стиль сегмента (INIT_STYLE_*). */
|
||||
#define INIT_STYLE_PLAIN 0x0
|
||||
#define INIT_STYLE_BOLD 0x1
|
||||
#define INIT_STYLE_ITALIC 0x2
|
||||
#define INIT_STYLE_UNDER 0x3
|
||||
#define INIT_STYLE_CODE 0x4
|
||||
#define INIT_STYLE_STRIKE 0x5
|
||||
|
||||
/* ---- Таблицы: CP866 light box-drawing (общий для индексатора и модуля) ---- */
|
||||
#define TBL_MAX_COLS 16
|
||||
#define TBL_ATTR ATTR_BOX
|
||||
#define TBL_H 0xC4 /* ─ */
|
||||
#define TBL_V 0xB3 /* │ */
|
||||
#define TBL_TL 0xDA /* ┌ */
|
||||
#define TBL_TM 0xC2 /* ┬ */
|
||||
#define TBL_TR 0xBF /* ┐ */
|
||||
#define TBL_ML 0xC3 /* ├ */
|
||||
#define TBL_MM 0xC5 /* ┼ */
|
||||
#define TBL_MR 0xB4 /* ┤ */
|
||||
#define TBL_BL 0xC0 /* └ */
|
||||
#define TBL_BM 0xC1 /* ┴ */
|
||||
#define TBL_BR 0xD9 /* ┘ */
|
||||
|
||||
/* ================================================================== *
|
||||
* Разделяемые символы mdview2.c, используемые модулем RAW.
|
||||
* ================================================================== */
|
||||
extern uint8_t g_view; /* активный режим просмотра (VIEW_*) */
|
||||
extern uint32_t file_size; /* размер активного буфера документа */
|
||||
extern const uint8_t *g_remap; /* таблица ремапа [128-255] (0 = нет) */
|
||||
extern uint8_t g_scratch_phys; /* scratch EMM-страница для win_rest */
|
||||
|
||||
char fb(uint32_t p); /* байт активного буфера (W3-маппинг) */
|
||||
void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off);
|
||||
void fill_row(uint8_t y, uint8_t attr);
|
||||
uint8_t pct16(uint16_t num, uint16_t den); /* num*100/den (0..100), 16-бит, без __divulong */
|
||||
uint32_t seg_off(uint16_t idx); /* offset исходника для логической строки idx */
|
||||
uint16_t line_at_off(uint32_t off); /* обратно: последняя строка с seg_off <= off */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля RAW (mdview2_raw.c).
|
||||
* ================================================================== */
|
||||
void raw_seed_from(uint16_t md_top_line); /* вход в RAW: позиция по top_line */
|
||||
uint32_t raw_pos(void); /* текущий байт-offset верха экрана RAW */
|
||||
void raw_reanchor(uint32_t off); /* поставить RAW на строку с байтом off */
|
||||
void raw_draw(void); /* перерисовать область документа в RAW */
|
||||
uint8_t raw_pct(void); /* % прокрутки по байтам (для render_raw_status_numbers) */
|
||||
uint8_t raw_key(uint8_t scan); /* навигация RAW; 1 = обработано */
|
||||
void raw_screen_init(void); /* очистка экрана с нужным аттрибутом */
|
||||
void raw_renorm(void); /* выровнять позицию при смене под-режима (F3) */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля HEX (mdview2_hex.c) — дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||
* Позиция hex_pos/hex_reanchor — в байтах ОРИГИНАЛА (orig_file_*);
|
||||
* конвертацию в/из активного буфера делает ядро (map_off).
|
||||
* ================================================================== */
|
||||
uint32_t hex_pos(void); /* offset верхнего ряда (кратен 16) */
|
||||
void hex_reanchor(uint32_t orig_off); /* поставить на ряд с байтом orig_off */
|
||||
void hex_draw(void); /* перерисовать область документа */
|
||||
uint8_t hex_key(uint8_t scan); /* навигация HEX; 1 = обработано */
|
||||
void hex_screen_init(void); /* очистка экрана */
|
||||
|
||||
/* ================================================================== *
|
||||
* Запись директории рендер-кэша (одна на видимую строку, РОВНО 8 байт —
|
||||
* cache_dir_get/put адресуют сдвигом idx<<3). Контент — len пар (char,attr).
|
||||
* ================================================================== */
|
||||
typedef struct cache_rec_s {
|
||||
uint8_t page; /* EMM-страница рендер-кэша (физический номер) */
|
||||
uint16_t off; /* смещение в странице, 0..16383 (байты, не ячейки) */
|
||||
uint8_t len; /* длина контента в ЯЧЕЙКАХ (char,attr пар), 0..255; 0 = пустая/HR */
|
||||
uint8_t flags; /* копия IF_NOWRAP/IF_CODE/IF_BLANK на момент рендера */
|
||||
uint8_t reserved; /* не используется */
|
||||
uint8_t pad[2]; /* явный резерв, добивка до 8 байт */
|
||||
} cache_rec_t;
|
||||
|
||||
void cache_dir_get(uint16_t idx, cache_rec_t *r); /* читатель директории кэша (ядро) */
|
||||
void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr); /* печать строки с атрибутом */
|
||||
|
||||
/* ================================================================== *
|
||||
* Состояние навигации/представления (определено в mdview2.c).
|
||||
* ================================================================== */
|
||||
extern uint16_t n_lines; /* всего строк в индексе текущего набора */
|
||||
extern uint16_t top_line; /* верхняя видимая строка */
|
||||
extern uint8_t viewport_x; /* горизонтальный сдвиг (nowrap-строки) */
|
||||
extern char filename[]; /* имя файла для статус-бара */
|
||||
extern uint8_t g_loading; /* 1 во время index_lines() */
|
||||
extern uint8_t g_ready; /* 1 когда первичный документ построен (F2 RAW) */
|
||||
extern uint8_t g_f8_enabled; /* можно ли сейчас переключать кодировку */
|
||||
|
||||
/* ================================================================== *
|
||||
* Статус-бар (строка 0), меню (строка 31), спиннер — mdview2_status.c.
|
||||
* Атомарные части: prerender (фикс. хром, один раз) / encoding (поле кодировки)
|
||||
* / numbers (диапазон строк + %) / menu / spinner — обновляются по отдельности.
|
||||
* ================================================================== */
|
||||
void prerender_status(void); /* фикс. часть: фон, разделители, MDVIEW, имя файла (1 раз) */
|
||||
void status_encoding(void); /* только поле кодировки (col 37) */
|
||||
void render_md_status_numbers(void); /* числа MD: диапазон строк + % */
|
||||
void render_raw_status(void); /* RAW mode: метка режима */
|
||||
void render_hex_status(void); /* HEX mode: метка режима */
|
||||
void render_full_status(void); /* encoding + numbers (MD) */
|
||||
void render_menu(void); /* строка меню (по смене режима/готовности) */
|
||||
uint16_t drawable_lines(void); /* число строк, готовых к показу */
|
||||
void spinner_tick(void); /* кадр спиннера загрузки */
|
||||
void spinner_show(uint8_t on); /* вкл/выкл спиннер */
|
||||
void render_percent_progress(uint8_t pct); /* показать процентный прогресс просмотра */
|
||||
|
||||
/* ================================================================== *
|
||||
* Отрисовка области MD-документа и прокрутка (mdview2_md.c).
|
||||
* ================================================================== */
|
||||
void draw_viewport_from_cache(void); /* перерисовать область документа из кэша */
|
||||
void clamp_top(void); /* привести top_line к диапазону */
|
||||
void md_scroll_up(uint16_t n);
|
||||
void md_scroll_down(uint16_t n);
|
||||
void md_scroll_horizon(int8_t delta); /* горизонтальный сдвиг nowrap-блоков */
|
||||
uint8_t md_key(uint8_t scan); /* навигация MD (после загрузки); 1 = обработано */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля справки (mdview2_help.c).
|
||||
* ================================================================== */
|
||||
void show_help(void); /* модальный диалог F1 */
|
||||
|
||||
/* ================================================================== *
|
||||
* Парсер/индексатор (mdview2_index.c) и разделяемое с ним состояние.
|
||||
* Хранилище индекса (index_blk/pages/phys, n_lines/max_lines) — в ядре
|
||||
* (аллокация/doc-slot), индексатор пишет в него; cache_*-писатели и
|
||||
* progress_tick ядро экспортирует индексатору (spinner_tick — из status).
|
||||
* ================================================================== */
|
||||
#define INDEX_RECS_PER_PAGE 2048u /* 16384 / 8; запись всегда в одной странице */
|
||||
#define MAX_CACHE_LINE_LEN 255u /* кап длины контента строки в ячейках (буфер g_cells) */
|
||||
|
||||
extern uint16_t max_lines; /* ёмкость индекса: index_pages * 2048 */
|
||||
extern uint8_t index_blk, index_pages; /* дескриптор EMM-блока индекса */
|
||||
extern uint8_t index_phys[]; /* физ. страницы индекса */
|
||||
extern uint8_t index_truncated; /* индекс упёрся в ёмкость */
|
||||
extern uint8_t g_abort; /* F10/Esc во время загрузки → прервать индексацию */
|
||||
extern uint8_t g_file_clamped; /* файл был обрезан до MAX_FILE при загрузке (>256 КБ) */
|
||||
|
||||
uint8_t cache_reserve(uint16_t nbytes, uint8_t *out_page, uint16_t *out_off);
|
||||
void cache_commit(uint8_t page, uint16_t off, const void *buf, uint16_t len);
|
||||
void cache_dir_put(uint16_t idx, const cache_rec_t *r);
|
||||
void progress_tick(void); /* кооперативный шаг loading-loop (рисует/листает) */
|
||||
|
||||
/* Резюмируемая индексация (для фоновой сборки второго набора): begin сбрасывает
|
||||
* проход, step выполняет до budget итераций (блоков исходника) и возвращает 1,
|
||||
* когда индекс финализирован. Межшаговое состояние — статики модуля index;
|
||||
* оно не входит в docset_t (см. bg_step ядра). */
|
||||
void index_begin(void);
|
||||
uint8_t index_step(uint8_t budget);
|
||||
extern uint8_t g_bg_building; /* 1 = фоновый шаг индексации: не трогать экран (спиннер) */
|
||||
|
||||
/* ================================================================== *
|
||||
* Буфер ячеек рендера (mdview2.c) — общий с модулем таблиц.
|
||||
* Таблицы эмитят свои строки теми же примитивами, что и индексатор.
|
||||
* ================================================================== */
|
||||
extern uint8_t g_ncells; /* ячеек в текущем сегменте */
|
||||
void gc_put(char ch, uint8_t attr); /* добавить ячейку в g_cells */
|
||||
void gc_fill(char ch, uint8_t attr, uint8_t n); /* n одинаковых ячеек */
|
||||
uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
||||
uint8_t ckind, uint8_t line_style, uint8_t base_attr,
|
||||
uint8_t nowrap);
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля таблиц (mdview2_table.c) — вызывается из index_lines().
|
||||
* ================================================================== */
|
||||
uint32_t row_end(uint32_t p); /* offset завершающего '\n' (или file_size) */
|
||||
uint32_t table_first_cell(uint32_t row_start); /* контент первой ячейки строки */
|
||||
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce);
|
||||
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend);
|
||||
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right);
|
||||
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols);
|
||||
|
||||
/* ================================================================== *
|
||||
* Кодировки и UTF-8 конвертация (mdview2_enc.c).
|
||||
* Состояние кодека владеется модулем; build_doc()/index_lines() ядра
|
||||
* оркестрируют его (привязка UTF-буфера к живому file_*, сборка наборов).
|
||||
* ================================================================== */
|
||||
extern uint8_t g_encoding; /* активная кодировка (ENC_*) */
|
||||
extern uint8_t g_utf_building; /* 1 пока UTF-конвертация не дошла до конца исходника */
|
||||
extern uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||
extern uint8_t utf_blk, utf_pages; /* дескриптор EMM-буфера UTF-набора */
|
||||
extern uint8_t utf_phys[]; /* физ. страницы UTF-буфера */
|
||||
extern uint32_t utf_size; /* размер сконвертированного UTF-потока */
|
||||
|
||||
/* Ядро-owned состояние, читаемое кодеком (определено в mdview2.c). */
|
||||
extern uint8_t cur_page; /* текущая страница файла в W3 (инвалидация маппинга) */
|
||||
extern uint8_t orig_file_phys[]; /* физ. страницы снимка исходного файла */
|
||||
extern uint8_t orig_file_pages;
|
||||
extern uint32_t orig_file_size;
|
||||
|
||||
void set_encoding(uint8_t enc); /* активная кодировка + таблица ремапа */
|
||||
const char *enc_name(uint8_t enc); /* короткое имя для статус-бара */
|
||||
uint8_t detect_encoding(void); /* автоопределение по сэмплу байт */
|
||||
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off); /* вывод среза с ремапом */
|
||||
void utf_convert_more(uint32_t target); /* инкрементальная конвертация до target байт */
|
||||
uint8_t utf_alloc(void); /* выделить EMM-страницы под UTF-набор; 0 = нет EMM */
|
||||
uint8_t utf_cp_glyph(uint32_t cp); /* кодпойнт → один CP866-глиф ('?' без аналога) */
|
||||
|
||||
#endif /* MDVIEW2_H */
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* mdview2_conf.h — конфигурация сборки mdview2: какие опциональные модули
|
||||
* просмотра включать. 1 = включить (код входит в бинарь), 0 = исключить
|
||||
* полностью (нулевой расход кода/памяти).
|
||||
*
|
||||
* Модули обёрнуты в #if WITH_xxx целиком, поэтому файлы можно держать в
|
||||
* сборке всегда (при 0 они компилируются в пустой объектник) — менять
|
||||
* нужно только эти define.
|
||||
*/
|
||||
#ifndef MDVIEW2_CONF_H
|
||||
#define MDVIEW2_CONF_H
|
||||
|
||||
#define WITH_RAW 1 /* RAW-просмотр исходника (F2): mdview2_raw.c */
|
||||
#define WITH_HEX 1 /* HEX-дамп оригинального файла (F4): mdview2_hex.c */
|
||||
|
||||
#endif /* MDVIEW2_CONF_H */
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* mdview2_enc.c — кодировки и конвертация UTF-8 → CP866.
|
||||
*
|
||||
* Две связанные подсистемы:
|
||||
* 1. 8-битные кодировки (CP866/CP1251/KOI8-R) — различаются только ремапом
|
||||
* старших байтов [128-255] на ОТРИСОВКЕ (g_remap, win_rest_remap).
|
||||
* Автоопределение — detect_encoding по частоте ходовых букв.
|
||||
* 2. UTF-8 — отдельный набор: исходник инкрементально декодируется в
|
||||
* utf_phys[] как чистый CP866 (utf_convert_more), после чего весь
|
||||
* конвейер индексации/кэша работает по нему без ремапа.
|
||||
*
|
||||
* Владеет состоянием кодека (conv-буфер, позиция конвертации, UTF-страницы);
|
||||
* описание разделяемых символов и API — в mdview2.h. Оркестрацией (привязка
|
||||
* UTF-буфера к живому file_*, сборка наборов) занимается build_doc() ядра.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в attr_is_content */
|
||||
#include <sprinter.h> /* sprinter_page_w3 */
|
||||
#include <sprinter_mem.h> /* bank_read/bank_write/mem_alloc_pages/mem_get_page */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* ---- Состояние кодировки/UTF (владелец — этот модуль) ------------- */
|
||||
uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
||||
|
||||
uint8_t utf_blk;
|
||||
uint8_t utf_pages;
|
||||
uint8_t utf_phys[MAX_PAGES];
|
||||
uint32_t utf_size;
|
||||
uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||
uint8_t g_utf_building; /* 1 пока конвертация не дошла до конца исходника */
|
||||
|
||||
/* Приватное состояние конвертера (наружу не торчит — сброс через
|
||||
* utf_conv_reset(), чтение оригинала через cv_read). */
|
||||
static uint32_t utf_src; /* позиция чтения ОРИГИНАЛА (utf-8) */
|
||||
static uint8_t cv_page = 0xFF; /* orig-страница в W3 для cv_read (своя от cur_page) */
|
||||
|
||||
/* Таблицы ремапа СТАРШИХ байтов [128-255] в CP866. Индексируются (ch-0x80):
|
||||
* младшие 128 (ASCII) — identity, в ремапе не участвуют (win_rest_remap
|
||||
* трогает только ch>=0x80), поэтому в таблицах их нет. Кириллица + ходовая
|
||||
* пунктуация; неизвестное → '?' (0x3F). Сгенерированы Python codecs. */
|
||||
static const uint8_t cp1251_to_866[128] = {
|
||||
/* 80 */ 0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
/* 90 */ 0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
/* A0 */ 0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
||||
/* B0 */ 0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
||||
/* C0 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
/* D0 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
/* E0 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||
/* F0 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||
};
|
||||
static const uint8_t koi8r_to_866[128] = {
|
||||
/* 80 */ 0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
||||
/* 90 */ 0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
||||
/* A0 */ 0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
||||
/* B0 */ 0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
||||
/* C0 */ 0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
||||
/* D0 */ 0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
||||
/* E0 */ 0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
/* F0 */ 0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
||||
};
|
||||
|
||||
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
||||
void set_encoding(uint8_t enc)
|
||||
{
|
||||
g_encoding = enc;
|
||||
g_remap = (enc == ENC_CP1251) ? cp1251_to_866 :
|
||||
(enc == ENC_KOI8R) ? koi8r_to_866 : (const uint8_t *)0;
|
||||
}
|
||||
|
||||
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
||||
* различают 8-битные кодировки по частоте. */
|
||||
// static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB};
|
||||
// static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB};
|
||||
// static const uint8_t commonkoi [10] = {0xCF,0xC5,0xC1,0xC9,0xCE,0xD4,0xD3,0xD2,0xD7,0xCC};
|
||||
// static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||
// {
|
||||
// for (uint8_t i = 0; i < 10; i++) if (s[i] == b) return 1;
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
/* Проверяем только пять самых популярных символов (о е а и н) */
|
||||
static const uint8_t common866 [5] = {0xAE,0xA5,0xA0,0xA8,0xAD};
|
||||
static const uint8_t common1251[5] = {0xEE,0xE5,0xE0,0xE8,0xED};
|
||||
static const uint8_t commonkoi [5] = {0xCF,0xC5,0xC1,0xC9,0xCE};
|
||||
static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||
{
|
||||
for (uint8_t i = 0; i < 5; i++) if (s[i] == b) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
||||
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
||||
* частоте ходовых букв; фолбэк CP866. */
|
||||
uint8_t detect_encoding(void)
|
||||
{
|
||||
if (file_size >= 3 && (uint8_t)fb(0) == 0xEF && (uint8_t)fb(1) == 0xBB && (uint8_t)fb(2) == 0xBF)
|
||||
return ENC_UTF8;
|
||||
|
||||
uint32_t n = file_size;
|
||||
if (n > 1024u) n = 1024u; /* сэмпл: первый 1 КБ — детекции хватает */
|
||||
uint8_t utf_ok = 1, has_mb = 0, has_high = 0, cont = 0;
|
||||
uint16_t s866 = 0, s1251 = 0, skoi = 0;
|
||||
|
||||
for (uint32_t p = 0; p < n; p++) {
|
||||
uint8_t b = (uint8_t)fb(p);
|
||||
if (b < 0x80) { if (cont) { utf_ok = 0; cont = 0; } continue; }
|
||||
has_high = 1;
|
||||
if (in_set10(common866, b)) s866++;
|
||||
if (in_set10(common1251, b)) s1251++;
|
||||
if (in_set10(commonkoi, b)) skoi++;
|
||||
if (cont) {
|
||||
if ((b & 0xC0) == 0x80) cont--;
|
||||
else { utf_ok = 0; cont = 0; }
|
||||
}
|
||||
else if (b >= 0xC2 && b <= 0xDF) { cont = 1; has_mb = 1; }
|
||||
else if (b >= 0xE0 && b <= 0xEF) { cont = 2; has_mb = 1; }
|
||||
else if (b >= 0xF0 && b <= 0xF4) { cont = 3; has_mb = 1; }
|
||||
else utf_ok = 0; /* битый лид/одиночный континюэйшн */
|
||||
}
|
||||
/* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только
|
||||
* если это настоящий EOF; на границе сэмпла (n<file_size) это просто
|
||||
* обрезка, не считаем за ошибку. */
|
||||
if (cont && n == file_size) utf_ok = 0;
|
||||
|
||||
if (!has_high) return ENC_CP866; /* чистый ASCII */
|
||||
if (utf_ok && has_mb) return ENC_UTF8;
|
||||
if (s1251 >= s866 && s1251 >= skoi) return ENC_CP1251;
|
||||
if (skoi >= s866) return ENC_KOI8R;
|
||||
return ENC_CP866;
|
||||
}
|
||||
|
||||
const char *enc_name(uint8_t enc)
|
||||
{
|
||||
return (enc == ENC_CP1251) ? "CP1251" :
|
||||
(enc == ENC_KOI8R) ? "KOI8-R" :
|
||||
(enc == ENC_UTF8) ? "UTF-8 " : "CP866 ";
|
||||
}
|
||||
|
||||
/* Контентный глиф (ремапим при смене кодировки) vs структурный (рамка/HR/
|
||||
* маркеры — уже CP866, не трогаем). Различаем по attr. */
|
||||
static uint8_t attr_is_content(uint8_t a)
|
||||
{
|
||||
return (uint8_t)(a != ATTR_BOX && a != ATTR_HR &&
|
||||
a != ATTR_LIST_MARKER && a != ATTR_QUOTE_MARKER);
|
||||
}
|
||||
|
||||
/* Вывод среза строки кэша с ремапом контентных глифов [128-255] через
|
||||
* g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */
|
||||
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
|
||||
{
|
||||
if (!g_remap || w == 0) {
|
||||
win_rest(row, 0, 1, w, page, off);
|
||||
return;
|
||||
}
|
||||
uint8_t buf[SCREEN_W * 2];
|
||||
bank_read(page, off, buf, (uint16_t)w * 2u);
|
||||
for (uint8_t i = 0; i < w; i++) {
|
||||
uint8_t ch = buf[(uint16_t)i * 2u];
|
||||
if (ch >= 0x80 && attr_is_content(buf[(uint16_t)i * 2u + 1u]))
|
||||
buf[(uint16_t)i * 2u] = g_remap[ch - 0x80]; /* таблицы хранят только старшие 128 */
|
||||
}
|
||||
bank_write(g_scratch_phys, 0, buf, (uint16_t)w * 2u);
|
||||
win_rest(row, 0, 1, w, g_scratch_phys, 0);
|
||||
}
|
||||
|
||||
/* ========================= UTF-8 → CP866 (Фаза 2) ======================== */
|
||||
|
||||
/* Кириллица U+0400..U+045F → CP866. Русский набор + Ё/ё и часть украинских
|
||||
* (Є є Ї ї Ў ў), которые есть в CP866; пропуски → '?' (0x3F). */
|
||||
static const uint8_t utf_cyr_to_866[96] = {
|
||||
/* 0400 */ 0x3F, 0xF0, 0x3F, 0x3F, 0xF2, 0x3F, 0x3F, 0xF4, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF6, 0x3F,
|
||||
/* 0410 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
/* 0420 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
/* 0430 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||
/* 0440 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||
/* 0450 */ 0x3F, 0xF1, 0x3F, 0x3F, 0xF3, 0x3F, 0x3F, 0xF5, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF7, 0x3F,
|
||||
};
|
||||
|
||||
/* Поток вывода конвертера: пишем в utf_phys чанками по странице, буфер в
|
||||
* near-памяти, флаш через один bank_write (не побайтовый swap W3). */
|
||||
static uint8_t conv_buf[256];
|
||||
static uint16_t conv_n; /* байт в буфере, ещё не сброшено */
|
||||
static uint8_t conv_page; /* индекс текущей страницы в utf_phys */
|
||||
static uint16_t conv_off; /* уже сброшенное смещение в этой странице */
|
||||
|
||||
static void conv_flush(void)
|
||||
{
|
||||
if (conv_n) {
|
||||
bank_write(utf_phys[conv_page], conv_off, conv_buf, conv_n);
|
||||
conv_off = (uint16_t)(conv_off + conv_n);
|
||||
conv_n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Добавляет один CP866-байт в выходной поток. Запись никогда не пересекает
|
||||
* границу страницы (256 делит 16384 нацело). */
|
||||
static void conv_put(uint8_t b)
|
||||
{
|
||||
if ((uint16_t)(conv_off + conv_n) == PAGE_SIZE) { /* страница заполнена */
|
||||
conv_flush();
|
||||
conv_page++;
|
||||
conv_off = 0;
|
||||
}
|
||||
conv_buf[conv_n++] = b;
|
||||
if (conv_n == (uint16_t)sizeof(conv_buf)) conv_flush();
|
||||
}
|
||||
|
||||
/* Подстановки одиночных не-кириллических символов UTF-8 → один CP866-байт.
|
||||
* Таблица вместо switch (экономия кода); линейный поиск дёшев — вызывается
|
||||
* лишь при конвертации на редких символьных кодпойнтах. Чтобы добавить новый
|
||||
* символ — достаточно дописать одну строку {кодпойнт, байт-CP866}.
|
||||
* (Символы с НЕ-1:1 заменой, напр. … -> "...", обрабатываются отдельно ниже.) */
|
||||
typedef struct { uint16_t utf8; uint8_t cp866; } utf_sym_t;
|
||||
static const utf_sym_t utf_sym[] = {
|
||||
{0x00A0, 0x20}, {0x00AB, 0x3C}, {0x00BB, 0x3E}, /* nbsp « » → space < > */
|
||||
{0x00B0, 0xF8}, {0x00B7, 0xFA}, /* ° · */
|
||||
{0x2013, 0x2D}, {0x2014, 0x2D}, /* – — → '-' */
|
||||
{0x2018, 0x27}, {0x2019, 0x27}, /* ‘ ’ → '\'' */
|
||||
{0x201C, 0x22}, {0x201D, 0x22}, /* “ ” → '"' */
|
||||
{0x2022, 0xF9}, {0x2116, 0xFC}, /* • → ∙ № */
|
||||
{0x2190, 0x1B}, {0x2191, 0x18}, {0x2192, 0x1A}, {0x2193, 0x19}, /* ← ↑ → ↓ */
|
||||
{0x2500, 0xC4}, {0x2502, 0xB3}, {0x250C, 0xDA}, {0x2510, 0xBF}, /* ─ │ ┌ ┐ */
|
||||
{0x2514, 0xC0}, {0x2518, 0xD9}, {0x251C, 0xC3}, {0x2524, 0xB4}, /* └ ┘ ├ ┤ */
|
||||
{0x252C, 0xC2}, {0x2534, 0xC1}, {0x253C, 0xC5}, /* ┬ ┴ ┼ */
|
||||
{0x2580, 0xDF}, {0x2584, 0xDC}, {0x2588, 0xDB}, /* ▀ ▄ █ */
|
||||
{0x2591, 0xB0}, {0x2592, 0xB1}, {0x2593, 0xB2}, {0x25A0, 0xFE}, /* ░ ▒ ▓ ■ */
|
||||
{0x2713, 0xFB}, {0x2714, 0xFB}, /* ✓ ✔ → галка */
|
||||
{0x2715, 0x78}, {0x2717, 0x78}, {0x2718, 0x78}, /* ✕ ✗ ✘ → 'x' */
|
||||
};
|
||||
#define UTF_SYM_N (uint8_t)(sizeof(utf_sym) / sizeof(utf_sym[0]))
|
||||
|
||||
/* Кодпойнт → ОДИН CP866-глиф: кириллица по таблице, ходовые символы по
|
||||
* utf_sym[], прочее → '?'. Общая для конвертера и HEX-printable (F4). */
|
||||
uint8_t utf_cp_glyph(uint32_t cp)
|
||||
{
|
||||
if (cp < 0x80) return (uint8_t)cp;
|
||||
if (cp >= 0x0400 && cp <= 0x045F) return utf_cyr_to_866[cp - 0x0400];
|
||||
for (uint8_t i = 0; i < UTF_SYM_N; i++)
|
||||
if (utf_sym[i].utf8 == (uint16_t)cp) return utf_sym[i].cp866;
|
||||
return '?';
|
||||
}
|
||||
|
||||
/* Кодпойнт → CP866-глиф(ы) в поток конвертера. Спецслучаи с НЕ-1:1 заменой
|
||||
* (… → "...", BOM → ничего) здесь; остальное через utf_cp_glyph(). */
|
||||
static void conv_emit_cp(uint32_t cp)
|
||||
{
|
||||
if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */
|
||||
if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */
|
||||
conv_put(utf_cp_glyph(cp));
|
||||
}
|
||||
|
||||
/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит
|
||||
* в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной
|
||||
* cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и
|
||||
* чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */
|
||||
static uint8_t cv_read(uint32_t s)
|
||||
{
|
||||
uint8_t pg = (uint8_t)(s >> PAGE_BITS);
|
||||
if (pg != cv_page) { sprinter_page_w3(orig_file_phys[pg]); cv_page = pg; }
|
||||
return *((volatile uint8_t *)(0xC000u + (uint16_t)(s & PAGE_MASK)));
|
||||
}
|
||||
|
||||
/* Возобновляемая конвертация: дописывает utf_phys из оригинала, пока
|
||||
* сконвертированных (flushed) байт меньше target и не достигнут конец
|
||||
* исходника. По выходу file_size = доступная (сконвертированная) часть; при
|
||||
* достижении конца фиксирует utf_size и снимает g_utf_building. */
|
||||
void utf_convert_more(uint32_t target)
|
||||
{
|
||||
cv_page = 0xFF; /* W3 был на utf (fb индексатора) — пере-смаппим orig */
|
||||
uint32_t n = orig_file_size;
|
||||
while (utf_src < n &&
|
||||
(uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) {
|
||||
uint8_t b = (uint8_t)cv_read(utf_src++);
|
||||
if (b < 0x80) {
|
||||
conv_emit_cp(b);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t cp;
|
||||
uint8_t need;
|
||||
if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; }
|
||||
else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; }
|
||||
else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; }
|
||||
else { conv_put('?'); continue; }
|
||||
|
||||
uint8_t ok = 1;
|
||||
for (uint8_t k = 0; k < need; k++) {
|
||||
if (utf_src >= n) { ok = 0; break; }
|
||||
uint8_t cb = (uint8_t)cv_read(utf_src);
|
||||
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
||||
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
||||
utf_src++;
|
||||
}
|
||||
if (!ok || need == 3) { conv_put('?'); continue; }
|
||||
conv_emit_cp(cp);
|
||||
}
|
||||
conv_flush();
|
||||
file_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; /* доступно индексатору */
|
||||
if (utf_src >= n) { utf_size = file_size; g_utf_building = 0; }
|
||||
cur_page = 0xFF; /* W3 был на orig — fb индексатора пере-смаппит utf */
|
||||
}
|
||||
|
||||
/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру)
|
||||
* и сбрасывает приватное состояние конвертера в начало (conv-буфер, utf_src,
|
||||
* g_utf_building). Сама конвертация — инкрементальная, через utf_convert_more().
|
||||
* Возврат: 1 — успех, 0 — нет EMM. */
|
||||
uint8_t utf_alloc(void)
|
||||
{
|
||||
utf_pages = orig_file_pages;
|
||||
utf_blk = mem_alloc_pages(utf_pages);
|
||||
if (utf_blk == 0) return 0;
|
||||
for (uint8_t i = 0; i < utf_pages; i++)
|
||||
utf_phys[i] = mem_get_page(utf_blk, i);
|
||||
conv_n = 0; conv_page = 0; conv_off = 0; /* converter reset (вызывается один раз) */
|
||||
utf_src = 0;
|
||||
g_utf_building = 1;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* mdview2_help.c — диалог справки (F1).
|
||||
*
|
||||
* Модальное окно поверх документа: рамка с заголовком, список горячих
|
||||
* клавиш и поддерживаемых markdown-элементов. Блокирует до нажатия любой
|
||||
* клавиши, после чего восстанавливает статус-бар, область документа и меню.
|
||||
*
|
||||
* Зависимости от ядра (mdview2.c): render_full_status / draw_viewport_from_cache
|
||||
* / render_menu для восстановления экрана. Текст справки — в CP866.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include "mdview2.h"
|
||||
|
||||
/* Геометрия диалога справки (в символьных координатах 80×32). */
|
||||
#define HELP_X 8u /* левая граница рамки */
|
||||
#define HELP_Y 3u /* верхняя граница рамки */
|
||||
#define HELP_W 64u /* ширина рамки (включая │) */
|
||||
#define HELP_H 26u /* высота рамки (включая ─) */
|
||||
|
||||
/* Заполняет одну внутреннюю строку диалога (r=0 — первая строка за рамкой).
|
||||
* Строка s в кодировке CP866; остаток до края дополняется пробелами. */
|
||||
static void help_line(uint8_t r, const char *s, uint8_t attr)
|
||||
{
|
||||
uint8_t x = HELP_X + 1u;
|
||||
uint8_t y = HELP_Y + 1u + r;
|
||||
uint8_t len = (uint8_t)strlen(s);
|
||||
|
||||
if (len > HELP_W - 2u)
|
||||
len = HELP_W - 2u;
|
||||
|
||||
bios_set_place(y, x);
|
||||
if (len > 0)
|
||||
bios_writeattr(s, len, attr);
|
||||
/* place уже продвинут bios_writeattr на len колонок (verified). */
|
||||
if (len < HELP_W - 2u) {
|
||||
bios_fillcharattr(' ', ATTR_HELP_BG, (uint8_t)(HELP_W - 2u - len));
|
||||
}
|
||||
}
|
||||
|
||||
void show_help(void)
|
||||
{
|
||||
/* " Помощь " в CP866 (8 байт) */
|
||||
static const char title[] = " \x8F\xAE\xAC\xAE\xE9\xEC ";
|
||||
uint8_t tlen = 8u;
|
||||
uint8_t lft = (uint8_t)((HELP_W - 2u - tlen) / 2u); /* = 27 */
|
||||
uint8_t rgt = (uint8_t)(HELP_W - 2u - tlen - lft);
|
||||
/* Верхняя граница рамки с заголовком по центру.
|
||||
* place продвигается каждым BIOS-вызовом (verified), поэтому
|
||||
* достаточно одной установки места на всю строку рамки. */
|
||||
wrchar(HELP_X, HELP_Y, 0xDA, ATTR_HELP_BDR); /* ┌ */
|
||||
bios_set_place(HELP_Y, (uint8_t)(HELP_X + 1u));
|
||||
if (lft > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, lft);
|
||||
if (tlen > 0) bios_writeattr(title, tlen, ATTR_HELP_TIT);
|
||||
if (rgt > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, rgt);
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y, 0xBF, ATTR_HELP_BDR); /* ┐ */
|
||||
|
||||
/* Боковые границы (левый и правый │ для каждой строки тела) */
|
||||
for (uint8_t r = 1u; r < HELP_H - 1u; r++) {
|
||||
wrchar(HELP_X, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||
}
|
||||
|
||||
/* Нижняя граница рамки */
|
||||
wrchar(HELP_X, HELP_Y + HELP_H - 1u, 0xC0, ATTR_HELP_BDR); /* └ */
|
||||
bios_set_place((uint8_t)(HELP_Y + HELP_H - 1u), (uint8_t)(HELP_X + 1u));
|
||||
bios_fillcharattr(0xC4, ATTR_HELP_BDR, (uint8_t)(HELP_W - 2u));
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y + HELP_H - 1u, 0xD9, ATTR_HELP_BDR); /* ┘ */
|
||||
|
||||
/* Содержимое (20 внутренних строк) */
|
||||
uint8_t r = 0;
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " MDView v1.0 (b3) -- Markdown Viewer for Sprinter", ATTR_HELP_HDR);
|
||||
help_line(r++, " (c) 2026 \x8F\xA5\xE2\xE0\xAE\xA2 \x80\x2E\x83\x2E",
|
||||
ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Navigation:", ATTR_HELP_HDR);
|
||||
help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG);
|
||||
help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG);
|
||||
help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG);
|
||||
help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables/unwrap)", ATTR_HELP_BG);
|
||||
help_line(r++, " Esc F10 Exit", ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Markdown elements:", ATTR_HELP_HDR);
|
||||
help_line(r++, " # ## ### Headings H1-H6, **bold**, *italic*,", ATTR_HELP_BG);
|
||||
help_line(r++, " `code`, ``` code ```, ~~strike~~, > quote", ATTR_HELP_BG);
|
||||
help_line(r++, " - * + 1. 2. Ordered list, |----|----| Tables", ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Encoding and View modes:", ATTR_HELP_HDR);
|
||||
help_line(r++, " F2 RAW Mode", ATTR_HELP_BG);
|
||||
help_line(r++, " F3 Wrap/Unwrap Mode for RAW View", ATTR_HELP_BG);
|
||||
help_line(r++, " F4 HEX Mode (dump of original file)", ATTR_HELP_BG);
|
||||
help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG);
|
||||
help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " File size: up to 256 KB (EMM). Lines: up to 18432.", ATTR_HELP_BG);
|
||||
|
||||
(void)getkey();
|
||||
|
||||
render_full_status();
|
||||
draw_viewport_from_cache();
|
||||
render_menu();
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* mdview2_hex.c — HEX-дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||
*
|
||||
* Показывает исходные байты файла (orig_file_phys), а не активный буфер:
|
||||
* hex-дамп декодированного UTF-набора был бы искажением. Printable-колонка
|
||||
* интерпретируется ТЕКУЩЕЙ кодировкой: CP866 как есть, CP1251/KOI8 через
|
||||
* g_remap, UTF-8 — глиф на позиции лид-байта (continuation-байты → '.').
|
||||
*
|
||||
* Формат ряда (79 колонок, один атрибут ATTR_TEXT — как RAW):
|
||||
* 0x012340 │ XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX │ 16 символов
|
||||
*
|
||||
* Ряд выровнен на 16 байт и никогда не пересекает границу EMM-страницы
|
||||
* (16 делит 16384 нацело) — один bank_read на ряд, fb()/W3-маппинг не нужны.
|
||||
* Позиция (hex_top) живёт в байтах ОРИГИНАЛА; конвертацию в/из байт-позиции
|
||||
* активного буфера делает ядро (view_pos/view_reanchor через map_off).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include <sprinter_mem.h> /* bank_read (чтение оригинала по страницам EMM) */
|
||||
#include "mdview2.h"
|
||||
|
||||
#if WITH_HEX /* весь модуль опционален (mdview2_conf.h); при 0 пустой объектник */
|
||||
|
||||
#define HEX_BPR 16u /* байт на ряд */
|
||||
#define HEX_SPAN ((uint32_t)(VIEW_H - 1) * HEX_BPR)
|
||||
|
||||
/* Геометрия ряда (колонки). */
|
||||
#define HX_OFF 1u /* "0x012340" */
|
||||
#define HX_SEP1 10u /* │ */
|
||||
#define HX_DUMP 12u /* пары hex-цифр */
|
||||
#define HX_SEP2 61u /* │ */
|
||||
#define HX_PRINT 63u /* printable */
|
||||
|
||||
static uint32_t hex_top; /* offset верхнего ряда (кратен 16) */
|
||||
|
||||
uint32_t hex_pos(void) { return hex_top; }
|
||||
|
||||
/* Максимальный hex_top: последний ряд файла на нижней строке экрана. */
|
||||
static uint32_t hex_max_top(void)
|
||||
{
|
||||
uint32_t last = (orig_file_size == 0) ? 0
|
||||
: ((orig_file_size - 1u) & ~(uint32_t)(HEX_BPR - 1u));
|
||||
return (last > HEX_SPAN) ? (last - HEX_SPAN) : 0;
|
||||
}
|
||||
|
||||
void hex_reanchor(uint32_t orig_off)
|
||||
{
|
||||
uint32_t mt = hex_max_top();
|
||||
orig_off &= ~(uint32_t)(HEX_BPR - 1u);
|
||||
hex_top = (orig_off > mt) ? mt : orig_off;
|
||||
}
|
||||
|
||||
void hex_screen_init(void)
|
||||
{
|
||||
bios_clearwin(VIEW_TOP_ROW, 0, VIEW_H, SCREEN_W, ATTR_TEXT);
|
||||
}
|
||||
|
||||
/* ---- отрисовка ----------------------------------------------------- */
|
||||
|
||||
static uint8_t hexdig(uint8_t v)
|
||||
{
|
||||
v &= 15u;
|
||||
return (uint8_t)((v < 10u) ? ('0' + v) : ('A' - 10 + v));
|
||||
}
|
||||
|
||||
/* Один байт оригинала (для UTF-lookahead за границей ряда — редкий путь). */
|
||||
static uint8_t fbo(uint32_t off)
|
||||
{
|
||||
uint8_t b;
|
||||
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), &b, 1);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* Printable-глиф для n байт ряда b[] (off — offset b[0] в файле) → out[]. */
|
||||
static void hex_print_cells(const uint8_t *b, uint32_t off, uint8_t n, uint8_t *out)
|
||||
{
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
uint8_t c = b[i];
|
||||
uint8_t g;
|
||||
if (c < 0x20u) {
|
||||
g = '.';
|
||||
} else if (c < 0x80u || g_encoding != ENC_UTF8) {
|
||||
g = (uint8_t)((c >= 0x80u && g_remap) ? g_remap[c - 0x80u] : c);
|
||||
} else if ((c & 0xC0u) == 0x80u) {
|
||||
g = '.'; /* continuation-байт */
|
||||
} else {
|
||||
/* Лид-байт: собрать кодпойнт (хвост может выйти за ряд → fbo).
|
||||
* 4-байтовые (за пределами CP866) и битые последовательности → '.'. */
|
||||
uint32_t cp;
|
||||
uint8_t need, ok = 1;
|
||||
if ((c & 0xE0u) == 0xC0u) { cp = (uint32_t)(c & 0x1Fu); need = 1; }
|
||||
else if ((c & 0xF0u) == 0xE0u) { cp = (uint32_t)(c & 0x0Fu); need = 2; }
|
||||
else { out[i] = '.'; continue; }
|
||||
for (uint8_t k = 1; k <= need; k++) {
|
||||
uint32_t p = off + i + k;
|
||||
if (p >= orig_file_size) { ok = 0; break; }
|
||||
uint8_t cb = (uint8_t)((i + k < n) ? b[i + k] : fbo(p));
|
||||
if ((cb & 0xC0u) != 0x80u) { ok = 0; break; }
|
||||
cp = (cp << 6) | (uint32_t)(cb & 0x3Fu);
|
||||
}
|
||||
g = ok ? utf_cp_glyph(cp) : '.';
|
||||
if (g < 0x20u) g = '.'; /* overlong мог дать управляющий */
|
||||
}
|
||||
out[i] = g;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t hex_line[SCREEN_W]; /* собранный ряд (только символы, единый attr) */
|
||||
|
||||
static void hex_draw_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint8_t buf[HEX_BPR];
|
||||
uint8_t n = 0;
|
||||
|
||||
memset(hex_line, ' ', SCREEN_W);
|
||||
if (off < orig_file_size) {
|
||||
uint32_t rem = orig_file_size - off;
|
||||
n = (rem >= HEX_BPR) ? (uint8_t)HEX_BPR : (uint8_t)rem;
|
||||
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), buf, n);
|
||||
}
|
||||
if (n) { /* ряды за EOF — пустые */
|
||||
hex_line[HX_OFF] = '0';
|
||||
hex_line[HX_OFF + 1] = 'x';
|
||||
hex_line[HX_OFF + 2] = hexdig((uint8_t)(off >> 20));
|
||||
hex_line[HX_OFF + 3] = hexdig((uint8_t)(off >> 16));
|
||||
hex_line[HX_OFF + 4] = hexdig((uint8_t)(off >> 12));
|
||||
hex_line[HX_OFF + 5] = hexdig((uint8_t)(off >> 8));
|
||||
hex_line[HX_OFF + 6] = hexdig((uint8_t)(off >> 4));
|
||||
hex_line[HX_OFF + 7] = hexdig((uint8_t)off);
|
||||
hex_line[HX_SEP1] = TBL_V;
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
uint8_t x = (uint8_t)(HX_DUMP + i * 3u + ((i >= 8u) ? 1u : 0u));
|
||||
hex_line[x] = hexdig((uint8_t)(buf[i] >> 4));
|
||||
hex_line[x + 1] = hexdig(buf[i]);
|
||||
}
|
||||
hex_line[HX_SEP2] = TBL_V;
|
||||
hex_print_cells(buf, off, n, hex_line + HX_PRINT);
|
||||
}
|
||||
bios_set_place(row, 0);
|
||||
bios_write_until((const char *)hex_line, SCREEN_W, 0);
|
||||
}
|
||||
|
||||
void hex_draw(void)
|
||||
{
|
||||
uint32_t off = hex_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||
hex_draw_row(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||
off += HEX_BPR;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- статус-бар: % по байтам (как calc_raw_pct) --------------------- */
|
||||
|
||||
static uint8_t calc_hex_pct(void)
|
||||
{
|
||||
return pct16((uint16_t)(hex_top >> 8), (uint16_t)(orig_file_size >> 8));
|
||||
}
|
||||
|
||||
/* ---- навигация (вызывается из главного цикла при g_view == VIEW_HEX) */
|
||||
|
||||
uint8_t hex_key(uint8_t scan)
|
||||
{
|
||||
uint32_t mt = hex_max_top();
|
||||
switch (scan) {
|
||||
/* Одна строка — аппаратный скролл окна + подрисовка одного ряда
|
||||
* (как в MD/RAW), без перерисовки всего экрана. */
|
||||
case KEY_DOWN:
|
||||
if (hex_top < mt) {
|
||||
hex_top += HEX_BPR;
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||
hex_draw_row(hex_top + HEX_SPAN, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||
}
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (hex_top) {
|
||||
hex_top -= HEX_BPR;
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||
hex_draw_row(hex_top, VIEW_TOP_ROW);
|
||||
}
|
||||
break;
|
||||
case KEY_PGDN: {
|
||||
uint32_t nt = hex_top + (uint32_t)VIEW_H * HEX_BPR;
|
||||
if (nt > mt) nt = mt;
|
||||
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||
break;
|
||||
}
|
||||
case KEY_PGUP: {
|
||||
uint32_t d = (uint32_t)VIEW_H * HEX_BPR;
|
||||
uint32_t nt = (hex_top >= d) ? hex_top - d : 0;
|
||||
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||
break;
|
||||
}
|
||||
case KEY_HOME:
|
||||
if (hex_top) { hex_top = 0; hex_draw(); }
|
||||
break;
|
||||
case KEY_END:
|
||||
if (hex_top != mt) { hex_top = mt; hex_draw(); }
|
||||
break;
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
render_percent_progress(calc_hex_pct());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* WITH_HEX */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* mdview2_md.c — представление MD-документа (peer к mdview2_raw.c): отрисовка
|
||||
* области из рендер-кэша + прокрутка (верт./гориз.) + навигация md_key().
|
||||
* Статус-бар, меню и спиннер — в mdview2_status.c (общие для MD/RAW).
|
||||
*
|
||||
* Работает поверх готового рендер-кэша (cache_dir_get → cache_rec_t) и
|
||||
* состояния навигации (top_line/viewport_x/n_lines, см. mdview2.h). Никакого
|
||||
* парсинга/обращения к исходному файлу: всё рисуется из (char,attr)-ячеек кэша
|
||||
* через win_rest_remap (ремап 8-битных кодировок — в mdview2_enc.c).
|
||||
*
|
||||
* Пара видов: mdview2_md.c (MD, из кэша) ↔ mdview2_raw.c (RAW, из fb()).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* wrchar/scroll/COLOR */
|
||||
#include <bios/text.h> /* bios_set_place/bios_fillcharattr */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
||||
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
||||
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
||||
* байт ВНУТРИ ТОГО ЖЕ кэш-буфера (cache_reserve гарантирует, что вся
|
||||
* строка лежит в одной странице, так что off+vx*2 не пересекает
|
||||
* границу страницы); не nowrap-строки игнорируют viewport_x, как и
|
||||
* раньше в живом render_line() (там это называлось effective_vx). */
|
||||
static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
|
||||
{
|
||||
cache_rec_t rec;
|
||||
cache_dir_get(line_idx, &rec);
|
||||
|
||||
/* Строка-обрыв (исчерпан контент-кэш / лимит строк): фикс. текст вживую,
|
||||
* без обращения к контент-кэшу (см. index_lines финализацию). */
|
||||
if (rec.flags & IF_TRUNC_MSG) {
|
||||
const char *msg;
|
||||
switch (rec.reserved) {
|
||||
case TRUNC_LINES: msg = "*** Line limit reached - document truncated ***"; break;
|
||||
case TRUNC_FILE: msg = "*** File too large - truncated at 256 KB ***"; break;
|
||||
default: msg = "*** Content cache exhausted - document truncated ***"; break;
|
||||
}
|
||||
bios_set_place(row, 0);
|
||||
bios_fillcharattr(' ', ATTR_WARN, SCREEN_W);
|
||||
put_str_attr(2, row, msg, ATTR_WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
bios_set_place(row, 0);
|
||||
bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);
|
||||
|
||||
/* Горизонтальный сдвиг применяется ко ВСЕМ строкам hscroll-блока (код,
|
||||
* таблицы) — по ТИПУ, а не по длине: блок двигается целиком, включая
|
||||
* строки короче 80 (короткая строка, ушедшая за левый край, просто
|
||||
* показывает маркер '<' на пустом месте). HR и границы fence не имеют
|
||||
* IF_HSCROLL и не сдвигаются. */
|
||||
uint8_t vx = (rec.flags & IF_HSCROLL) ? viewport_x : 0;
|
||||
|
||||
if (rec.len > vx) {
|
||||
uint16_t remaining = (uint16_t)(rec.len - vx);
|
||||
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
||||
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
||||
win_rest_remap(row, w, rec.page, off);
|
||||
if (remaining > SCREEN_W) {
|
||||
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||||
}
|
||||
}
|
||||
if (vx > 0) {
|
||||
wrchar(0, row, '<', ATTR_TRUNC);
|
||||
}
|
||||
}
|
||||
|
||||
/* Перерисовывает все VIEW_H строк окна из кэша — основной путь
|
||||
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
|
||||
void draw_viewport_from_cache(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================
|
||||
* Прокрутка
|
||||
* ================================================================== */
|
||||
|
||||
/* Ограничивает top_line допустимым диапазоном с учётом высоты окна. */
|
||||
void clamp_top(void)
|
||||
{
|
||||
if (n_lines <= VIEW_H) {
|
||||
top_line = 0;
|
||||
} else if (top_line > n_lines - VIEW_H) {
|
||||
top_line = (uint16_t)(n_lines - VIEW_H);
|
||||
}
|
||||
}
|
||||
|
||||
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
|
||||
void md_scroll_up(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
|
||||
draw_line_from_cache((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
|
||||
} else {
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
|
||||
void md_scroll_down(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||||
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
|
||||
draw_line_from_cache((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||
} else {
|
||||
clamp_top();
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
|
||||
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране.
|
||||
* Фаза 5: ширина берётся прямо из директории кэша (rec.len — уже
|
||||
* посчитанная реальная длина в ячейках после рендера), без единого
|
||||
* обращения к fb()/seg_off()/исходному файлу. */
|
||||
void md_scroll_horizon(int8_t delta)
|
||||
{
|
||||
uint16_t maxw = 0;
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
uint16_t li = (uint16_t)(top_line + i);
|
||||
if (li >= n_lines) break;
|
||||
cache_rec_t rec;
|
||||
cache_dir_get(li, &rec);
|
||||
if (!(rec.flags & IF_HSCROLL)) continue; /* только код/таблицы */
|
||||
if (rec.len > maxw) maxw = rec.len; /* самая широкая строка блока */
|
||||
}
|
||||
if (maxw == 0) return; /* нет строк шире экрана */
|
||||
|
||||
/* Максимальный сдвиг = ширина за пределами экрана, в границах uint8. */
|
||||
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
|
||||
if (over > 248u) over = 248u;
|
||||
uint8_t max_vx = (uint8_t)over;
|
||||
|
||||
int16_t nx = (int16_t)viewport_x + delta;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > (int16_t)max_vx) nx = max_vx;
|
||||
|
||||
/* ПОРЯДОК ВАЖЕН: записываем viewport_x ДО сравнения, а сравниваем уже
|
||||
* сохранённую копию old_vx с new_vx. Иначе SDCC генерирует для
|
||||
* `if (new_vx != viewport_x) viewport_x = new_vx;` такой код:
|
||||
* ld a, new_vx ; sub a,(viewport_x) ; jr Z,.. ; ld (viewport_x),a
|
||||
* — т.е. для записи переиспользует регистр A, испорченный вычитанием в
|
||||
* сравнении, и кладёт в viewport_x не new_vx, а (new_vx - old_vx).
|
||||
* (asm-дамп 2026-06-24 подтвердил; даёт 10-8=2, 10-2=8 — ровно
|
||||
* наблюдавшийся "прыжок" скролла.) Записав viewport_x первой, мы убираем
|
||||
* портящий sub с пути записи; сравнение ниже только решает, перерисовывать
|
||||
* ли экран. */
|
||||
uint8_t new_vx = (uint8_t)nx;
|
||||
uint8_t old_vx = viewport_x;
|
||||
viewport_x = new_vx;
|
||||
if (new_vx != old_vx)
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
|
||||
/* Процент прокрутки (0..100) относительно доступного диапазона. */
|
||||
static uint8_t calc_md_pct(void)
|
||||
{
|
||||
uint16_t total = drawable_lines();
|
||||
if (total <= VIEW_H) return 100;
|
||||
return pct16(top_line, (uint16_t)(total - VIEW_H));
|
||||
}
|
||||
|
||||
/* ---- навигация MD после загрузки (peer к raw_key) ---------------- */
|
||||
|
||||
/* Обрабатывает навигационную клавишу в MD-режиме (вызывается из главного
|
||||
* цикла). Сама перерисовывает область/статус. Возврат: 1 — обработано,
|
||||
* 0 — не наша клавиша (F1/F8/F10 разбирает main). */
|
||||
uint8_t md_key(uint8_t scan)
|
||||
{
|
||||
switch (scan) {
|
||||
case KEY_UP:
|
||||
md_scroll_up(1);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
md_scroll_down(1);
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
md_scroll_horizon(-(int8_t)HPAN_STEP);
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
md_scroll_horizon(+(int8_t)HPAN_STEP);
|
||||
break;
|
||||
case KEY_PGUP:
|
||||
md_scroll_up(VIEW_H);
|
||||
break;
|
||||
case KEY_PGDN:
|
||||
md_scroll_down(VIEW_H);
|
||||
break;
|
||||
case KEY_HOME:
|
||||
if(top_line != 0 || viewport_x != 0 ) {
|
||||
top_line = 0;
|
||||
viewport_x = 0;
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
break;
|
||||
case KEY_END:
|
||||
uint16_t new_top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
|
||||
if(top_line != new_top_line || viewport_x != 0 ) {
|
||||
top_line = new_top_line;
|
||||
viewport_x = 0;
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
|
||||
render_percent_progress(calc_md_pct());
|
||||
render_md_status_numbers();
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* mdview2_raw.c — RAW-просмотр исходного текста (без markdown-форматирования).
|
||||
*
|
||||
* Работает по АКТИВНОМУ буферу документа через fb() (8-бит = исходные байты,
|
||||
* UTF-8 = декодированный в CP866 буфер), ремап ≥0x80 (CP1251/KOI8) — на
|
||||
* отрисовке. Индекс/кэш markdown не используются: позиция — байт-offset,
|
||||
* 1 байт = 1 ячейка, \t показываем пробелом. Два режима:
|
||||
* VIEW_RAW_WRAP — длинные строки переносятся кратно 80 (рвём слова);
|
||||
* VIEW_RAW_HSCROLL — строка в один ряд, горизонтальный скролл.
|
||||
*
|
||||
* Память: 0 доп. EMM; near-буфер строки + переиспользование g_scratch_phys.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include "mdview2.h"
|
||||
|
||||
#if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */
|
||||
|
||||
static uint32_t raw_top; /* байт-offset верхней строки экрана */
|
||||
static uint16_t raw_hpan; /* гориз. сдвиг (только HSCROLL) */
|
||||
|
||||
/* ---- примитивы навигации по физическим строкам / переносам -------- */
|
||||
|
||||
static uint32_t raw_line_start(uint32_t off)
|
||||
{
|
||||
while (off && fb(off - 1) != '\n') off--;
|
||||
return off;
|
||||
}
|
||||
|
||||
/* WRAP: начало следующего ряда от off (\n завершает строку; иначе +80). */
|
||||
static uint32_t raw_next_wrap(uint32_t off)
|
||||
{
|
||||
uint32_t e = off, lim = off + SCREEN_W;
|
||||
while (e < file_size) {
|
||||
if (fb(e) == '\n') return e + 1;
|
||||
e++;
|
||||
if (e >= lim) { /* прошли 80 без \n */
|
||||
if (e < file_size && fb(e) == '\n') return e + 1; /* ровно 80 + \n */
|
||||
return e; /* длинная строка → продолжение */
|
||||
}
|
||||
}
|
||||
return e; /* EOF */
|
||||
}
|
||||
|
||||
static uint32_t raw_prev_wrap(uint32_t off)
|
||||
{
|
||||
if (off == 0) return 0;
|
||||
uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */
|
||||
while (pls + SCREEN_W < (off - 1)) pls += SCREEN_W;
|
||||
return pls;
|
||||
}
|
||||
|
||||
/* HSCROLL: начало следующей/предыдущей физической строки. */
|
||||
static uint32_t raw_next_line(uint32_t off)
|
||||
{
|
||||
while (off < file_size && fb(off) != '\n') off++;
|
||||
return (off < file_size) ? off + 1 : off;
|
||||
}
|
||||
|
||||
static uint32_t raw_prev_line(uint32_t off)
|
||||
{
|
||||
if (off == 0) return 0;
|
||||
return raw_line_start(off - 1);
|
||||
}
|
||||
|
||||
/* ---- отрисовка ряда (общий near-буфер → g_scratch_phys → win_rest) - */
|
||||
|
||||
static uint8_t raw_buf[SCREEN_W + 1]; /* только символы (атрибут единый, задаётся при выводе) */
|
||||
|
||||
/* В RAW атрибут единый (ATTR_TEXT): буфер — только 80 символов, без пар
|
||||
* (char,attr). Кладём один байт-глиф (\t→пробел, ремап ≥0x80). */
|
||||
static void raw_cell(uint8_t i, uint8_t ch)
|
||||
{
|
||||
if (ch >= 0x80 && g_remap) ch = g_remap[ch - 0x80];
|
||||
raw_buf[i] = ch;
|
||||
}
|
||||
/* Вывод готового 80-символьного буфера в строку экрана одним BIOS-вызовом
|
||||
* (char-буфер + единый attr — без WINREST/scratch-страницы). */
|
||||
static void raw_flush(uint8_t row)
|
||||
{
|
||||
bios_set_place(row, 0);
|
||||
// bios_writeattr((const char *)raw_buf, SCREEN_W, ATTR_TEXT);
|
||||
// bios_writeattr_until((const char *)raw_buf, SCREEN_W, ATTR_TEXT, 0);
|
||||
bios_write_until((const char *)raw_buf, SCREEN_W, 0);
|
||||
}
|
||||
|
||||
static uint32_t raw_draw_wrap_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
uint32_t e = off, lim = off + SCREEN_W;
|
||||
while (i < SCREEN_W && e < file_size) {
|
||||
char c = fb(e);
|
||||
if (c == '\n') {
|
||||
raw_cell(i, 0);
|
||||
break;
|
||||
}
|
||||
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||
i++; e++;
|
||||
}
|
||||
raw_flush(row);
|
||||
return raw_next_wrap(off);
|
||||
}
|
||||
|
||||
static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint32_t e = off;
|
||||
while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */
|
||||
uint16_t len = e - off;
|
||||
|
||||
uint8_t i = 0;
|
||||
uint32_t p = off + raw_hpan;
|
||||
while (i < SCREEN_W && p < e) {
|
||||
char c = fb(p);
|
||||
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||
i++; p++;
|
||||
}
|
||||
// raw_buf[i] = 0;
|
||||
|
||||
if (raw_hpan > 0)
|
||||
raw_buf[0] = '<';
|
||||
if (len > (uint16_t)raw_hpan + SCREEN_W)
|
||||
raw_buf[SCREEN_W - 1] = '>';
|
||||
if (i < SCREEN_W)
|
||||
raw_buf[i] = 0;
|
||||
raw_flush(row);
|
||||
return (e < file_size) ? e + 1 : e;
|
||||
}
|
||||
|
||||
/* Рисует один ряд от off (по режиму) и возвращает offset следующего ряда. */
|
||||
static uint32_t raw_draw_one(uint32_t off, uint8_t row)
|
||||
{
|
||||
if (off >= file_size) { fill_row(row, ATTR_TEXT); return off; }
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_draw_wrap_row(off, row)
|
||||
: raw_draw_hscroll_row(off, row);
|
||||
}
|
||||
|
||||
void raw_draw(void)
|
||||
{
|
||||
uint32_t off = raw_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++)
|
||||
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||
}
|
||||
|
||||
/* Прокрутка на одну строку: аппаратный scroll окна документа + отрисовка
|
||||
* ОДНОЙ новой строки (как в MD-режиме). Смещение нижнего ряда вычисляется
|
||||
* проходом VIEW_H рядов от raw_top — без отдельного инкрементального счётчика,
|
||||
* который рассинхронизировался, когда контент кончается в середине экрана
|
||||
* (короче окна после wrap→unwrap у конца файла). */
|
||||
static uint32_t raw_next(uint32_t off)
|
||||
{
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_next_wrap(off) : raw_next_line(off);
|
||||
}
|
||||
static uint32_t raw_prev(uint32_t off)
|
||||
{
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_prev_wrap(off) : raw_prev_line(off);
|
||||
}
|
||||
|
||||
/* ---- статус-бар: % по байтам (отрисовка — render_raw_status_numbers
|
||||
* в mdview2_status.c, читает позицию через этот raw_pct) ---- */
|
||||
|
||||
/* Процент по байтам без 32-битного деления: масштабируем offset'ы (>>8 →
|
||||
* ≤1024), дальше 16-битный pct16 (без __divulong). raw_top приватен модулю,
|
||||
* поэтому % считаем здесь и отдаём готовым числом в статус-модуль. */
|
||||
uint8_t calc_raw_pct(void)
|
||||
{
|
||||
uint16_t a = (uint16_t)(raw_top >> 8);
|
||||
uint16_t b = (uint16_t)(file_size >> 8);
|
||||
return pct16(a, b); /* den==0 (файл < 1 КБ) → pct16 вернёт 0 */
|
||||
}
|
||||
|
||||
/* ---- посев позиции / пересев при смене активного буфера ----------- */
|
||||
|
||||
uint32_t raw_pos(void)
|
||||
{
|
||||
return raw_top;
|
||||
}
|
||||
|
||||
/* Поставить RAW на начало строки, содержащей байт off (позиционирование
|
||||
* при F2 MD→RAW и F8-переносе позиции между наборами). */
|
||||
void raw_reanchor(uint32_t off)
|
||||
{
|
||||
if (off > file_size) off = file_size;
|
||||
raw_top = raw_line_start(off);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_seed_from(uint16_t md_top_line)
|
||||
{
|
||||
raw_reanchor(seg_off(md_top_line));
|
||||
}
|
||||
|
||||
/* Сброс в начало (клавиша Home). */
|
||||
static void raw_home(void)
|
||||
{
|
||||
raw_top = 0;
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_end(void)
|
||||
{
|
||||
raw_top = file_size; /* отмотать VIEW_H рядов назад */
|
||||
for (uint8_t k = 0; k < VIEW_H; k++)
|
||||
raw_top = raw_prev(raw_top);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_screen_init(void) {
|
||||
bios_clearwin(1, 0, 30, 80, ATTR_TEXT);
|
||||
}
|
||||
|
||||
/* Нормализация позиции при смене под-режима RAW (F3 Wrap/Unwrap): выровнять
|
||||
* raw_top на начало физической строки и снять горизонтальный сдвиг. */
|
||||
void raw_renorm(void)
|
||||
{
|
||||
raw_top = raw_line_start(raw_top);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_scroll_down(uint8_t delta) {
|
||||
uint32_t off = raw_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||
if (off >= file_size)
|
||||
return; /* экран не заполнен контентом */
|
||||
off = raw_next(off);
|
||||
}
|
||||
if (off >= file_size)
|
||||
return; /* контент ровно по экран, ниже пусто */
|
||||
|
||||
if(delta ==1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1)); /* новый нижний ряд */
|
||||
raw_top = raw_next(raw_top);
|
||||
} else {
|
||||
for (uint8_t k = 0; k < delta; k++) {
|
||||
uint32_t n2 = raw_next(raw_top);
|
||||
if (n2 >= file_size)
|
||||
break;
|
||||
raw_top = n2;
|
||||
}
|
||||
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||
}
|
||||
}
|
||||
|
||||
void raw_scroll_up(uint8_t delta) {
|
||||
if (raw_top == 0)
|
||||
return;
|
||||
|
||||
if (delta == 1) {
|
||||
raw_top = raw_prev(raw_top);
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||
(void)raw_draw_one(raw_top, VIEW_TOP_ROW);
|
||||
} else {
|
||||
for (uint8_t k = 0; k < delta; k++)
|
||||
raw_top = raw_prev(raw_top);
|
||||
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */
|
||||
|
||||
uint8_t raw_key(uint8_t scan)
|
||||
{
|
||||
switch (scan) {
|
||||
case KEY_DOWN:
|
||||
raw_scroll_down(1);
|
||||
break;
|
||||
case KEY_UP:
|
||||
raw_scroll_up(1);
|
||||
break;
|
||||
case KEY_PGDN:
|
||||
raw_scroll_down(VIEW_H);
|
||||
break;
|
||||
case KEY_PGUP:
|
||||
raw_scroll_up(VIEW_H);
|
||||
break;
|
||||
case KEY_HOME:
|
||||
raw_home();
|
||||
raw_draw();
|
||||
break;
|
||||
case KEY_END:
|
||||
raw_end();
|
||||
raw_draw();
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
if (g_view == VIEW_RAW_HSCROLL && raw_hpan) {
|
||||
raw_hpan = (raw_hpan >= HPAN_STEP) ? (uint16_t)(raw_hpan - HPAN_STEP) : 0;
|
||||
raw_draw();
|
||||
}
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
if (g_view == VIEW_RAW_HSCROLL && raw_hpan < 248u) {
|
||||
raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP);
|
||||
raw_draw();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
|
||||
// render_raw_status_numbers();
|
||||
render_percent_progress(calc_raw_pct());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* WITH_RAW */
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* mdview2_status.c — статус-бар (строка 0), нижнее меню (строка 31) и спиннер.
|
||||
*
|
||||
* Хром бара АТОМИЗИРОВАН — обновляется по частям, без перерисовки всего бара:
|
||||
* prerender_status() — фон + разделители │ + "MDVIEW" + имя файла (1 раз);
|
||||
* status_encoding() — только поле кодировки (col 37), по F8;
|
||||
* render_md_status_numbers() — числа MD: диапазон строк + % (при скролле);
|
||||
* render_raw_status_numbers()— числа RAW: метка режима + % по байтам;
|
||||
* render_full_status() — encoding + numbers (MD), при смене набора/режима;
|
||||
* render_menu() — строка меню, по смене режима/готовности;
|
||||
* spinner_tick/show — индикатор занятости во время загрузки.
|
||||
* Оба *_status_numbers() самоочищаются по геометрии разделителей (DIV1_X/DIV2_X
|
||||
* в mdview2.h) — фикс. хром не перерисовывается. RAW-позицию даёт raw_pct()
|
||||
* из mdview2_raw.c (raw_top там приватен).
|
||||
*
|
||||
* Раскладка строки 0:
|
||||
* 0 1..6 8 10.. 37..43 45 46..70 71 72..79
|
||||
* . MDVIEW spinner filename encod. │ L a-b / total │ pct%
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h> /* dec8/dec16, cputs */
|
||||
#include <conio.h> /* textattr/gotoxy/wrchar/COLOR */
|
||||
#include <limits.h>
|
||||
#include <bios/text.h> /* bios_fillcharattr/bios_write_until */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* ---- Спиннер (слот SPINNER_COL в баре) --------------------------- */
|
||||
static const char spinner_chars[4] = { '|', '/', '-', '\\' };
|
||||
static uint8_t spinner_phase = 0;
|
||||
static uint8_t spinner_active = 0;
|
||||
|
||||
/* Продвигает спиннер на один кадр (если включён). */
|
||||
void spinner_tick(void)
|
||||
{
|
||||
if (!spinner_active)
|
||||
return;
|
||||
wrchar(SPINNER_COL, 0, spinner_chars[spinner_phase & 3], ATTR_BAR_SPINNER);
|
||||
spinner_phase++;
|
||||
}
|
||||
|
||||
/* Вкл/выкл спиннер; при выключении гасит его позицию. */
|
||||
void spinner_show(uint8_t on)
|
||||
{
|
||||
spinner_active = on;
|
||||
if (!on) wrchar(SPINNER_COL, 0, ' ', ATTR_BAR);
|
||||
}
|
||||
|
||||
/* ---- Вычисления для чисел статус-бара ----------------------------- */
|
||||
|
||||
/* Число строк, готовых к показу. Во время загрузки последняя эмитированная
|
||||
* строка ещё в g_cells (не зафлашена), поэтому доступны [0..n_lines-2]. */
|
||||
uint16_t drawable_lines(void)
|
||||
{
|
||||
if (g_loading)
|
||||
return (n_lines >= 1) ? (uint16_t)(n_lines - 1) : 0;
|
||||
return n_lines;
|
||||
}
|
||||
|
||||
|
||||
/* ---- Атомарные части статус-бара ---------------------------------- */
|
||||
|
||||
/* Фиксированный хром бара — рисуется ОДИН раз при старте: фон, два
|
||||
* разделителя │, метка "MDVIEW" и имя файла (они не меняются за сессию). */
|
||||
void prerender_status(void)
|
||||
{
|
||||
fill_row(0, ATTR_BAR);
|
||||
wrchar(DIV1_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||
wrchar(DIV2_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
|
||||
put_str_attr(10, 0, filename, ATTR_BAR);
|
||||
}
|
||||
|
||||
/* Поле текущей кодировки (col 37, 7 ячеек). Меняется только по F8. */
|
||||
void status_encoding(void)
|
||||
{
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X - 10, 0);
|
||||
bios_write_until(enc_name(g_encoding), 8, 0);
|
||||
}
|
||||
|
||||
static uint16_t local_total = UINT_MAX;
|
||||
static uint16_t local_last = UINT_MAX;
|
||||
static uint8_t local_loading = UCHAR_MAX;
|
||||
static uint8_t local_pct = UCHAR_MAX;
|
||||
|
||||
/* Числовая часть (MD): "L a-b / total" между разделителями + "pct%" справа.
|
||||
* Перезаписывает ТОЛЬКО области между/после разделителей, сами │ и фикс.
|
||||
* хром не трогает. */
|
||||
void render_md_status_numbers(void)
|
||||
{
|
||||
uint16_t total = drawable_lines();
|
||||
uint16_t last = top_line + VIEW_H;
|
||||
if (last > total)
|
||||
last = total;
|
||||
|
||||
if(local_total != total || local_last != last || local_loading != g_loading) {
|
||||
local_total = total; local_last = last; local_loading = g_loading;
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 2, 0);
|
||||
bios_fillcharattr(' ', ATTR_BAR, DIV2_X - DIV1_X - 2); /* очистить [DIV1_X+1 .. DIV2_X-1] */
|
||||
gotoxy(DIV1_X + 2, 0);
|
||||
cputs("L ");
|
||||
dec16(top_line + 1);
|
||||
cputs("-");
|
||||
dec16(last);
|
||||
cputs(" / ");
|
||||
dec16(total);
|
||||
if (g_loading) cputs("..."); /* ещё грузится */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Полное обновление переменной части (MD): кодировка + числа. */
|
||||
void render_full_status(void)
|
||||
{
|
||||
local_loading = UCHAR_MAX;
|
||||
status_encoding();
|
||||
render_md_status_numbers();
|
||||
}
|
||||
|
||||
void render_percent_progress(uint8_t pct) {
|
||||
if (local_pct != pct) {
|
||||
local_pct = pct;
|
||||
gotoxy(DIV2_X + 2, 0);
|
||||
bios_fillcharattr(' ', ATTR_BAR, SCREEN_W - DIV2_X - 2); /* очистить [DIV2_X+1 .. конец] */
|
||||
gotoxy(DIV2_X + 2, 0);
|
||||
dec8(pct);
|
||||
cputs("%");
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_RAW
|
||||
/* RAW-вариант числовой части бара (peer к render_md_status_numbers): метка
|
||||
* режима между разделителями + процент справа. Сам очищает свои области (та же
|
||||
* геометрия DIVn_X), атомарен — не требует общего fill всего бара. Позиция
|
||||
* берётся готовым числом из raw_pct() (raw_top приватен mdview2_raw.c). */
|
||||
void render_raw_status(void)
|
||||
{
|
||||
// uint8_t pct = raw_pct();
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 1, 0);
|
||||
bios_writeattr_until(g_view == VIEW_RAW_WRAP ? " RAW wrap" : " RAW pan",
|
||||
DIV2_X - DIV1_X - 1, ATTR_BAR, 0); /* метка + пад до DIV2_X-1 */
|
||||
}
|
||||
#endif /* WITH_RAW */
|
||||
|
||||
#if WITH_HEX
|
||||
/* HEX-вариант числовой части бара (peer к render_raw_status). */
|
||||
void render_hex_status(void)
|
||||
{
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 1, 0);
|
||||
bios_writeattr_until(" HEX", DIV2_X - DIV1_X - 1, ATTR_BAR, 0);
|
||||
}
|
||||
#endif /* WITH_HEX */
|
||||
|
||||
/* ---- Нижнее меню (строка 31) -------------------------------------- */
|
||||
|
||||
void render_menu(void)
|
||||
{
|
||||
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
|
||||
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
|
||||
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
|
||||
* только у задействованных и доступных сейчас. */
|
||||
char num[3];
|
||||
|
||||
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||
|
||||
num[2] = 0;
|
||||
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
|
||||
num[0] = ' '; num[1] = (char)('1' + i);
|
||||
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
|
||||
}
|
||||
put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */
|
||||
|
||||
put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T);
|
||||
#if WITH_RAW
|
||||
if (g_ready) /* RAW доступен после постройки документа */
|
||||
put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T);
|
||||
if (VIEW_IS_RAW(g_view)) /* Wrap/Unwrap — только в RAW */
|
||||
put_str_attr(2 * 8 + 2, MENU_ROW,
|
||||
(g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T);
|
||||
#endif
|
||||
#if WITH_HEX
|
||||
if (g_ready) /* HEX доступен после постройки документа */
|
||||
put_str_attr(3 * 8 + 2, MENU_ROW, (g_view == VIEW_HEX) ? "Back" : "Hex", ATTR_MENU_T);
|
||||
#endif
|
||||
if (g_f8_enabled) /* смена кодировки сейчас возможна */
|
||||
put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T);
|
||||
put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* mdview2_table.c — выровненная отрисовка markdown-таблиц.
|
||||
*
|
||||
* Разбор |-разделённых ячеек и эмиссия рамок/строк в общий буфер ячеек
|
||||
* рендера (gc_put/gc_fill, см. mdview2.h). Вызывается из index_lines()
|
||||
* ядра при обнаружении таблицы. Геометрия рамок (TBL_n), словарь стилей
|
||||
* (CK_n, INIT_STYLE_n) и cell-buffer API объявлены в mdview2.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* COLOR() / COLOR_* для атрибутов TBL_ATTR/ATTR_TEXT */
|
||||
#include "mdview2.h"
|
||||
|
||||
uint32_t row_end(uint32_t p)
|
||||
{
|
||||
while (p < file_size && fb(p) != '\n') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Начало контента первой ячейки строки (после ведущих пробелов и '|'). */
|
||||
uint32_t table_first_cell(uint32_t row_start)
|
||||
{
|
||||
uint32_t p = row_start;
|
||||
while (p < file_size && fb(p) == ' ') p++;
|
||||
if (p < file_size && fb(p) == '|') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Следующая ячейка: [*cs,*ce) — обрезанный диапазон; *pp продвигается за '|'.
|
||||
* 0 — ячеек больше нет (включая хвостовую пустоту после последнего '|'). */
|
||||
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce)
|
||||
{
|
||||
uint32_t p = *pp;
|
||||
if (p >= lineend) return 0;
|
||||
uint32_t a = p;
|
||||
while (p < lineend && fb(p) != '|') p++;
|
||||
uint32_t b = p;
|
||||
uint8_t had_pipe = (uint8_t)(p < lineend && fb(p) == '|');
|
||||
if (had_pipe) p++;
|
||||
*pp = p;
|
||||
while (a < b && fb(a) == ' ') a++;
|
||||
while (b > a && fb(b - 1) == ' ') b--;
|
||||
if (!had_pipe && a == b) return 0;
|
||||
*cs = a; *ce = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Строка-разделитель (|---|:-:|): каждая ячейка непустая и только '-'/':'. */
|
||||
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend)
|
||||
{
|
||||
uint32_t p = table_first_cell(row_start);
|
||||
uint32_t cs, ce;
|
||||
uint8_t n = 0;
|
||||
while (table_next_cell(&p, lineend, &cs, &ce)) {
|
||||
if (cs >= ce) return 0;
|
||||
for (uint32_t q = cs; q < ce; q++) { char c = fb(q); if (c != '-' && c != ':') return 0; }
|
||||
n++;
|
||||
}
|
||||
return (uint8_t)(n > 0);
|
||||
}
|
||||
|
||||
/* Горизонтальная рамка в g_cells: left + (H×(w+2) + mid|right) по колонкам. */
|
||||
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right)
|
||||
{
|
||||
gc_put(left, TBL_ATTR);
|
||||
for (uint8_t c = 0; c < ncols; c++) {
|
||||
gc_fill(TBL_H, TBL_ATTR, (uint8_t)(widths[c] + 2));
|
||||
gc_put((c == (uint8_t)(ncols - 1)) ? right : mid, TBL_ATTR);
|
||||
}
|
||||
}
|
||||
|
||||
/* Строка данных в g_cells: │ <ячейка, добитая до widths[c]> │ … */
|
||||
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols)
|
||||
{
|
||||
uint32_t p = table_first_cell(row_start);
|
||||
uint32_t cs, ce;
|
||||
gc_put(TBL_V, TBL_ATTR);
|
||||
for (uint8_t c = 0; c < ncols; c++) {
|
||||
uint8_t got = table_next_cell(&p, lineend, &cs, &ce);
|
||||
gc_put(' ', TBL_ATTR);
|
||||
uint8_t rendered = 0;
|
||||
if (got && ce > cs) {
|
||||
uint8_t before = g_ncells;
|
||||
(void)inline_scan(cs, ce, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1);
|
||||
rendered = (uint8_t)(g_ncells - before);
|
||||
}
|
||||
if (rendered < widths[c]) gc_fill(' ', ATTR_TEXT, (uint8_t)(widths[c] - rendered));
|
||||
gc_put(' ', TBL_ATTR);
|
||||
gc_put(TBL_V, TBL_ATTR);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Генератор тестовых файлов mdview2 для проверки лимита 256 КБ и путей обрыва.
|
||||
|
||||
Результат кладётся рядом со скриптом (examples/mdview2/testfiles/):
|
||||
BIG.MD — ~216 КБ обычной прозы (CP866): успешный рендер большого файла.
|
||||
TABLES.MD — таблично-тяжёлый (ASCII): пробивает кап контент-кэша (40 стр./набор),
|
||||
т.к. неровные таблицы (короткие ячейки + один широкий столбец)
|
||||
раздувают число (символ,атрибут)-ячеек кратно относительно источника.
|
||||
LINES.MD — ~22000 коротких строк (ASCII): пробивает лимит строк (18432).
|
||||
|
||||
Запуск: python3 gen_testfiles.py
|
||||
"""
|
||||
import os
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(HERE)
|
||||
|
||||
|
||||
def write(name, data):
|
||||
path = os.path.join(HERE, name)
|
||||
mode = 'wb' if isinstance(data, (bytes, bytearray)) else 'w'
|
||||
with open(path, mode) as f:
|
||||
f.write(data)
|
||||
print(f"{name}: {os.path.getsize(path)} bytes")
|
||||
|
||||
|
||||
# --- BIG.MD / HUGE.MD: склейка README + READMEBG, перекодировано в CP866 -----
|
||||
# BIG (×2 ≈170 КБ) — успешный рендер большого файла (<256 КБ).
|
||||
# HUGE (×4 ≈340 КБ) — БОЛЬШЕ 256 КБ: проверка клампа (читаются первые 256 КБ +
|
||||
# строка-предупреждение TRUNC_FILE).
|
||||
prose = bytearray()
|
||||
for fn in ('README.MD', 'READMEBG.MD'):
|
||||
with open(os.path.join(ROOT, fn), encoding='utf-8') as f:
|
||||
prose += f.read().encode('cp866', errors='replace')
|
||||
write('BIG.MD', bytes(prose * 2))
|
||||
write('HUGE.MD', bytes(prose * 4))
|
||||
|
||||
# --- TABLES.MD: неровные таблицы -> исчерпание контент-кэша ------------------
|
||||
# Один широкий столбец (72 симв.) на таблицу задаёт ширину; остальные ячейки
|
||||
# короткие, но при рендере добиваются до этой ширины -> ~80 ячеек/строка из
|
||||
# ~8 байт источника. ~300 таблиц × 30 строк дают ~840K ячеек (1.68 МБ контента)
|
||||
# при ~108 КБ источника -> кап 640 КБ/набор пробивается, строк (~11K) < 18432,
|
||||
# значит первой срабатывает причина TRUNC_CONTENT.
|
||||
NT, ROWS = 300, 30
|
||||
wide = 'x' * 72
|
||||
out = ['# Tables test - content cache exhaustion trigger', '']
|
||||
for t in range(NT):
|
||||
out.append(f'## Table {t + 1}')
|
||||
out.append('| Column A | B |')
|
||||
out.append('|---|---|')
|
||||
out.append(f'| {wide} | y |')
|
||||
for r in range(ROWS):
|
||||
out.append(f'| a{r} | b |')
|
||||
out.append('')
|
||||
write('TABLES.MD', '\n'.join(out) + '\n')
|
||||
|
||||
# --- LINES.MD: масса коротких строк -> исчерпание лимита строк ---------------
|
||||
# ВАЖНО: вьювер склеивает подряд идущие непустые строки в один параграф
|
||||
# (markdown soft-wrap), поэтому простые строки потекли бы в абзац. Нужен
|
||||
# контент БЕЗ склейки -> кладём всё в fenced code block (verbatim, 1:1
|
||||
# строка-источник = экранная строка). 22000 строк > 18432 -> TRUNC_LINES;
|
||||
# контент мал (~9 ячеек/строка), кап контента не задевается.
|
||||
N = 25000 # ~250 КБ -> 16 страниц -> max_lines = 18432; строк больше -> обрыв на 18432
|
||||
out = ['# Short lines test - line limit trigger', '', '```']
|
||||
out += [f'row {i:05d}' for i in range(1, N + 1)]
|
||||
out.append('```')
|
||||
write('LINES.MD', '\n'.join(out) + '\n')
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
# Build lib/libsprinter.lib — the Sprinter target libc archive.
|
||||
#
|
||||
# Includes all libc/*.c modules plus the runtime helpers that get
|
||||
# auto-pulled by SDCC's codegen (heap for malloc, bank trampolines for
|
||||
# __banked). The crt0 family is NOT in the lib — they are always
|
||||
# explicitly linked, never DCE-eligible.
|
||||
#
|
||||
# Each .c file becomes its own .rel inside the archive. The linker
|
||||
# pulls only those .rel files whose exported symbols are referenced,
|
||||
# giving free dead-code elimination at module granularity.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/..)
|
||||
SDCC_BIN := $(PROJ_ROOT)/third_party/sdcc/bin
|
||||
SDCC := $(SDCC_BIN)/sdcc
|
||||
SDAR := $(SDCC_BIN)/sdar
|
||||
SDASZ80 := $(SDCC_BIN)/sdasz80
|
||||
|
||||
INC := -I$(PROJ_ROOT)/libc/include
|
||||
CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
|
||||
|
||||
BUILD := $(PROJ_ROOT)/lib/build
|
||||
|
||||
# All libc C modules.
|
||||
LIBC_C := \
|
||||
libc/sys/atexit.c \
|
||||
libc/conio/conio.c \
|
||||
libc/conio/cprintf.c \
|
||||
libc/conio/text_palette.c \
|
||||
libc/bios/text.c \
|
||||
libc/io/dir.c \
|
||||
libc/video/videomode_raw.c \
|
||||
libc/video/palette.c \
|
||||
libc/errno/_errno_set.c \
|
||||
libc/env/env.c \
|
||||
libc/errno/errno.c \
|
||||
libc/io/fsdir.c \
|
||||
libc/io/lseek.c \
|
||||
libc/mouse/mouse.c \
|
||||
libc/io/open.c \
|
||||
libc/io/read.c \
|
||||
libc/time/sleep.c \
|
||||
libc/time/time.c \
|
||||
libc/time/posix_time.c \
|
||||
libc/io/unlink.c \
|
||||
libc/io/stat.c \
|
||||
libc/mem/bank_io_w3.c \
|
||||
libc/mem/bank_io_w1.c \
|
||||
libc/mem/mem_estex.c \
|
||||
libc/mem/mem_bios.c \
|
||||
libc/gfx/gfx_core.c \
|
||||
libc/gfx/gfx_palette.c \
|
||||
libc/gfx/gfx_raw_common.c \
|
||||
libc/gfx/gfx_raw_256.c \
|
||||
libc/gfx/gfx_raw_16.c \
|
||||
libc/gfx/gfx_256.c \
|
||||
libc/gfx/gfx_16.c \
|
||||
libc/gfx/gfx_font.c \
|
||||
libc/gfx/gfx_text_256.c \
|
||||
libc/gfx/gfx_text_16.c \
|
||||
libc/stdio/getchar.c \
|
||||
libc/stdio/putchar.c \
|
||||
libc/stdio/puts.c \
|
||||
libc/file/file.c \
|
||||
libc/stdio/hex_print.c \
|
||||
libc/stdio/dec_print.c \
|
||||
libc/string/strlwr.c \
|
||||
libc/string/strupr.c
|
||||
|
||||
# Runtime modules to bundle (pulled by symbol references from libc-using code).
|
||||
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
|
||||
# window (W1 for BIG, W3 for HUGE) which is decided per-build by sprinter-cc.
|
||||
# That tool assembles bank.s on each banked build with the right BANK_W1 flag.
|
||||
RUNTIME_S := runtime/heap.s
|
||||
|
||||
LIBC_RELS := $(patsubst libc/%.c,$(BUILD)/%.rel,$(LIBC_C))
|
||||
RUNTIME_RELS := $(patsubst runtime/%.s,$(BUILD)/%.rel,$(RUNTIME_S))
|
||||
|
||||
ALL_RELS := $(LIBC_RELS) $(RUNTIME_RELS)
|
||||
|
||||
LIB := sprinter.lib
|
||||
|
||||
all: $(LIB)
|
||||
|
||||
# Pattern rule for C modules — preserves the libc/io|mem|stdio path
|
||||
# so .rel members keep their natural names inside the archive.
|
||||
$(BUILD)/%.rel: $(PROJ_ROOT)/libc/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(SDCC) $(CC_FLAGS) -c -o $@ $<
|
||||
|
||||
# Runtime .s → .rel
|
||||
$(BUILD)/%.rel: $(PROJ_ROOT)/runtime/%.s
|
||||
@mkdir -p $(dir $@)
|
||||
$(SDASZ80) -o $@ $<
|
||||
|
||||
# Archive — sdar with `rcs` = replace/create/symtab.
|
||||
$(LIB): $(ALL_RELS)
|
||||
rm -f $@
|
||||
$(SDAR) -rcs $@ $(ALL_RELS)
|
||||
@echo
|
||||
@echo " Built $@ with $(words $(ALL_RELS)) modules:"
|
||||
@$(SDAR) -t $@ | sed 's/^/ /'
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD) $(LIB)
|
||||
|
||||
.PHONY: all clean
|
||||
@@ -0,0 +1,79 @@
|
||||
# Build lib/bgi256.lib — Sprinter BGI graphics driver (320×256×256).
|
||||
#
|
||||
# libbgi/ — самостоятельная графическая библиотека, отделённая от libc.
|
||||
# Две driver-библиотеки собираются из одной исходной базы:
|
||||
# bgi256.lib = common/*.rel + bgi256/*.rel (эта — Фаза 1)
|
||||
# bgi16.lib = common/*.rel + bgi16/*.rel (Фаза 2; пока не собирается)
|
||||
# common/ — mode-agnostic: .rel компилируется ОДИН раз и кладётся в ОБА
|
||||
# архива (один исходник для функций, общих 256- и 16-цветной версиям).
|
||||
# bgi256/ — mode-specific leaf'ы (реальные реализации, без обёрток).
|
||||
#
|
||||
# Каждый .c — отдельный .rel в архиве (1 публичная функция = 1 модуль);
|
||||
# линкер тянет только нужные .rel → DCE на уровне файлов. См. план
|
||||
# «Рефакторинг GFX → BGI» и memory/bgi_two_lib_design.
|
||||
#
|
||||
# make build lib/bgi256.lib
|
||||
# make clean remove build/ + lib/bgi256.lib
|
||||
#
|
||||
# (Фаза 2 добавит bgi16.lib = common + bgi16; common .rel переиспользуются.)
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/..)
|
||||
SDCC_BIN := $(PROJ_ROOT)/third_party/sdcc/bin
|
||||
SDCC := $(SDCC_BIN)/sdcc
|
||||
SDAR := $(SDCC_BIN)/sdar
|
||||
|
||||
# -I libbgi/include (gfx.h, graphics.h) + -I libc/include (palette.h,
|
||||
# sprinter_exit.h — публичные заголовки libc; libbgi всегда линкуется с
|
||||
# libc). SDCC stdint и пр. — из собственного include-пути SDCC.
|
||||
INC := -I$(CURDIR)/include -I$(PROJ_ROOT)/libc/include
|
||||
CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
|
||||
|
||||
BUILD := $(CURDIR)/build
|
||||
|
||||
COMMON_C := $(wildcard $(CURDIR)/common/*.c)
|
||||
BGI256_C := $(wildcard $(CURDIR)/bgi256/*.c)
|
||||
# BGI16_C := $(wildcard $(CURDIR)/bgi16/*.c) # Фаза 2
|
||||
|
||||
COMMON_RELS := $(patsubst $(CURDIR)/common/%.c,$(BUILD)/common/%.rel,$(COMMON_C))
|
||||
BGI256_RELS := $(patsubst $(CURDIR)/bgi256/%.c,$(BUILD)/bgi256/%.rel,$(BGI256_C))
|
||||
KNOWN_RELS := $(COMMON_RELS) $(BGI256_RELS)
|
||||
|
||||
LIB_DIR := $(PROJ_ROOT)/lib
|
||||
LIB256 := $(LIB_DIR)/bgi256.lib
|
||||
|
||||
# Штамп списка модулей — иначе удаление/переименование исходника не
|
||||
# триггерит перелинковку и stale .rel остаётся в архиве. На exFAT
|
||||
# грубая mtime — сравниваем по списку, не по времени.
|
||||
MODLIST := $(BUILD)/.modules
|
||||
ifneq ($(shell cat $(MODLIST) 2>/dev/null),$(KNOWN_RELS))
|
||||
_ := $(shell mkdir -p $(BUILD); echo "$(KNOWN_RELS)" > $(MODLIST); rm -f $(LIB256))
|
||||
endif
|
||||
|
||||
all: $(LIB256)
|
||||
|
||||
# Pattern rule для common/ и bgi256/ (один уровень под build/).
|
||||
$(BUILD)/common/%.rel: $(CURDIR)/common/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(SDCC) $(CC_FLAGS) -c -o $@ $<
|
||||
|
||||
$(BUILD)/bgi256/%.rel: $(CURDIR)/bgi256/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(SDCC) $(CC_FLAGS) -c -o $@ $<
|
||||
|
||||
# Archive — sdar rcs = replace/create/symtab. Перед упаковкой удаляем
|
||||
# stale .rel (wildcard не знает об удалённых исходниках).
|
||||
$(LIB256): $(COMMON_RELS) $(BGI256_RELS) $(MODLIST)
|
||||
@find $(BUILD) -name '*.rel' -print | sort > $(BUILD)/.have
|
||||
@printf '%s\n' $(KNOWN_RELS) | sort > $(BUILD)/.want
|
||||
@comm -23 $(BUILD)/.have $(BUILD)/.want | while read f; do \
|
||||
echo " stale: $$f (исходник удалён) — rm"; \
|
||||
rm -f "$$f" "$${f%.rel}.asm" "$${f%.rel}.lst" "$${f%.rel}.sym"; \
|
||||
done
|
||||
rm -f $@
|
||||
$(SDAR) -rcs $@ $(COMMON_RELS) $(BGI256_RELS)
|
||||
@echo " Built $@ with $(words $(COMMON_RELS)) common + $(words $(BGI256_RELS)) bgi256 modules."
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD) $(LIB256)
|
||||
|
||||
.PHONY: all clean
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* _bgi.h — внутренности графической подсистемы BGI (НЕ публичный заголовок).
|
||||
*
|
||||
* Слияние старых libc/bgi/_bgi.h и libc/gfx/_gfx.h. Графика вынесена из
|
||||
* libc в самостоятельную библиотеку libbgi/ (см. docs + memory
|
||||
* bgi_two_lib_design). Из libbgi собираются две driver-библиотеки
|
||||
* (каждый .c → отдельный .rel):
|
||||
* bgi256.lib ← common/ + bgi256/ (320×256×256, mode 0x81)
|
||||
* bgi16.lib ← common/ + bgi16/ (640×256×16, mode 0x82)
|
||||
* common/ — mode-agnostic: .rel компилируется ОДИН раз и кладётся в
|
||||
* ОБА архива (один исходник для функций, общих 256- и 16-цветной
|
||||
* версиям). bgi256/ и bgi16/ — mode-specific leaf'ы (реальные
|
||||
* реализации, БЕЗ обёрток-делегатов). Режим выбирается линковкой
|
||||
* (--gfx 256 / --gfx 16), одновременно один.
|
||||
*
|
||||
* Публичные leaf-символы (_bgi_plot_raw, _bgi_hspan_raw, _bgi_read_raw,
|
||||
* _bgi_vspan_raw, _bgi_clear_raw, _bgi_mode_set) одинаково объявлены в
|
||||
* обоих driver-каталогах, поэтому common-математика не знает режима —
|
||||
* нужный .rel тянет линкер из выбранной библиотеки.
|
||||
*/
|
||||
#ifndef _BGI_INTERNAL_H
|
||||
#define _BGI_INTERNAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <graphics.h>
|
||||
#include <gfx.h> /* GFX_MODE_*, размеры — для внутренних таблиц */
|
||||
|
||||
/* ---- Состояние BGI (_bgi_state.c) -------------------------------- */
|
||||
extern uint8_t _bgi_fg; /* текущий цвет рисования */
|
||||
extern uint8_t _bgi_bg; /* фоновый цвет */
|
||||
extern int _bgi_cx; /* текущая позиция X (CP) */
|
||||
extern int _bgi_cy; /* текущая позиция Y */
|
||||
extern int _bgi_maxx; /* правая граница (319 / 639) */
|
||||
extern int _bgi_maxy; /* нижняя граница (255) */
|
||||
extern int _bgi_maxcolor; /* макс. индекс цвета (255 / 15) */
|
||||
extern uint8_t _bgi_prevmode; /* видеорежим до initgraph */
|
||||
extern int _bgi_result; /* код graphresult */
|
||||
|
||||
/* ---- Общее gfx-состояние (common/_gfx_state.c) ------------------ *
|
||||
* Пишут gfx_set_visible_page / gfx_set_draw_page /
|
||||
* gfx_set_bank; читают все mode-специфичные примитивы. */
|
||||
|
||||
extern uint8_t _gfx_visible_page;
|
||||
extern uint8_t _gfx_draw_page;
|
||||
|
||||
/* Байт страницы W3 (0x50..0x5F) — какой видеобанк мапится при записи.
|
||||
* См. memory/sprinter_vram_transparency.md (0x50 normal, 0x54 temp,
|
||||
* 0x58 transparent, 0x5C оба). */
|
||||
extern uint8_t _gfx_bank;
|
||||
|
||||
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
|
||||
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
|
||||
extern uint16_t _gfx_addr_base;
|
||||
|
||||
/* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */
|
||||
uint8_t _videomode_raw_get(void);
|
||||
int _videomode_raw_set(uint8_t mode);
|
||||
|
||||
/* ---- W3-скобки (common/_bgi_begin.c, _bgi_end.c) ----------------- *
|
||||
* begin: DI + сохранить текущий W3 и замапить _gfx_bank;
|
||||
* end: восстановить W3 + EI. Не реентерабельно (сейв в статике). */
|
||||
|
||||
extern uint8_t _gfx_saved_w3;
|
||||
|
||||
void _bgi_begin(void);
|
||||
void _bgi_end(void);
|
||||
|
||||
/* ---- LEAF-операции драйвера (bgi256/ или bgi16/) ----------------- *
|
||||
* Каждая self-contained: raw-варианты НЕ ставят свою W3-скобку
|
||||
* (вызывающий обязан обернуть в _bgi_begin/_bgi_end); self-bracket
|
||||
* (_bgi_read/_bgi_hspan/_bgi_clearall) ставят скобку сами. */
|
||||
|
||||
uint8_t _bgi_mode_set(void); /* войти в режим + палитра + границы */
|
||||
|
||||
void _bgi_plot_raw (int x, int y, uint8_t color); /* точка, без скобки */
|
||||
uint8_t _bgi_read_raw (int x, int y); /* чтение, без скобки */
|
||||
void _bgi_hspan_raw(int x, int y, int len, uint8_t color); /* H-отрезок, без скобки */
|
||||
void _bgi_vspan_raw(int x, int y, int len, uint8_t color); /* V-отрезок, без скобки */
|
||||
void _bgi_clear_raw(uint8_t color); /* заливка экрана, без скобки */
|
||||
|
||||
/* Self-bracket версии (begin + raw + end) — для разовых вызовов из
|
||||
* математики, которой не нужен пакетный режим (floodfill, cleardevice).
|
||||
* _bgi_read возвращает цвет пикселя как unsigned (в SDCC __sdcccall(1)
|
||||
* 8-битный возврат идёт в A, расширяется до int). */
|
||||
unsigned _bgi_read (int x, int y);
|
||||
void _bgi_hspan (int x, int y, int len, uint8_t color);
|
||||
void _bgi_clearall(uint8_t color);
|
||||
|
||||
/* ---- Скретч акселератора 256 (bgi256/_gfx_acc256.c) -------------- *
|
||||
* Общий для hspan/vspan/clear; однопоточно, IRQ выключены между
|
||||
* _bgi_begin/_bgi_end. */
|
||||
extern uint8_t _gfx_acc_color;
|
||||
extern uint8_t _gfx_acc_y;
|
||||
extern uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
|
||||
extern uint16_t _gfx_acc_addr;
|
||||
|
||||
/* Горизонтальный Fill-burst: _gfx_acc_len байт с _gfx_acc_addr на
|
||||
* строке _gfx_acc_y (W3 замаплен, DI активен). */
|
||||
void _gfx_hfill256_chunk(void);
|
||||
|
||||
/* ---- Скретч 16-цветных примитивов (bgi16/_gfx_g16_state.c) ------- */
|
||||
extern uint8_t _gfx_g16_y;
|
||||
extern uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) */
|
||||
extern uint8_t _gfx_g16_nibble; /* цвет в нужной половине байта */
|
||||
extern uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
|
||||
extern uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
|
||||
extern uint16_t _gfx_g16_addr;
|
||||
|
||||
/* Горизонтальный Fill-burst байтами _gfx_g16_byte (bgi16/_gfx_hfill16.c). */
|
||||
void _gfx_hfill16_chunk(void);
|
||||
|
||||
/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (bgi16/_gfx_rmw16.c). */
|
||||
void _gfx_rmw16_pixel(void);
|
||||
|
||||
/* ---- Шрифт (common/_gfx_font_state.c, _gfx_font_ensure.c) -------- *
|
||||
* Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char;
|
||||
* биты MSB-first. См. memory/sprinter_font_format. */
|
||||
|
||||
#define _GFX_FONT_BYTES 2048
|
||||
|
||||
extern uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
|
||||
extern const uint8_t *_gfx_font_ptr;
|
||||
extern uint8_t _gfx_font_loaded;
|
||||
|
||||
/* Ленивая инициализация: грузит системный шрифт при первом использовании. */
|
||||
void _gfx_font_ensure(void);
|
||||
|
||||
/* 8 байт строк глифа c (грузит шрифт при первом использовании) —
|
||||
* mode-agnostic, просто читает _gfx_font_ptr. common/_bgi_font_rows.c. */
|
||||
void _bgi_font_rows(unsigned char c, uint8_t *rows8);
|
||||
|
||||
/* ---- Тригонометрия для дуг (common/_bgi_trig.c) ------------------ *
|
||||
* Угол в градусах; результат sin/cos × 128 (Q7 — чтобы радиус×значение
|
||||
* влезало в int без 32-бит арифметики). */
|
||||
int _bgi_sin128(int deg);
|
||||
int _bgi_cos128(int deg);
|
||||
|
||||
/* Общий рисователь дуги/контура эллипса (common/_bgi_arc_draw.c):
|
||||
* полилиния по точкам от stangle до endangle с полуосями xr,yr вокруг
|
||||
* (cx,cy). */
|
||||
void _bgi_ellipse_arc(int cx, int cy, int stangle, int endangle,
|
||||
int xr, int yr, uint8_t color);
|
||||
|
||||
/* Целочисленный sqrt (0..~32767) для fillellipse (common/_bgi_isqrt.c). */
|
||||
int _bgi_isqrt(int n);
|
||||
|
||||
/* ---- Состояние линий (common/_bgi_line_state.c) ------------------ */
|
||||
extern uint8_t _bgi_line_style; /* SOLID_LINE..USERBIT_LINE */
|
||||
extern uint8_t _bgi_line_thick; /* 1 (NORM) или 3 (THICK) */
|
||||
extern uint16_t _bgi_line_pattern; /* маска для USERBIT_LINE */
|
||||
|
||||
/* Отрезок текущим стилем/толщиной линии (common/_bgi_styled_line.c). */
|
||||
void _bgi_styled_line(int x0, int y0, int x1, int y1, uint8_t color);
|
||||
|
||||
/* Отрезок (x0,y0)->(x1,y1) БЕЗ стиля: ортогонал → hspan/vspan,
|
||||
* диагональ → Брезенхэм over _bgi_plot_raw, своя W3-скобка
|
||||
* (common/_bgi_lineseg.c). Реальная логика, не обёртка. */
|
||||
void _bgi_lineseg(int x0, int y0, int x1, int y1, uint8_t color);
|
||||
|
||||
/* ---- Состояние заливки (common/_bgi_fill_state.c) ---------------- */
|
||||
extern uint8_t _bgi_fill_pattern; /* *_FILL */
|
||||
extern uint8_t _bgi_fill_color;
|
||||
|
||||
/* Залить строку [x0..x1] на y ТЕКУЩИМ стилем заливки (паттерн+цвет),
|
||||
* с клипом по экрану. Вызывать между _bgi_begin/_bgi_end. */
|
||||
void _bgi_fill_span(int x0, int x1, int y);
|
||||
|
||||
/* ---- Стиль текста (common/_bgi_text_state.c) --------------------- */
|
||||
extern uint8_t _bgi_text_dir; /* HORIZ_DIR / VERT_DIR */
|
||||
extern uint8_t _bgi_text_size; /* масштаб 1..10 */
|
||||
|
||||
/* Нарисовать строку s в (x,y) текущим стилем текста (масштаб/направление,
|
||||
* цвет _bgi_fg, прозрачный фон) — common/_bgi_draw_scaled.c. */
|
||||
void _bgi_draw_scaled(int x, int y, const char *s);
|
||||
|
||||
/* ---- Скан-таблицы min/max X по строкам для fillpoly -------------- *
|
||||
* (common/_bgi_poly_state.c). GFX_HEIGHT — высота экрана (256 в обоих
|
||||
* режимах), см. <gfx.h>. */
|
||||
extern int _bgi_span_min[GFX_HEIGHT];
|
||||
extern int _bgi_span_max[GFX_HEIGHT];
|
||||
|
||||
/* Прогнать ребро (Брезенхэм) обновляя _bgi_span_min/max
|
||||
* (common/_bgi_poly_edge.c). */
|
||||
void _bgi_poly_edge(int x0, int y0, int x1, int y1);
|
||||
|
||||
/* ---- Cross-library: внутренние символы libc/cbl ------------------ *
|
||||
* libbgi ВСЕГДА линкуется вместе с libc (sprinter.lib), поэтому
|
||||
* зависимость от внутренних символов libc допустима. gfx_wait_vsync
|
||||
* держит bit7 порта 0x004E через рефкаунт _cbl_port_ref/unref, чтобы
|
||||
* бит 5 порта 0xFE (позиция луча) был доступен.
|
||||
* ВНИМАНИЕ: это ВНУТРЕННИЕ символы libc/cbl — при изменении сигнатур
|
||||
* СВЕРЯТЬ с libc/cbl/_cbl.h и синхронизировать. (По тому же паттерну,
|
||||
* каким здесь живут _videomode_raw_* из libc/video.) */
|
||||
extern void _cbl_port_ref(void);
|
||||
extern void _cbl_port_unref(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _bgi_clear_raw — залить всё поле 640×256 цветом color, mode 0x82.
|
||||
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта). W3-naive.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_clear_raw(uint8_t color)
|
||||
{
|
||||
_gfx_g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4));
|
||||
for (int y = 0; y < GFX_HEIGHT_16; y++) {
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
_gfx_g16_addr = _gfx_addr_base;
|
||||
_gfx_g16_len = 0;
|
||||
_gfx_hfill16_chunk();
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + 0x100);
|
||||
_gfx_g16_len = 64;
|
||||
_gfx_hfill16_chunk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* _bgi_hspan_raw — горизонтальная линия len пикселей от (x,y),
|
||||
* mode 0x82: невыровненные крайние ниблы через RMW, середина —
|
||||
* Fill-burst'ами акселератора по целым байтам. W3-naive.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_hspan_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)y >= GFX_HEIGHT_16) return;
|
||||
if (x < 0) { len += x; x = 0; }
|
||||
if (x >= GFX_WIDTH_16) return;
|
||||
if (x + len > GFX_WIDTH_16) len = GFX_WIDTH_16 - x;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
uint8_t cnib = color & 0x0F;
|
||||
_gfx_g16_byte = (uint8_t)(cnib | (cnib << 4));
|
||||
|
||||
/* Ведущий невыровненный пиксель: x нечётный — ПРАВАЯ половина
|
||||
* крайнего левого байта. */
|
||||
if (x & 1) {
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
_gfx_g16_nibble = cnib;
|
||||
_gfx_g16_mask = 0xF0;
|
||||
_gfx_rmw16_pixel();
|
||||
x++;
|
||||
len--;
|
||||
if (len <= 0) return;
|
||||
}
|
||||
|
||||
/* x чётный; len/2 целых байтов через accel hfill. */
|
||||
int full = len >> 1;
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
while (full > 0) {
|
||||
int chunk = full > 256 ? 256 : full;
|
||||
_gfx_g16_len = (chunk == 256) ? 0 : (uint8_t)chunk;
|
||||
_gfx_hfill16_chunk();
|
||||
full -= chunk;
|
||||
_gfx_g16_addr += chunk;
|
||||
}
|
||||
|
||||
/* Хвостовой пиксель нечётной длины: ЛЕВАЯ половина след. байта. */
|
||||
if (len & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(cnib << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* _bgi_plot_raw — пиксель (x,y) цветом 0..15, mode 0x82.
|
||||
* Байт по адресу _gfx_addr_base + x/2 держит два пикселя: high nibble =
|
||||
* ЛЕВЫЙ (чётный x), low = ПРАВЫЙ (нечётный). W3-naive.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_plot_raw(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
if (x & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
|
||||
_gfx_g16_mask = 0xF0;
|
||||
} else {
|
||||
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
}
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* _bgi_vspan_raw — вертикальная линия len пикселей от (x,y),
|
||||
* mode 0x82. Вертикального accel-пути нет: байт покрывает два
|
||||
* горизонтальных пикселя, Fill затёр бы соседний нибл — поэтому
|
||||
* RMW на каждую строку. W3-naive.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_vspan_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16) return;
|
||||
if (y < 0) { len += y; y = 0; }
|
||||
if (y >= GFX_HEIGHT_16) return;
|
||||
if (y + len > GFX_HEIGHT_16) len = GFX_HEIGHT_16 - y;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
if (x & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
|
||||
_gfx_g16_mask = 0xF0;
|
||||
} else {
|
||||
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
_gfx_g16_y = (uint8_t)(y + i);
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* _gfx_g16_state — скретч 16-цветных raw-примитивов (только данные).
|
||||
* Общий для putpixel/hline/vline/clear; однопоточно, IRQ выключены
|
||||
* между W3-begin/end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _gfx_g16_y;
|
||||
uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) — цвет в обе половины */
|
||||
uint8_t _gfx_g16_nibble; /* цвет в нужной половине (low или high) */
|
||||
uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
|
||||
uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
|
||||
uint16_t _gfx_g16_addr;
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* _gfx_hfill16_chunk — горизонтальный Fill-burst акселератора для
|
||||
* mode 0x82: _gfx_g16_len байт значения _gfx_g16_byte с _gfx_g16_addr
|
||||
* на строке _gfx_g16_y. Один burst красит до 256 байт = 512 пикселей.
|
||||
* Pre: W3 замаплен, DI активен.
|
||||
*
|
||||
* SMC только у байта длины. Цвет предзагружается в C и уходит через
|
||||
* `ld a, c` (0x79) — вставить ещё один `ld a,#n` между LD C,C и
|
||||
* стреляющим LD (HL),A нельзя: FSM акселератора перечитает immediate
|
||||
* как новый размер блока.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_hfill16_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_g16_len)
|
||||
ld (_hfill16_len_imm), a
|
||||
|
||||
ld a, (__gfx_g16_byte)
|
||||
ld c, a
|
||||
|
||||
ld a, (__gfx_g16_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (__gfx_g16_addr)
|
||||
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; 0x3E nn — длина (патчится)
|
||||
_hfill16_len_imm = . - 1
|
||||
ld c, c ; 0x49 — горизонтальный Fill
|
||||
ld a, c ; 0x79 — A = байт цвета
|
||||
ld (hl), a ; выстрел
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* _gfx_rmw16_pixel — RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y):
|
||||
* прочитать байт, стереть целевой нибл маской _gfx_g16_mask, вписать
|
||||
* _gfx_g16_nibble. ~10 инструкций на пиксель — медленно, но байт
|
||||
* покрывает два горизонтальных пикселя, иначе никак.
|
||||
* Pre: W3 замаплен, DI активен.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_rmw16_pixel(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_g16_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (__gfx_g16_addr)
|
||||
ld a, (__gfx_g16_mask)
|
||||
ld b, a ; B = маска сохранения
|
||||
ld a, (__gfx_g16_nibble)
|
||||
ld c, a ; C = новый нибл (в нужной половине)
|
||||
ld a, (hl)
|
||||
and a, b ; стереть целевой нибл
|
||||
or a, c ; вписать новый
|
||||
ld (hl), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* _bgi_clear_raw — залить всё поле 320×256 цветом color, mode 0x81.
|
||||
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта) = 512.
|
||||
* W3-naive: вызывающий обрамляет begin/end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_clear_raw(uint8_t color)
|
||||
{
|
||||
_gfx_acc_color = color;
|
||||
for (int y = 0; y < GFX_HEIGHT; y++) {
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = _gfx_addr_base; /* burst 256 байт */
|
||||
_gfx_acc_len = 0;
|
||||
_gfx_hfill256_chunk();
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + 256); /* burst 64 байта */
|
||||
_gfx_acc_len = 64;
|
||||
_gfx_hfill256_chunk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* _bgi_hspan_raw — горизонтальная линия len пикселей от (x,y),
|
||||
* mode 0x81, через Fill-burst'ы акселератора (до 256 байт за выстрел).
|
||||
* W3-naive: вызывающий обрамляет begin/end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_hspan_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)y >= GFX_HEIGHT) return;
|
||||
if (x < 0) { len += x; x = 0; }
|
||||
if (x >= GFX_WIDTH) return;
|
||||
if (x + len > GFX_WIDTH) len = GFX_WIDTH - x;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_acc_color = color;
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
|
||||
while (len > 0) {
|
||||
int chunk = len > 256 ? 256 : len;
|
||||
_gfx_acc_len = (chunk == 256) ? 0 : (uint8_t)chunk;
|
||||
_gfx_hfill256_chunk();
|
||||
len -= chunk;
|
||||
_gfx_acc_addr += chunk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* _bgi_mode_set (drv256) — вход в графический режим 320×256×256.
|
||||
*
|
||||
* Загружает EGA-совместимые цвета 0..15 в палитру 0, чтобы setcolor(RED)
|
||||
* и прочие BGI-константы давали ожидаемый цвет. Заполняет границы
|
||||
* экрана в состоянии BGI. Возвращает предыдущий видеорежим.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
#include <gfx.h>
|
||||
|
||||
/* 16 EGA-цветов, формат записи палитры (B, G, R, pad). */
|
||||
static const uint8_t _bgi_ega_pal[16 * 4] = {
|
||||
0, 0, 0, 0, /* 0 BLACK */
|
||||
168, 0, 0, 0, /* 1 BLUE */
|
||||
0, 168, 0, 0, /* 2 GREEN */
|
||||
168, 168, 0, 0, /* 3 CYAN */
|
||||
0, 0, 168, 0, /* 4 RED */
|
||||
168, 0, 168, 0, /* 5 MAGENTA */
|
||||
0, 84, 168, 0, /* 6 BROWN */
|
||||
168, 168, 168, 0, /* 7 LIGHTGRAY */
|
||||
84, 84, 84, 0, /* 8 DARKGRAY */
|
||||
255, 84, 84, 0, /* 9 LIGHTBLUE */
|
||||
84, 255, 84, 0, /* 10 LIGHTGREEN */
|
||||
255, 255, 84, 0, /* 11 LIGHTCYAN */
|
||||
84, 84, 255, 0, /* 12 LIGHTRED */
|
||||
255, 84, 255, 0, /* 13 LIGHTMAGENTA */
|
||||
84, 255, 255, 0, /* 14 YELLOW */
|
||||
255, 255, 255, 0, /* 15 WHITE */
|
||||
};
|
||||
|
||||
uint8_t _bgi_mode_set(void)
|
||||
{
|
||||
uint8_t prev = _videomode_raw_get();
|
||||
_videomode_raw_set(GFX_MODE_320x256x256);
|
||||
_gfx_bank = 0x50;
|
||||
gfx_set_visible_page(0);
|
||||
gfx_set_draw_page(0);
|
||||
gfx_pal_load(0, 0, 16, _bgi_ega_pal);
|
||||
_bgi_maxx = GFX_WIDTH - 1;
|
||||
_bgi_maxy = GFX_HEIGHT - 1;
|
||||
_bgi_maxcolor = 255;
|
||||
return prev;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* _bgi_plot_raw — пиксель (x,y) цветом color, mode 0x81.
|
||||
*
|
||||
* "Raw" = W3-naive: вызывающий уже сделал _gfx_w3_video_begin (W3 на
|
||||
* видеобанке, DI). Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y.
|
||||
*
|
||||
* Скретч отдельный от _gfx_acc_* — чтобы putpixel внутри цикла
|
||||
* Брезенхэма не топтал внешнее состояние акселератора.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
static uint8_t _gfx_pp_y;
|
||||
static uint16_t _gfx_pp_addr;
|
||||
static uint8_t _gfx_pp_color;
|
||||
|
||||
void _bgi_plot_raw(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
_gfx_pp_y = (uint8_t)y;
|
||||
_gfx_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
_gfx_pp_color = color;
|
||||
__asm
|
||||
ld a, (__gfx_pp_y)
|
||||
out (#0x89), a
|
||||
ld hl, (__gfx_pp_addr)
|
||||
ld a, (__gfx_pp_color)
|
||||
ld (hl), a
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* _bgi_read_raw — прочитать пиксель (x,y) БЕЗ своей W3-скобки
|
||||
* (вызывающий уже сделал _gfx_w3_video_begin). Для блиттинга image:
|
||||
* getimage/putimage читают тысячи пикселей в одной скобке.
|
||||
* За пределами экрана возвращает 0.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
static uint8_t _gfx_gpr_y;
|
||||
static uint16_t _gfx_gpr_addr;
|
||||
static uint8_t _gfx_gpr_color;
|
||||
|
||||
uint8_t _bgi_read_raw(int x, int y)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return 0;
|
||||
_gfx_gpr_y = (uint8_t)y;
|
||||
_gfx_gpr_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
__asm
|
||||
ld a, (__gfx_gpr_y)
|
||||
out (#0x89), a
|
||||
ld hl, (__gfx_gpr_addr)
|
||||
ld a, (hl)
|
||||
ld (__gfx_gpr_color), a
|
||||
__endasm;
|
||||
return _gfx_gpr_color;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* _bgi_vspan_raw — вертикальная линия len пикселей от (x,y),
|
||||
* mode 0x81. Вертикальный Fill акселератора сам инкрементирует Port_Y,
|
||||
* так что вся колонка (высота 256) — один burst. W3-naive.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* Вертикальный Fill: _gfx_acc_len пикселей в колонке _gfx_acc_addr,
|
||||
* верхняя строка _gfx_acc_y. Про SMC и правила FSM акселератора —
|
||||
* см. _gfx_hfill256.c. */
|
||||
static void vfill_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_acc_len)
|
||||
ld (_vfill256_len_imm), a
|
||||
|
||||
ld a, (__gfx_acc_y)
|
||||
out (#0x89), a ; стартовый Y
|
||||
|
||||
ld a, (__gfx_acc_color)
|
||||
ld c, a
|
||||
ld hl, (__gfx_acc_addr)
|
||||
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; immediate-длина (патчится)
|
||||
_vfill256_len_imm = . - 1
|
||||
ld e, e ; 0x5B — вертикальный Fill
|
||||
ld a, c ; A = цвет
|
||||
ld (hl), a ; выстрел
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void _bgi_vspan_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH) return;
|
||||
if (y < 0) { len += y; y = 0; }
|
||||
if (y >= GFX_HEIGHT) return;
|
||||
if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y;
|
||||
if (len <= 0) return;
|
||||
|
||||
/* GFX_HEIGHT = 256, полная колонка — один accel-burst. */
|
||||
_gfx_acc_color = color;
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
_gfx_acc_len = (len == 256) ? 0 : (uint8_t)len;
|
||||
vfill_chunk();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* _gfx_acc256 — скретч акселератора для 256-цветных raw-примитивов
|
||||
* (модуль только с данными). Общий для hline/vline/clear; однопоточно —
|
||||
* GFX работает с выключенными прерываниями между W3-begin/end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _gfx_acc_color;
|
||||
uint8_t _gfx_acc_y;
|
||||
uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
|
||||
uint16_t _gfx_acc_addr;
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* _gfx_hfill256_chunk — горизонтальный Fill-burst акселератора:
|
||||
* _gfx_acc_len байт цвета _gfx_acc_color с адреса _gfx_acc_addr на
|
||||
* строке _gfx_acc_y. Pre: W3 замаплен на видеобанк, DI активен.
|
||||
*
|
||||
* Опкоды акселератора (docs/converted/accel_r.txt):
|
||||
* LD D,D (0x52) режим «задать размер блока»; СЛЕДУЮЩИЙ байт — длина,
|
||||
* ОБЯЗАН быть immediate-операндом LD A,n (0x3E)
|
||||
* LD C,C (0x49) горизонтальный Fill (LD (HL),A заливает n байт)
|
||||
* LD B,B (0x40) выключить акселератор
|
||||
*
|
||||
* Байт длины патчится SMC — HOME после загрузки DSS это RAM, патчить
|
||||
* собственный .EXE безопасно. Вставлять другой `ld a,#n` между LD C,C
|
||||
* и стреляющим LD (HL),A нельзя — FSM акселератора перечитает immediate
|
||||
* как новый размер блока.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_hfill256_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
;; Пропатчить immediate LD A,#n байтом длины.
|
||||
ld a, (__gfx_acc_len)
|
||||
ld (_hfill256_len_imm), a
|
||||
|
||||
ld a, (__gfx_acc_y)
|
||||
out (#0x89), a ; Port_Y = y
|
||||
|
||||
;; Цвет в C и адрес в HL готовим ДО взведения акселератора.
|
||||
ld a, (__gfx_acc_color)
|
||||
ld c, a
|
||||
ld hl, (__gfx_acc_addr)
|
||||
|
||||
;; --- ПОСЛЕДОВАТЕЛЬНОСТЬ АКСЕЛЕРАТОРА ---
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; 0x3E nn — размер (nn патчится выше)
|
||||
_hfill256_len_imm = . - 1
|
||||
ld c, c ; 0x49 — горизонтальный Fill
|
||||
ld a, c ; 0x79 — A = цвет (НЕ ещё один ld a,#n)
|
||||
ld (hl), a ; выстрел; заливает _gfx_acc_len байт
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* getpixel (bgi256) — цвет пикселя (x,y), mode 0x81. Полностью inline
|
||||
* (bounds + W3-скобка + чтение пикселя), без вызовов _bgi_read/raw.
|
||||
* Возвращает 0 вне экрана.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
static uint8_t _gp_y;
|
||||
static uint16_t _gp_addr;
|
||||
static uint8_t _gp_color;
|
||||
|
||||
unsigned getpixel(int x, int y)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return 0;
|
||||
_gp_y = (uint8_t)y;
|
||||
_gp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
ld (__gfx_saved_w3), a
|
||||
ld a, (__gfx_bank)
|
||||
out (#0xE2), a
|
||||
ld a, (_gp_y)
|
||||
out (#0x89), a
|
||||
ld hl, (_gp_addr)
|
||||
ld a, (hl)
|
||||
ld (_gp_color), a
|
||||
ld a, (__gfx_saved_w3)
|
||||
out (#0xE2), a
|
||||
ei
|
||||
__endasm;
|
||||
return _gp_color;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* putpixel (bgi256) — точка (x,y) цветом color, mode 0x81.
|
||||
* ПОЛНОСТЬЬЮ inline: bounds + W3-скобка + запись пикселя в теле, БЕЗ
|
||||
* вызовов _bgi_plot / gfx_putpixel256 / _bgi_plot_raw (устранены
|
||||
* прокладки старой цепочки putpixel→_bgi_plot→gfx_putpixel256→raw).
|
||||
* Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y; W3 мапится на
|
||||
* _gfx_bank, сейв восстанавливается. Bounds — compile-time GFX_WIDTH/
|
||||
* HEIGHT (меньше/быстрее, ≡ runtime _bgi_maxx/_bgi_maxy).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* File-static скретч (как в _bgi_plot_raw, но свой — putpixel не должен
|
||||
* топтать скретч акселератора/plot_raw, если звать вперемешку). */
|
||||
static uint8_t _pp_y;
|
||||
static uint16_t _pp_addr;
|
||||
static uint8_t _pp_color;
|
||||
|
||||
void putpixel(int x, int y, int color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
_pp_y = (uint8_t)y;
|
||||
_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
_pp_color = (uint8_t)color;
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
ld (__gfx_saved_w3), a
|
||||
ld a, (__gfx_bank)
|
||||
out (#0xE2), a
|
||||
ld a, (_pp_y)
|
||||
out (#0x89), a
|
||||
ld hl, (_pp_addr)
|
||||
ld a, (_pp_color)
|
||||
ld (hl), a
|
||||
ld a, (__gfx_saved_w3)
|
||||
out (#0xE2), a
|
||||
ei
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* _bgi_ellipse_arc — контур дуги/эллипса полилинией.
|
||||
*
|
||||
* Точки параметризованы углом (BGI: 0°=восток, против часовой, ось Y
|
||||
* вниз): px = cx + xr*cos(a), py = cy - yr*sin(a). Соседние выборки
|
||||
* соединяются отрезками — контур сплошной при любом радиусе.
|
||||
*
|
||||
* Тригонометрия в Q7 (×128): xr*cos128 ≤ 255×128 < 32767, помещается в
|
||||
* int — БЕЗ 32-битной арифметики (её SDCC/Z80 собирает криво: раньше
|
||||
* эллипс через (long)…>>8 рисовался прямоугольником).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_ellipse_arc(int cx, int cy, int stangle, int endangle,
|
||||
int xr, int yr, uint8_t color)
|
||||
{
|
||||
int a, x, y, px, py, first;
|
||||
|
||||
if (endangle < stangle) endangle += 360; /* дуга через 0° */
|
||||
|
||||
px = 0;
|
||||
py = 0;
|
||||
first = 1;
|
||||
for (a = stangle; a <= endangle; a++) {
|
||||
x = cx + ((xr * _bgi_cos128(a)) >> 7);
|
||||
y = cy - ((yr * _bgi_sin128(a)) >> 7);
|
||||
if (!first) _bgi_lineseg(px, py, x, y, color);
|
||||
px = x;
|
||||
py = y;
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* _bgi_begin — открыть W3-сессию для пакетного рисования: DI, сохранить
|
||||
* текущую страницу W3 и замапить _gfx_bank (видеобанк 0x50..0x5F).
|
||||
* Композитные примитивы обрамляют одной парой _bgi_begin/_bgi_end весь
|
||||
* внутренний цикл — W3-танец оплачивается один раз на операцию, а не
|
||||
* на пиксель. НЕ реентерабельно (сейв в _gfx_saved_w3): между begin и
|
||||
* end IRQ выключены.
|
||||
*
|
||||
* Real код (поглощён из старого libc/gfx/_gfx_w3_video_begin.c) — не
|
||||
* обёртка. Mode-agnostic: использует только _gfx_bank/_gfx_saved_w3.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_begin(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
ld (__gfx_saved_w3), a
|
||||
ld a, (__gfx_bank)
|
||||
out (#0xE2), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* _bgi_clearall — залить весь экран цветом, self-bracket (своя W3-скобка).
|
||||
* cleardevice зовёт это фоновым цветом. Реальная композиция
|
||||
* _bgi_begin + _bgi_clear_raw + _bgi_end.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_clearall(uint8_t color)
|
||||
{
|
||||
_bgi_begin();
|
||||
_bgi_clear_raw(color);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* _bgi_draw_scaled — строка растровым шрифтом 8×8 с целочисленным
|
||||
* масштабом и направлением, цветом _bgi_fg, прозрачным фоном.
|
||||
*
|
||||
* HORIZ_DIR: пиксель глифа (c,r) → экран (x+c*size, y+r*size).
|
||||
* VERT_DIR (поворот 90° против часовой): (c,r) → (x+r*size, y-c*size).
|
||||
* Каждый «пиксель» глифа — блок size×size из raw-плотов, всё в одной
|
||||
* W3-скобке.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_draw_scaled(int x, int y, const char *s)
|
||||
{
|
||||
uint8_t rows[8];
|
||||
uint8_t b, fg, size, dir;
|
||||
int r, c, bx, by, px, py;
|
||||
|
||||
fg = _bgi_fg;
|
||||
size = _bgi_text_size ? _bgi_text_size : 1;
|
||||
dir = _bgi_text_dir;
|
||||
|
||||
_bgi_begin();
|
||||
while (*s) {
|
||||
_bgi_font_rows((unsigned char)*s, rows);
|
||||
for (r = 0; r < 8; r++) {
|
||||
b = rows[r];
|
||||
for (c = 0; c < 8; c++) {
|
||||
if (!((b >> (7 - c)) & 1)) continue;
|
||||
for (by = 0; by < size; by++) {
|
||||
for (bx = 0; bx < size; bx++) {
|
||||
if (dir == VERT_DIR) {
|
||||
px = x + r * size + by;
|
||||
py = y - c * size - bx;
|
||||
} else {
|
||||
px = x + c * size + bx;
|
||||
py = y + r * size + by;
|
||||
}
|
||||
_bgi_plot_raw(px, py, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir == VERT_DIR) y -= 8 * size;
|
||||
else x += 8 * size;
|
||||
s++;
|
||||
}
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* _bgi_end — закрыть W3-сессию: восстановить сохранённую страницу W3 и
|
||||
* EI. Real код (поглощён из старого libc/gfx/_gfx_w3_video_end.c).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_end(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_saved_w3)
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* _bgi_fill_span — залить строку [x0..x1] на y текущим стилем заливки.
|
||||
*
|
||||
* SOLID → быстрый raw-hline; EMPTY → hline фоновым цветом; паттерн →
|
||||
* поточечно по 8×8-битовой маске (строка паттерна = pat[y&7], бит x&7).
|
||||
* Клип по экрану внутри. ВЫЗЫВАТЬ между _bgi_begin/_bgi_end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* Стандартные 8×8-паттерны Borland, индексы LINE_FILL..CLOSE_DOT_FILL. */
|
||||
static const uint8_t _bgi_pat[10][8] = {
|
||||
{ 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00 }, /* LINE_FILL */
|
||||
{ 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 }, /* LTSLASH_FILL */
|
||||
{ 0xE0,0xC1,0x83,0x07,0x0E,0x1C,0x38,0x70 }, /* SLASH_FILL */
|
||||
{ 0xF0,0x78,0x3C,0x1E,0x0F,0x87,0xC3,0xE1 }, /* BKSLASH_FILL */
|
||||
{ 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 }, /* LTBKSLASH_FILL */
|
||||
{ 0xFF,0x88,0x88,0x88,0xFF,0x88,0x88,0x88 }, /* HATCH_FILL */
|
||||
{ 0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81 }, /* XHATCH_FILL */
|
||||
{ 0xCC,0x33,0xCC,0x33,0xCC,0x33,0xCC,0x33 }, /* INTERLEAVE_FILL */
|
||||
{ 0x80,0x00,0x08,0x00,0x80,0x00,0x08,0x00 }, /* WIDE_DOT_FILL */
|
||||
{ 0x88,0x00,0x22,0x00,0x88,0x00,0x22,0x00 }, /* CLOSE_DOT_FILL */
|
||||
};
|
||||
|
||||
void _bgi_fill_span(int x0, int x1, int y)
|
||||
{
|
||||
int x;
|
||||
uint8_t p, row;
|
||||
|
||||
if (x1 < x0) { x = x0; x0 = x1; x1 = x; }
|
||||
if (y < 0 || y > _bgi_maxy) return;
|
||||
if (x0 < 0) x0 = 0;
|
||||
if (x1 > _bgi_maxx) x1 = _bgi_maxx;
|
||||
if (x1 < x0) return;
|
||||
|
||||
p = _bgi_fill_pattern;
|
||||
if (p == EMPTY_FILL) { _bgi_hspan_raw(x0, y, x1 - x0 + 1, _bgi_bg); return; }
|
||||
if (p == SOLID_FILL || p == USER_FILL || p > CLOSE_DOT_FILL) {
|
||||
_bgi_hspan_raw(x0, y, x1 - x0 + 1, _bgi_fill_color);
|
||||
return;
|
||||
}
|
||||
/* Паттерн LINE_FILL..CLOSE_DOT_FILL. */
|
||||
row = _bgi_pat[p - LINE_FILL][y & 7];
|
||||
for (x = x0; x <= x1; x++)
|
||||
if ((row >> (x & 7)) & 1)
|
||||
_bgi_plot_raw(x, y, _bgi_fill_color);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* _bgi_fill_state — текущий стиль заливки BGI (паттерн + цвет).
|
||||
* Значения по умолчанию выставляет initgraph (SOLID_FILL/WHITE).
|
||||
* Без инициализации (crt0 зануляет _DATA).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _bgi_fill_pattern;
|
||||
uint8_t _bgi_fill_color;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _bgi_font_rows — 8 байт строк глифа символа c (mode-agnostic: просто
|
||||
* читает шрифт через _gfx_font_ptr). Шрифт системный, interleaved:
|
||||
* offset = row*256 + char (MSB-first), см. memory/sprinter_font_format.
|
||||
* Грузится лениво (_gfx_font_ensure) при первом использовании.
|
||||
*
|
||||
* Раньше жил в drv256/ по недосмотру — перенесён в common/, т.к. логика
|
||||
* не зависит от видеорежима (один и тот же .rel в bgi256.lib и bgi16.lib).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_font_rows(unsigned char c, uint8_t *rows8)
|
||||
{
|
||||
const uint8_t *f;
|
||||
int r;
|
||||
_gfx_font_ensure();
|
||||
f = _gfx_font_ptr;
|
||||
for (r = 0; r < 8; r++)
|
||||
rows8[r] = f[r * 256 + c];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* _bgi_hspan — горизонтальный отрезок len пикселей от (x,y), self-bracket
|
||||
* (своя W3-скобка). Для floodfill (заливка строки вне пакетной сессии).
|
||||
* Реальная композиция _bgi_begin + _bgi_hspan_raw + _bgi_end.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_hspan(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_bgi_begin();
|
||||
_bgi_hspan_raw(x, y, len, color);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* _bgi_isqrt — целочисленный floor(sqrt(n)) для n в 0..~32767.
|
||||
* Линейный поиск (n≤180² → ≤180 шагов) — для fillellipse достаточно,
|
||||
* без float и 32-бит.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
int _bgi_isqrt(int n)
|
||||
{
|
||||
int x = 0;
|
||||
if (n < 0) return 0;
|
||||
while ((x + 1) * (x + 1) <= n) x++;
|
||||
return x;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* _bgi_line_state — текущий стиль/толщина/маска линий BGI.
|
||||
* initgraph ставит SOLID_LINE / NORM / 0xFFFF. Без инициализации.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _bgi_line_style;
|
||||
uint8_t _bgi_line_thick;
|
||||
uint16_t _bgi_line_pattern;
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* _bgi_lineseg — отрезок (x0,y0)->(x1,y1) БЕЗ стиля/толщины (SOLID+NORM).
|
||||
* Real логика (поглощена из старого libc/gfx/gfx_line256.c), не обёртка.
|
||||
*
|
||||
* Ортогональные отрезки уходят на driver-leaf'ы _bgi_hspan_raw /
|
||||
* _bgi_vspan_raw (акселератор); наклонные — Брезенхэм поточечно через
|
||||
* _bgi_plot_raw. Каждый путь ставит свою W3-скобку (_bgi_begin/_bgi_end)
|
||||
* — вызывать _bgi_lineseg ВНЕ чужой скобки (W3-сейв не реентерабелен).
|
||||
* Вызывают: _bgi_styled_line (SOLID+NORM), _bgi_arc_draw, fillpoly,
|
||||
* sector — все вне скобки.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_lineseg(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
/* Горизонталь — один H-burst. */
|
||||
if (y0 == y1) {
|
||||
int x = x0 <= x1 ? x0 : x1;
|
||||
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
|
||||
_bgi_begin();
|
||||
_bgi_hspan_raw(x, y0, w, color);
|
||||
_bgi_end();
|
||||
return;
|
||||
}
|
||||
/* Вертикаль — один V-burst (256) или RMW-цикл (16). */
|
||||
if (x0 == x1) {
|
||||
int y = y0 <= y1 ? y0 : y1;
|
||||
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
|
||||
_bgi_begin();
|
||||
_bgi_vspan_raw(x0, y, h, color);
|
||||
_bgi_end();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Наклонный — Брезенхэм в одной W3-скобке. */
|
||||
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
|
||||
int err = (dx > dy ? dx : -dy) / 2;
|
||||
int x = x0, y = y0;
|
||||
_bgi_begin();
|
||||
for (;;) {
|
||||
_bgi_plot_raw(x, y, color);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x += sx; }
|
||||
if (e2 < dy) { err += dx; y += sy; }
|
||||
}
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* _bgi_poly_edge — прогнать ребро (x0,y0)->(x1,y1) по Брезенхэму,
|
||||
* обновляя _bgi_span_min/_bgi_span_max для каждой затронутой строки.
|
||||
* Только целочисленные сложения (без 32-бит). Строки вне экрана
|
||||
* пропускаются. Заливка потом идёт от min до max по строке.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_poly_edge(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
int dx, dy, sx, sy, err, e2;
|
||||
|
||||
dx = x1 - x0; if (dx < 0) dx = -dx;
|
||||
dy = y1 - y0; if (dy < 0) dy = -dy;
|
||||
sx = (x0 < x1) ? 1 : -1;
|
||||
sy = (y0 < y1) ? 1 : -1;
|
||||
err = dx - dy;
|
||||
|
||||
for (;;) {
|
||||
if (y0 >= 0 && y0 < GFX_HEIGHT) {
|
||||
if (x0 < _bgi_span_min[y0]) _bgi_span_min[y0] = x0;
|
||||
if (x0 > _bgi_span_max[y0]) _bgi_span_max[y0] = x0;
|
||||
}
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* _bgi_poly_state — скан-таблицы min/max X по строкам для fillpoly.
|
||||
* По строке экрана хранит крайние левый/правый X контура — заливка
|
||||
* идёт от min до max (выпуклая оболочка строки). ~1 КБ в _DATA,
|
||||
* однопоточно (рисование не реентерабельно).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
int _bgi_span_min[GFX_HEIGHT];
|
||||
int _bgi_span_max[GFX_HEIGHT];
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* _bgi_read — цвет пикселя (x,y), self-bracket (своя W3-скобка).
|
||||
* Для разовых чтений из математики, которой не нужен пакетный режим
|
||||
* (floodfill). getpixel тоже зовёт это. Реальная композиция
|
||||
* _bgi_begin + _bgi_read_raw + _bgi_end — не обёртка над обёрткой.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
unsigned _bgi_read(int x, int y)
|
||||
{
|
||||
unsigned c;
|
||||
_bgi_begin();
|
||||
c = _bgi_read_raw(x, y);
|
||||
_bgi_end();
|
||||
return c;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* _bgi_state.c — общее состояние BGI-слоя.
|
||||
*
|
||||
* File-scope переменные НЕ инициализируются (crt0 зануляет _DATA;
|
||||
* см. memory/sdcc_static_storage_gotcha). Осмысленные значения
|
||||
* выставляет initgraph().
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _bgi_fg;
|
||||
uint8_t _bgi_bg;
|
||||
int _bgi_cx;
|
||||
int _bgi_cy;
|
||||
int _bgi_maxx;
|
||||
int _bgi_maxy;
|
||||
int _bgi_maxcolor;
|
||||
uint8_t _bgi_prevmode;
|
||||
int _bgi_result;
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* _bgi_styled_line — отрезок текущим стилем и толщиной линии.
|
||||
*
|
||||
* SOLID+NORM → быстрый _bgi_lineseg (accel). Иначе — Брезенхэм с
|
||||
* 16-битной маской (бит на шаг; когда бит 0 — пропуск) и, для THICK,
|
||||
* дублированием перпендикулярно основной оси (±1). Всё raw в одной
|
||||
* W3-скобке.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* Маски стандартных стилей (SOLID/DOTTED/CENTER/DASHED). */
|
||||
static const uint16_t _bgi_line_pat[4] = { 0xFFFF, 0xCCCC, 0xFF18, 0xF8F8 };
|
||||
|
||||
void _bgi_styled_line(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
uint16_t pat;
|
||||
uint8_t bit, style, thick;
|
||||
int dx, dy, sx, sy, err, e2, major_x;
|
||||
|
||||
style = _bgi_line_style;
|
||||
thick = _bgi_line_thick;
|
||||
if (style == SOLID_LINE && thick <= 1) {
|
||||
_bgi_lineseg(x0, y0, x1, y1, color);
|
||||
return;
|
||||
}
|
||||
pat = (style == USERBIT_LINE) ? _bgi_line_pattern
|
||||
: _bgi_line_pat[style & 3];
|
||||
|
||||
dx = x1 - x0; if (dx < 0) dx = -dx;
|
||||
dy = y1 - y0; if (dy < 0) dy = -dy;
|
||||
sx = (x0 < x1) ? 1 : -1;
|
||||
sy = (y0 < y1) ? 1 : -1;
|
||||
err = dx - dy;
|
||||
major_x = (dx >= dy);
|
||||
bit = 0;
|
||||
|
||||
_bgi_begin();
|
||||
for (;;) {
|
||||
if ((pat >> (bit & 15)) & 1) {
|
||||
_bgi_plot_raw(x0, y0, color);
|
||||
if (thick > 1) {
|
||||
if (major_x) {
|
||||
_bgi_plot_raw(x0, y0 - 1, color);
|
||||
_bgi_plot_raw(x0, y0 + 1, color);
|
||||
} else {
|
||||
_bgi_plot_raw(x0 - 1, y0, color);
|
||||
_bgi_plot_raw(x0 + 1, y0, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
bit++;
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* _bgi_text_state — направление и масштаб текста BGI.
|
||||
* initgraph ставит HORIZ_DIR / size 1. Без инициализации.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _bgi_text_dir;
|
||||
uint8_t _bgi_text_size;
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* _bgi_trig — целочисленная тригонометрия для дуг/эллипсов BGI.
|
||||
*
|
||||
* Угол в градусах (Turbo-C); sin/cos масштабированы на 128 (Q7):
|
||||
* _bgi_sin128(90) = 128. Масштаб 128 (а не 256) выбран так, чтобы
|
||||
* произведение радиус×значение помещалось в 16-бит int для любого
|
||||
* экранного радиуса (255×128 = 32640 < 32767) — и НЕ приходилось
|
||||
* трогать 32-битную арифметику, которую SDCC/Z80 собирает криво
|
||||
* (см. memory/avoid_32bit_arith_z80; ловили эллипс-в-прямоугольник).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* sin(0°..90°) × 128. */
|
||||
static const int _bgi_sintab[91] = {
|
||||
0, 2, 4, 7, 9, 11, 13, 16, 18, 20,
|
||||
22, 24, 27, 29, 31, 33, 35, 37, 40, 42,
|
||||
44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
|
||||
64, 66, 68, 70, 72, 73, 75, 77, 79, 81,
|
||||
82, 84, 86, 87, 89, 91, 92, 94, 95, 97,
|
||||
98, 99, 101, 102, 104, 105, 106, 107, 109, 110,
|
||||
111, 112, 113, 114, 115, 116, 117, 118, 119, 119,
|
||||
120, 121, 122, 122, 123, 124, 124, 125, 125, 126,
|
||||
126, 126, 127, 127, 127, 128, 128, 128, 128, 128,
|
||||
128,
|
||||
};
|
||||
|
||||
/* sin(угол°) × 128, угол приводится к 0..359. */
|
||||
int _bgi_sin128(int deg)
|
||||
{
|
||||
deg %= 360;
|
||||
if (deg < 0) deg += 360;
|
||||
if (deg <= 90) return _bgi_sintab[deg];
|
||||
if (deg <= 180) return _bgi_sintab[180 - deg];
|
||||
if (deg <= 270) return -_bgi_sintab[deg - 180];
|
||||
return -_bgi_sintab[360 - deg];
|
||||
}
|
||||
|
||||
/* cos(угол°) × 128 = sin(угол+90). */
|
||||
int _bgi_cos128(int deg)
|
||||
{
|
||||
return _bgi_sin128(deg + 90);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* _gfx_font_ensure — ленивая инициализация шрифта: текстовые рендеры
|
||||
* зовут её при каждом входе, системный шрифт грузится при первом
|
||||
* использовании (чисто графическая программа BIOS-вызов не платит).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_font_ensure(void)
|
||||
{
|
||||
if (!_gfx_font_loaded) gfx_load_default_font();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* _gfx_font_state — буфер шрифта, указатель и флаг загрузки (данные).
|
||||
*
|
||||
* Формат (ZX-Spectrum-совместимый): 256 глифов × 8 рядов × 1 байт = 2 КБ,
|
||||
* INTERLEAVED — offset = row*256 + char (ряд 0 всех глифов занимает
|
||||
* 0x000..0x0FF и т.д.), биты MSB-first (бит 7 = левый пиксель).
|
||||
*
|
||||
* _gfx_font_ptr всегда указывает на валидные данные после
|
||||
* _gfx_font_ensure(); программы могут подменить шрифт gfx_set_font().
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
|
||||
const uint8_t *_gfx_font_ptr = _gfx_font_buf;
|
||||
uint8_t _gfx_font_loaded;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _gfx_state — общее состояние графики (модуль только с данными).
|
||||
*
|
||||
* Пишут gfx_set_visible_page / gfx_set_draw_page /
|
||||
* gfx_set_bank; читают все mode-специфичные примитивы.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _gfx_visible_page;
|
||||
uint8_t _gfx_draw_page;
|
||||
|
||||
/* См. memory/sprinter_vram_transparency.md про биты 0x50..0x5F
|
||||
* (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба). */
|
||||
uint8_t _gfx_bank = 0x50;
|
||||
|
||||
/* Каждая VRAM-строка — 1024 байта: page 0 занимает байты 0..319
|
||||
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
|
||||
* режима/палитра, их не трогаем. */
|
||||
uint16_t _gfx_addr_base = 0xC000;
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* _gfx_w3_state — сохранённый байт W3 между begin и end (данные).
|
||||
*
|
||||
* НЕ реентерабельно — безопасно только потому, что GFX держит
|
||||
* прерывания выключенными между _gfx_w3_video_begin и _end.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
uint8_t _gfx_saved_w3;
|
||||
@@ -0,0 +1,7 @@
|
||||
/* arc — дуга окружности радиуса r от stangle до endangle (градусы). */
|
||||
#include "../_bgi.h"
|
||||
|
||||
void arc(int x, int y, int stangle, int endangle, int radius)
|
||||
{
|
||||
_bgi_ellipse_arc(x, y, stangle, endangle, radius, radius, _bgi_fg);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* bar — залитый прямоугольник (left,top)-(right,bottom) включительно
|
||||
* ТЕКУЩИМ стилем заливки (setfillstyle), без рамки — как в Turbo-C.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void bar(int left, int top, int right, int bottom)
|
||||
{
|
||||
int y, t;
|
||||
|
||||
if (right < left) { t = left; left = right; right = t; }
|
||||
if (bottom < top) { t = top; top = bottom; bottom = t; }
|
||||
|
||||
_bgi_begin();
|
||||
for (y = top; y <= bottom; y++)
|
||||
_bgi_fill_span(left, right, y);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* bar3d — трёхмерный брусок: передняя грань залита текущим стилем
|
||||
* заливки, рёбра — текущим цветом рисования. depth — глубина (вправо-
|
||||
* вверх), topflag != 0 рисует верхнюю грань.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void bar3d(int left, int top, int right, int bottom, int depth, int topflag)
|
||||
{
|
||||
int t;
|
||||
|
||||
if (right < left) { t = left; left = right; right = t; }
|
||||
if (bottom < top) { t = top; top = bottom; bottom = t; }
|
||||
|
||||
bar(left, top, right, bottom); /* передняя грань — заливка */
|
||||
rectangle(left, top, right, bottom); /* рамка переда — тек. цвет */
|
||||
|
||||
if (depth != 0) {
|
||||
line(right, top, right + depth, top - depth);
|
||||
line(right, bottom, right + depth, bottom - depth);
|
||||
line(right + depth, top - depth, right + depth, bottom - depth);
|
||||
if (topflag) {
|
||||
line(left, top, left + depth, top - depth);
|
||||
line(left + depth, top - depth, right + depth, top - depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* circle — окружность (midpoint) с центром (cx,cy) радиуса r,
|
||||
* текущим цветом. Целочисленный алгоритм, 8 октантов за шаг.
|
||||
*
|
||||
* Вся окружность рисуется в ОДНОЙ W3-скобке (_bgi_begin/_bgi_end) +
|
||||
* raw-плот — иначе скобка на каждый пиксель делает большие окружности
|
||||
* мучительно медленными (тысячи DI/EI).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void circle(int cx, int cy, int r)
|
||||
{
|
||||
int x, y, d;
|
||||
uint8_t c = _bgi_fg;
|
||||
|
||||
if (r < 0) return;
|
||||
|
||||
x = 0;
|
||||
y = r;
|
||||
d = 1 - r;
|
||||
_bgi_begin();
|
||||
while (x <= y) {
|
||||
_bgi_plot_raw(cx + x, cy + y, c);
|
||||
_bgi_plot_raw(cx - x, cy + y, c);
|
||||
_bgi_plot_raw(cx + x, cy - y, c);
|
||||
_bgi_plot_raw(cx - x, cy - y, c);
|
||||
_bgi_plot_raw(cx + y, cy + x, c);
|
||||
_bgi_plot_raw(cx - y, cy + x, c);
|
||||
_bgi_plot_raw(cx + y, cy - x, c);
|
||||
_bgi_plot_raw(cx - y, cy - x, c);
|
||||
if (d < 0) {
|
||||
d += 2 * x + 3;
|
||||
} else {
|
||||
d += 2 * (x - y) + 5;
|
||||
y--;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* cleardevice — очистить экран фоновым цветом, вернуть CP в (0,0). */
|
||||
#include "../_bgi.h"
|
||||
|
||||
void cleardevice(void)
|
||||
{
|
||||
_bgi_clearall(_bgi_bg);
|
||||
_bgi_cx = 0;
|
||||
_bgi_cy = 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* closegraph — вернуть текстовый режим, действовавший до initgraph.
|
||||
* _bgi_mode_done удалён — _videomode_raw_set (mode-agnostic) */
|
||||
#include "../_bgi.h"
|
||||
|
||||
void closegraph(void)
|
||||
{
|
||||
_videomode_raw_set(_bgi_prevmode);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* drawpoly — ломаная по numpoints точкам (массив x0,y0,x1,y1,…),
|
||||
* текущим цветом. Как в Turbo-C, контур НЕ замыкается автоматически:
|
||||
* чтобы получить замкнутый полигон, повторите первую точку последней.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void drawpoly(int numpoints, const int *polypoints)
|
||||
{
|
||||
int i;
|
||||
if (numpoints < 2) return;
|
||||
for (i = 0; i < numpoints - 1; i++) {
|
||||
_bgi_styled_line(polypoints[2 * i], polypoints[2 * i + 1],
|
||||
polypoints[2 * i + 2], polypoints[2 * i + 3],
|
||||
_bgi_fg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* ellipse — контур эллипса/эллиптической дуги с полуосями xradius,
|
||||
* yradius от stangle до endangle. Полный эллипс: stangle=0,endangle=360.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void ellipse(int x, int y, int stangle, int endangle,
|
||||
int xradius, int yradius)
|
||||
{
|
||||
_bgi_ellipse_arc(x, y, stangle, endangle, xradius, yradius, _bgi_fg);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* fillellipse — залитый эллипс с полуосями rx,ry, центр (cx,cy).
|
||||
* Нутро — текущим стилем заливки, контур — текущим цветом.
|
||||
*
|
||||
* По строкам: полуширина w = rx*sqrt(ry²-dy²)/ry (целочисленно через
|
||||
* _bgi_isqrt). ry²,dy² ≤ ~180² < 32767, rx*s ≤ rx*ry < 32767 — без
|
||||
* 32-бит (см. memory/avoid_32bit_arith_z80).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void fillellipse(int cx, int cy, int rx, int ry)
|
||||
{
|
||||
int dy, s, w;
|
||||
|
||||
if (rx < 0 || ry < 0) return;
|
||||
|
||||
_bgi_begin();
|
||||
if (ry == 0) {
|
||||
_bgi_fill_span(cx - rx, cx + rx, cy);
|
||||
} else {
|
||||
for (dy = -ry; dy <= ry; dy++) {
|
||||
s = _bgi_isqrt(ry * ry - dy * dy); /* 0..ry */
|
||||
w = (rx * s) / ry;
|
||||
_bgi_fill_span(cx - w, cx + w, cy + dy);
|
||||
}
|
||||
}
|
||||
_bgi_end();
|
||||
|
||||
/* Контур текущим цветом. */
|
||||
ellipse(cx, cy, 0, 360, rx, ry);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* fillpoly — залитый многоугольник по numpoints точкам {x0,y0,…}.
|
||||
* Нутро — текущим стилем заливки, контур — текущим цветом. Полигон
|
||||
* авто-замыкается (последняя точка соединяется с первой).
|
||||
*
|
||||
* Заливка: для каждой строки берём крайние X контура (min/max) и
|
||||
* заполняем между ними — точно для выпуклых, для вогнутых = выпуклая
|
||||
* оболочка по строкам (упрощение). Крайние X даёт брезенхэмовский
|
||||
* проход рёбер (_bgi_poly_edge) — без 32-бит арифметики.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void fillpoly(int numpoints, const int *polypoints)
|
||||
{
|
||||
int i, j, y, ymin, ymax, xa, ya, xb, yb;
|
||||
|
||||
if (numpoints < 2) return;
|
||||
|
||||
ymin = 32767;
|
||||
ymax = -32768;
|
||||
for (i = 0; i < numpoints; i++) {
|
||||
y = polypoints[2 * i + 1];
|
||||
if (y < ymin) ymin = y;
|
||||
if (y > ymax) ymax = y;
|
||||
}
|
||||
if (ymin < 0) ymin = 0;
|
||||
if (ymax > GFX_HEIGHT - 1) ymax = GFX_HEIGHT - 1;
|
||||
if (ymax < ymin) return;
|
||||
|
||||
for (y = ymin; y <= ymax; y++) {
|
||||
_bgi_span_min[y] = 32767;
|
||||
_bgi_span_max[y] = -32768;
|
||||
}
|
||||
|
||||
for (i = 0; i < numpoints; i++) {
|
||||
j = (i + 1) % numpoints;
|
||||
_bgi_poly_edge(polypoints[2 * i], polypoints[2 * i + 1],
|
||||
polypoints[2 * j], polypoints[2 * j + 1]);
|
||||
}
|
||||
|
||||
_bgi_begin();
|
||||
for (y = ymin; y <= ymax; y++)
|
||||
if (_bgi_span_max[y] >= _bgi_span_min[y])
|
||||
_bgi_fill_span(_bgi_span_min[y], _bgi_span_max[y], y);
|
||||
_bgi_end();
|
||||
|
||||
/* Контур текущим цветом (замкнутый). */
|
||||
for (i = 0; i < numpoints; i++) {
|
||||
j = (i + 1) % numpoints;
|
||||
xa = polypoints[2 * i]; ya = polypoints[2 * i + 1];
|
||||
xb = polypoints[2 * j]; yb = polypoints[2 * j + 1];
|
||||
_bgi_lineseg(xa, ya, xb, yb, _bgi_fg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* floodfill — заливка связной области с (x,y) до границы цвета border.
|
||||
*
|
||||
* Скан-строчный (span) flood: заполняем горизонтальный отрезок текущим
|
||||
* цветом заливки, затем ищем новые «затравки» в строках сверху/снизу.
|
||||
* Стоп-условие пикселя: вне экрана / цвет == border / цвет уже == цвет
|
||||
* заливки (иначе не сойдётся). Паттерн в floodfill не применяется —
|
||||
* заливаем сплошным _bgi_fill_color (упрощение).
|
||||
*
|
||||
* Чтение/запись — self-bracket (_bgi_read/_bgi_hspan): чуть медленнее,
|
||||
* зато не держим DI на всю (возможно большую) заливку.
|
||||
*
|
||||
* Стек затравок ограничен FF_STACK; при переполнении лишние затравки
|
||||
* отбрасываются (заливка может остаться неполной на очень «рваных»
|
||||
* фигурах) — компромисс по памяти.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
#define FF_STACK 300
|
||||
static int ff_sx[FF_STACK];
|
||||
static int ff_sy[FF_STACK];
|
||||
static int ff_sp;
|
||||
|
||||
/* 1 = пиксель нельзя заливать (стоп): вне экрана / border / уже залит. */
|
||||
static int ff_stop(int x, int y, int border)
|
||||
{
|
||||
int c;
|
||||
if (x < 0 || x > _bgi_maxx || y < 0 || y > _bgi_maxy) return 1;
|
||||
c = _bgi_read(x, y);
|
||||
return (c == border || c == _bgi_fill_color);
|
||||
}
|
||||
|
||||
static void ff_push(int x, int y)
|
||||
{
|
||||
if (ff_sp < FF_STACK) { ff_sx[ff_sp] = x; ff_sy[ff_sp] = y; ff_sp++; }
|
||||
}
|
||||
|
||||
void floodfill(int x, int y, int border)
|
||||
{
|
||||
int xl, xr, xi, yy, d;
|
||||
uint8_t fc = _bgi_fill_color;
|
||||
|
||||
if (ff_stop(x, y, border)) return;
|
||||
|
||||
ff_sp = 0;
|
||||
ff_push(x, y);
|
||||
|
||||
while (ff_sp > 0) {
|
||||
ff_sp--;
|
||||
x = ff_sx[ff_sp];
|
||||
y = ff_sy[ff_sp];
|
||||
if (ff_stop(x, y, border)) continue;
|
||||
|
||||
for (xl = x; !ff_stop(xl - 1, y, border); ) xl--;
|
||||
for (xr = x; !ff_stop(xr + 1, y, border); ) xr++;
|
||||
_bgi_hspan(xl, y, xr - xl + 1, fc);
|
||||
|
||||
for (d = -1; d <= 1; d += 2) {
|
||||
yy = y + d;
|
||||
xi = xl;
|
||||
while (xi <= xr) {
|
||||
while (xi <= xr && ff_stop(xi, yy, border)) xi++;
|
||||
if (xi > xr) break;
|
||||
while (xi <= xr && !ff_stop(xi, yy, border)) xi++;
|
||||
ff_push(xi - 1, yy); /* правый край найденного пробега */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* getbkcolor — текущий фоновый цвет. */
|
||||
#include "../_bgi.h"
|
||||
|
||||
int getbkcolor(void)
|
||||
{
|
||||
return _bgi_bg;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* getcolor — текущий цвет рисования. */
|
||||
#include "../_bgi.h"
|
||||
|
||||
int getcolor(void)
|
||||
{
|
||||
return _bgi_fg;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* getfillsettings — прочитать текущий паттерн и цвет заливки. */
|
||||
#include "../_bgi.h"
|
||||
|
||||
void getfillsettings(struct fillsettingstype *fillinfo)
|
||||
{
|
||||
if (!fillinfo) return;
|
||||
fillinfo->pattern = _bgi_fill_pattern;
|
||||
fillinfo->color = _bgi_fill_color;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* getimage — сохранить прямоугольник экрана в буфер bitmap.
|
||||
* Формат: uint16 width, uint16 height, затем w*h байт (по строкам).
|
||||
* Чтение raw в одной W3-скобке.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void getimage(int left, int top, int right, int bottom, void *bitmap)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)bitmap;
|
||||
int w = right - left + 1;
|
||||
int h = bottom - top + 1;
|
||||
int x, y;
|
||||
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
*p++ = (uint8_t)w;
|
||||
*p++ = (uint8_t)(w >> 8);
|
||||
*p++ = (uint8_t)h;
|
||||
*p++ = (uint8_t)(h >> 8);
|
||||
|
||||
_bgi_begin();
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = 0; x < w; x++)
|
||||
*p++ = (uint8_t)_bgi_read_raw(left + x, top + y);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* getlinesettings — прочитать текущий стиль/маску/толщину линий. */
|
||||
#include "../_bgi.h"
|
||||
|
||||
void getlinesettings(struct linesettingstype *lineinfo)
|
||||
{
|
||||
if (!lineinfo) return;
|
||||
lineinfo->linestyle = _bgi_line_style;
|
||||
lineinfo->upattern = _bgi_line_pattern;
|
||||
lineinfo->thickness = _bgi_line_thick;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* getmaxcolor — максимальный индекс цвета (255 в 256-цветном режиме). */
|
||||
#include "../_bgi.h"
|
||||
|
||||
int getmaxcolor(void)
|
||||
{
|
||||
return _bgi_maxcolor;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user