Compare commits
7 Commits
46553f4e07
...
v0.3-libc
| Author | SHA1 | Date | |
|---|---|---|---|
| 8531b25e75 | |||
| 7187752b29 | |||
| a4c8c79428 | |||
| 60373930fb | |||
| 057dd615ba | |||
| 48d552bf3a | |||
| 4a081501d8 |
@@ -0,0 +1,68 @@
|
|||||||
|
# Sprinter C-Compiler — правила проекта
|
||||||
|
|
||||||
|
Target-слой SDCC 4.5 (z80) для компьютера Sprinter Sp2000: crt0,
|
||||||
|
линковка, libc, mkexe. Общение и комментарии — на русском.
|
||||||
|
|
||||||
|
## Сборка и проверка
|
||||||
|
|
||||||
|
```
|
||||||
|
make # tools + lib + все тесты (43) + examples
|
||||||
|
make -C lib # только libc → lib/sprinter.lib
|
||||||
|
make floppy # упаковать все .exe в mame/v306/IMG/mc.img
|
||||||
|
make size-check # размерный регресс: _CODE vs docs/size_baseline.tsv
|
||||||
|
make size-baseline # принять текущие размеры эталоном
|
||||||
|
```
|
||||||
|
|
||||||
|
Одиночный тест: `cd tests/<имя> && make run` (пакует ТОЛЬКО этот exe
|
||||||
|
+ EXTRA_DATA на дискету и запускает MAME). Тесты в MAME гоняет
|
||||||
|
пользователь — готовь дискету и проси прогнать.
|
||||||
|
|
||||||
|
После правок libc: пересборка от чистого листа (`make -C lib 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.
|
||||||
|
- Имя файла = имя функции. lib/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 в lib/build/, дамп, репро) — см.
|
||||||
|
memory/defer_unexplained_quirks.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- `libc/<area>/*.c` — модули libc; `libc/include/` — публичные заголовки
|
||||||
|
- `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 несовместим — только как образец)
|
||||||
@@ -13,12 +13,15 @@
|
|||||||
# Most heavy lifting is delegated to sub-Makefiles.
|
# Most heavy lifting is delegated to sub-Makefiles.
|
||||||
|
|
||||||
# Small libc-feature tests (one program per .c-language feature or libc API).
|
# 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 \
|
TESTS := hello hello2 simple banked bankedbg banktest strtest cat seek \
|
||||||
rt_test openenv ls conio attrprob timedir mouse banklocl stdlib \
|
malloc mem_test argv errno rt_test openenv ls conio conio2 \
|
||||||
assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
attrprob timedir mouse banklocl stdlib assrtest ptime stattest \
|
||||||
|
filetest fdmax fbench solidt dec_test gets stest2 winrest \
|
||||||
|
bios_text text_palette \
|
||||||
|
gfx_demo gfx_d16 gfx_text gfx_mous gfx_dbuf
|
||||||
|
|
||||||
# Larger end-user applications under examples/.
|
# Larger end-user applications under examples/.
|
||||||
APPS := mdview
|
APPS := mdview mdview2
|
||||||
|
|
||||||
MAME_DIR := mame/v306
|
MAME_DIR := mame/v306
|
||||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
||||||
@@ -33,7 +36,8 @@ DATA_FILES := \
|
|||||||
tests/seek/big.txt \
|
tests/seek/big.txt \
|
||||||
examples/mdview/SAMPLE.MD
|
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
|
all: tools lib tests examples
|
||||||
|
|
||||||
@@ -66,6 +70,14 @@ floppy: tests examples tests/seek/big.txt
|
|||||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@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:
|
clean:
|
||||||
$(MAKE) -C toolchain/mkexe clean
|
$(MAKE) -C toolchain/mkexe clean
|
||||||
$(MAKE) -C lib clean
|
$(MAKE) -C lib clean
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ CC_FLAGS += $(EXTRA_FLAGS)
|
|||||||
|
|
||||||
all: $(EXAMPLE).exe
|
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)
|
$(SPRINTER_CC) $(CC_FLAGS) -o $@ $(SOURCES)
|
||||||
|
|
||||||
$(MKEXE):
|
$(MKEXE):
|
||||||
|
|||||||
+150
-433
@@ -1,436 +1,153 @@
|
|||||||
# TODO / Roadmap
|
# TODO / Roadmap
|
||||||
|
|
||||||
Открытые задачи в порядке убывания приоритета. По мере появления реальных программ — приоритеты будут смещаться.
|
Открытые задачи в порядке убывания приоритета; закрытые этапы — в
|
||||||
|
«Истории» внизу. Текущий срез libc-работ: docs/libc-roadmap.md.
|
||||||
|
|
||||||
## Этап 5 — malloc / free + banking-aware page allocator ✅ ГОТОВО
|
## Ближайшее
|
||||||
|
|
||||||
- [x] SDCC's `malloc`/`free` + наш `runtime/heap.s` (полностью заменяет library heap.rel, 14000-байтный heap в окне 2)
|
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
|
||||||
- [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`
|
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
|
||||||
- [x] `libc/mem/bank_io.c` — HOME-резидентные `bank_read`/`bank_write`/`bank_load_byte`/`bank_store_byte` со свопом W3 внутри
|
трогался asm акселератора); затем прогон на реальном Sprinter
|
||||||
- [x] `examples/malloc_test/` — проверка SDCC's malloc (~210 64-байтных allocations через всю heap)
|
(mdview2 + FILE* v2 + fdmax — подтвердить лимит 8 манипуляторов
|
||||||
- [x] `examples/mem_test/` — проверка page allocator: 3 страницы, разные паттерны через bank_write, верификация через bank_read
|
и зависание DSS на 9-м OPEN)
|
||||||
|
- [ ] Мигрировать оставшиеся examples на sprinter-cc вместо ручных
|
||||||
## Этап 6 — argv parsing + sprinter-cc wrapper ✅ ГОТОВО
|
Makefile (косметика)
|
||||||
|
- [ ] check_banks.py: разбивка size = code + const + bss per bank
|
||||||
- [x] crt0 парсит ESTEX command-line из IX-prefix (inline asm в `runtime/crt0.s`)
|
|
||||||
- [x] Strip leading CP/M-style space (DSS quirk)
|
## Auto-banking (memory/banking_roadmap.md)
|
||||||
- [x] Передача `argc`/`argv` в main() через HL/DE (SDCC __sdcccall(1) ABI)
|
|
||||||
- [x] argv[0] = basename .EXE через ESTEX APPINFO ($47 subfn 2)
|
Phase 1 — file-level bin-packing (`toolchain/auto_bank.py`) — когда
|
||||||
- [x] `runtime/crt0_minimal.s` — opt-out для очень маленьких программ
|
проект перерастёт ~30 KB кода: парсинг размеров из .rel/.map,
|
||||||
- [x] `runtime/crt0_banked.s` — теперь тоже парсит argv (parse_argv + get_progname скопированы из crt0.s; будет factored в argv.s когда возьмёмся за libsprinter.lib)
|
first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинковка,
|
||||||
- [x] Bash-обёртка `bin/sprinter-cc`: `sprinter-cc -o foo.exe foo.c` одной строкой
|
печать плана. Phase 2-5 (rebalance, banks.toml, function-level) —
|
||||||
- [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) ✅ ГОТОВО
|
## ОБЯЗАТЕЛЬНОЕ ДЛЯ V2
|
||||||
|
|
||||||
- [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.
|
### Turbo-C-style graphics API (BGI-like)
|
||||||
- [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 горизонтальных пикселя).
|
Расширить `gfx_*` до уровня Turbo-C `<graphics.h>`: initgraph/
|
||||||
- [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.
|
closegraph/cleardevice-алиасы, current point (moveto/lineto/linerel),
|
||||||
|
setcolor/setbkcolor, getpixel, circle/arc/ellipse/pieslice, drawpoly/
|
||||||
См. memory/sprinter_graphics.md, sprinter_accelerator.md, sprinter_graphics_16.md, sprinter_font_format.md.
|
fillpoly, floodfill, bar/bar3d, outtext[xy]/settextstyle/textwidth,
|
||||||
|
imagesize/getimage/putimage (COPY/XOR/AND/OR/NOT_PUT), setviewport/
|
||||||
Открытые мелочи (не блокируют):
|
клиппинг, setactivepage/setvisualpage (2 страницы есть), setlinestyle.
|
||||||
- [ ] Шрифт-quad для 640: per-cell палитра (mode 0x82 разрешает 1 из 4 палитр per 16×8 cell) — через прямой доступ к area-описания экрана 0x0300..0x039F
|
Acceptance: типичная BGI-программа переносится без существенных
|
||||||
|
правок. Референс: Turbo C 2.x BGIDEMO.
|
||||||
## Auto-banking (см. `memory/banking_roadmap.md` для деталей)
|
|
||||||
|
### IM2 Interrupt Handlers
|
||||||
Phase 1 — file-level bin-packing — реализовывать когда проект перерастёт ~30 KB кода.
|
|
||||||
|
User-ISR через IM 2 — timer ticks, музыка (AY/COVOX), real-time
|
||||||
- [ ] `toolchain/auto_bank.py`:
|
игры, async input. Решение: отдельный memory mode `--memory im2`.
|
||||||
- Парсит размеры из `.rel`-файлов (или из .map после dry-run link'а)
|
Полный research/design: docs/im2_isr_design.md (vector 0xFF общий,
|
||||||
- First-fit-decreasing bin-packing
|
disambiguation по портам 0x19/0xFE; таблица/ISR/стек в W2; chain к
|
||||||
- Уважает `#pragma codeseg BANKn` как manual override
|
DSS-хендлеру обязателен).
|
||||||
- Перелинковывает с новыми `-Wl-b_BANKn=...` параметрами
|
|
||||||
- Печатает план распределения
|
### Прочее v2
|
||||||
|
|
||||||
Phase 2-5: incremental rebalance, declarative `banks.toml`, function-level, call-graph-aware. Только если/когда понадобится.
|
- [ ] **Audio API** — AY-3-8910 + COVOX (требует IM2)
|
||||||
|
- [ ] **ISA-8 slot support** — ZX-Bus карты (требует IM2)
|
||||||
## Bank-local static data (mutable data в том же банке что и код) — ✅ ГОТОВО
|
|
||||||
|
## GFX: расширения по accelerator_doc.txt
|
||||||
- [x] Пример `examples/bank_local_data/` — функция в BANK1 со своим writable BSS array + const table + malloc-тест
|
|
||||||
- [x] `mkexe -p 0` для нулевого padding банков (BSS-storage обнуляется при загрузке)
|
Quick wins:
|
||||||
- [x] Канонический рецепт: `--codeseg BANK1 --constseg BANK1 --dataseg BANK1` для bank1.c + `-Wl-b_BANK1=0x1C000` для линковки. **`--dataseg BANK1` РАБОТАЕТ** — раньше казалось обратное из-за trampoline bug который маскировал результат.
|
- [ ] block-size через `LD A,(nn)` вместо SMC (док разрешает LD A,(HL/BC/DE))
|
||||||
- [x] **Критичный фикс trampoline'a в runtime/bank.s** — старый `pop af; out (n), a` клобберил A → все banked-функции возвращающие uint8_t тихо возвращали мусор. Новый `pop bc; out (c), b` сохраняет A.
|
- [ ] кэширование block-size между burst'ами (accel помнит размер)
|
||||||
- [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)
|
- [ ] `gfx_blit` / `gfx_blit_transparent` — block copy (LD L,L / LD A,A),
|
||||||
|
прозрачность через bank 0x58 («FF is transparent»)
|
||||||
Зачем: для модулей с большим private state (level loader, audio engine, scene data). Экономит W2 heap для динамики, а статика остаётся в бэке.
|
- [ ] `gfx_xor_rect` / `gfx_or_rect` / `gfx_and_rect` / `gfx_invert_rect`
|
||||||
|
- [ ] шрифты ≠ 8×8: gfx_set_font_data(ptr,w,h,advance), proportional,
|
||||||
## Подсказки из solid-c (нативный Sprinter C — `third_party/solid-c/`)
|
8×16/16×16, отдельный font_id API; font-quad для 640×256
|
||||||
|
(per-cell палитра через дескрипторы 0x0300..0x039F)
|
||||||
После анализа solid-c'овской libc (см. `memory/solid_c_findings.md`) выявлены готовые паттерны для следующих недостающих функций. Приоритет от **высокого** к низкому:
|
|
||||||
|
Оптимизации (не сейчас):
|
||||||
### High-priority gaps (легко портировать, большая польза)
|
- [ ] gfx_line через accel для пологих диагоналей (runs ≥ 4-5 px)
|
||||||
- [x] **`errno` + `strerror`/`perror`** — табличка 32 ошибок (libc/io/errno.c)
|
- [ ] композитные примитивы с одним W3-swap на операцию
|
||||||
- [x] **Расширенный `open()`** для O_CREAT/O_TRUNC/O_APPEND/O_EXCL state machine
|
|
||||||
- [x] **`atexit`** — 8-callback LIFO + `exit()` + `_exit()` (libc/io/atexit.c)
|
## Прочий backlog
|
||||||
- [x] **`setjmp`/`longjmp`** — 6-байт jmp_buf={sp,ix,pc} (libc/io/setjmp.c)
|
|
||||||
- [x] **`sleep(seconds)`** — 50Hz halt-loop (libc/io/sleep.c)
|
- [ ] factoring parse_argv из crt0/crt0_banked в общий argv.s
|
||||||
- [x] **ESTEX ENV API** ($46, getenv/putenv) — libc/io/env.c. Учли doc-bug: реально A=0 это NOT FOUND
|
- [ ] `restore SP on EXIT` (паттерн z88dk +pps) — проверить нужность
|
||||||
|
- [ ] CI: MAME с -aviwrite для screenshot-сравнения без человека
|
||||||
### Medium-priority (нужно для shell-like утилит)
|
- [ ] linker duplicate-symbol warnings: сейчас фильтруются в
|
||||||
- [ ] **Mouse driver** — `rst $30h`, 17 функций. **Сначала тест что работает в MAME**.
|
sprinter-cc (наши overrides _puts/___sdcc_heap/_asctime/…);
|
||||||
- [x] **`ffirst`/`fnext` + ffblk_t struct** для directory listing — реализовано, demo: ls.exe
|
радикально — --nostdlib с ручным списком модулей z80.lib
|
||||||
- [x] **`getdatetime`/`setdatetime`** через ESTEX $21/$22 — libc/io/time.c, demo: time_dir_test
|
- [ ] ZX Spectrum-совместимый target; ZX-Bus драйверы; PGO-tools
|
||||||
- [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
|
|
||||||
|
- [ ] **Port_Y banking trick** (адреса 0xC000+0x400*N → строки
|
||||||
### Low-priority — ✅ FILE* stack ГОТОВО
|
Y..Y+15): в MAME 0.283 НЕ работает (2026-06-01). На железе:
|
||||||
|
dual-write тест → если работает, кэшировать Port_Y в putpixel
|
||||||
- [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`.
|
(~8× меньше OUT для Брезенхэма); если нет — вычистить из доков.
|
||||||
- [ ] fprintf / fscanf — нужна printf-через-callback machinery. Пока пользователь может `sprintf(buf, ...) + fputs(buf, fp)`.
|
- [ ] fdmax: лимит манипуляторов и зависание 9-го OPEN — MAME vs железо.
|
||||||
- [ ] Опциональный buffered mode (setvbuf, line/block buffering) — если когда-то понадобится.
|
|
||||||
|
## Known quirks (зафиксированы, обходы в libc)
|
||||||
### POSIX time API — ✅ ГОТОВО
|
|
||||||
- [x] `libc/io/posix_time.c` — time/localtime/gmtime/mktime/asctime/ctime поверх getdatetime. SDCC's time.rel избегаем (нельзя override _RtcRead). См. `examples/ptime`.
|
- ESTEX $46 ENV: A=0 это NOT FOUND (док врёт) — memory/sprinter_platform
|
||||||
|
- ESTEX WRITE $14: на успехе DE=0, не счётчик; успех = CF=0 & A=0 —
|
||||||
### sys/stat — ✅ ГОТОВО
|
memory/estex_write_de_quirk
|
||||||
- [x] `libc/io/stat.c` — POSIX stat/fstat. Гибрид open+fstat для файлов, ffirst+iter для папок (включая "."/".."). См. `examples/stattest` и `memory/estex_ffirst_dotdot.md`.
|
- DSS: 8 манипуляторов, 9-й OPEN вешает систему; _fd_guard в libc —
|
||||||
|
memory/dss_fd_limit
|
||||||
### assert — ✅ ГОТОВО (используем SDCC's __assert через fallback include path)
|
- SDCC z80 `if(n!=g)g=n;` пишет (n-g) — memory/sdcc_z80_cmp_store_a_bug
|
||||||
|
|
||||||
## 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.)
|
## Этап 10 — libc: сплит + FILE v2 + Solid-C (2026-07-05/06) ✅
|
||||||
- `<ctype.h>` (toupper/tolower)
|
|
||||||
- `<math.h>` (sinf/cosf/sqrtf/etc.)
|
Полный план/итоги: docs/libc-roadmap.md. Кратко:
|
||||||
|
- вся libc разложена «1 функция = 1 модуль» (~250 модулей, wildcard-
|
||||||
Линкер автоматически тянет из z80.lib когда нужно. **НЕ переписывать**.
|
сборка, DCE на уровне файлов): gfx_text −4.4 КБ, timedir/ls/stattest
|
||||||
|
−3 КБ и т.д.; правила asm-связок: docs/libc-split-asm-cases.md
|
||||||
Наши Sprinter-specific обязательные модули остаются: atexit, env, errno, setjmp, putchar/puts/getchar, conio, fsdir, time, mouse, open/read/lseek/close.
|
- **FILE* v2 (B+)**: ленивый буфер 512 на чтение/запись с
|
||||||
|
автопереключением, таблица OPEN_MAX=8, flush-on-exit, ungetc,
|
||||||
## Build-system: libsprinter.lib + sprinter-cc — ✅ ГОТОВО
|
fprintf/vfprintf, fdopen/freopen/fclosall/fgetpos/fsetpos; горячие
|
||||||
|
пути fgetc/fputc/fgets на asm (fgets 100 КБ: 144с unbuffered-оценка
|
||||||
- [x] `lib/Makefile` — собирает каждый libc/*.c в `.rel`, архивирует через sdar в `lib/sprinter.lib`
|
→ ~1 с). Дизайн: docs/file-buffering-design.md
|
||||||
- [x] Включает runtime/bank.s и runtime/heap.s (auto-pulled при __banked/malloc)
|
- **scanf/fscanf/sscanf** — своё C-ядро (в SDCC z80 нет)
|
||||||
- [x] `bin/sprinter-cc` — bash-wrapper: `sprinter-cc -o foo.exe foo.c` одной строкой
|
- **Solid-C совместимость закрыта**: <dos.h> (даты/диски/absread),
|
||||||
- [x] Поддержка опций `--crt0=default|minimal|banked`, `--bank N=FILE.c`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
|
errno-алиасы, <sprinter_solid.h> — docs/solid_c_compatibility.md
|
||||||
- [x] `examples/hello_sccc/` — демо: `hello.c` собирается за один shell-вызов, размер совпадает с ручным Makefile (925 байт)
|
- гигиена: stale .rel чистка, все 43 теста в make all, размерный
|
||||||
- [x] Split `putchar.c` → `putchar.c` + `puts.c` для per-function granularity (puts override SDCC's z80.lib version)
|
регресс (make size-check), контракт заголовков docs/libc-headers.md
|
||||||
- [x] Включён в `make all` (зависимость `lib` перед `examples`)
|
- справочник API: docs/libc-reference.md
|
||||||
|
|
||||||
Возможные улучшения (опционально):
|
## Этап 9 — memory modes (tiny/small/big/huge/manual) ✅ 2026-05-30
|
||||||
- [ ] Мигрировать остальные examples на sprinter-cc вместо ручных Makefile (косметика)
|
|
||||||
- [ ] Дальнейшая декомпозиция libc/*.c per-function (но текущая granularity уже даёт нужный размер — линкер пакетует .rel целиком, и для большинства файлов это одна функция)
|
`--memory MODE` в sprinter-cc; crt0-семейство (crt0/minimal/small/
|
||||||
|
banked); small: ESTEX GETMEM+SETWIN2 до gsinit, auto-detect W2 по
|
||||||
## Этап 9 — memory modes для sprinter-cc
|
порту 0xC2; big/huge: параметризация crt0_banked/bank.s через
|
||||||
|
BANK_W1; --debug, --stack-size. Детали: memory/memory_modes_
|
||||||
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).
|
implemented, memory/sprinter_memory_modes.
|
||||||
|
|
||||||
- [x] **`tiny`**: всё (CODE+DATA+стек) в W2. Default. Verified hello/argv/conio/malloc/file/etc.
|
Дизайн-решения: одна sprinter.lib на все режимы (DCE per-member);
|
||||||
- [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.
|
gfx.lib отдельно не нужен; libc_banked + sprinter_home.lib — идея
|
||||||
- [x] **`--memory-manual SPEC`**: парсит `CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3`. Реализовано 2026-05-30.
|
на потом (триггер: HOME забит user-кодом).
|
||||||
- [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.
|
## Этап 8 — графика ✅
|
||||||
- [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.
|
320×256×256 + 640×256×16, акселератор (Fill h/v, SMC block-size),
|
||||||
- [x] **`--debug` флаг**: prepend `DEBUG_RT = 1` в crt0 + `-DDEBUG_RT` в sdcc. Открывает symbol `_w2_self_allocated` (uint8_t) — runtime diagnostic кто аллоцировал W2. Реализовано 2026-05-30.
|
Брезенхэм, bitmap font (WIN_GET_ZG, interleaved), gfx_text.
|
||||||
|
memory/sprinter_graphics*, sprinter_accelerator, sprinter_font_format.
|
||||||
### Дизайн-решения по libc и crt0
|
|
||||||
|
## Bank-local data ✅
|
||||||
**Одна `sprinter.lib`** работает для всех memory mode — `.rel`-члены relocatable, SDLD делает dead-code elimination per-member (без графики не подтягивает `gfx_core.rel` и т.д.). Verified hello vs malloc_test через map-файлы.
|
|
||||||
|
--codeseg/--constseg/--dataseg BANKn + mkexe -p 0; фикс трамплина
|
||||||
**`gfx.lib` отдельно — НЕ нужен**: dead-code elimination уже работает.
|
(pop bc/out (c),b — сохраняет A); malloc из банка прозрачен (heap в
|
||||||
|
W2). memory/bank_local_data_pattern.
|
||||||
**`libc_banked` (libc в bank вместо HOME)** — идея на потом, когда HOME (16 KB) забит user-кодом + libc в `huge` mode. Реализуется через `--codeseg BANK0` при компиляции libc; trade-off: trampoline ~30 циклов на каждый libc-вызов. Триггер: реальная программа упрётся в HOME budget.
|
|
||||||
|
## Этапы 5-7 и ранняя libc ✅
|
||||||
**HW-зависимые модули — `sprinter_home.lib` отдельно.** Часть libc физически не может быть забанкована в W3, потому что она РАБОТАЕТ с W3:
|
|
||||||
- `gfx_*` — пишет в видеопамять `0xC000+` после swap W3 на video page
|
- malloc/free (SDCC + runtime/heap.s в W2), page allocator
|
||||||
- `bank_io` (mem_alloc_pages/bank_read/bank_write) — swap'ит W3 через `OUT (0xE2)`
|
(mem_alloc_pages, ESTEX $3C-$3E + BIOS $C4), bank_read/bank_write
|
||||||
- Будущие ISR — прерывание может прийти когда W3 на чём угодно
|
- crt0 argv-парсинг (IX-prefix, CP/M-space quirk, APPINFO basename),
|
||||||
|
sprinter-cc wrapper со всеми опциями
|
||||||
В huge mode эти модули ДОЛЖНЫ остаться в HOME (W1). Когда будем делать `libc_banked`, **одновременно** выделяем `sprinter_home.lib` (HOME-only) из `sprinter.lib` (bankable). Финальная схема:
|
- errno+strerror/perror, open state-machine, atexit, setjmp/longjmp,
|
||||||
```
|
sleep, ENV API ($46), ffirst/fnext, getdatetime/setdatetime,
|
||||||
sprinter_home.lib HOME-only: gfx, bank_io, ISR shims
|
chdir/getcwd/mkdir/rmdir, conio (полный), mouse (RST 30h, 14 ф-й),
|
||||||
sprinter.lib bankable: printf, malloc, string, conio, stdio, env, ...
|
POSIX time API, sys/stat, assert
|
||||||
sprinter_banked.lib тот же sprinter.lib но --codeseg BANK0 (для huge)
|
- text I/O split (stdio fast / conio attr) — memory/text_output_api_split
|
||||||
```
|
- SDCC stdlib НЕ переписываем — memory/sdcc_stdlib_works
|
||||||
Триггер: реализация `--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 × количество строк/столбцов экономии. Применимо ко всем композитным примитивам.
|
|
||||||
|
|||||||
@@ -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*).
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Заголовки 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, gfx.h, mouse.h, palette.h,
|
||||||
|
sprinter*.h, unistd.h, bios/*.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- новый стандартный заголовок — сначала пробовать паттерн 1
|
||||||
|
(include_next); паттерн 2 — только если надо переопределить
|
||||||
|
реализацию (как puts/putchar на ESTEX);
|
||||||
|
- в заголовках паттерна 2 — комментарий, какие декларации обслуживают
|
||||||
|
z80.lib;
|
||||||
|
- internal-заголовки libc (`_conio.h`, `_file.h`, `_gfx.h`, …) живут
|
||||||
|
РЯДОМ с исходниками в libc/<area>/, не в libc/include.
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
# 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)` | 50 halt/с по кадровому IRQ |
|
||||||
|
|
||||||
|
**Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS —
|
||||||
|
libc отказывает сама (EMFILE, предохранитель _fd_guard).
|
||||||
|
|
||||||
|
## <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> — графика (0x81: 320×256×256; 0x82: 640×256×16)
|
||||||
|
|
||||||
|
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()`.
|
||||||
|
Примитивы (суффикс _256 / _16): `clear putpixel hline vline line
|
||||||
|
rect fill_rect` — через акселератор (hline/vline burst до 256 байт;
|
||||||
|
в 0x82 vline через RMW). Координаты int, клиппинг по краям.
|
||||||
|
Текст: `gfx_putchar256/16, gfx_text256/16` (8×8, fg/bg; в 0x82 x —
|
||||||
|
чётный), `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
|
||||||
|
font[row*256+char]).
|
||||||
|
Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3).
|
||||||
|
|
||||||
|
## <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] lib/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. Верификация после сплита
|
||||||
|
|
||||||
|
- [ ] `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,44 @@
|
|||||||
|
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
|
||||||
|
argv 3431
|
||||||
|
assrtest 3847
|
||||||
|
attrprob 4091
|
||||||
|
banked 1056
|
||||||
|
bankedbg 1067
|
||||||
|
banklocl 4832
|
||||||
|
banktest 3767
|
||||||
|
bios_text 4470
|
||||||
|
cat 915
|
||||||
|
conio 4634
|
||||||
|
conio2 3946
|
||||||
|
dec_test 860
|
||||||
|
errno 6216
|
||||||
|
fbench 9040
|
||||||
|
fdmax 6337
|
||||||
|
filetest 11624
|
||||||
|
gets 509
|
||||||
|
gfx_d16 3319
|
||||||
|
gfx_dbuf 2396
|
||||||
|
gfx_demo 3219
|
||||||
|
gfx_mous 5542
|
||||||
|
gfx_text 2560
|
||||||
|
hello 4176
|
||||||
|
hello2 4251
|
||||||
|
ls 4919
|
||||||
|
malloc 4462
|
||||||
|
mdview 19122
|
||||||
|
mdview2 27029
|
||||||
|
mem_test 4558
|
||||||
|
mouse 4394
|
||||||
|
openenv 6127
|
||||||
|
ptime 5896
|
||||||
|
rt_test 4103
|
||||||
|
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.
|
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
|
## 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
|
- 2026-06-01 — initial gap analysis vs Solid-C v2004
|
||||||
|
|||||||
@@ -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.
|
||||||
+24
-47
@@ -20,52 +20,10 @@ CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
|
|||||||
|
|
||||||
BUILD := $(PROJ_ROOT)/lib/build
|
BUILD := $(PROJ_ROOT)/lib/build
|
||||||
|
|
||||||
# All libc C modules.
|
# All libc C modules — every .c under libc/<area>/ becomes its own .rel.
|
||||||
LIBC_C := \
|
# One PUBLIC function per file (see docs/libc-split-asm-cases.md): the
|
||||||
libc/sys/atexit.c \
|
# linker pulls whole .rel modules, so file granularity == DCE granularity.
|
||||||
libc/conio/conio.c \
|
LIBC_C := $(patsubst $(PROJ_ROOT)/%,%,$(wildcard $(PROJ_ROOT)/libc/*/*.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/stdlib/minmax.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).
|
# 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
|
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
|
||||||
@@ -78,6 +36,16 @@ RUNTIME_RELS := $(patsubst runtime/%.s,$(BUILD)/%.rel,$(RUNTIME_S))
|
|||||||
|
|
||||||
ALL_RELS := $(LIBC_RELS) $(RUNTIME_RELS)
|
ALL_RELS := $(LIBC_RELS) $(RUNTIME_RELS)
|
||||||
|
|
||||||
|
# Штамп списка модулей: обновляется при любом изменении состава
|
||||||
|
# (добавление/удаление/переименование исходника) — иначе удаление
|
||||||
|
# исходника не триггерит перелинковку и stale .rel остаётся в архиве.
|
||||||
|
# Сразу сносим и архив: на exFAT (SAM8) гранулярность mtime грубая,
|
||||||
|
# и «список изменился» через сравнение времён можно проглядеть.
|
||||||
|
MODLIST := $(BUILD)/.modules
|
||||||
|
ifneq ($(shell cat $(MODLIST) 2>/dev/null),$(ALL_RELS))
|
||||||
|
_ := $(shell mkdir -p $(BUILD); echo "$(ALL_RELS)" > $(MODLIST); rm -f sprinter.lib)
|
||||||
|
endif
|
||||||
|
|
||||||
LIB := sprinter.lib
|
LIB := sprinter.lib
|
||||||
|
|
||||||
all: $(LIB)
|
all: $(LIB)
|
||||||
@@ -94,7 +62,16 @@ $(BUILD)/%.rel: $(PROJ_ROOT)/runtime/%.s
|
|||||||
$(SDASZ80) -o $@ $<
|
$(SDASZ80) -o $@ $<
|
||||||
|
|
||||||
# Archive — sdar with `rcs` = replace/create/symtab.
|
# Archive — sdar with `rcs` = replace/create/symtab.
|
||||||
$(LIB): $(ALL_RELS)
|
# Перед упаковкой удаляем stale .rel: wildcard не знает об удалённых/
|
||||||
|
# переименованных исходниках, и осиротевший .rel из build/ иначе жил бы
|
||||||
|
# вечно (уже кусало: старый file.rel после сплита).
|
||||||
|
$(LIB): $(ALL_RELS) $(MODLIST)
|
||||||
|
@find $(BUILD) -name '*.rel' -print | sort > $(BUILD)/.have
|
||||||
|
@printf '%s\n' $(ALL_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 $@
|
rm -f $@
|
||||||
$(SDAR) -rcs $@ $(ALL_RELS)
|
$(SDAR) -rcs $@ $(ALL_RELS)
|
||||||
@echo
|
@echo
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* bios_clearwin — очистить окно (row, col, height, width) пробелами с
|
||||||
|
* атрибутом attr. BIOS LP_CLS_WIN ($89, rst 8): D=row, E=col,
|
||||||
|
* H=height, L=width, B=attr.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width,
|
||||||
|
uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)row; (void)col; (void)height; (void)width; (void)attr;
|
||||||
|
__asm
|
||||||
|
;; row→A, col→L, height/width/attr на стеке [SP+2..4].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld h, 0 (iy) ; H = height
|
||||||
|
ld c, 1 (iy) ; спрятать width (L пока держит col)
|
||||||
|
ld e, l ; E = col
|
||||||
|
ld d, a ; D = row
|
||||||
|
ld l, c ; L = width
|
||||||
|
ld b, 2 (iy) ; B = attr
|
||||||
|
push ix
|
||||||
|
ld c, #0x89
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
inc sp ; съесть height + width + attr
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* bios_clearwin_ch — залить окно (row, col, height, width) символом
|
||||||
|
* fillch с атрибутом attr. BIOS LP_CLS_WIN2 ($8D, rst 8): D=row,
|
||||||
|
* E=col, H=height, L=width, B=attr, A=fillch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height,
|
||||||
|
uint8_t width, uint8_t attr, char fillch) __naked
|
||||||
|
{
|
||||||
|
(void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch;
|
||||||
|
__asm
|
||||||
|
;; row→A, col→L, height/width/attr/fillch на стеке [SP+2..5].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld d, a ; D = row (освободить A под fillch)
|
||||||
|
ld e, l ; E = col (освободить L под width)
|
||||||
|
ld h, 0 (iy) ; H = height
|
||||||
|
ld c, 1 (iy) ; спрятать width
|
||||||
|
ld b, 2 (iy) ; B = attr
|
||||||
|
ld a, 3 (iy) ; A = fillch
|
||||||
|
ld l, c ; L = width
|
||||||
|
push ix
|
||||||
|
ld c, #0x8D
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
inc sp ; съесть height + width + attr + fillch
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* bios_fillattr — залить count позиций атрибутом attr (символы не
|
||||||
|
* трогаются) от текущего place. BIOS LP_PRINT_ATR ($83, rst 8):
|
||||||
|
* E=attr, B=count. Сохраняет HL и IX.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_fillattr(uint8_t attr, uint8_t count) __naked
|
||||||
|
{
|
||||||
|
(void)attr; (void)count;
|
||||||
|
__asm
|
||||||
|
;; attr→A, count→L.
|
||||||
|
ld e, a
|
||||||
|
ld b, l
|
||||||
|
ld c, #0x83
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* bios_fillchar — вывести символ ch count раз (атрибут не трогается)
|
||||||
|
* от текущего place. BIOS LP_PRINT_SYM ($82, rst 8): A=ch, B=count.
|
||||||
|
* Сохраняет HL и IX.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_fillchar(char ch, uint8_t count) __naked
|
||||||
|
{
|
||||||
|
(void)ch; (void)count;
|
||||||
|
__asm
|
||||||
|
;; ch→A, count→L.
|
||||||
|
ld b, l
|
||||||
|
ld c, #0x82
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* bios_fillcharattr — вывести символ ch с атрибутом attr count раз от
|
||||||
|
* текущего place. BIOS LP_PRINT_ALL ($81, rst 8): A=ch, E=attr,
|
||||||
|
* B=count. Сохраняет HL и IX — push/pop ix не нужен.
|
||||||
|
*
|
||||||
|
* SDCC __sdcccall(1): ch→A, attr→L, count на стеке [SP+2]; стековый
|
||||||
|
* байт съедает callee (inc sp), IY — скретч для чтения без порчи IX.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked
|
||||||
|
{
|
||||||
|
(void)ch; (void)attr; (void)count;
|
||||||
|
__asm
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = count
|
||||||
|
ld e, l ; E = attr
|
||||||
|
ld c, #0x81
|
||||||
|
rst #0x08
|
||||||
|
pop hl ; адрес возврата
|
||||||
|
inc sp ; съесть байт count
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* bios_get_place — текущая позиция BIOS-вывода: (row << 8) | col.
|
||||||
|
* BIOS LP_GET_PLACE ($8E, rst 8) возвращает D=row, E=col — это уже
|
||||||
|
* конвенция возврата uint16_t (DE), перестановок не нужно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
uint16_t bios_get_place(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld c, #0x8E
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* bios_scrollwin — прокрутить экран от строки row на count строк.
|
||||||
|
* BIOS LP_SCROLL_UD ($8A, rst 8): B=dir (1=вверх, 2=вниз), D=row,
|
||||||
|
* E=count.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked
|
||||||
|
{
|
||||||
|
(void)dir; (void)row; (void)count;
|
||||||
|
__asm
|
||||||
|
;; dir→A, row→L, count на стеке [SP+2].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld e, 0 (iy) ; E = count
|
||||||
|
ld d, l ; D = row
|
||||||
|
ld b, a ; B = dir
|
||||||
|
push ix
|
||||||
|
ld c, #0x8A
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* bios_set_place — установить позицию вывода BIOS-текста (place).
|
||||||
|
* BIOS LP_SET_PLACE ($84, rst 8): D=row, E=col. Используется без
|
||||||
|
* push/pop ix (как gotoxy в conio — эмпирически безопасно).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_set_place(uint8_t row, uint8_t col) __naked
|
||||||
|
{
|
||||||
|
(void)row; (void)col;
|
||||||
|
__asm
|
||||||
|
;; row→A, col→L.
|
||||||
|
ld d, a
|
||||||
|
ld e, l
|
||||||
|
ld c, #0x84
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* bios_write — вывести len символов строки s (атрибут не трогается).
|
||||||
|
* BIOS LP_PRINT_LN2 ($86, rst 8): HL=s, B=len. Place продвигается.
|
||||||
|
* s должен лежать в #4000-#BFFF.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_write(const char *s, uint8_t len) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)len;
|
||||||
|
__asm
|
||||||
|
;; s→HL, len на стеке [SP+2].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = len
|
||||||
|
push ix
|
||||||
|
ld c, #0x86
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* bios_write_stop — вывести строку s до разделителя sep БЕЗ добивки
|
||||||
|
* (максимум maxlen символов, атрибут не трогается).
|
||||||
|
* BIOS LP_PRINT_LN6 ($8C, rst 8): HL=s, B=maxlen, D=sep.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)maxlen; (void)sep;
|
||||||
|
__asm
|
||||||
|
;; s→HL, maxlen/sep на стеке [SP+2..3].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = maxlen
|
||||||
|
ld d, 1 (iy) ; D = sep
|
||||||
|
push ix
|
||||||
|
ld c, #0x8C
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* bios_write_until — вывести строку s до разделителя sep, ДОБИВ
|
||||||
|
* пробелами до len (атрибут не трогается). BIOS LP_PRINT_LN4 ($88,
|
||||||
|
* rst 8): HL=s, B=len, D=sep.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_write_until(const char *s, uint8_t len, char sep) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)len; (void)sep;
|
||||||
|
__asm
|
||||||
|
;; s→HL, len/sep на стеке [SP+2..3].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = len
|
||||||
|
ld d, 1 (iy) ; D = sep
|
||||||
|
push ix
|
||||||
|
ld c, #0x88
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* bios_writeattr — вывести len символов строки s с атрибутом attr.
|
||||||
|
* BIOS LP_PRINT_LN ($85, rst 8): HL=s, B=len, E=attr. Place
|
||||||
|
* продвигается после печати. s должен лежать в #4000-#BFFF.
|
||||||
|
*
|
||||||
|
* SDCC __sdcccall(1): s→HL, len/attr на стеке [SP+2]/[SP+3];
|
||||||
|
* стековые байты съедает callee.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)len; (void)attr;
|
||||||
|
__asm
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = len
|
||||||
|
ld e, 1 (iy) ; E = attr
|
||||||
|
push ix
|
||||||
|
ld c, #0x85
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl ; адрес возврата
|
||||||
|
inc sp
|
||||||
|
inc sp ; съесть len + attr
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* bios_writeattr_stop — вывести строку s с атрибутом attr до
|
||||||
|
* разделителя sep БЕЗ добивки пробелами (максимум maxlen символов).
|
||||||
|
* BIOS LP_PRINT_LN5 ($8B, rst 8): HL=s, B=maxlen, E=attr, D=sep.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr,
|
||||||
|
char sep) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)maxlen; (void)attr; (void)sep;
|
||||||
|
__asm
|
||||||
|
;; s→HL, maxlen/attr/sep на стеке [SP+2..4].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = maxlen
|
||||||
|
ld e, 1 (iy) ; E = attr
|
||||||
|
ld d, 2 (iy) ; D = sep
|
||||||
|
push ix
|
||||||
|
ld c, #0x8B
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* bios_writeattr_until — вывести строку s с атрибутом attr до
|
||||||
|
* разделителя sep, ДОБИВ пробелами до len. BIOS LP_PRINT_LN3 ($87,
|
||||||
|
* rst 8): HL=s, B=len, E=attr, D=sep.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <bios/text.h>
|
||||||
|
|
||||||
|
void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked
|
||||||
|
{
|
||||||
|
(void)s; (void)len; (void)attr; (void)sep;
|
||||||
|
__asm
|
||||||
|
;; s→HL, len/attr/sep на стеке [SP+2..4].
|
||||||
|
ld iy, #2
|
||||||
|
add iy, sp
|
||||||
|
ld b, 0 (iy) ; B = len
|
||||||
|
ld e, 1 (iy) ; E = attr
|
||||||
|
ld d, 2 (iy) ; D = sep
|
||||||
|
push ix
|
||||||
|
ld c, #0x87
|
||||||
|
rst #0x08
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
inc sp
|
||||||
|
inc sp
|
||||||
|
inc sp ; съесть len + attr + sep
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
/*
|
|
||||||
* text.c — wrappers for the Sprinter BIOS text-output calls (RST 8,
|
|
||||||
* 081h..08Eh "LP_*"). See <bios/text.h> for the full opcode -> name
|
|
||||||
* mapping and semantics.
|
|
||||||
*
|
|
||||||
* ABI recap (docs/converted/bios.txt):
|
|
||||||
* - 081h/082h/083h explicitly preserve HL and IX -> no push/pop ix
|
|
||||||
* needed, and the result is the smallest/fastest of the set.
|
|
||||||
* - 084h/08Eh (place get/set) are used without push/pop ix elsewhere
|
|
||||||
* in this libc (libc/conio/conio.c gotoxy/wherex/wherey via the same
|
|
||||||
* opcodes) — empirically safe, followed here too.
|
|
||||||
* - Everything else clobbers IX like any other BIOS/ESTEX call, so
|
|
||||||
* every RST 8 below is bracketed with push ix / pop ix.
|
|
||||||
*
|
|
||||||
* Stack-argument layout: SDCC __sdcccall(1) places the first 8-bit arg
|
|
||||||
* in A, the second (only if the first was also 8-bit) in L; every arg
|
|
||||||
* beyond that lands on the stack in left-to-right declaration order,
|
|
||||||
* starting at [SP+2] (right after the 2-byte return address) — verified
|
|
||||||
* empirically via `sdcc -S` for 2..6 uint8 args and for pointer+N uint8
|
|
||||||
* args. IY is used as a scratch index register to read those without
|
|
||||||
* disturbing IX (the caller's frame pointer).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <bios/text.h>
|
|
||||||
|
|
||||||
void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked
|
|
||||||
{
|
|
||||||
(void)ch; (void)attr; (void)count;
|
|
||||||
__asm
|
|
||||||
;; ch->A, attr->L, count on stack at [SP+2].
|
|
||||||
;; LP_PRINT_ALL (081h): A=ch, E=attr, B=count. Preserves HL,IX.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = count
|
|
||||||
ld e, l ; E = attr
|
|
||||||
ld c, #0x81
|
|
||||||
rst #0x08
|
|
||||||
pop hl ; return address
|
|
||||||
inc sp ; consume count byte
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_fillchar(char ch, uint8_t count) __naked
|
|
||||||
{
|
|
||||||
(void)ch; (void)count;
|
|
||||||
__asm
|
|
||||||
;; ch->A, count->L. LP_PRINT_SYM (082h): A=ch, B=count.
|
|
||||||
;; Preserves HL,IX.
|
|
||||||
ld b, l
|
|
||||||
ld c, #0x82
|
|
||||||
rst #0x08
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_fillattr(uint8_t attr, uint8_t count) __naked
|
|
||||||
{
|
|
||||||
(void)attr; (void)count;
|
|
||||||
__asm
|
|
||||||
;; attr->A, count->L. LP_PRINT_ATR (083h): E=attr, B=count.
|
|
||||||
;; Preserves HL,IX.
|
|
||||||
ld e, a
|
|
||||||
ld b, l
|
|
||||||
ld c, #0x83
|
|
||||||
rst #0x08
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_set_place(uint8_t row, uint8_t col) __naked
|
|
||||||
{
|
|
||||||
(void)row; (void)col;
|
|
||||||
__asm
|
|
||||||
;; row->A, col->L. LP_SET_PLACE (084h): D=row, E=col.
|
|
||||||
ld d, a
|
|
||||||
ld e, l
|
|
||||||
ld c, #0x84
|
|
||||||
rst #0x08
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t bios_get_place(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
;; LP_GET_PLACE (08Eh): returns D=row, E=col — already the
|
|
||||||
;; uint16_t return convention (DE); nothing left to rearrange.
|
|
||||||
ld c, #0x8E
|
|
||||||
rst #0x08
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)len; (void)attr;
|
|
||||||
__asm
|
|
||||||
;; s->HL, len/attr on stack at [SP+2]/[SP+3].
|
|
||||||
;; LP_PRINT_LN (085h): HL=s, B=len, E=attr.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = len
|
|
||||||
ld e, 1 (iy) ; E = attr
|
|
||||||
push ix
|
|
||||||
ld c, #0x85
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl ; return address
|
|
||||||
inc sp
|
|
||||||
inc sp ; consume len + attr
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_write(const char *s, uint8_t len) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)len;
|
|
||||||
__asm
|
|
||||||
;; s->HL, len on stack at [SP+2].
|
|
||||||
;; LP_PRINT_LN2 (086h): HL=s, B=len.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = len
|
|
||||||
push ix
|
|
||||||
ld c, #0x86
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)len; (void)attr; (void)sep;
|
|
||||||
__asm
|
|
||||||
;; s->HL, len/attr/sep on stack at [SP+2..4].
|
|
||||||
;; LP_PRINT_LN3 (087h): HL=s, B=len, E=attr, D=sep.
|
|
||||||
;; Pads with spaces after sep up to len.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = len
|
|
||||||
ld e, 1 (iy) ; E = attr
|
|
||||||
ld d, 2 (iy) ; D = sep
|
|
||||||
push ix
|
|
||||||
ld c, #0x87
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
inc sp ; consume len + attr + sep
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_write_until(const char *s, uint8_t len, char sep) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)len; (void)sep;
|
|
||||||
__asm
|
|
||||||
;; s->HL, len/sep on stack at [SP+2..3].
|
|
||||||
;; LP_PRINT_LN4 (088h): HL=s, B=len, D=sep. Pads with spaces.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = len
|
|
||||||
ld d, 1 (iy) ; D = sep
|
|
||||||
push ix
|
|
||||||
ld c, #0x88
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width,
|
|
||||||
uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)row; (void)col; (void)height; (void)width; (void)attr;
|
|
||||||
__asm
|
|
||||||
;; row->A, col->L, height/width/attr on stack at [SP+2..4].
|
|
||||||
;; LP_CLS_WIN (089h): D=row, E=col, H=height, L=width, B=attr.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld h, 0 (iy) ; H = height
|
|
||||||
ld c, 1 (iy) ; stash width (L is still col for now)
|
|
||||||
ld e, l ; E = col
|
|
||||||
ld d, a ; D = row
|
|
||||||
ld l, c ; L = width
|
|
||||||
ld b, 2 (iy) ; B = attr
|
|
||||||
push ix
|
|
||||||
ld c, #0x89
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
inc sp ; consume height + width + attr
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked
|
|
||||||
{
|
|
||||||
(void)dir; (void)row; (void)count;
|
|
||||||
__asm
|
|
||||||
;; dir->A, row->L, count on stack at [SP+2].
|
|
||||||
;; LP_SCROLL_UD (08Ah): B=dir(1 up/2 down), D=row, E=count.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld e, 0 (iy) ; E = count
|
|
||||||
ld d, l ; D = row
|
|
||||||
ld b, a ; B = dir
|
|
||||||
push ix
|
|
||||||
ld c, #0x8A
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr,
|
|
||||||
char sep) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)maxlen; (void)attr; (void)sep;
|
|
||||||
__asm
|
|
||||||
;; s->HL, maxlen/attr/sep on stack at [SP+2..4].
|
|
||||||
;; LP_PRINT_LN5 (08Bh): HL=s, B=maxlen, E=attr, D=sep.
|
|
||||||
;; Stops at sep (no padding).
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = maxlen
|
|
||||||
ld e, 1 (iy) ; E = attr
|
|
||||||
ld d, 2 (iy) ; D = sep
|
|
||||||
push ix
|
|
||||||
ld c, #0x8B
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked
|
|
||||||
{
|
|
||||||
(void)s; (void)maxlen; (void)sep;
|
|
||||||
__asm
|
|
||||||
;; s->HL, maxlen/sep on stack at [SP+2..3].
|
|
||||||
;; LP_PRINT_LN6 (08Ch): HL=s, B=maxlen, D=sep. Stops at sep.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld b, 0 (iy) ; B = maxlen
|
|
||||||
ld d, 1 (iy) ; D = sep
|
|
||||||
push ix
|
|
||||||
ld c, #0x8C
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height,
|
|
||||||
uint8_t width, uint8_t attr, char fillch) __naked
|
|
||||||
{
|
|
||||||
(void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch;
|
|
||||||
__asm
|
|
||||||
;; row->A, col->L, height/width/attr/fillch on stack [SP+2..5].
|
|
||||||
;; LP_CLS_WIN2 (08Dh): D=row, E=col, H=height, L=width, B=attr,
|
|
||||||
;; A=fillch.
|
|
||||||
ld iy, #2
|
|
||||||
add iy, sp
|
|
||||||
ld d, a ; D = row (free A for fillch later)
|
|
||||||
ld e, l ; E = col (free L for width later)
|
|
||||||
ld h, 0 (iy) ; H = height
|
|
||||||
ld c, 1 (iy) ; stash width
|
|
||||||
ld b, 2 (iy) ; B = attr
|
|
||||||
ld a, 3 (iy) ; A = fillch
|
|
||||||
ld l, c ; L = width
|
|
||||||
push ix
|
|
||||||
ld c, #0x8D
|
|
||||||
rst #0x08
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
inc sp
|
|
||||||
inc sp ; consume height + width + attr + fillch
|
|
||||||
jp (hl)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* _bios_putchar — быстрый вывод символа через ESTEX PUTCHAR ($5B):
|
||||||
|
* без атрибута, CR/LF/скролл и курсор ведёт сам ESTEX. Используется
|
||||||
|
* (задуман) для пути g_text_attr == KEEP_EXIST_ATTR.
|
||||||
|
* PUTCHAR не меняет IX (проверено), поэтому push/pop ix не нужен.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
char _bios_putchar(char ch) __naked
|
||||||
|
{
|
||||||
|
(void)ch;
|
||||||
|
__asm
|
||||||
|
;; ch в A. push af сохраняет его через RST (который портит A).
|
||||||
|
push af
|
||||||
|
ld c, #0x5B ; ESTEX PUTCHAR
|
||||||
|
rst #0x10
|
||||||
|
pop af
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* _conio.h — внутренние состояние и хелперы conio.
|
||||||
|
*
|
||||||
|
* НЕ публичный заголовок: живёт рядом с исходниками. После сплита
|
||||||
|
* «1 функция = 1 модуль» каждый элемент лежит в своём модуле, чтобы
|
||||||
|
* приложение тянуло из архива только то, что реально вызывает:
|
||||||
|
*
|
||||||
|
* _g_text_attr.c — g_text_attr (текущий атрибут, KEEP_EXIST_ATTR)
|
||||||
|
* _pc_place.c — pc_place (курсор col:row; global — читают тесты)
|
||||||
|
* _pc_raw_mode.c — pc_raw_mode (режим управляющих символов putch)
|
||||||
|
* _get_cursor.c — чтение курсора BIOS → pc_place
|
||||||
|
* _set_cursor.c — запись pc_place → курсор BIOS
|
||||||
|
* _putch_wrchar.c — воркер вывода mode-0 (BS/TAB/LF/CR интерпретируются)
|
||||||
|
* _putch_wrchar_raw.c — воркер mode-1 (всё как глифы)
|
||||||
|
* _bios_putchar.c — быстрый вывод через ESTEX PUTCHAR (без атрибута)
|
||||||
|
*/
|
||||||
|
#ifndef _CONIO_INTERNAL_H
|
||||||
|
#define _CONIO_INTERNAL_H
|
||||||
|
|
||||||
|
#include <conio.h> /* two_bytes, KEEP_EXIST_ATTR */
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Текущий текстовый атрибут: 0x00..0xFF — реальный байт атрибута,
|
||||||
|
* KEEP_EXIST_ATTR (0xFFFF) — быстрый путь без атрибута. */
|
||||||
|
extern two_bytes g_text_attr;
|
||||||
|
|
||||||
|
/* Позиция курсора: .byte.low = col, .byte.high = row.
|
||||||
|
* Поддерживается gotoxy/wherex/wherey/wherexy и воркерами putch. */
|
||||||
|
extern two_bytes pc_place;
|
||||||
|
|
||||||
|
/* 0 — BS/TAB/LF/CR интерпретируются; 1 — печатаются как глифы. */
|
||||||
|
extern uint8_t pc_raw_mode;
|
||||||
|
|
||||||
|
/* Прочитать курсор BIOS (rst 8, $8E) в pc_place. */
|
||||||
|
void _get_cursor(void);
|
||||||
|
|
||||||
|
/* Установить курсор BIOS (rst 8, $84) из pc_place. */
|
||||||
|
void _set_cursor(void);
|
||||||
|
|
||||||
|
/* Быстрый вывод символа через ESTEX PUTCHAR ($5B) — атрибут не
|
||||||
|
* трогается, CR/LF/скролл делает ESTEX. */
|
||||||
|
char _bios_putchar(char ch);
|
||||||
|
|
||||||
|
/* Воркеры вывода в (pc_place) атрибутом attr; курсор BIOS НЕ трогают —
|
||||||
|
* вызывающий делает _get_cursor()/_set_cursor() один раз на операцию. */
|
||||||
|
void _putch_wrchar(char ch, uint8_t attr);
|
||||||
|
void _putch_wrchar_raw(char ch, uint8_t attr);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* g_text_attr — текущий текстовый атрибут conio (Turbo-C-стиль: stdio
|
||||||
|
* putchar/puts быстрые и без атрибута; putch/cputs/cprintf применяют
|
||||||
|
* этот атрибут). Модуль только с данными.
|
||||||
|
*
|
||||||
|
* 0x00..0xFF — реальный атрибут (4 бита FG | 3 бита BG | 1 бит blink)
|
||||||
|
* KEEP_EXIST_ATTR (0xFFFF) — putch/cputs уходят на быстрый путь
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
two_bytes g_text_attr;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* _get_cursor — прочитать текущий курсор BIOS GetCursor (rst 8, $8E)
|
||||||
|
* в pc_place (D=row, E=col → сохраняются парой).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void _get_cursor(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld c, #0x8e ; BIOS GetCursor
|
||||||
|
rst #0x08
|
||||||
|
ld (_pc_place), de
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* pc_place — кэш позиции курсора conio: .byte.low = col (0..79),
|
||||||
|
* .byte.high = row (0..31). Обновляется gotoxy/wherex/wherey/wherexy
|
||||||
|
* и воркерами putch; глобальный (не `_`-имя) — читается и тестами
|
||||||
|
* (tests/conio2). Модуль только с данными.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
two_bytes pc_place;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* pc_raw_mode — режим обработки управляющих символов (< 0x20) в
|
||||||
|
* воркерах putch/cputs:
|
||||||
|
* 0 (по умолчанию) — BS/TAB/LF/CR интерпретируются (без глифа);
|
||||||
|
* 1 — все символы печатаются как глифы CP437.
|
||||||
|
* Действует только на WRCHAR-пути (attr ≤ 0xFF); при KEEP_EXIST_ATTR
|
||||||
|
* курсором и управляющими символами занимается сам ESTEX.
|
||||||
|
* Модуль только с данными.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint8_t pc_raw_mode;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* _putch_wrchar — воркер вывода mode-0: пишет символ в (pc_place)
|
||||||
|
* через ESTEX WRCHAR ($58) атрибутом attr, интерпретируя управляющие:
|
||||||
|
*
|
||||||
|
* 0x08 BS → pc_col-- (если не 0)
|
||||||
|
* 0x09 TAB → pc_col к следующему кратному 8 (потолок 80)
|
||||||
|
* 0x0A LF → pc_row++ (потолок 32; без глифа)
|
||||||
|
* 0x0D CR → pc_col = 0
|
||||||
|
* прочее → WRCHAR + pc_col++
|
||||||
|
*
|
||||||
|
* WRCHAR подавляется при pc_col ≥ 80 или pc_row ≥ 32 (вне экрана) —
|
||||||
|
* координаты только [0..79] × [0..31].
|
||||||
|
*
|
||||||
|
* Курсор BIOS НЕ трогает — вызывающий читает его один раз до серии
|
||||||
|
* вызовов и записывает один раз после: плата BIOS за ОПЕРАЦИЮ,
|
||||||
|
* а не за символ.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void _putch_wrchar(char ch, uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)ch; (void)attr;
|
||||||
|
__asm
|
||||||
|
;; __sdcccall(1): ch в A, attr в L.
|
||||||
|
;; Диспетчеризация по управляющим, пока A держит ch (cp не
|
||||||
|
;; трогает A). B/C грузятся только на пути вывода, поэтому
|
||||||
|
;; ветки управляющих символов дешевле.
|
||||||
|
cp #0x08
|
||||||
|
jr z, _rp0_bs
|
||||||
|
cp #0x09
|
||||||
|
jr z, _rp0_tab
|
||||||
|
cp #0x0A
|
||||||
|
jr z, _rp0_lf
|
||||||
|
cp #0x0D
|
||||||
|
jr z, _rp0_cr
|
||||||
|
;; Всё остальное (печатное или незнакомый ctrl) → глиф.
|
||||||
|
|
||||||
|
_rp0_pri:
|
||||||
|
|
||||||
|
ld c, a ; C = ch (сохранить до порчи A)
|
||||||
|
ld a, (_pc_place + 1)
|
||||||
|
cp #32
|
||||||
|
ret nc ; за нижним краем — молча пропустить
|
||||||
|
ld d, a ; D = row (конвенция ESTEX WRCHAR)
|
||||||
|
ld a, (_pc_place)
|
||||||
|
cp #80
|
||||||
|
ret nc ; за правым краем — молча пропустить
|
||||||
|
ld e, a ; E = col
|
||||||
|
inc a
|
||||||
|
ld (_pc_place), a ; pc_col++
|
||||||
|
|
||||||
|
_rp0_wr:
|
||||||
|
ld b, l ; B = attr
|
||||||
|
ld a, c ; A = ch
|
||||||
|
push ix
|
||||||
|
ld c, #0x58 ; ESTEX WRCHAR
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
ret
|
||||||
|
|
||||||
|
_rp0_bs:
|
||||||
|
ld a, (_pc_place)
|
||||||
|
or a, a
|
||||||
|
ret z ; уже в колонке 0 — без изменений
|
||||||
|
dec a
|
||||||
|
ld (_pc_place), a
|
||||||
|
ld e, a ; E = col
|
||||||
|
ld a, (_pc_place + 1)
|
||||||
|
ld d, a ; D = row (конвенция ESTEX WRCHAR)
|
||||||
|
ld c, #0x20
|
||||||
|
jr _rp0_wr
|
||||||
|
|
||||||
|
_rp0_tab:
|
||||||
|
ld a, (_pc_place)
|
||||||
|
or #0x07 ; округлить вниз к кратному 8
|
||||||
|
inc a ; → следующее кратное 8
|
||||||
|
cp #81 ; сравнить A с 81 (0x51)
|
||||||
|
jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку
|
||||||
|
ld a, #80 ; иначе A > 80 → установить A = 80
|
||||||
|
_rp0_tab_skip:
|
||||||
|
ld (_pc_place), a
|
||||||
|
ret
|
||||||
|
|
||||||
|
_rp0_lf:
|
||||||
|
ld a, (_pc_place + 1)
|
||||||
|
cp #32
|
||||||
|
ret nc ; уже у нижнего края
|
||||||
|
inc a
|
||||||
|
ld (_pc_place + 1), a
|
||||||
|
ret
|
||||||
|
|
||||||
|
_rp0_cr:
|
||||||
|
xor a, a
|
||||||
|
ld (_pc_place), a
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* _putch_wrchar_raw — воркер вывода mode-1: КАЖДЫЙ байт идёт через
|
||||||
|
* ESTEX WRCHAR ($58) как глиф CP437 (включая 0x08/0x09/0x0A/0x0D),
|
||||||
|
* атрибутом attr, в позицию (pc_place) с pc_col++.
|
||||||
|
* Вне экрана (col ≥ 80 / row ≥ 32) вывод молча подавляется.
|
||||||
|
* Курсор BIOS не трогает — см. _putch_wrchar.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void _putch_wrchar_raw(char ch, uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)ch; (void)attr;
|
||||||
|
__asm
|
||||||
|
;; __sdcccall(1): ch в A, attr в L.
|
||||||
|
ld c, a ; C = ch (сохранить)
|
||||||
|
|
||||||
|
ld a, (_pc_place + 1)
|
||||||
|
cp #32
|
||||||
|
ret nc ; за нижним краем — молча пропустить
|
||||||
|
ld d, a ; D = row
|
||||||
|
ld a, (_pc_place)
|
||||||
|
cp #80
|
||||||
|
ret nc ; за правым краем — молча пропустить
|
||||||
|
ld e, a ; E = col
|
||||||
|
inc a
|
||||||
|
ld (_pc_place), a ; pc_col++
|
||||||
|
|
||||||
|
ld b, l ; B = attr
|
||||||
|
ld a, c ; A = ch
|
||||||
|
push ix
|
||||||
|
ld c, #0x58 ; ESTEX WRCHAR
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* _set_cursor — установить курсор BIOS SetCursor (rst 8, $84) из
|
||||||
|
* pc_place (D=row, E=col).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void _set_cursor(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld de, (_pc_place)
|
||||||
|
ld c, #0x84 ; BIOS SetCursor
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* cgets — построчный ввод в стиле Solid-C / Turbo-C:
|
||||||
|
* buf[0] = максимум символов (вход)
|
||||||
|
* buf[1] = фактическое количество (выход)
|
||||||
|
* buf[2..] = символы + NUL
|
||||||
|
* Ввод через getche() (с эхом), Enter завершает строку (выводится
|
||||||
|
* "\r\n"), Backspace откатывает символ. Возвращает &buf[2].
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
char *cgets(char *buf)
|
||||||
|
{
|
||||||
|
uint8_t maxlen = (uint8_t)buf[0];
|
||||||
|
uint8_t n = 0;
|
||||||
|
while (n < maxlen) {
|
||||||
|
int ch = getche();
|
||||||
|
if (ch == '\n' || ch == '\r') {
|
||||||
|
putch('\r'); putch('\n');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ch == 8) { /* backspace */
|
||||||
|
if (n > 0) { n--; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buf[2 + n] = (char)ch;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
buf[1] = (char)n;
|
||||||
|
buf[2 + n] = 0;
|
||||||
|
return &buf[2];
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* clrscr — очистить экран атрибутом по умолчанию 0x0F (ярко-белый на
|
||||||
|
* чёрном). Tail-call в clrscr_attr (jp по глобальному имени — работает
|
||||||
|
* через границу модулей).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void clrscr(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld a, #0x0F
|
||||||
|
jp _clrscr_attr
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* clrscr_attr — очистить экран (80×32) пробелами с заданным атрибутом.
|
||||||
|
* ESTEX CLEAR ($56): A=символ-заполнитель, B=атрибут, DE=верхний левый
|
||||||
|
* угол, H=строк, L=колонок.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void clrscr_attr(uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)attr;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
;; SDCC __sdcccall(1): первый uint8_t аргумент в A.
|
||||||
|
ld b, a ; B = атрибут (цвет заливки)
|
||||||
|
ld de, #0x0000 ; верхний левый угол
|
||||||
|
ld hl, #0x2050 ; H=32 строки, L=80 колонок
|
||||||
|
ld a, #0x20 ; заполнение пробелом
|
||||||
|
ld c, #0x56 ; ESTEX CLEAR
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -1,678 +0,0 @@
|
|||||||
/*
|
|
||||||
* conio.c — console I/O wrappers around ESTEX kbd/screen syscalls.
|
|
||||||
*
|
|
||||||
* $30 WAITKEY — blocking read, returns scan / ASCII / modifiers
|
|
||||||
* $31 SCANKEY — non-blocking poll
|
|
||||||
* $32 ECHOKEY — blocking read + auto-echo to the screen
|
|
||||||
* $52 LOCATE — set cursor to (D=row, E=col)
|
|
||||||
* $56 CLEAR — fill a window with (A=char, B=attr)
|
|
||||||
* $5B PUTCHAR — write single character (CR/LF/scroll handled by ESTEX)
|
|
||||||
*
|
|
||||||
* Every RST 10h is bracketed with push/pop IX (caller's frame pointer).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <conio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
|
|
||||||
/* Forward extern — definition is further down (after putch/cputs which
|
|
||||||
* reference it from asm by linker-symbol name). */
|
|
||||||
static two_bytes g_text_attr = {0};
|
|
||||||
static uint8_t pc_ch = 0;
|
|
||||||
extern two_bytes pc_place = {0};
|
|
||||||
static uint8_t pc_raw_mode = 0;
|
|
||||||
|
|
||||||
|
|
||||||
// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey)
|
|
||||||
// не должны менять IX и им можно не делать push ix / pop ix
|
|
||||||
//
|
|
||||||
char kbhit(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld c, #0x33 ; ESTEX CTRLKEY — peeks without consuming
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
;; A=0 → no key waiting; non-zero → there is one.
|
|
||||||
or a, a
|
|
||||||
ret z
|
|
||||||
ld a, #0x01
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
char getch(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld c, #0x30 ; ESTEX WAITKEY (no echo)
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
;; ESTEX returns ASCII in E (and copy in A)
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
char getche(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld c, #0x32 ; ESTEX ECHOKEY (echo to console)
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
;; ESTEX returns ASCII in E (and copy in A)
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* getkey — like getch() but exposes BOTH the ASCII value and the
|
|
||||||
* positional scan code, so callers can distinguish extended keys
|
|
||||||
* (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry
|
|
||||||
* ASCII == 0 from ESTEX) from plain ASCII keys.
|
|
||||||
*
|
|
||||||
* return = (scan << 8) | ascii
|
|
||||||
*
|
|
||||||
* For plain keys: ascii holds the character, scan holds the positional
|
|
||||||
* code (bit 7 set when Ctrl/Alt/Shift is held).
|
|
||||||
* For extended keys: ascii == 0, scan identifies the key (see KEY_* in
|
|
||||||
* <conio.h>).
|
|
||||||
*/
|
|
||||||
uint16_t getkey(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
ld e, a ; E = ASCII (defensive: A is the canonical copy)
|
|
||||||
ret ; __sdcccall(1) returns uint16_t in DE
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- putch / cputs: Turbo-C conio convention ---------------------- *
|
|
||||||
* Both APPLY the current text attribute (g_text_attr). When attr is
|
|
||||||
* KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path
|
|
||||||
* (putchar / puts-like raw PCHARS).
|
|
||||||
*
|
|
||||||
* No '\n' to CR LF translation here — Turbo-C cputs/putch require the
|
|
||||||
* caller to use "\r\n" explicitly. Stdio puts/putchar do translate.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* Controls how _raw_putch treats control characters (< 0x20):
|
|
||||||
* 0 (default) — BS/TAB/LF/CR are interpreted (no glyph output);
|
|
||||||
* other chars print as glyphs via WRCHAR.
|
|
||||||
* 1 — all characters print as glyphs, no interpretation.
|
|
||||||
*
|
|
||||||
* Only takes effect on the WRCHAR (attr ≤ 0xFF) path. When
|
|
||||||
* g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle
|
|
||||||
* cursor and control chars — pc_raw_mode is irrelevant. */
|
|
||||||
|
|
||||||
void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; }
|
|
||||||
uint8_t get_putch_raw_mode(void) { return pc_raw_mode; }
|
|
||||||
|
|
||||||
/* ---- Internal helpers ------------------------------------------- */
|
|
||||||
|
|
||||||
/* Read current cursor into pc_row / pc_col via ESTEX CURSOR ($53). */
|
|
||||||
static void _get_cursor(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
; push ix
|
|
||||||
ld c, #0x8e ; BIOS GetCursor
|
|
||||||
rst #0x08
|
|
||||||
ld (_pc_place), de
|
|
||||||
; pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Move cursor to (pc_col, pc_row) via ESTEX LOCATE ($52). */
|
|
||||||
static void _set_cursor(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
; push ix
|
|
||||||
ld de, (_pc_place)
|
|
||||||
ld c, #0x84 ; BIOS SetCursor
|
|
||||||
rst #0x08
|
|
||||||
; pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ESTEX PUTCHAR ($5B) — fast no-attr path; ESTEX handles CR/LF/scroll
|
|
||||||
* and cursor itself. Used when g_text_attr = KEEP_EXIST_ATTR. */
|
|
||||||
static char _bios_putchar(char ch) __naked
|
|
||||||
{
|
|
||||||
(void)ch;
|
|
||||||
__asm
|
|
||||||
;; c in A. push af stashes it across the RST (which clobbers A).
|
|
||||||
;; push ix ; PUTCHAR не меняет IX
|
|
||||||
push af
|
|
||||||
ld c, #0x5B ; ESTEX PUTCHAR
|
|
||||||
rst #0x10
|
|
||||||
pop af
|
|
||||||
;; pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Raw putch: low-level WRCHAR-based output at (pc_col, pc_row) using
|
|
||||||
* the given attribute byte (caller has already verified that the high
|
|
||||||
* byte of g_text_attr is zero — this function takes only the low byte).
|
|
||||||
* Updates pc_col / pc_row per pc_raw_mode:
|
|
||||||
*
|
|
||||||
* pc_raw_mode == 0: BS/TAB/LF/CR are INTERPRETED:
|
|
||||||
* 0x08 BS → pc_col-- (if not already 0)
|
|
||||||
* 0x09 TAB → pc_col rounded up to next multiple of 8 (capped 80)
|
|
||||||
* 0x0A LF → pc_row++ (capped at 32; no glyph)
|
|
||||||
* 0x0D CR → pc_col = 0
|
|
||||||
* other → WRCHAR + pc_col++
|
|
||||||
*
|
|
||||||
* pc_raw_mode == 1: ALL characters print as glyphs via WRCHAR
|
|
||||||
* + pc_col++ (including 0x08, 0x09, 0x0A, 0x0D — they render as
|
|
||||||
* their CP437 glyphs).
|
|
||||||
*
|
|
||||||
* WRCHAR itself is suppressed when pc_col ≥ 80 or pc_row ≥ 32 (off-
|
|
||||||
* screen) — coordinates [0..79] × [0..31] only.
|
|
||||||
*
|
|
||||||
* Does NOT call CURSOR / LOCATE — caller is expected to fetch cursor
|
|
||||||
* once before a sequence of _raw_putch calls and write it back once
|
|
||||||
* after, so we pay the BIOS overhead per OPERATION instead of per CHAR. */
|
|
||||||
/* Mode-0 worker: interprets BS/TAB/LF/CR, outputs other chars as glyphs. */
|
|
||||||
static void _putch_wrchar(char ch, uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)ch; (void)attr;
|
|
||||||
__asm
|
|
||||||
;; __sdcccall(1): ch in A, attr in L.
|
|
||||||
;; Dispatch on control chars while A still holds ch (cp does not
|
|
||||||
;; modify A). B/C only get loaded on the output path so the
|
|
||||||
;; ctrl-char paths are cheaper.
|
|
||||||
|
|
||||||
;; ld c, a
|
|
||||||
;; ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags)
|
|
||||||
;; jr nz, _rp0_pri
|
|
||||||
|
|
||||||
;; ld a, c
|
|
||||||
cp #0x08
|
|
||||||
jr z, _rp0_bs
|
|
||||||
cp #0x09
|
|
||||||
jr z, _rp0_tab
|
|
||||||
cp #0x0A
|
|
||||||
jr z, _rp0_lf
|
|
||||||
cp #0x0D
|
|
||||||
jr z, _rp0_cr
|
|
||||||
;; Anything else (printable or unrecognised ctrl) → glyph.
|
|
||||||
|
|
||||||
_rp0_pri:
|
|
||||||
|
|
||||||
ld c, a ; C = ch (save before A is clobbered)
|
|
||||||
ld a, (_pc_place + 1)
|
|
||||||
cp #32
|
|
||||||
ret nc ; off-screen bottom — silently skip
|
|
||||||
ld d, a ; D = row (ESTEX WRCHAR convention)
|
|
||||||
ld a, (_pc_place)
|
|
||||||
cp #80
|
|
||||||
ret nc ; off-screen right — silently skip
|
|
||||||
ld e, a ; E = col
|
|
||||||
inc a
|
|
||||||
ld (_pc_place), a ; pc_col++
|
|
||||||
|
|
||||||
_rp0_wr:
|
|
||||||
ld b, l ; B = attr
|
|
||||||
ld a, c ; A = ch
|
|
||||||
push ix
|
|
||||||
ld c, #0x58 ; ESTEX WRCHAR
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
ret
|
|
||||||
|
|
||||||
_rp0_bs:
|
|
||||||
ld a, (_pc_place)
|
|
||||||
or a, a
|
|
||||||
ret z ; already at col 0 — no change
|
|
||||||
dec a
|
|
||||||
ld (_pc_place), a
|
|
||||||
ld e, a ; E = col
|
|
||||||
ld a, (_pc_place + 1)
|
|
||||||
ld d, a ; D = row (ESTEX WRCHAR convention)
|
|
||||||
ld c, #0x20
|
|
||||||
jr _rp0_wr
|
|
||||||
|
|
||||||
_rp0_tab:
|
|
||||||
ld a, (_pc_place)
|
|
||||||
or #0x07 ; floor to mult of 8
|
|
||||||
inc a ; → next mult of 8
|
|
||||||
cp #81 ; сравнить A с 81 (0x51)
|
|
||||||
jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку
|
|
||||||
ld a, #80 ; иначе A > 80 → установить A = 80
|
|
||||||
_rp0_tab_skip:
|
|
||||||
ld (_pc_place), a
|
|
||||||
ret
|
|
||||||
|
|
||||||
_rp0_lf:
|
|
||||||
ld a, (_pc_place + 1)
|
|
||||||
cp #32
|
|
||||||
ret nc ; already at bottom edge
|
|
||||||
inc a
|
|
||||||
ld (_pc_place + 1), a
|
|
||||||
ret
|
|
||||||
|
|
||||||
_rp0_cr:
|
|
||||||
xor a, a
|
|
||||||
ld (_pc_place), a
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */
|
|
||||||
static void _putch_wrchar_raw(char ch, uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)ch; (void)attr;
|
|
||||||
__asm
|
|
||||||
;; __sdcccall(1): ch in A, attr in L.
|
|
||||||
ld c, a ; C = ch (save)
|
|
||||||
|
|
||||||
ld a, (_pc_place + 1)
|
|
||||||
cp #32
|
|
||||||
ret nc ; off-screen bottom — silently skip
|
|
||||||
ld d, a ; D = row
|
|
||||||
ld a, (_pc_place)
|
|
||||||
cp #80
|
|
||||||
ret nc ; off-screen right — silently skip
|
|
||||||
ld e, a ; E = col
|
|
||||||
inc a
|
|
||||||
ld (_pc_place), a ; pc_col++
|
|
||||||
|
|
||||||
ld b, l ; B = attr
|
|
||||||
ld a, c ; A = ch
|
|
||||||
push ix
|
|
||||||
ld c, #0x58 ; ESTEX WRCHAR
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ---- Public putch / cputs --------------------------------------- *
|
|
||||||
*
|
|
||||||
* KEEP_EXIST_ATTR (high byte != 0) → fast PUTCHAR/PCHARS through
|
|
||||||
* ESTEX, which manages its own cursor.
|
|
||||||
*
|
|
||||||
* Otherwise → fetch cursor ONCE via CURSOR ($53), run one or many
|
|
||||||
* _raw_putch calls, write cursor back ONCE via LOCATE ($52). This
|
|
||||||
* folds the per-char CURSOR/LOCATE pair from the old design into a
|
|
||||||
* single pair per operation. */
|
|
||||||
|
|
||||||
|
|
||||||
char putch(char ch) __naked
|
|
||||||
{
|
|
||||||
(void)ch;
|
|
||||||
__asm
|
|
||||||
;; A = ch on entry; char return → A.
|
|
||||||
ld (_pc_ch), a ; stash c (for both return and re-load)
|
|
||||||
|
|
||||||
ld a, (_g_text_attr) ; A = low byte = attr
|
|
||||||
ld l, a ; L = attr
|
|
||||||
|
|
||||||
ld a, (_pc_raw_mode)
|
|
||||||
or a, a ; Z = (mode == 0)
|
|
||||||
ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags)
|
|
||||||
jr nz, _putch_use_raw
|
|
||||||
call __putch_wrchar
|
|
||||||
jr _putch_after_raw
|
|
||||||
_putch_use_raw:
|
|
||||||
call __putch_wrchar_raw
|
|
||||||
_putch_after_raw:
|
|
||||||
call __set_cursor
|
|
||||||
ld a, (_pc_ch) ; return value
|
|
||||||
ret
|
|
||||||
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
char cputs(const char *s) __naked
|
|
||||||
{
|
|
||||||
(void)s;
|
|
||||||
__asm
|
|
||||||
;; HL = s on entry; char return → A.
|
|
||||||
|
|
||||||
;; NULL-check: cputs(NULL) → return 0 immediately.
|
|
||||||
ld a, h
|
|
||||||
or a, l
|
|
||||||
ret z
|
|
||||||
|
|
||||||
push ix
|
|
||||||
|
|
||||||
call __get_cursor
|
|
||||||
|
|
||||||
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
|
|
||||||
ld a, (_g_text_attr + 1)
|
|
||||||
or a, a
|
|
||||||
jr nz, _cputs_fast
|
|
||||||
|
|
||||||
ld a, (_pc_raw_mode)
|
|
||||||
or a, a
|
|
||||||
jr nz, _cputs_bios
|
|
||||||
|
|
||||||
push hl
|
|
||||||
pop de
|
|
||||||
|
|
||||||
_cputs_wrloop:
|
|
||||||
ld a, (_g_text_attr)
|
|
||||||
ld l, a
|
|
||||||
ld a, (de) ; загрузить байт
|
|
||||||
or a ; установить флаг Z, если A == 0
|
|
||||||
jr z, _cputs_ex ; завершить подпрограмму (конец строки)
|
|
||||||
push de
|
|
||||||
call __putch_wrchar ; вывести символ (A передаётся как аргумент)
|
|
||||||
pop de
|
|
||||||
inc de ; перейти к следующему байту
|
|
||||||
jr _cputs_wrloop ; повторить
|
|
||||||
|
|
||||||
_cputs_bios:
|
|
||||||
ld a, (_g_text_attr)
|
|
||||||
ld b, #0xFF
|
|
||||||
ld d, #0x0
|
|
||||||
ld e, a
|
|
||||||
ld c, #0x8B ; BIOS LP_PRINT_LN5
|
|
||||||
rst #0x08
|
|
||||||
jr _cputs_ex
|
|
||||||
_cputs_fast:
|
|
||||||
ld c, #0x5C ; ESTEX PCHARS
|
|
||||||
rst #0x10
|
|
||||||
_cputs_ex:
|
|
||||||
call __set_cursor
|
|
||||||
pop ix ; restore callers IX
|
|
||||||
xor a, a ; return 0
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clrscr(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
ld a, #0x0F
|
|
||||||
jp _clrscr_attr
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clrscr_attr(uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)attr;
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
;; SDCC __sdcccall(1): uint8_t 1st arg is in A.
|
|
||||||
ld b, a ; B = attribute (mode-fill colour)
|
|
||||||
ld de, #0x0000 ; top-left
|
|
||||||
ld hl, #0x2050 ; H=32 rows, L=80 cols
|
|
||||||
ld a, #0x20 ; space fill
|
|
||||||
ld c, #0x56 ; ESTEX CLEAR
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void gotoxy(uint8_t x, uint8_t y) __naked
|
|
||||||
{
|
|
||||||
(void)x; (void)y;
|
|
||||||
__asm
|
|
||||||
;; __sdcccall(1) 2 uint8 args: x in A, y in L.
|
|
||||||
;; ESTEX LOCATE ($52) wants: D = row, E = col.
|
|
||||||
;; push ix
|
|
||||||
ld d, l ; D = row (y)
|
|
||||||
ld e, a ; E = col (x)
|
|
||||||
ld (_pc_place), de
|
|
||||||
ld c, #0x84 ; BIOS SetCursor
|
|
||||||
rst #0x08
|
|
||||||
;; ld c, #0x52
|
|
||||||
;; rst #0x10
|
|
||||||
;; pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t wherex(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
|
|
||||||
;; push ix
|
|
||||||
ld c, #0x8e ; BIOS GetCursor
|
|
||||||
rst #0x08
|
|
||||||
;; ld c, #0x53
|
|
||||||
;; rst #0x10
|
|
||||||
;; pop ix
|
|
||||||
ld a, e
|
|
||||||
ld (_pc_place), de
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t wherey(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
;; push ix
|
|
||||||
ld c, #0x8e ; BIOS GetCursor
|
|
||||||
rst #0x08
|
|
||||||
;; ld c, #0x53
|
|
||||||
;; rst #0x10
|
|
||||||
;; pop ix
|
|
||||||
ld a, d
|
|
||||||
ld (_pc_place), de
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t wherexy(void) __naked
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
|
|
||||||
;; push ix
|
|
||||||
ld c, #0x8e ; BIOS GetCursor
|
|
||||||
rst #0x08
|
|
||||||
;; ld c, #0x53
|
|
||||||
;; rst #0x10
|
|
||||||
;; pop ix
|
|
||||||
ld (_pc_place), de
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
|
||||||
*
|
|
||||||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
|
||||||
* packed and pushed on the stack as a single 16-bit value (caller does
|
|
||||||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
|
||||||
* [SP+0..1] = return address
|
|
||||||
* [SP+2] = ch (low half of pushed pair)
|
|
||||||
* [SP+3] = attr (high half)
|
|
||||||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
|
||||||
*/
|
|
||||||
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
|
|
||||||
{
|
|
||||||
(void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
|
|
||||||
__asm
|
|
||||||
pop iy ; return address
|
|
||||||
ld d, l ; D = row (y)
|
|
||||||
ld e, a ; E = col (x)
|
|
||||||
pop hl ; H = heigth(h), L = width(w)
|
|
||||||
pop bc ; C = direction, B = clear
|
|
||||||
ld a, b ; A = clear(B)
|
|
||||||
ld b, c ; B = direction(C)
|
|
||||||
push ix
|
|
||||||
ld c, #0x55 ; ESTEX SCROLL
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
jp (iy)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
|
||||||
*
|
|
||||||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
|
||||||
* packed and pushed on the stack as a single 16-bit value (caller does
|
|
||||||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
|
||||||
* [SP+0..1] = return address
|
|
||||||
* [SP+2] = ch (low half of pushed pair)
|
|
||||||
* [SP+3] = attr (high half)
|
|
||||||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
|
||||||
*/
|
|
||||||
void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked
|
|
||||||
{
|
|
||||||
(void)x; (void)y; (void)ch; (void)attr;
|
|
||||||
__asm
|
|
||||||
pop iy ; return address
|
|
||||||
pop bc ; C = ch, B = attr
|
|
||||||
push ix
|
|
||||||
ld d, l ; D = row (y)
|
|
||||||
ld e, a ; E = col (x)
|
|
||||||
ld a, c ; A = ch
|
|
||||||
ld c, #0x58 ; ESTEX WRCHAR
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
jp (iy)
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rdchar(int x, int y) → (attr << 8) | ch */
|
|
||||||
uint16_t rdchar(uint8_t x, uint8_t y) __naked
|
|
||||||
{
|
|
||||||
(void)x; (void)y;
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld d, l ; D = row
|
|
||||||
ld e, a ; E = col
|
|
||||||
ld c, #0x57 ; ESTEX RDCHAR
|
|
||||||
rst #0x10
|
|
||||||
;; A = ch, B = attr
|
|
||||||
ld d, b ; high byte → attr
|
|
||||||
ld e, a ; low byte → ch
|
|
||||||
pop ix
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public text-mode video API — defined here so it's pulled in with the
|
|
||||||
* rest of conio. The raw setters/getters live in videomode_raw.c so
|
|
||||||
* pure graphics programs can pick them up without conio's other
|
|
||||||
* dependencies. */
|
|
||||||
extern uint8_t _videomode_raw_get(void);
|
|
||||||
extern int _videomode_raw_set(uint8_t mode);
|
|
||||||
|
|
||||||
uint8_t gettextmode(void)
|
|
||||||
{
|
|
||||||
return _videomode_raw_get();
|
|
||||||
}
|
|
||||||
|
|
||||||
int settextmode(uint8_t mode)
|
|
||||||
{
|
|
||||||
/* Refuse anything that isn't a known text mode — otherwise a stray
|
|
||||||
* GFX_MODE_* value could swap the screen out from under text I/O. */
|
|
||||||
if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) {
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _videomode_raw_set(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- text attribute state ----------------------------------------
|
|
||||||
* g_text_attr is owned by conio.c now (Turbo-C-style: stdio putchar/puts
|
|
||||||
* are fast and attribute-free; only conio's putch/cputs/cprintf apply
|
|
||||||
* the attribute). Default = 0x0F (bright white on black).
|
|
||||||
*
|
|
||||||
* 0x00..0xFF — real attribute (4-bit FG | 3-bit BG | 1-bit blink)
|
|
||||||
* KEEP_EXIST_ATTR (0xFFFF) — putch/cputs fall back to fast no-attr path */
|
|
||||||
|
|
||||||
int16_t set_text_attr(int16_t attr)
|
|
||||||
{
|
|
||||||
int16_t prev = g_text_attr.value;
|
|
||||||
g_text_attr.value = attr;
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t get_text_attr(void)
|
|
||||||
{
|
|
||||||
return g_text_attr.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Turbo-C-style palette helpers --------------------------------
|
|
||||||
* textcolor / textbackground touch only their nibble; the other nibble
|
|
||||||
* (and the blink bit) are preserved. textattr replaces the whole byte. */
|
|
||||||
|
|
||||||
void textcolor(uint8_t fg)
|
|
||||||
{
|
|
||||||
fg = (fg & 0x07);
|
|
||||||
g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
|
|
||||||
// g_text_attr.byte.high = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void textbackground(uint8_t bg)
|
|
||||||
{
|
|
||||||
bg = (bg & 0x07) << 4;
|
|
||||||
g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
|
|
||||||
// g_text_attr.byte.high = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void textattr(uint8_t attr)
|
|
||||||
{
|
|
||||||
g_text_attr.value = (uint16_t)attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Solid-C compatibility ---------------------------------------- */
|
|
||||||
|
|
||||||
/* Direct port I/O. Z80 has 256 IN/OUT ports; we wrap the Z80 IN/OUT
|
|
||||||
* opcodes with a stable C API. Names match Solid-C / MS-DOS Turbo-C. */
|
|
||||||
uint8_t z80_inp(uint8_t port) __naked
|
|
||||||
{
|
|
||||||
(void)port;
|
|
||||||
__asm
|
|
||||||
;; SDCC __sdcccall(1): single uint8_t arg in A; uint8_t return in A.
|
|
||||||
ld c, a
|
|
||||||
in a, (c)
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void z80_outp(uint8_t port, uint8_t value) __naked
|
|
||||||
{
|
|
||||||
(void)port; (void)value;
|
|
||||||
__asm
|
|
||||||
;; __sdcccall(1): 2 uint8 args → arg1 in A, arg2 in L.
|
|
||||||
ld c, a ; C = port
|
|
||||||
out (c), l ; out (port), value
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cgets — Solid-C / Turbo-C style line input.
|
|
||||||
* buf[0] = max characters (in)
|
|
||||||
* buf[1] = actual count (out)
|
|
||||||
* buf[2..] = chars + NUL
|
|
||||||
* Returns &buf[2]. */
|
|
||||||
char *cgets(char *buf)
|
|
||||||
{
|
|
||||||
uint8_t maxlen = (uint8_t)buf[0];
|
|
||||||
uint8_t n = 0;
|
|
||||||
while (n < maxlen) {
|
|
||||||
int ch = getche();
|
|
||||||
if (ch == '\n' || ch == '\r') {
|
|
||||||
putch('\r'); putch('\n');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ch == 8) { /* backspace */
|
|
||||||
if (n > 0) { n--; }
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
buf[2 + n] = (char)ch;
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
buf[1] = (char)n;
|
|
||||||
buf[2 + n] = 0;
|
|
||||||
return &buf[2];
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* cprintf — printf for the conio output set. Formats into a static
|
* cprintf — printf для conio-набора вывода. Форматирует в статический
|
||||||
* buffer with vsprintf (from SDCC's stdlib), then emits via cputs which
|
* буфер через vsprintf (из stdlib SDCC), затем выводит cputs'ом,
|
||||||
* applies the current text attribute per character.
|
* который применяет текущий текстовый атрибут посимвольно.
|
||||||
*
|
*
|
||||||
* No '\n' to CR LF translation — Turbo-C convention: callers write
|
* '\n' в CR LF НЕ транслируется — конвенция Turbo-C: перевод строки
|
||||||
* "\r\n" explicitly in the format string for line breaks.
|
* пишется в format-строке явно как "\r\n".
|
||||||
*
|
*
|
||||||
* Not reentrant (single static buffer) but Z80 single-threaded is fine.
|
* Не реентерабельно (один статический буфер), но на однопоточном Z80
|
||||||
|
* это не проблема.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* cputs — вывод ASCIIZ-строки с текущим атрибутом (Turbo-C conio).
|
||||||
|
*
|
||||||
|
* Курсор BIOS читается один раз до строки и записывается один раз
|
||||||
|
* после. Три пути:
|
||||||
|
* g_text_attr == KEEP_EXIST_ATTR → быстрый ESTEX PCHARS ($5C);
|
||||||
|
* pc_raw_mode == 1 → BIOS LP_PRINT_LN5 (глифы с атрибутом);
|
||||||
|
* иначе → цикл _putch_wrchar (интерпретация
|
||||||
|
* BS/TAB/LF/CR).
|
||||||
|
* Трансляции '\n' → CR LF нет — вызывающий пишет "\r\n" сам.
|
||||||
|
* cputs(NULL) — no-op. Возвращает 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
char cputs(const char *s) __naked
|
||||||
|
{
|
||||||
|
(void)s;
|
||||||
|
__asm
|
||||||
|
;; HL = s на входе; возврат char → A.
|
||||||
|
|
||||||
|
;; NULL-check: cputs(NULL) → сразу вернуть 0.
|
||||||
|
ld a, h
|
||||||
|
or a, l
|
||||||
|
ret z
|
||||||
|
|
||||||
|
push ix
|
||||||
|
|
||||||
|
call __get_cursor
|
||||||
|
|
||||||
|
;; KEEP_EXIST_ATTR? старший байт g_text_attr != 0
|
||||||
|
ld a, (_g_text_attr + 1)
|
||||||
|
or a, a
|
||||||
|
jr nz, _cputs_fast
|
||||||
|
|
||||||
|
ld a, (_pc_raw_mode)
|
||||||
|
or a, a
|
||||||
|
jr nz, _cputs_bios
|
||||||
|
|
||||||
|
push hl
|
||||||
|
pop de
|
||||||
|
|
||||||
|
_cputs_wrloop:
|
||||||
|
ld a, (_g_text_attr)
|
||||||
|
ld l, a
|
||||||
|
ld a, (de) ; загрузить байт
|
||||||
|
or a ; установить флаг Z, если A == 0
|
||||||
|
jr z, _cputs_ex ; завершить подпрограмму (конец строки)
|
||||||
|
push de
|
||||||
|
call __putch_wrchar ; вывести символ (A передаётся как аргумент)
|
||||||
|
pop de
|
||||||
|
inc de ; перейти к следующему байту
|
||||||
|
jr _cputs_wrloop ; повторить
|
||||||
|
|
||||||
|
_cputs_bios:
|
||||||
|
ld a, (_g_text_attr)
|
||||||
|
ld b, #0xFF
|
||||||
|
ld d, #0x0
|
||||||
|
ld e, a
|
||||||
|
ld c, #0x8B ; BIOS LP_PRINT_LN5
|
||||||
|
rst #0x08
|
||||||
|
jr _cputs_ex
|
||||||
|
_cputs_fast:
|
||||||
|
ld c, #0x5C ; ESTEX PCHARS
|
||||||
|
rst #0x10
|
||||||
|
_cputs_ex:
|
||||||
|
call __set_cursor
|
||||||
|
pop ix ; восстановить IX вызывающего
|
||||||
|
xor a, a ; вернуть 0
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* get_putch_raw_mode — текущий режим обработки управляющих символов
|
||||||
|
* putch/cputs (см. set_putch_raw_mode).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint8_t get_putch_raw_mode(void)
|
||||||
|
{
|
||||||
|
return pc_raw_mode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* get_text_attr — текущий 16-битный атрибут conio (см. set_text_attr).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
int16_t get_text_attr(void)
|
||||||
|
{
|
||||||
|
return g_text_attr.value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* getch — блокирующее чтение клавиши БЕЗ эха на экран.
|
||||||
|
* ESTEX WAITKEY ($30); ASCII возвращается в A.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
char getch(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld c, #0x30 ; ESTEX WAITKEY (без эха)
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
;; ESTEX возвращает ASCII в E (и копию в A)
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* getche — блокирующее чтение клавиши С эхом на экран.
|
||||||
|
* ESTEX ECHOKEY ($32); ASCII возвращается в A.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
char getche(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld c, #0x32 ; ESTEX ECHOKEY (эхо на консоль)
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
;; ESTEX возвращает ASCII в E (и копию в A)
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* getkey — как getch(), но возвращает И ASCII, И позиционный скан-код,
|
||||||
|
* чтобы различать расширенные клавиши (стрелки, F1..F12, PgUp/PgDn,
|
||||||
|
* Home/End, Ins/Del — у них ASCII == 0 из ESTEX) и обычные символы.
|
||||||
|
*
|
||||||
|
* возврат = (scan << 8) | ascii
|
||||||
|
*
|
||||||
|
* Обычные клавиши: ascii — символ, scan — позиционный код (бит 7
|
||||||
|
* взведён при Ctrl/Alt/Shift).
|
||||||
|
* Расширенные: ascii == 0, клавишу определяет scan (см. KEY_* в
|
||||||
|
* <conio.h>).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint16_t getkey(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
ld e, a ; E = ASCII (защитно: канонична копия в A)
|
||||||
|
ret ; __sdcccall(1) возвращает uint16_t в DE
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* gettextmode — текущий видеорежим (обёртка _videomode_raw_get из
|
||||||
|
* video/videomode_raw.c: сырые геттер/сеттер живут отдельно, чтобы
|
||||||
|
* чисто графические программы не тянули conio).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern uint8_t _videomode_raw_get(void);
|
||||||
|
|
||||||
|
uint8_t gettextmode(void)
|
||||||
|
{
|
||||||
|
return _videomode_raw_get();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* gotoxy — установить курсор в (x=col, y=row), 0-based.
|
||||||
|
* Обновляет pc_place и ставит курсор BIOS SetCursor (rst 8, $84).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void gotoxy(uint8_t x, uint8_t y) __naked
|
||||||
|
{
|
||||||
|
(void)x; (void)y;
|
||||||
|
__asm
|
||||||
|
;; __sdcccall(1), 2 аргумента uint8: x в A, y в L.
|
||||||
|
ld d, l ; D = row (y)
|
||||||
|
ld e, a ; E = col (x)
|
||||||
|
ld (_pc_place), de
|
||||||
|
ld c, #0x84 ; BIOS SetCursor
|
||||||
|
rst #0x08
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* kbhit — 1, если в буфере клавиатуры есть символ, иначе 0.
|
||||||
|
* ESTEX CTRLKEY ($33) подглядывает, не забирая символ из буфера.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey)
|
||||||
|
// не должны менять IX и им можно не делать push ix / pop ix
|
||||||
|
char kbhit(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld c, #0x33 ; ESTEX CTRLKEY — подглядывает без изъятия
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
;; A=0 → клавиши нет; ненулевой → есть.
|
||||||
|
or a, a
|
||||||
|
ret z
|
||||||
|
ld a, #0x01
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* putch — вывод символа с текущим атрибутом (Turbo-C conio).
|
||||||
|
*
|
||||||
|
* Применяет g_text_attr; при pc_raw_mode == 1 управляющие символы
|
||||||
|
* печатаются как глифы (воркер _putch_wrchar_raw), иначе
|
||||||
|
* интерпретируются (_putch_wrchar). После вывода записывает курсор
|
||||||
|
* BIOS один раз (_set_cursor). Трансляции '\n' → CR LF нет —
|
||||||
|
* Turbo-C-конвенция, вызывающий пишет "\r\n" сам.
|
||||||
|
* Возвращает выведенный символ.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
/* Стэш символа на время вызовов воркеров (только для putch). */
|
||||||
|
static uint8_t pc_ch;
|
||||||
|
|
||||||
|
char putch(char ch) __naked
|
||||||
|
{
|
||||||
|
(void)ch;
|
||||||
|
__asm
|
||||||
|
;; A = ch на входе; возврат char → A.
|
||||||
|
ld (_pc_ch), a ; спрятать ch (для возврата и перезагрузки)
|
||||||
|
|
||||||
|
ld a, (_g_text_attr) ; A = младший байт = attr
|
||||||
|
ld l, a ; L = attr
|
||||||
|
|
||||||
|
ld a, (_pc_raw_mode)
|
||||||
|
or a, a ; Z = (mode == 0)
|
||||||
|
ld a, (_pc_ch) ; A = ch (`ld a,(nn)` не трогает флаги)
|
||||||
|
jr nz, _putch_use_raw
|
||||||
|
call __putch_wrchar
|
||||||
|
jr _putch_after_raw
|
||||||
|
_putch_use_raw:
|
||||||
|
call __putch_wrchar_raw
|
||||||
|
_putch_after_raw:
|
||||||
|
call __set_cursor
|
||||||
|
ld a, (_pc_ch) ; возвращаемое значение
|
||||||
|
ret
|
||||||
|
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* rdchar — прочитать символ и атрибут из позиции (x, y):
|
||||||
|
* возврат (attr << 8) | ch. ESTEX RDCHAR ($57).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint16_t rdchar(uint8_t x, uint8_t y) __naked
|
||||||
|
{
|
||||||
|
(void)x; (void)y;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld d, l ; D = row
|
||||||
|
ld e, a ; E = col
|
||||||
|
ld c, #0x57 ; ESTEX RDCHAR
|
||||||
|
rst #0x10
|
||||||
|
;; A = ch, B = attr
|
||||||
|
ld d, b ; старший байт → attr
|
||||||
|
ld e, a ; младший байт → ch
|
||||||
|
pop ix
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* scroll — прокрутка окна (x, y, w, h) на 1 позицию в направлении
|
||||||
|
* direction, с очисткой освободившейся строки/колонки атрибутом clear.
|
||||||
|
* ESTEX SCROLL ($55).
|
||||||
|
*
|
||||||
|
* SDCC __sdcccall(1): x в A, y в L; w,h и direction,clear запушены
|
||||||
|
* парами на стек. Void-возврат → callee снимает стековые аргументы
|
||||||
|
* (pop до RST) и уходит через jp (iy).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
|
||||||
|
{
|
||||||
|
(void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
|
||||||
|
__asm
|
||||||
|
pop iy ; адрес возврата
|
||||||
|
ld d, l ; D = row (y)
|
||||||
|
ld e, a ; E = col (x)
|
||||||
|
pop hl ; H = высота (h), L = ширина (w)
|
||||||
|
pop bc ; C = direction, B = clear
|
||||||
|
ld a, b ; A = clear(B)
|
||||||
|
ld b, c ; B = direction(C)
|
||||||
|
push ix
|
||||||
|
ld c, #0x55 ; ESTEX SCROLL
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
jp (iy)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* set_putch_raw_mode — режим обработки управляющих символов в
|
||||||
|
* putch/cputs: 0 — BS/TAB/LF/CR интерпретируются, 1 — все байты
|
||||||
|
* печатаются как глифы CP437 (см. pc_raw_mode).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void set_putch_raw_mode(uint8_t mode)
|
||||||
|
{
|
||||||
|
pc_raw_mode = mode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* set_text_attr — установить полный 16-битный атрибут conio
|
||||||
|
* (0x00..0xFF — реальный атрибут; KEEP_EXIST_ATTR — быстрый путь без
|
||||||
|
* атрибута). Возвращает прежнее значение.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
int16_t set_text_attr(int16_t attr)
|
||||||
|
{
|
||||||
|
int16_t prev = g_text_attr.value;
|
||||||
|
g_text_attr.value = attr;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* settextmode — установить ТЕКСТОВЫЙ видеорежим (TEXT_MODE_40x32 /
|
||||||
|
* TEXT_MODE_80x32). Любое другое значение отвергается с EINVAL —
|
||||||
|
* иначе случайный GFX_MODE_* мог бы выдернуть экран из-под текстового
|
||||||
|
* I/O. Возвращает 0 или -1 + errno.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
extern int _videomode_raw_set(uint8_t mode);
|
||||||
|
|
||||||
|
int settextmode(uint8_t mode)
|
||||||
|
{
|
||||||
|
/* Отвергаем всё, что не известный текстовый режим. */
|
||||||
|
if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return _videomode_raw_set(mode);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* text_pal_get — прочитать блок записей текстовой палитры;
|
||||||
|
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <palette.h>
|
||||||
|
|
||||||
|
void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data)
|
||||||
|
{
|
||||||
|
pal_get((uint8_t)(plane + 4u), start, count, data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* text_pal_get_color — прочитать одну запись текстовой палитры в R/G/B;
|
||||||
|
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <palette.h>
|
||||||
|
|
||||||
|
void text_pal_get_color(uint8_t plane, uint8_t attr,
|
||||||
|
uint8_t *r, uint8_t *g, uint8_t *b)
|
||||||
|
{
|
||||||
|
pal_get_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* text_pal_load — загрузить блок записей текстовой палитры.
|
||||||
|
* Текстовый «план» 0..3 (paper/ink/blink-paper/blink-ink) транслируется
|
||||||
|
* в палитровую страницу BIOS 4..7 (см. <palette.h>).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <palette.h>
|
||||||
|
|
||||||
|
void text_pal_load(uint8_t plane, uint8_t start, uint8_t count,
|
||||||
|
const uint8_t *data)
|
||||||
|
{
|
||||||
|
pal_load((uint8_t)(plane + 4u), start, count, data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* text_pal_reset — восстановить системную текстовую палитру
|
||||||
|
* (BIOS $A6, type = PAL_CGA, страницы 4..7).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <palette.h>
|
||||||
|
|
||||||
|
void text_pal_reset(void)
|
||||||
|
{
|
||||||
|
pal_reset(PAL_CGA);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* text_pal_set_color — одна запись текстовой палитры из RGB;
|
||||||
|
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <conio.h>
|
||||||
|
#include <palette.h>
|
||||||
|
|
||||||
|
void text_pal_set_color(uint8_t plane, uint8_t attr,
|
||||||
|
uint8_t r, uint8_t g, uint8_t b)
|
||||||
|
{
|
||||||
|
pal_set_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* text_palette.c — text-mode palette wrappers.
|
|
||||||
*
|
|
||||||
* Thin layer over libc/video/palette.c. Translates the text "plane
|
|
||||||
* number" 0..3 (paper/ink/blink-paper/blink-ink) into the underlying
|
|
||||||
* BIOS palette page 4..7 used by $A4.
|
|
||||||
*
|
|
||||||
* For an introduction to the four-plane text colour model see
|
|
||||||
* <palette.h> (top-of-file doc-block).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <conio.h>
|
|
||||||
#include <palette.h>
|
|
||||||
|
|
||||||
void text_pal_load(uint8_t plane, uint8_t start, uint8_t count,
|
|
||||||
const uint8_t *data)
|
|
||||||
{
|
|
||||||
pal_load((uint8_t)(plane + 4u), start, count, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_pal_set_color(uint8_t plane, uint8_t attr,
|
|
||||||
uint8_t r, uint8_t g, uint8_t b)
|
|
||||||
{
|
|
||||||
pal_set_color((uint8_t)(plane + 4u), attr, r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data)
|
|
||||||
{
|
|
||||||
pal_get((uint8_t)(plane + 4u), start, count, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_pal_get_color(uint8_t plane, uint8_t attr,
|
|
||||||
uint8_t *r, uint8_t *g, uint8_t *b)
|
|
||||||
{
|
|
||||||
pal_get_color((uint8_t)(plane + 4u), attr, r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_pal_reset(void)
|
|
||||||
{
|
|
||||||
pal_reset(PAL_CGA);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* textattr — заменить атрибут целиком одним байтом (fg | bg | blink).
|
||||||
|
* Turbo-C-стиль. Сбрасывает KEEP_EXIST_ATTR (старший байт = 0).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void textattr(uint8_t attr)
|
||||||
|
{
|
||||||
|
g_text_attr.value = (uint16_t)attr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* textbackground — установить цвет фона (биты 4..6 атрибута),
|
||||||
|
* цвет символов и бит blink сохраняются. Turbo-C-стиль. Попутно
|
||||||
|
* сбрасывает KEEP_EXIST_ATTR (старший байт становится 0).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void textbackground(uint8_t bg)
|
||||||
|
{
|
||||||
|
bg = (bg & 0x07) << 4;
|
||||||
|
g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* textcolor — установить цвет символов (нижний нибл атрибута),
|
||||||
|
* фон и бит blink сохраняются. Turbo-C-стиль. Попутно сбрасывает
|
||||||
|
* KEEP_EXIST_ATTR (старший байт становится 0).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void textcolor(uint8_t fg)
|
||||||
|
{
|
||||||
|
fg = (fg & 0x07);
|
||||||
|
g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* wherex — текущая колонка курсора (0..79).
|
||||||
|
* Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint8_t wherex(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||||
|
rst #0x08
|
||||||
|
ld a, e
|
||||||
|
ld (_pc_place), de
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* wherexy — обе координаты курсора одним вызовом:
|
||||||
|
* (row << 8) | col в DE. Читает курсор BIOS GetCursor (rst 8, $8E),
|
||||||
|
* попутно обновляя pc_place.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint16_t wherexy(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||||
|
rst #0x08
|
||||||
|
ld (_pc_place), de
|
||||||
|
ret ; __sdcccall(1): uint16_t в DE
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* wherey — текущая строка курсора (0..31).
|
||||||
|
* Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint8_t wherey(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||||
|
rst #0x08
|
||||||
|
ld a, d
|
||||||
|
ld (_pc_place), de
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* wrchar — записать символ ch с атрибутом attr в позицию (x, y), не
|
||||||
|
* трогая курсор. ESTEX WRCHAR ($58).
|
||||||
|
*
|
||||||
|
* SDCC __sdcccall(1): x в A, y в L (2 uint8 → A, L); ch и attr
|
||||||
|
* упакованы и запушены на стек одной 16-битной парой (вызывающий
|
||||||
|
* делает `ld hl, #(attr<<8)|ch; push hl`). Раскладка после CALL:
|
||||||
|
* [SP+0..1] = адрес возврата
|
||||||
|
* [SP+2] = ch (младшая половина пары)
|
||||||
|
* [SP+3] = attr (старшая половина)
|
||||||
|
* Void-возврат → callee снимает 2 байта стековых аргументов через
|
||||||
|
* `pop bc` + jp (iy).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked
|
||||||
|
{
|
||||||
|
(void)x; (void)y; (void)ch; (void)attr;
|
||||||
|
__asm
|
||||||
|
pop iy ; адрес возврата
|
||||||
|
pop bc ; C = ch, B = attr
|
||||||
|
push ix
|
||||||
|
ld d, l ; D = row (y)
|
||||||
|
ld e, a ; E = col (x)
|
||||||
|
ld a, c ; A = ch
|
||||||
|
ld c, #0x58 ; ESTEX WRCHAR
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
jp (iy)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* z80_inp — прочитать байт из порта Z80 (опкод IN). Обёртка с
|
||||||
|
* стабильным C API; имя как в Solid-C / MS-DOS Turbo-C.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
uint8_t z80_inp(uint8_t port) __naked
|
||||||
|
{
|
||||||
|
(void)port;
|
||||||
|
__asm
|
||||||
|
;; SDCC __sdcccall(1): единственный uint8_t аргумент в A; возврат в A.
|
||||||
|
ld c, a
|
||||||
|
in a, (c)
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* z80_outp — записать байт value в порт Z80 (опкод OUT). Обёртка с
|
||||||
|
* стабильным C API; имя как в Solid-C / MS-DOS Turbo-C.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_conio.h"
|
||||||
|
|
||||||
|
void z80_outp(uint8_t port, uint8_t value) __naked
|
||||||
|
{
|
||||||
|
(void)port; (void)value;
|
||||||
|
__asm
|
||||||
|
;; __sdcccall(1): 2 аргумента uint8 → arg1 в A, arg2 в L.
|
||||||
|
ld c, a ; C = port
|
||||||
|
out (c), l ; out (port), value
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
Vendored
-93
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* getenv / putenv via ESTEX ENVIRON ($46).
|
|
||||||
*
|
|
||||||
* ESTEX $46 subfn 1 (getenv):
|
|
||||||
* in: HL = name (ASCIIZ), DE = output buffer (caller-owned)
|
|
||||||
* out: CF=0 + A != 0 → variable found, value written into [DE..end-1]
|
|
||||||
* CF=0 + A == 0 → variable not present (note: this is the
|
|
||||||
* opposite of what DiskSyscalls.txt v1.6 docs
|
|
||||||
* claim — verified empirically and against
|
|
||||||
* solid-c's IO.ASM:763-766 implementation)
|
|
||||||
* CF=1 → error, A = code
|
|
||||||
* DE on exit points at one past the last byte written
|
|
||||||
*
|
|
||||||
* ESTEX $46 subfn 2 (putenv):
|
|
||||||
* in: HL = "NAME=value" (NUL-terminated)
|
|
||||||
* out: CF=1 / A = error code on failure
|
|
||||||
*
|
|
||||||
* We hand back a pointer into a private 128-byte buffer for getenv().
|
|
||||||
* Caller must copy the bytes before the next getenv() call if they
|
|
||||||
* need to outlive it.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <sprinter.h>
|
|
||||||
|
|
||||||
static char env_buf[128];
|
|
||||||
|
|
||||||
char *getenv(const char *name) __naked
|
|
||||||
{
|
|
||||||
(void)name;
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
;; HL = name on entry; we need to also load DE = env_buf.
|
|
||||||
ld de, #_env_buf
|
|
||||||
ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
jr c, _getenv_err
|
|
||||||
or a, a
|
|
||||||
jr Z, _getenv_miss
|
|
||||||
ld de, #_env_buf
|
|
||||||
ret
|
|
||||||
_getenv_err:
|
|
||||||
call __errno_set
|
|
||||||
_getenv_miss:
|
|
||||||
ld de, #0
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
int putenv(const char *namevalue) __naked
|
|
||||||
{
|
|
||||||
(void)namevalue;
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv
|
|
||||||
rst #0x10
|
|
||||||
pop ix
|
|
||||||
jr c, _putenv_err
|
|
||||||
ld de, #0
|
|
||||||
ret
|
|
||||||
_putenv_err:
|
|
||||||
call __errno_set
|
|
||||||
ld de, #-1
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ESTEX $46 subfn 0 (sysenv):
|
|
||||||
* in: HL = caller-owned buffer
|
|
||||||
* out: caller's buffer is filled with one NUL-terminated "NAME=value"
|
|
||||||
* per env var, then a trailing extra NUL marks the end:
|
|
||||||
* "PATH=...\0SOLID=H\0\0"
|
|
||||||
* return: buf on success, -1 on error (errno set).
|
|
||||||
* Buffer must be large enough for the whole environment.
|
|
||||||
*/
|
|
||||||
char *sysenv(char *buf) __naked
|
|
||||||
{
|
|
||||||
(void)buf;
|
|
||||||
__asm
|
|
||||||
push ix
|
|
||||||
push hl ; stash buffer pointer (= return value)
|
|
||||||
ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv
|
|
||||||
rst #0x10
|
|
||||||
pop de ; DE = buffer pointer
|
|
||||||
pop ix
|
|
||||||
ret nc ; success: DE already has buf
|
|
||||||
;; CF=1 → A = ESTEX error code.
|
|
||||||
call __errno_set
|
|
||||||
ld de, #-1
|
|
||||||
ret
|
|
||||||
__endasm;
|
|
||||||
}
|
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* getenv — значение переменной окружения через ESTEX ENVIRON ($46, subfn 1):
|
||||||
|
* in: HL = имя (ASCIIZ), DE = выходной буфер
|
||||||
|
* out: CF=0 + A != 0 → найдена, значение записано в [DE..end-1]
|
||||||
|
* CF=0 + A == 0 → переменной нет (примечание: это ПРОТИВОПОЛОЖНО
|
||||||
|
* тому, что пишет DiskSyscalls.txt v1.6 — проверено
|
||||||
|
* эмпирически и по solid-c IO.ASM:763-766)
|
||||||
|
* CF=1 → ошибка, A = код
|
||||||
|
*
|
||||||
|
* Возвращает указатель в приватный 128-байтный буфер — скопируй до
|
||||||
|
* следующего вызова getenv(), если значение должно пережить его.
|
||||||
|
* NULL — нет переменной или ошибка (errno).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
static char env_buf[128];
|
||||||
|
|
||||||
|
char *getenv(const char *name) __naked
|
||||||
|
{
|
||||||
|
(void)name;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
;; HL = имя на входе; дополнительно DE = env_buf.
|
||||||
|
ld de, #_env_buf
|
||||||
|
ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
jr c, _getenv_err
|
||||||
|
or a, a
|
||||||
|
jr Z, _getenv_miss
|
||||||
|
ld de, #_env_buf
|
||||||
|
ret
|
||||||
|
_getenv_err:
|
||||||
|
call __errno_set
|
||||||
|
_getenv_miss:
|
||||||
|
ld de, #0
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* putenv — установить переменную окружения строкой "NAME=value" (ASCIIZ).
|
||||||
|
* ESTEX ENVIRON ($46, subfn 2): HL = строка; CF=1 / A = код при ошибке.
|
||||||
|
* Возвращает 0 или -1 + errno.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
int putenv(const char *namevalue) __naked
|
||||||
|
{
|
||||||
|
(void)namevalue;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv
|
||||||
|
rst #0x10
|
||||||
|
pop ix
|
||||||
|
jr c, _putenv_err
|
||||||
|
ld de, #0
|
||||||
|
ret
|
||||||
|
_putenv_err:
|
||||||
|
call __errno_set
|
||||||
|
ld de, #-1
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
Vendored
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* sysenv — снять всё окружение в буфер вызывающего.
|
||||||
|
* ESTEX ENVIRON ($46, subfn 0): HL = буфер; на выходе буфер заполнен
|
||||||
|
* ASCIIZ-строками "NAME=value" подряд, конец — дополнительный NUL:
|
||||||
|
* "PATH=...\0SOLID=H\0\0"
|
||||||
|
* Буфер должен вмещать всё окружение. Возвращает buf или -1 + errno.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
char *sysenv(char *buf) __naked
|
||||||
|
{
|
||||||
|
(void)buf;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
push hl ; спрятать указатель (= возврат)
|
||||||
|
ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv
|
||||||
|
rst #0x10
|
||||||
|
pop de ; DE = указатель буфера
|
||||||
|
pop ix
|
||||||
|
ret nc ; успех: DE уже держит buf
|
||||||
|
;; CF=1 → A = код ошибки ESTEX.
|
||||||
|
call __errno_set
|
||||||
|
ld de, #-1
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
+22
-22
@@ -1,40 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
* _errno_set — set `errno` from an ESTEX error code (0..255 in A).
|
* _errno_set — установить `errno` из кода ошибки ESTEX (0..255 в A).
|
||||||
*
|
*
|
||||||
* Replaces the inline pattern
|
* Заменяет inline-паттерн
|
||||||
* ld (_errno), a
|
* ld (_errno), a
|
||||||
* xor a, a
|
* xor a, a
|
||||||
* ld (_errno+1), a ; 7 bytes per error path
|
* ld (_errno+1), a ; 7 байт на каждый error path
|
||||||
* with a single
|
* одним
|
||||||
* call __errno_set ; 3 bytes per error path
|
* call __errno_set ; 3 байта на error path
|
||||||
*
|
*
|
||||||
* Saves ~4 bytes at every libc error handler that converts an ESTEX
|
* Экономит ~4 байта в каждом обработчике ошибок libc, конвертирующем
|
||||||
* code into the C-side `errno`. Helper itself is 7 bytes; with 10+
|
* код ESTEX в C-шный errno. Сам хелпер — 7 байт; при 10+ error path
|
||||||
* error paths in our libc the size win is net-positive.
|
* в нашей libc выигрыш по размеру суммарно положительный.
|
||||||
*
|
*
|
||||||
* ABI:
|
* ABI:
|
||||||
* in: A = ESTEX error code (0..255)
|
* вход: A = код ошибки ESTEX (0..255)
|
||||||
* out: HL = A (zero-extended); errno fully overwritten so a prior
|
* выход: errno перезаписан целиком, чтобы прежнее большое значение
|
||||||
* large value (e.g. errno = -1) can't leak its high byte.
|
* (например errno = -1) не оставило свой старший байт.
|
||||||
* clobbers: HL, AF flags. Caller must not depend on HL afterwards.
|
* клоббер: A, флаги. HL/BC/DE/IX/IY не трогаются.
|
||||||
*
|
*
|
||||||
* Defensive 16-bit store — see chat 2026-06-02: if anyone ever assigns
|
* Защитная 16-битная запись — см. чат 2026-06-02: если кто-то присвоит
|
||||||
* errno via C (`errno = -1`), the high byte becomes 0xFF, and a partial
|
* errno из C (`errno = -1`), старший байт станет 0xFF, и частичная
|
||||||
* 8-bit write here would leave that 0xFF in place. Always writing the
|
* 8-битная запись здесь оставила бы этот 0xFF на месте. Полное слово
|
||||||
* full word keeps errno honest regardless of who set it last.
|
* держит errno честным независимо от того, кто писал последним.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void _errno_set(unsigned char code) __naked
|
void _errno_set(unsigned char code) __naked
|
||||||
{
|
{
|
||||||
(void)code;
|
(void)code;
|
||||||
__asm
|
__asm
|
||||||
;; __sdcccall(1): single uint8_t arg already in A.
|
;; __sdcccall(1): единственный uint8_t-аргумент уже в A.
|
||||||
;; Write the two bytes separately so HL/BC/DE/IX/IY remain
|
;; Пишем два байта по отдельности, чтобы HL/BC/DE/IX/IY остались
|
||||||
;; untouched. Only A is clobbered: it is the input register,
|
;; нетронутыми. Клобберится только A: это входной регистр, и
|
||||||
;; and ABI does not require preserving it across a void call.
|
;; ABI не требует сохранять его через void-вызов.
|
||||||
ld (_errno), a ; low byte = code
|
ld (_errno), a ; младший байт = код
|
||||||
xor a, a
|
xor a, a
|
||||||
ld (_errno+1), a ; high byte = 0
|
ld (_errno+1), a ; старший байт = 0
|
||||||
ret
|
ret
|
||||||
__endasm;
|
__endasm;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* perror — напечатать "prefix: <strerror(errno)>\r\n" в stderr.
|
||||||
|
* Пустой/NULL prefix — печатается только текст ошибки.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void perror(const char *prefix)
|
||||||
|
{
|
||||||
|
if (prefix && *prefix) {
|
||||||
|
fputs(prefix, stderr);
|
||||||
|
fputs(": ", stderr);
|
||||||
|
}
|
||||||
|
fputs(strerror(errno), stderr);
|
||||||
|
fputs("\r\n", stderr);
|
||||||
|
}
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* errno.c — strerror / perror over the SDCC-provided `errno` global.
|
* strerror — текст ошибки по коду ESTEX (0..32); вне диапазона —
|
||||||
|
* "Unknown error". Таблица повторяет solid-c IO.ASM (английские
|
||||||
|
* формулировки сохранены для grep-ability). Таблица и функция
|
||||||
|
* неразделимы — живут одним модулем.
|
||||||
*
|
*
|
||||||
* The message table mirrors the one in solid-c's IO.ASM (we kept the
|
* Примечание: `_errno` здесь сознательно НЕ определяется — его даёт
|
||||||
* English wording for grep-ability). ESTEX returns codes 0..32 in the
|
* SDCC z80.lib/errno.rel (int в _DATA), наши обёртки просто пишут в
|
||||||
* meaningful range; anything beyond gets "Unknown error".
|
* `errno`. Это убирает варнинг "multiple definition of _errno".
|
||||||
*
|
|
||||||
* Note: we deliberately do NOT define `_errno` here — SDCC's
|
|
||||||
* z80.lib/errno.rel provides it (a single int in _DATA), and our libc
|
|
||||||
* wrappers (read.c, open.c, etc.) just assign to `errno`. This
|
|
||||||
* removes the "multiple definition of _errno" link warning.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stored verbatim — pointers in the lookup table cost 2 bytes each plus
|
* Хранится как есть — указатель в таблице стоит 2 байта плюс сами
|
||||||
* the message bytes themselves. Sentinel "" entries pad out gaps so
|
* байты сообщения. Заглушки "RESERVED" затыкают дыры, чтобы
|
||||||
* indexing stays direct.
|
* индексирование оставалось прямым.
|
||||||
*/
|
*/
|
||||||
static const char *const messages[] = {
|
static const char *const messages[] = {
|
||||||
/* 0 */ "No error",
|
/* 0 */ "No error",
|
||||||
@@ -65,13 +61,3 @@ const char *strerror(int err)
|
|||||||
}
|
}
|
||||||
return messages[err];
|
return messages[err];
|
||||||
}
|
}
|
||||||
|
|
||||||
void perror(const char *prefix)
|
|
||||||
{
|
|
||||||
if (prefix && *prefix) {
|
|
||||||
fputs(prefix, stderr);
|
|
||||||
fputs(": ", stderr);
|
|
||||||
}
|
|
||||||
fputs(strerror(errno), stderr);
|
|
||||||
fputs("\r\n", stderr);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* _fclosall — сбросить буферы и закрыть все открытые потоки таблицы.
|
||||||
|
* Регистрируется через atexit() первым fopen — так exit() посреди
|
||||||
|
* программы не теряет несброшенную запись (требование стандарта).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
void _fclosall(void)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < OPEN_MAX; i++) {
|
||||||
|
if (_file_slots[i].flags) fclose(&_file_slots[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* _file.h — внутренности буферизованного FILE* (вариант B+, НЕ
|
||||||
|
* публичный заголовок). Дизайн: docs/file-buffering-design.md.
|
||||||
|
*
|
||||||
|
* Инварианты:
|
||||||
|
* - слот таблицы свободен, когда flags == 0;
|
||||||
|
* - level < BUFSIZ на входе в любую операцию записи (конвенция
|
||||||
|
* «сброс ПЕРЕД записью в буфер»);
|
||||||
|
* - hold != EOF означает отложенный байт ungetc; логическая позиция
|
||||||
|
* потока на 1 меньше учтённой в level;
|
||||||
|
* - консольные псевдопотоки (_F_CONIN/_F_CONOUT) буфер не заводят
|
||||||
|
* и в _file_sync не попадают.
|
||||||
|
*/
|
||||||
|
#ifndef _FILE_INTERNAL_H
|
||||||
|
#define _FILE_INTERNAL_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Таблица потоков (_file_slots.c); свободный слот: flags == 0. */
|
||||||
|
extern FILE _file_slots[OPEN_MAX];
|
||||||
|
|
||||||
|
/* Найти свободный слот и взвести atexit(_fclosall) при первом
|
||||||
|
* использовании. Слот НЕ помечается занятым — вызывающий заполняет
|
||||||
|
* поля и последним ставит flags. NULL + errno=EMFILE, если все
|
||||||
|
* заняты. (_file_slot.c; используют fopen и fdopen.) */
|
||||||
|
FILE *_file_slot_take(void);
|
||||||
|
|
||||||
|
/* Транслировать строку режима fopen ("r/w/a", '+' в любом месте
|
||||||
|
* хвоста, 'b'/'t' игнорируются) в флаги open() (возврат) и наши
|
||||||
|
* _F_-флаги (*ff). -1 + errno=EINVAL при плохом режиме.
|
||||||
|
* (_file_mode.c; используют fopen, fdopen, freopen.) */
|
||||||
|
int _file_mode_flags(const char *mode, uint8_t *ff);
|
||||||
|
|
||||||
|
/* Обеспечить буфер: вернуть fp->buf, при необходимости malloc(BUFSIZ).
|
||||||
|
* NULL = malloc не дал — вызывающий уходит на небуферизованный путь
|
||||||
|
* (прямой syscall на 1 байт), поток продолжает работать. */
|
||||||
|
uint8_t *_file_buf(FILE *fp);
|
||||||
|
|
||||||
|
/* Нейтрализовать буфер (перед seek/tell/close и при смене направления):
|
||||||
|
* - направление «запись»: write() несброшенного хвоста;
|
||||||
|
* - направление «чтение»: lseek(fd, -(level + hold), SEEK_CUR) —
|
||||||
|
* откат непрочитанного readahead и ungetc-байта.
|
||||||
|
* После вызова: level = 0, hold = EOF, _F_DIROUT снят, curp = buf.
|
||||||
|
* Возврат 0 или EOF (ставит _F_ERROR). */
|
||||||
|
int _file_sync(FILE *fp);
|
||||||
|
|
||||||
|
/* fclose всех открытых слотов; вешается на atexit первым fopen. */
|
||||||
|
void _fclosall(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* _file_buf — ленивое выделение буфера потока: malloc(BUFSIZ) при
|
||||||
|
* первой буферизуемой операции. NULL при нехватке памяти — поток
|
||||||
|
* НЕ ломается, вызывающие уходят на прямой syscall (небуферизованный
|
||||||
|
* fallback), попытка выделения повторится при следующей операции.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
uint8_t *_file_buf(FILE *fp)
|
||||||
|
{
|
||||||
|
if (!fp->buf) {
|
||||||
|
fp->buf = (uint8_t *)malloc(BUFSIZ);
|
||||||
|
fp->curp = fp->buf;
|
||||||
|
}
|
||||||
|
return fp->buf;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* _file_mode_flags — парсер строки режима fopen/fdopen/freopen.
|
||||||
|
* Поддержаны r, w, a; '+' в любом месте хвоста; 'b'/'t' игнорируются —
|
||||||
|
* весь I/O на Sprinter бинарный.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int _file_mode_flags(const char *mode, uint8_t *ff)
|
||||||
|
{
|
||||||
|
if (!mode || !*mode) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int oflags = 0;
|
||||||
|
uint8_t f = 0;
|
||||||
|
char base = *mode;
|
||||||
|
int plus = 0;
|
||||||
|
for (const char *p = mode + 1; *p; p++) {
|
||||||
|
if (*p == '+') plus = 1;
|
||||||
|
}
|
||||||
|
switch (base) {
|
||||||
|
case 'r':
|
||||||
|
oflags = plus ? O_RDWR : O_RDONLY;
|
||||||
|
f = _F_READ | (plus ? _F_WRITE : 0);
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
|
||||||
|
f = _F_WRITE | (plus ? _F_READ : 0);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND;
|
||||||
|
f = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*ff = f;
|
||||||
|
return oflags;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* _file_slot_take — найти свободный слот таблицы потоков и при первом
|
||||||
|
* обращении взвести atexit(_fclosall), чтобы exit() сбрасывал
|
||||||
|
* несброшенную запись всех потоков (требование стандарта).
|
||||||
|
* Слот НЕ помечается занятым — вызывающий (fopen/fdopen) заполняет
|
||||||
|
* поля и последним выставляет flags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sprinter_exit.h> /* atexit */
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE *_file_slot_take(void)
|
||||||
|
{
|
||||||
|
static uint8_t atexit_armed;
|
||||||
|
|
||||||
|
FILE *fp = _file_slots;
|
||||||
|
for (uint8_t i = 0; ; i++, fp++) {
|
||||||
|
if (i >= OPEN_MAX) {
|
||||||
|
errno = EMFILE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fp->flags == 0) break;
|
||||||
|
}
|
||||||
|
if (!atexit_armed) {
|
||||||
|
atexit_armed = 1;
|
||||||
|
atexit(_fclosall);
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* _file_slots — статическая таблица потоков FILE (только данные).
|
||||||
|
*
|
||||||
|
* OPEN_MAX слотов; свободный слот распознаётся по flags == 0 (BSS
|
||||||
|
* зануляется crt0). malloc для структур не используется — лимит
|
||||||
|
* одновременных файлов у DSS всё равно фиксированный (ошибка 06h
|
||||||
|
* "Too many open files"), таблица его зеркалит.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE _file_slots[OPEN_MAX];
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* _file_sync — нейтрализовать буфер потока (см. _file.h и
|
||||||
|
* docs/file-buffering-design.md, «семантика инвалидации»).
|
||||||
|
*
|
||||||
|
* Направление «запись»: сбросить несброшенный хвост одним write();
|
||||||
|
* короткая запись = ошибка потока. Направление «чтение»: откатить
|
||||||
|
* позицию fd назад на непрочитанный readahead (+1 за hold-байт
|
||||||
|
* ungetc) — после этого реальная позиция fd совпадает с логической
|
||||||
|
* позицией потока, буфер объявляется пустым.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int _file_sync(FILE *fp)
|
||||||
|
{
|
||||||
|
if (fp->flags & _F_DIROUT) {
|
||||||
|
uint16_t n = fp->level;
|
||||||
|
fp->flags &= (uint8_t)~_F_DIROUT;
|
||||||
|
fp->level = 0;
|
||||||
|
fp->curp = fp->buf;
|
||||||
|
if (n) {
|
||||||
|
int w = write(fp->fd, fp->buf, n);
|
||||||
|
if (w < 0 || (uint16_t)w != n) {
|
||||||
|
fp->flags |= _F_ERROR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
long back = fp->level;
|
||||||
|
if (fp->hold != EOF) { back++; fp->hold = EOF; }
|
||||||
|
fp->level = 0;
|
||||||
|
fp->curp = fp->buf;
|
||||||
|
if (back) {
|
||||||
|
if (lseek(fp->fd, -back, SEEK_CUR) < 0) {
|
||||||
|
fp->flags |= _F_ERROR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* clearerr — сбросить флаги _F_EOF и _F_ERROR потока. NULL-безопасно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void clearerr(FILE *fp)
|
||||||
|
{
|
||||||
|
if (fp) fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* fclosall — закрыть все открытые потоки таблицы (имя Solid-C);
|
||||||
|
* публичная обёртка над _fclosall, который и так навешен на atexit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
void fclosall(void)
|
||||||
|
{
|
||||||
|
_fclosall();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* fclose — закрыть поток: сбросить буфер (_file_sync), закрыть fd,
|
||||||
|
* освободить буфер и слот таблицы. ВАЖНО: ошибки отложенной записи
|
||||||
|
* вылезают именно здесь — проверять результат fclose у пишущих
|
||||||
|
* потоков. Консольные псевдопотоки (stdin/stdout/stderr) распознаются
|
||||||
|
* по флагам и успешно игнорируются.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int fclose(FILE *fp)
|
||||||
|
{
|
||||||
|
if (!fp) {
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
if (fp->flags & (_F_CONIN | _F_CONOUT)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int rc = _file_sync(fp); /* сброс несброшенной записи */
|
||||||
|
if (close(fp->fd) < 0) rc = EOF;
|
||||||
|
if (fp->buf) free(fp->buf);
|
||||||
|
fp->flags = 0; /* слот свободен */
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* fdopen — завернуть уже открытый низкоуровневый fd в поток FILE*
|
||||||
|
* (Solid-C категория C). Режим должен соответствовать тому, с которым
|
||||||
|
* открывался fd — библиотека проверить это не может. После fdopen
|
||||||
|
* файл закрывается через fclose (не close!): fclose сбросит буфер и
|
||||||
|
* закроет сам fd. Счётчик _fd_guard не трогаем — fd уже посчитан
|
||||||
|
* своим open().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE *fdopen(int fd, const char *mode)
|
||||||
|
{
|
||||||
|
if (fd < 0) { errno = EBADF; return 0; }
|
||||||
|
|
||||||
|
uint8_t ff;
|
||||||
|
if (_file_mode_flags(mode, &ff) < 0) return 0;
|
||||||
|
|
||||||
|
FILE *fp = _file_slot_take();
|
||||||
|
if (!fp) return 0;
|
||||||
|
|
||||||
|
fp->fd = fd;
|
||||||
|
fp->buf = 0;
|
||||||
|
fp->curp = 0;
|
||||||
|
fp->level = 0;
|
||||||
|
fp->hold = EOF;
|
||||||
|
fp->flags = ff;
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* feof — 1, если на потоке защёлкнут флаг конца файла (_F_EOF),
|
||||||
|
* иначе 0. NULL-безопасно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int feof(FILE *fp)
|
||||||
|
{
|
||||||
|
return fp && (fp->flags & _F_EOF) ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* ferror — 1, если на потоке защёлкнут флаг ошибки (_F_ERROR),
|
||||||
|
* иначе 0. NULL-безопасно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int ferror(FILE *fp)
|
||||||
|
{
|
||||||
|
return fp && (fp->flags & _F_ERROR) ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* fflush — сбросить буфер потока: накопленную запись — write()'ом,
|
||||||
|
* readahead — откатом позиции fd (стандартная POSIX-семантика fflush
|
||||||
|
* для читаемых потоков). fflush(NULL) сбрасывает ВСЕ открытые потоки
|
||||||
|
* таблицы (по стандарту). Консольные псевдопотоки небуферизованы —
|
||||||
|
* всегда успех.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int fflush(FILE *fp)
|
||||||
|
{
|
||||||
|
if (!fp) {
|
||||||
|
int rc = 0;
|
||||||
|
for (uint8_t i = 0; i < OPEN_MAX; i++) {
|
||||||
|
if (_file_slots[i].flags && _file_sync(&_file_slots[i])) rc = EOF;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
if (fp->flags & (_F_CONIN | _F_CONOUT)) return 0;
|
||||||
|
return _file_sync(fp);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* fgetc — прочитать один байт из потока (буферизованно, вариант B+).
|
||||||
|
*
|
||||||
|
* Горячий путь («в readahead-буфере есть байт») написан на asm —
|
||||||
|
* SDCC на эту цепочку проверок генерит ~90 инструкций с IX-фреймом,
|
||||||
|
* asm укладывается в ~30 без фрейма (замер fbench: ~176 мкс/байт
|
||||||
|
* у C-версии). Всё остальное — медленный C-путь fgetc_slow:
|
||||||
|
* hold-байт от ungetc, консольный getchar(), сброс накопленной
|
||||||
|
* записи, дозаполнение буфера read(fd, buf, BUFSIZ), fallback без
|
||||||
|
* буфера. Возвращает байт, EOF на конце файла (_F_EOF) или при
|
||||||
|
* ошибке (_F_ERROR).
|
||||||
|
*
|
||||||
|
* Раскладка FILE (см. stdio.h, байтовые смещения):
|
||||||
|
* +0 fd, +2 flags, +3 buf, +5 curp, +7 level, +9 hold
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
/* Медленный путь; зовётся из asm-обёртки (метка _fgetc_slow видна в
|
||||||
|
* пределах этого модуля). */
|
||||||
|
static int fgetc_slow(FILE *fp)
|
||||||
|
{
|
||||||
|
if (!fp) { errno = EBADF; return EOF; }
|
||||||
|
if (fp->hold != EOF) {
|
||||||
|
int c = fp->hold;
|
||||||
|
fp->hold = EOF;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (fp->flags & _F_CONIN) {
|
||||||
|
return getchar();
|
||||||
|
}
|
||||||
|
if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; }
|
||||||
|
|
||||||
|
if (fp->flags & _F_DIROUT) {
|
||||||
|
if (_file_sync(fp)) return EOF; /* сброс накопленной записи */
|
||||||
|
}
|
||||||
|
if (fp->level == 0) {
|
||||||
|
uint8_t *b = _file_buf(fp);
|
||||||
|
if (!b) {
|
||||||
|
/* Небуферизованный fallback. */
|
||||||
|
uint8_t ch;
|
||||||
|
int r = read(fp->fd, &ch, 1);
|
||||||
|
if (r == 0) { fp->flags |= _F_EOF; return EOF; }
|
||||||
|
if (r < 0) { fp->flags |= _F_ERROR; return EOF; }
|
||||||
|
return (int)ch;
|
||||||
|
}
|
||||||
|
int r = read(fp->fd, b, BUFSIZ);
|
||||||
|
if (r == 0) { fp->flags |= _F_EOF; return EOF; }
|
||||||
|
if (r < 0) { fp->flags |= _F_ERROR; return EOF; }
|
||||||
|
fp->level = (uint16_t)r;
|
||||||
|
fp->curp = b;
|
||||||
|
}
|
||||||
|
fp->level--;
|
||||||
|
return (int)*fp->curp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fgetc(FILE *fp) __naked
|
||||||
|
{
|
||||||
|
(void)fp;
|
||||||
|
(void)fgetc_slow; /* используется из asm */
|
||||||
|
__asm
|
||||||
|
;; HL = fp.
|
||||||
|
ld a, h
|
||||||
|
or a, l
|
||||||
|
jr Z, _fg_slow ; NULL — C-путь выставит errno
|
||||||
|
push hl
|
||||||
|
pop iy ; IY = fp (IY — scratch в __sdcccall(1))
|
||||||
|
|
||||||
|
ld a, 7 (iy) ; level != 0 ?
|
||||||
|
or a, 8 (iy)
|
||||||
|
jr Z, _fg_slow ; буфер пуст/не наш режим
|
||||||
|
ld a, 2 (iy) ; flags
|
||||||
|
and a, #0x08 ; _F_DIROUT — буфер копит запись?
|
||||||
|
jr NZ, _fg_slow
|
||||||
|
ld a, 9 (iy) ; hold == EOF (0xFFFF)?
|
||||||
|
and a, 10 (iy)
|
||||||
|
inc a
|
||||||
|
jr NZ, _fg_slow ; есть отложенный ungetc-байт
|
||||||
|
|
||||||
|
;; --- горячий путь: level--, вернуть *curp++ ---
|
||||||
|
ld a, 7 (iy)
|
||||||
|
sub a, #1
|
||||||
|
ld 7 (iy), a
|
||||||
|
jr NC, _fg_nb
|
||||||
|
dec 8 (iy)
|
||||||
|
_fg_nb:
|
||||||
|
ld l, 5 (iy) ; HL = curp
|
||||||
|
ld h, 6 (iy)
|
||||||
|
ld e, (hl) ; E = байт
|
||||||
|
ld d, #0 ; zero-extend в int
|
||||||
|
inc hl
|
||||||
|
ld 5 (iy), l ; curp++
|
||||||
|
ld 6 (iy), h
|
||||||
|
ret
|
||||||
|
|
||||||
|
_fg_slow:
|
||||||
|
jp _fgetc_slow ; HL = fp уже на месте
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* fgetpos — запомнить текущую позицию потока в *pos (fpos_t = long,
|
||||||
|
* без побочных эффектов — через ftell). 0 при успехе, -1 + errno.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int fgetpos(FILE *fp, fpos_t *pos)
|
||||||
|
{
|
||||||
|
if (!pos) return -1;
|
||||||
|
long p = ftell(fp);
|
||||||
|
if (p < 0) return -1;
|
||||||
|
*pos = p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* fgets — прочитать строку (до n-1 символов, '\n' сохраняется) из
|
||||||
|
* потока в buf с завершающим NUL. Возвращает buf или NULL, если EOF
|
||||||
|
* наступил до первого сохранённого символа. n=1 по стандарту —
|
||||||
|
* валидный вызов: ничего не читает, возвращает пустую строку.
|
||||||
|
*
|
||||||
|
* Быстрый путь: пока строка лежит в readahead-буфере, она копируется
|
||||||
|
* блоком asm-сканером (LDI + сравнение с '\n', ~50 тактов/байт) —
|
||||||
|
* fgetc-на-символ был в ~10 раз медленнее (замер fbench). Медленные
|
||||||
|
* случаи (консоль, hold от ungetc, режим записи, дозаполнение буфера,
|
||||||
|
* EOF) уходят посимвольно через fgetc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
/* Копировать из src в dst до max байт, остановившись ПОСЛЕ '\n'.
|
||||||
|
* Возврат: число скопированных байт | 0x8000, если '\n' найден.
|
||||||
|
* max: 1..512 (бит 15 счётчика свободен). */
|
||||||
|
static uint16_t fgets_scan(uint8_t *dst, const uint8_t *src,
|
||||||
|
uint16_t max) __naked
|
||||||
|
{
|
||||||
|
(void)dst; (void)src; (void)max;
|
||||||
|
__asm
|
||||||
|
;; HL = dst, DE = src, max на стеке (callee чистит).
|
||||||
|
pop iy ; адрес возврата
|
||||||
|
pop bc ; BC = max (> 0)
|
||||||
|
ex de, hl ; HL = src, DE = dst
|
||||||
|
push bc ; max понадобится для подсчёта
|
||||||
|
_fgs_loop:
|
||||||
|
ld a, (hl)
|
||||||
|
cp a, #0x0A ; сравнить ДО ldi: LDI не трогает Z,
|
||||||
|
ldi ; но выставляет P/V по BC ((DE)<-(HL))
|
||||||
|
jr Z, _fgs_found ; Z — от cp
|
||||||
|
jp PE, _fgs_loop ; P/V — от LDI: BC != 0
|
||||||
|
;; max исчерпан, '\n' нет: count = max
|
||||||
|
pop de
|
||||||
|
jp (iy)
|
||||||
|
_fgs_found:
|
||||||
|
;; count = max - BC, бит 15 = флаг '\n' (count <= 512 — безопасно)
|
||||||
|
pop hl ; HL = max
|
||||||
|
ld a, l
|
||||||
|
sub a, c
|
||||||
|
ld e, a
|
||||||
|
ld a, h
|
||||||
|
sbc a, b
|
||||||
|
or a, #0x80
|
||||||
|
ld d, a
|
||||||
|
jp (iy)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *fgets(char *buf, int n, FILE *fp)
|
||||||
|
{
|
||||||
|
if (!buf || n < 1 || !fp) return NULL;
|
||||||
|
uint8_t *dst = (uint8_t *)buf;
|
||||||
|
int space = n - 1;
|
||||||
|
|
||||||
|
while (space > 0) {
|
||||||
|
if (fp->level && !(fp->flags & _F_DIROUT) && fp->hold == EOF) {
|
||||||
|
/* Быстрый блок: сканировать readahead до '\n'. */
|
||||||
|
uint16_t take = fp->level;
|
||||||
|
if (take > (uint16_t)space) take = (uint16_t)space;
|
||||||
|
uint16_t got = fgets_scan(dst, fp->curp, take);
|
||||||
|
uint16_t cnt = got & 0x7FFF;
|
||||||
|
fp->curp += cnt;
|
||||||
|
fp->level -= cnt;
|
||||||
|
dst += cnt;
|
||||||
|
space -= (int)cnt;
|
||||||
|
if (got & 0x8000) break; /* строка закончена */
|
||||||
|
continue; /* буфер иссяк — дозаполнить */
|
||||||
|
}
|
||||||
|
/* Медленные случаи + refill/EOF — посимвольно. */
|
||||||
|
int c = fgetc(fp);
|
||||||
|
if (c == EOF) {
|
||||||
|
if (dst == (uint8_t *)buf) return NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*dst++ = (uint8_t)c;
|
||||||
|
space--;
|
||||||
|
if (c == '\n') break;
|
||||||
|
}
|
||||||
|
*dst = '\0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* file.c — *** PROVISIONAL *** minimal unbuffered FILE * implementation
|
|
||||||
* on top of POSIX-style fd I/O
|
|
||||||
* (open/read/write/lseek/close).
|
|
||||||
*
|
|
||||||
* ============================================================
|
|
||||||
* TODO (v2): replace with a proper BUFFERED implementation.
|
|
||||||
* See docs/TODO.md / Solid-C reference layout:
|
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
*
|
|
||||||
* The current implementation maps each fputc/fgetc to one read/write
|
|
||||||
* syscall — fine for correctness checks, awful for throughput. Issues
|
|
||||||
* 3/4/5 from the stdio-review (fwrite short-write flag, fgets n=1,
|
|
||||||
* mode_to_flags break) are deferred until that rewrite.
|
|
||||||
* ============================================================
|
|
||||||
*
|
|
||||||
* stdin/stdout/stderr are STATIC sentinel FILEs flagged with
|
|
||||||
* _F_CONIN/_F_CONOUT; fputc/fgetc detect them and call putchar() /
|
|
||||||
* getchar() which already handle CR/LF translation and ESTEX calls.
|
|
||||||
*
|
|
||||||
* Their fd fields are 0 / -1 / -2. Negative values were chosen because
|
|
||||||
* ESTEX OPEN can return small positive fds (1, 2, …) for ordinary
|
|
||||||
* files — if we marked stdout/stderr with fd=1/2 a real file could
|
|
||||||
* collide with their identifier. fd=0 for stdin is kept (POSIX-style)
|
|
||||||
* because ESTEX does not return 0. Even so, none of these fd fields
|
|
||||||
* is ever passed to a syscall — the _F_CONIN/_F_CONOUT flags drive
|
|
||||||
* the dispatch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
/* ---- console pseudo-streams ----------------------------------------*/
|
|
||||||
static FILE _stdin = { 0, _F_READ | _F_CONIN };
|
|
||||||
static FILE _stdout = { -1, _F_WRITE | _F_CONOUT };
|
|
||||||
static FILE _stderr = { -2, _F_WRITE | _F_CONOUT };
|
|
||||||
|
|
||||||
FILE *const stdin = &_stdin;
|
|
||||||
FILE *const stdout = &_stdout;
|
|
||||||
FILE *const stderr = &_stderr;
|
|
||||||
|
|
||||||
/* ---- fopen / fclose -------------------------------------------------*/
|
|
||||||
|
|
||||||
/* Translate a fopen() mode string to the open() flags subset our
|
|
||||||
* libc/io/open.c understands. Supported: r, w, a, with optional "+"
|
|
||||||
* and trailing "b" (binary — we ignore as all I/O is binary).
|
|
||||||
*/
|
|
||||||
static int mode_to_flags(const char *mode, uint8_t *file_flags)
|
|
||||||
{
|
|
||||||
if (!mode || !*mode) {
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int oflags = 0;
|
|
||||||
uint8_t ff = 0;
|
|
||||||
char base = *mode;
|
|
||||||
int plus = 0;
|
|
||||||
for (const char *p = mode + 1; *p; p++) {
|
|
||||||
if (*p == '+') plus = 1;
|
|
||||||
/* 'b' and 't' are ignored — all I/O is binary on Sprinter. */
|
|
||||||
}
|
|
||||||
switch (base) {
|
|
||||||
case 'r':
|
|
||||||
oflags = plus ? O_RDWR : O_RDONLY;
|
|
||||||
ff = _F_READ | (plus ? _F_WRITE : 0);
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
|
|
||||||
ff = _F_WRITE | (plus ? _F_READ : 0);
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND;
|
|
||||||
ff = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*file_flags = ff;
|
|
||||||
return oflags;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *fopen(const char *path, const char *mode)
|
|
||||||
{
|
|
||||||
uint8_t ff;
|
|
||||||
int oflags = mode_to_flags(mode, &ff);
|
|
||||||
if (oflags < 0) return NULL;
|
|
||||||
|
|
||||||
int fd = open(path, oflags);
|
|
||||||
if (fd < 0) return NULL;
|
|
||||||
|
|
||||||
FILE *fp = (FILE *)malloc(sizeof(FILE));
|
|
||||||
if (!fp) {
|
|
||||||
int saved = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = saved ? saved : ENOMEM;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
fp->fd = fd;
|
|
||||||
fp->flags = ff;
|
|
||||||
return fp;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fclose(FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp) {
|
|
||||||
errno = EBADF;
|
|
||||||
return EOF;
|
|
||||||
}
|
|
||||||
/* Don't close stdin/stdout/stderr. */
|
|
||||||
if (fp == &_stdin || fp == &_stdout || fp == &_stderr) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int r = close(fp->fd);
|
|
||||||
free(fp);
|
|
||||||
return r < 0 ? EOF : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fflush(FILE *fp)
|
|
||||||
{
|
|
||||||
/* Unbuffered — nothing to flush. */
|
|
||||||
(void)fp;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- char-at-a-time -------------------------------------------------*/
|
|
||||||
|
|
||||||
int fputc(int c, FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp) { errno = EBADF; return EOF; }
|
|
||||||
if (fp->flags & _F_CONOUT) {
|
|
||||||
putchar(c);
|
|
||||||
return (int)c;
|
|
||||||
}
|
|
||||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
|
||||||
uint8_t ch = (uint8_t)c;
|
|
||||||
if (write(fp->fd, &ch, 1) != 1) {
|
|
||||||
fp->flags |= _F_ERROR;
|
|
||||||
return EOF;
|
|
||||||
}
|
|
||||||
return (int)ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fgetc(FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp) { errno = EBADF; return EOF; }
|
|
||||||
if (fp->flags & _F_CONIN) {
|
|
||||||
return getchar();
|
|
||||||
}
|
|
||||||
if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; }
|
|
||||||
uint8_t ch;
|
|
||||||
int r = read(fp->fd, &ch, 1);
|
|
||||||
if (r == 0) { fp->flags |= _F_EOF; return EOF; }
|
|
||||||
if (r < 0) { fp->flags |= _F_ERROR; return EOF; }
|
|
||||||
return (int)ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- string-at-a-time ----------------------------------------------*/
|
|
||||||
|
|
||||||
int fputs(const char *s, FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp || !s) { errno = EBADF; return EOF; }
|
|
||||||
if (fp->flags & _F_CONOUT) {
|
|
||||||
while (*s) {
|
|
||||||
putchar((unsigned char)*s++);;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
|
||||||
size_t n = strlen(s);
|
|
||||||
int w = write(fp->fd, s, (uint16_t)n);
|
|
||||||
if (w < 0 || (size_t)w != n) {
|
|
||||||
fp->flags |= _F_ERROR;
|
|
||||||
return EOF;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *fgets(char *buf, int n, FILE *fp)
|
|
||||||
{
|
|
||||||
if (!buf || n < 2 || !fp) return NULL;
|
|
||||||
int i = 0;
|
|
||||||
while (i < n - 1) {
|
|
||||||
int c = fgetc(fp);
|
|
||||||
if (c == EOF) {
|
|
||||||
if (i == 0) return NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buf[i++] = (char)c;
|
|
||||||
if (c == '\n') break;
|
|
||||||
}
|
|
||||||
buf[i] = '\0';
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- block-at-a-time -----------------------------------------------*/
|
|
||||||
|
|
||||||
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp)
|
|
||||||
{
|
|
||||||
if (!ptr || !fp) return 0;
|
|
||||||
if (size == 0 || nmemb == 0) return 0;
|
|
||||||
if (fp->flags & _F_CONIN) {
|
|
||||||
/* line-buffered console read — not very useful but functional. */
|
|
||||||
char *p = (char *)ptr;
|
|
||||||
size_t total = size * nmemb;
|
|
||||||
for (size_t i = 0; i < total; i++) {
|
|
||||||
int c = getchar();
|
|
||||||
if (c == EOF) return i / size;
|
|
||||||
p[i] = (char)c;
|
|
||||||
}
|
|
||||||
return nmemb;
|
|
||||||
}
|
|
||||||
if (!(fp->flags & _F_READ)) { errno = EBADF; return 0; }
|
|
||||||
size_t total = size * nmemb;
|
|
||||||
int r = read(fp->fd, ptr, (uint16_t)total);
|
|
||||||
if (r < 0) { fp->flags |= _F_ERROR; return 0; }
|
|
||||||
if ((size_t)r < total) fp->flags |= _F_EOF;
|
|
||||||
return (size_t)r / size;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp)
|
|
||||||
{
|
|
||||||
if (!ptr || !fp) return 0;
|
|
||||||
if (size == 0 || nmemb == 0) return 0;
|
|
||||||
if (fp->flags & _F_CONOUT) {
|
|
||||||
const char *p = (const char *)ptr;
|
|
||||||
size_t total = size * nmemb;
|
|
||||||
for (size_t i = 0; i < total; i++) {
|
|
||||||
putchar((unsigned char)p[i]);
|
|
||||||
}
|
|
||||||
return nmemb;
|
|
||||||
}
|
|
||||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return 0; }
|
|
||||||
size_t total = size * nmemb;
|
|
||||||
int w = write(fp->fd, ptr, (uint16_t)total);
|
|
||||||
if (w < 0) { fp->flags |= _F_ERROR; return 0; }
|
|
||||||
return (size_t)w / size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- positioning ---------------------------------------------------*/
|
|
||||||
|
|
||||||
int fseek(FILE *fp, long off, int whence)
|
|
||||||
{
|
|
||||||
if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) {
|
|
||||||
errno = EBADF;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
long r = lseek(fp->fd, off, whence);
|
|
||||||
if (r < 0) return -1;
|
|
||||||
fp->flags &= (uint8_t)~_F_EOF;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long ftell(FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) {
|
|
||||||
errno = EBADF;
|
|
||||||
return -1L;
|
|
||||||
}
|
|
||||||
return lseek(fp->fd, 0L, SEEK_CUR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rewind(FILE *fp)
|
|
||||||
{
|
|
||||||
if (!fp) return;
|
|
||||||
fseek(fp, 0L, SEEK_SET);
|
|
||||||
fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- status --------------------------------------------------------*/
|
|
||||||
|
|
||||||
int feof (FILE *fp) { return fp && (fp->flags & _F_EOF) ? 1 : 0; }
|
|
||||||
int ferror(FILE *fp) { return fp && (fp->flags & _F_ERROR) ? 1 : 0; }
|
|
||||||
void clearerr(FILE *fp) {
|
|
||||||
if (fp) fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* fopen — открыть файл как буферизованный поток FILE* (вариант B+).
|
||||||
|
*
|
||||||
|
* FILE берётся из статической таблицы (_file_slot_take, он же взводит
|
||||||
|
* atexit(_fclosall)); сам буфер выделится лениво при первой операции.
|
||||||
|
* NULL при: плохом режиме (EINVAL), занятой таблице (EMFILE), ошибке
|
||||||
|
* open (errno от него).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE *fopen(const char *path, const char *mode)
|
||||||
|
{
|
||||||
|
uint8_t ff;
|
||||||
|
int oflags = _file_mode_flags(mode, &ff);
|
||||||
|
if (oflags < 0) return 0;
|
||||||
|
|
||||||
|
FILE *fp = _file_slot_take(); /* слот — ДО open, чтобы не открывать зря */
|
||||||
|
if (!fp) return 0;
|
||||||
|
|
||||||
|
int fd = open(path, oflags);
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
|
||||||
|
fp->fd = fd;
|
||||||
|
fp->buf = 0;
|
||||||
|
fp->curp = 0;
|
||||||
|
fp->level = 0;
|
||||||
|
fp->hold = EOF;
|
||||||
|
fp->flags = ff; /* последним — занимает слот */
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* fprintf — форматированный вывод в поток; обёртка над vfprintf.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int fprintf(FILE *fp, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int n = vfprintf(fp, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* fputc — записать один байт в поток (буферизованно, вариант B+).
|
||||||
|
*
|
||||||
|
* Горячий путь («поток уже копит запись и в буфере есть место»)
|
||||||
|
* написан на asm — см. пояснение в fgetc.c. Остальное — медленный
|
||||||
|
* C-путь fputc_slow: консольный putchar(), переключение направления
|
||||||
|
* (_file_sync откатывает readahead), сброс полного буфера, ленивое
|
||||||
|
* выделение, fallback без буфера (прямой write() 1 байта).
|
||||||
|
* ВНИМАНИЕ: успех fputc = «байт принят в буфер»; ошибка реальной
|
||||||
|
* записи может вылезти позже (fflush/fclose).
|
||||||
|
*
|
||||||
|
* Раскладка FILE: +0 fd, +2 flags, +3 buf, +5 curp, +7 level, +9 hold.
|
||||||
|
* asm рассчитывает на BUFSIZ == 512 (проверка уровня по старшему
|
||||||
|
* байту level < 2).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
#if BUFSIZ != 512
|
||||||
|
#error "asm-горячий путь fputc рассчитан на BUFSIZ == 512"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Медленный путь; зовётся из asm-обёртки. */
|
||||||
|
static int fputc_slow(int c, FILE *fp)
|
||||||
|
{
|
||||||
|
if (!fp) { errno = EBADF; return EOF; }
|
||||||
|
if (fp->flags & _F_CONOUT) {
|
||||||
|
putchar(c);
|
||||||
|
return (int)(uint8_t)c;
|
||||||
|
}
|
||||||
|
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
||||||
|
|
||||||
|
if (!(fp->flags & _F_DIROUT)) {
|
||||||
|
if (_file_sync(fp)) return EOF; /* откат readahead/hold */
|
||||||
|
} else if (fp->level >= BUFSIZ) {
|
||||||
|
if (_file_sync(fp)) return EOF; /* буфер полон — сброс */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *b = _file_buf(fp);
|
||||||
|
if (!b) {
|
||||||
|
/* Небуферизованный fallback. */
|
||||||
|
uint8_t ch = (uint8_t)c;
|
||||||
|
if (write(fp->fd, &ch, 1) != 1) {
|
||||||
|
fp->flags |= _F_ERROR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
return (int)ch;
|
||||||
|
}
|
||||||
|
if (!(fp->flags & _F_DIROUT)) {
|
||||||
|
fp->flags |= _F_DIROUT;
|
||||||
|
fp->curp = b;
|
||||||
|
}
|
||||||
|
*fp->curp++ = (uint8_t)c;
|
||||||
|
fp->level++;
|
||||||
|
return (int)(uint8_t)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fputc(int c, FILE *fp) __naked
|
||||||
|
{
|
||||||
|
(void)c; (void)fp;
|
||||||
|
(void)fputc_slow; /* используется из asm */
|
||||||
|
__asm
|
||||||
|
;; HL = c, DE = fp.
|
||||||
|
ld a, d
|
||||||
|
or a, e
|
||||||
|
jr Z, _fp_slow ; NULL — C-путь выставит errno
|
||||||
|
push de
|
||||||
|
pop iy ; IY = fp
|
||||||
|
|
||||||
|
ld a, 2 (iy) ; flags
|
||||||
|
and a, #0x08 ; _F_DIROUT — уже копим запись?
|
||||||
|
jr Z, _fp_slow ; нет (или консоль/чтение) — C-путь
|
||||||
|
ld a, 8 (iy) ; level_hi < 2 <=> level < 512
|
||||||
|
cp a, #2
|
||||||
|
jr NC, _fp_slow ; буфер полон — сброс в C-пути
|
||||||
|
|
||||||
|
;; --- горячий путь: *curp++ = c, level++ ---
|
||||||
|
ld e, 5 (iy) ; DE = curp (fp больше не нужен в DE)
|
||||||
|
ld d, 6 (iy)
|
||||||
|
ld a, l
|
||||||
|
ld (de), a
|
||||||
|
inc de
|
||||||
|
ld 5 (iy), e
|
||||||
|
ld 6 (iy), d
|
||||||
|
ld a, 7 (iy) ; level++
|
||||||
|
inc a
|
||||||
|
ld 7 (iy), a
|
||||||
|
jr NZ, _fp_ret
|
||||||
|
inc 8 (iy)
|
||||||
|
_fp_ret:
|
||||||
|
ld e, l ; вернуть (uint8_t)c
|
||||||
|
ld d, #0
|
||||||
|
ret
|
||||||
|
|
||||||
|
_fp_slow:
|
||||||
|
jp _fputc_slow ; HL = c, DE = fp уже на месте
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user