Compare commits
19 Commits
c71e249a4e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bd116d7361 | |||
| 858e5755ad | |||
| f87b52bb7f | |||
| 07c4004bd9 | |||
| 6992c1436e | |||
| 07c398a560 | |||
| 4bed9d3f3f | |||
| 463a058f56 | |||
| 8abc1d6c16 | |||
| 982af12710 | |||
| 394ee3a2cd | |||
| 47c9cd326a | |||
| ca5f30b332 | |||
| 0ad0559fc8 | |||
| 035d93ab51 | |||
| 0dedc4dac8 | |||
| 737c974400 | |||
| b851e22fa6 | |||
| 527d4a6a18 |
+19
-1
@@ -9,7 +9,8 @@ build/
|
||||
# sprinter-cc per-example intermediate directory
|
||||
.sprinter-cc-*/
|
||||
|
||||
# Per-example final/intermediate outputs landing alongside the source
|
||||
# Per-program final/intermediate outputs landing alongside the source
|
||||
# (real apps under examples/ and libc feature tests under tests/).
|
||||
examples/*/*.exe
|
||||
examples/*/*.asm
|
||||
examples/*/*.lst
|
||||
@@ -23,6 +24,22 @@ examples/*/*.cdb
|
||||
examples/*/*.mem
|
||||
examples/*/*.rst
|
||||
|
||||
# Temporary build directory for floppy disk image preparation
|
||||
examples/*/.disk_tmp/
|
||||
|
||||
tests/*/*.exe
|
||||
tests/*/*.asm
|
||||
tests/*/*.lst
|
||||
tests/*/*.lk
|
||||
tests/*/*.ihx
|
||||
tests/*/*.noi
|
||||
tests/*/*.sym
|
||||
tests/*/*.map
|
||||
tests/*/*.rel
|
||||
tests/*/*.cdb
|
||||
tests/*/*.mem
|
||||
tests/*/*.rst
|
||||
|
||||
# libc archive (built from libc/, see lib/Makefile)
|
||||
lib/*.lib
|
||||
|
||||
@@ -68,6 +85,7 @@ mame/
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
.kilo/
|
||||
|
||||
# Claude Code local settings (per-machine, not for the repo)
|
||||
.claude/
|
||||
|
||||
@@ -1,58 +1,41 @@
|
||||
# Sprinter C Compiler — top-level Makefile
|
||||
#
|
||||
# make build host tools, libc archive, and all examples
|
||||
# make build host tools, libc archive, all tests, all apps
|
||||
# make tools build only host tools (mkexe)
|
||||
# make lib build lib/sprinter.lib (libc archive used by sprinter-cc)
|
||||
# make examples build all examples
|
||||
# make floppy package every example + test files into mame/v306/IMG/mc.img
|
||||
# make tests build all libc feature tests under tests/
|
||||
# make examples build all real applications under examples/
|
||||
# make floppy package every .exe + test fixtures into mame/v306/IMG/mc.img
|
||||
# make check run mkexe unit tests
|
||||
# make clean remove all build artefacts
|
||||
# make sdcc download/extract vendored SDCC
|
||||
#
|
||||
# Most heavy lifting is delegated to sub-Makefiles.
|
||||
|
||||
EXAMPLES := hello banked bankedbg strtest cat seek malloc mem_test argv errno rt_test openenv ls conio attrprob timedir mouse banklocl stdlib assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
||||
# Small libc-feature tests (one program per .c-language feature or libc API).
|
||||
TESTS := hello banked bankedbg strtest cat seek malloc mem_test argv errno \
|
||||
rt_test openenv ls conio attrprob timedir mouse banklocl stdlib \
|
||||
assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
||||
|
||||
# Larger end-user applications under examples/.
|
||||
APPS := mdview
|
||||
|
||||
MAME_DIR := mame/v306
|
||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
||||
MAKE_DISK := $(MAME_DIR)/make_disk.py
|
||||
|
||||
EXE_FILES := \
|
||||
examples/hello/hello.exe \
|
||||
examples/banked/banked.exe \
|
||||
examples/bankedbg/bankedbg.exe \
|
||||
examples/strtest/strtest.exe \
|
||||
examples/cat/cat.exe \
|
||||
examples/seek/seek.exe \
|
||||
examples/malloc/malloc.exe \
|
||||
examples/mem_test/mem_test.exe \
|
||||
examples/argv/argv.exe \
|
||||
examples/errno/errno.exe \
|
||||
examples/rt_test/rt_test.exe \
|
||||
examples/openenv/openenv.exe \
|
||||
examples/ls/ls.exe \
|
||||
examples/conio/conio.exe \
|
||||
examples/attrprob/attrprob.exe \
|
||||
examples/timedir/timedir.exe \
|
||||
examples/mouse/mouse.exe \
|
||||
examples/banklocl/banklocl.exe \
|
||||
examples/stdlib/stdlib.exe \
|
||||
examples/assrtest/assrtest.exe \
|
||||
examples/ptime/ptime.exe \
|
||||
examples/stattest/stattest.exe \
|
||||
examples/filetest/filetest.exe \
|
||||
examples/gfx_demo/gfx_demo.exe \
|
||||
examples/gfx_d16/gfx_d16.exe \
|
||||
examples/gfx_text/gfx_text.exe \
|
||||
examples/gfx_mous/gfx_mous.exe
|
||||
TEST_EXES := $(foreach t,$(TESTS),tests/$(t)/$(t).exe)
|
||||
APP_EXES := $(foreach a,$(APPS),examples/$(a)/$(a).exe)
|
||||
ALL_EXES := $(TEST_EXES) $(APP_EXES)
|
||||
|
||||
DATA_FILES := \
|
||||
examples/cat/test.txt \
|
||||
examples/seek/big.txt
|
||||
tests/cat/test.txt \
|
||||
tests/seek/big.txt \
|
||||
examples/mdview/SAMPLE.MD
|
||||
|
||||
.PHONY: all tools lib examples check clean sdcc floppy $(EXAMPLES)
|
||||
.PHONY: all tools lib tests examples check clean sdcc floppy $(TESTS) $(APPS)
|
||||
|
||||
all: tools lib examples
|
||||
all: tools lib tests examples
|
||||
|
||||
tools:
|
||||
$(MAKE) -C toolchain/mkexe
|
||||
@@ -63,18 +46,22 @@ lib:
|
||||
check: tools
|
||||
$(MAKE) -C toolchain/mkexe check
|
||||
|
||||
examples: $(EXAMPLES)
|
||||
tests: $(TESTS)
|
||||
examples: $(APPS)
|
||||
|
||||
$(EXAMPLES): tools lib
|
||||
$(TESTS): tools lib
|
||||
$(MAKE) -C tests/$@
|
||||
|
||||
$(APPS): tools lib
|
||||
$(MAKE) -C examples/$@
|
||||
|
||||
# Generate big.txt if missing (gen_bigfile.py creates 100 KB marker file).
|
||||
examples/seek/big.txt:
|
||||
cd examples/seek && python3 gen_bigfile.py big.txt 102400
|
||||
tests/seek/big.txt:
|
||||
cd tests/seek && python3 gen_bigfile.py big.txt 102400
|
||||
|
||||
# Re-pack the MAME floppy image with every built example + needed data files.
|
||||
floppy: examples examples/seek/big.txt
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXE_FILES) $(DATA_FILES)
|
||||
# Re-pack the MAME floppy image with every built exe + needed data files.
|
||||
floppy: tests examples tests/seek/big.txt
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(ALL_EXES) $(DATA_FILES)
|
||||
@echo
|
||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||
@@ -82,7 +69,8 @@ floppy: examples examples/seek/big.txt
|
||||
clean:
|
||||
$(MAKE) -C toolchain/mkexe clean
|
||||
$(MAKE) -C lib clean
|
||||
@for e in $(EXAMPLES); do $(MAKE) -C examples/$$e clean; done
|
||||
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
|
||||
@for a in $(APPS); do $(MAKE) -C examples/$$a clean; done
|
||||
|
||||
sdcc:
|
||||
bash third_party/setup-sdcc.sh
|
||||
|
||||
@@ -51,13 +51,13 @@ Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocat
|
||||
pages by program size — small programs get only one page. Pick a memory mode based
|
||||
on what your program needs:
|
||||
|
||||
| Mode | Code lives in | Banking | Use when |
|
||||
|---|---|---|---|
|
||||
| `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB |
|
||||
| `small` | W1 (0x4100+) | no | code+data < 30 KB |
|
||||
| `big` | W2 + W1 banks | yes (W1) | tiny + extra code modules |
|
||||
| `huge` | W1 + W3 banks | yes (W3) | small + extra code modules |
|
||||
| `manual` | user-specified | optional | special layouts |
|
||||
| Mode | Code lives in | Banking | Use when | Note |
|
||||
|---|---|---|---|---|
|
||||
| `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | |
|
||||
| `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | |
|
||||
| `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | |
|
||||
| `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | |
|
||||
| `manual` | user-specified | optional | special layouts | Not implemented |
|
||||
|
||||
```sh
|
||||
sprinter-cc --memory small -o big.exe bigprog.c
|
||||
@@ -157,20 +157,24 @@ sprinter-cc -o foo.exe foo.c [more.c ...] [options]
|
||||
|
||||
What works in v1.0:
|
||||
* Compile / link / pack to SprintEXE — verified on all 27 examples
|
||||
* All five memory modes (tiny / small / big / huge / manual)
|
||||
* Four memory modes (tiny / small / big / huge)
|
||||
* Graphics (both modes) with accelerator
|
||||
* Mouse (text + graphics cursor)
|
||||
* File I/O, directories, environment, time
|
||||
* All headers listed above
|
||||
|
||||
Deferred to v2.0 (see `docs/TODO.md`):
|
||||
* **IM2 interrupt handlers** — research complete (`docs/im2_isr_design.md`),
|
||||
implementation scheduled for v2
|
||||
* **Turbo-C-style BGI graphics API** — `initgraph` / `setcolor` / `circle` /
|
||||
`getimage` / `putimage` / etc. on top of our `gfx_*` primitives
|
||||
* **Audio API** (AY-3-8910 + COVOX) — requires IM2
|
||||
* **ISA-8 slot drivers** — requires IM2
|
||||
* Remaining Solid-C compatibility gaps (Phase 2/3) — see `docs/solid_c_compatibility.md`
|
||||
* Manual memory mode
|
||||
* Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers)
|
||||
|
||||
Deferred to v3.0:
|
||||
* **IM2 interrupt handlers** — research complete (`docs/im2_isr_design.md`),
|
||||
implementation scheduled for v3
|
||||
* **Audio API** (AY-3-8910 + COVOX) — requires IM2
|
||||
* **ISA-8 slot drivers** — requires IM2 (???)
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -213,7 +217,6 @@ This repository contains:
|
||||
|
||||
* **Sprinter / Peters Plus** — Иван Мак, Дмитрий Паринов and the original team
|
||||
* **SDCC** — for the underlying Z80 compiler
|
||||
* **z88dk +pps** — Дмитрий M. for paving the way with the first Sprinter target
|
||||
* **MAME** — for the Sprinter Sp2000 emulation
|
||||
|
||||
---
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# example.mk — shared Makefile fragment for simple Sprinter ESTEX examples.
|
||||
# app.mk — shared Makefile fragment for any standalone Sprinter ESTEX
|
||||
# program — used both by libc feature tests under tests/ and by real
|
||||
# applications under examples/.
|
||||
#
|
||||
# Usage in an example's Makefile:
|
||||
# Usage in a per-program Makefile:
|
||||
#
|
||||
# PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
# EXAMPLE := my_example # base name (matches my_example.c)
|
||||
# EXAMPLE := my_program # base name (matches my_program.c)
|
||||
#
|
||||
# # Optional overrides (any combination):
|
||||
# # MEMORY := small # tiny | small | big | huge | manual
|
||||
@@ -12,7 +14,7 @@
|
||||
# # EXTRA_FLAGS := --crt0=minimal # passed through to sprinter-cc
|
||||
# # EXTRA_DATA := test.txt # extra files to add to `make floppy`
|
||||
#
|
||||
# include $(PROJ_ROOT)/examples/example.mk
|
||||
# include $(PROJ_ROOT)/app.mk
|
||||
#
|
||||
# Pipeline (all driven by sprinter-cc):
|
||||
# crt0 + EXAMPLE.c + EXTRA_SRCS --sdcc--> .ihx
|
||||
@@ -61,9 +63,9 @@ $(LIB):
|
||||
clean:
|
||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe
|
||||
|
||||
# `make floppy` packs ONLY this example (+ optional EXTRA_DATA files) into
|
||||
# `make floppy` packs ONLY this program (+ optional EXTRA_DATA files) into
|
||||
# the MAME floppy image, replacing whatever was there. Handy for trying a
|
||||
# single program without rebuilding all 27 examples.
|
||||
# single program without rebuilding everything.
|
||||
floppy: $(EXAMPLE).exe
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(EXTRA_DATA)
|
||||
@echo
|
||||
+38
-18
@@ -186,9 +186,15 @@ ENTRY_ADDR="${ENTRY_ADDR:-$LOAD_ADDR}"
|
||||
|
||||
# Pick crt0 source. Memory mode drives default crt0 selection; explicit
|
||||
# --crt0=TYPE still wins (e.g. --crt0=minimal for tiny w/o argv).
|
||||
#
|
||||
# big/huge ALWAYS use crt0_banked, even without explicit --bank flags:
|
||||
# big — banks in W1 (BANK_W1=1 prepended) — code stays in W2 = tiny layout
|
||||
# huge — banks in W3 — code in W1 = small layout, crt0_banked auto-detects W2
|
||||
# crt0_banked also exports startup-info symbols (estex_file_handle etc.).
|
||||
case "$MEMORY_MODE" in
|
||||
small|huge) DEFAULT_CRT0="small";;
|
||||
*) DEFAULT_CRT0="default";;
|
||||
small) DEFAULT_CRT0="small";;
|
||||
big|huge) DEFAULT_CRT0="banked";;
|
||||
*) DEFAULT_CRT0="default";; # tiny, manual
|
||||
esac
|
||||
if [[ "$CRT0_TYPE" == "default" && "$DEFAULT_CRT0" != "default" ]]; then
|
||||
CRT0_TYPE="$DEFAULT_CRT0"
|
||||
@@ -272,32 +278,46 @@ for src in "${SOURCES[@]}"; do
|
||||
USER_RELS+=("$rel")
|
||||
done
|
||||
|
||||
# 3. bank sources + trampoline (bank.s). In BIG mode banks live in W1 at
|
||||
# low16 = 0x4000; in HUGE / default mode they live in W3 at low16 = 0xC000.
|
||||
# 3. bank infrastructure — required whenever crt0_banked is in play.
|
||||
# Always assemble bank.s for _bank_pages + the bcall/bjump trampolines.
|
||||
# If the user did not pass any --bank, generate a tiny stub providing
|
||||
# n_banks = 0 (crt0_banked.s imports this symbol unconditionally).
|
||||
# In BIG mode banks live in W1 at low16 = 0x4000; in HUGE / default
|
||||
# mode they live in W3 at low16 = 0xC000.
|
||||
BANK_RELS=()
|
||||
BANK_LD_FLAGS=()
|
||||
if [[ ${#BANK_SPECS[@]} -gt 0 ]]; then
|
||||
if [[ "$CRT0_TYPE" == "banked" ]]; then
|
||||
if [[ $BANK_W1 -eq 1 ]]; then
|
||||
BANK_LOW16=0x4000
|
||||
else
|
||||
BANK_LOW16=0xC000
|
||||
fi
|
||||
# Trampoline — depends on BANK_W1 so we assemble it here, not from the lib.
|
||||
# Trampoline + _bank_pages table — depend on BANK_W1, so assemble per build.
|
||||
BANK_TRAMP_REL="$WORK/bank_trampoline.rel"
|
||||
asm_runtime "$RUNTIME/bank.s" "$BANK_TRAMP_REL"
|
||||
BANK_RELS+=("$BANK_TRAMP_REL")
|
||||
for spec in "${BANK_SPECS[@]}"; do
|
||||
bank_n="${spec%%=*}"
|
||||
bank_src="${spec#*=}"
|
||||
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
|
||||
run "$SDCC" "${CC_FLAGS[@]}" \
|
||||
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \
|
||||
-c -o "$rel" "$bank_src"
|
||||
BANK_RELS+=("$rel")
|
||||
# Virtual address: bank_n in upper byte, BANK_LOW16 in low half.
|
||||
addr=$(printf "0x%X" $(( (bank_n << 16) | BANK_LOW16 )))
|
||||
BANK_LD_FLAGS+=("-Wl-b_BANK${bank_n}=${addr}")
|
||||
done
|
||||
|
||||
if [[ ${#BANK_SPECS[@]} -eq 0 ]]; then
|
||||
# User has no banks — supply n_banks=0 so crt0_banked links.
|
||||
STUB_C="$WORK/_n_banks_stub.c"
|
||||
STUB_REL="$WORK/_n_banks_stub.rel"
|
||||
echo "const unsigned char n_banks = 0;" > "$STUB_C"
|
||||
run "$SDCC" "${CC_FLAGS[@]}" -c -o "$STUB_REL" "$STUB_C"
|
||||
BANK_RELS+=("$STUB_REL")
|
||||
else
|
||||
for spec in "${BANK_SPECS[@]}"; do
|
||||
bank_n="${spec%%=*}"
|
||||
bank_src="${spec#*=}"
|
||||
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
|
||||
run "$SDCC" "${CC_FLAGS[@]}" \
|
||||
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \
|
||||
-c -o "$rel" "$bank_src"
|
||||
BANK_RELS+=("$rel")
|
||||
# Virtual address: bank_n in upper byte, BANK_LOW16 in low half.
|
||||
addr=$(printf "0x%X" $(( (bank_n << 16) | BANK_LOW16 )))
|
||||
BANK_LD_FLAGS+=("-Wl-b_BANK${bank_n}=${addr}")
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. link → .ihx
|
||||
|
||||
@@ -255,6 +255,43 @@ User-задаваемые ISR через Z80 IM 2 mode. Нужны для:
|
||||
|
||||
### Прочие крупные пункты для 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ов
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# MDView: описание работы и тестовые паттерны
|
||||
## Назначение
|
||||
`mdview` — консольный просмотрщик Markdown для текстового режима 80x32.
|
||||
Программа загружает `.MD`-файл, индексирует его на экранные сегменты и рендерит содержимое с учётом переносов, стилей и специальных блоков.
|
||||
|
||||
## Как работает программа
|
||||
### 1) Загрузка файла
|
||||
- На старте вызывается `load_file(path)`.
|
||||
- Если путь не передан, используется `SAMPLE.MD`.
|
||||
- При ошибках загрузки программа показывает причину и завершает работу после нажатия клавиши.
|
||||
|
||||
### 2) Индексация (`index_lines`)
|
||||
- Файл разбирается в массив сегментов для быстрого рендера.
|
||||
- Поддерживаются ключевые типы строк:
|
||||
- заголовки `#..####`,
|
||||
- маркированные/нумерованные списки,
|
||||
- цитаты `>`,
|
||||
- горизонтальные разделители,
|
||||
- fenced code-блоки,
|
||||
- таблицы (nowrap-режим),
|
||||
- обычные параграфы.
|
||||
- Для list/quote/plain используется общая логика сканирования с режимами склейки строк.
|
||||
|
||||
### 3) Рендер (`render_line`, `render_viewport`)
|
||||
- Для каждой видимой строки рисуется префикс (маркер списка, цитаты и т.д.) и текст.
|
||||
- Поддерживаются inline-стили:
|
||||
- `**жирный**`,
|
||||
- `*курсив*`,
|
||||
- `_подчёркнутый_`,
|
||||
- `~~зачёркнутый~~`,
|
||||
- `` `code` ``.
|
||||
- Табы расширяются до фиксированного шага.
|
||||
- Для длинных nowrap-строк применяется горизонтальный сдвиг и индикаторы `<`/`>`.
|
||||
|
||||
### 4) Навигация
|
||||
- `Up/Down` — прокрутка на 1 строку.
|
||||
- `PgUp/PgDn` — прокрутка на экран.
|
||||
- `Home/End` — начало/конец документа.
|
||||
- `Left/Right` — горизонтальная прокрутка nowrap-контента.
|
||||
- `Esc` или `F10` — выход.
|
||||
|
||||
## Сборка и запуск
|
||||
```bash
|
||||
make -C examples/mdview
|
||||
```
|
||||
|
||||
Запуск на целевой системе:
|
||||
```bash
|
||||
mdview.exe <путь_к_файлу.md>
|
||||
```
|
||||
|
||||
Если путь не указан, открывается `SAMPLE.MD`.
|
||||
|
||||
## Тестовые паттерны
|
||||
Ниже набор паттернов для ручной регрессии.
|
||||
|
||||
### P01 — Базовый smoke-тест открытия
|
||||
Вход:
|
||||
- валидный markdown-файл среднего размера.
|
||||
|
||||
Ожидается:
|
||||
- файл открывается без ошибок;
|
||||
- статус-бар и меню отображаются корректно;
|
||||
- прокрутка работает.
|
||||
|
||||
### P02 — Ошибка открытия файла
|
||||
Вход:
|
||||
- несуществующий путь к файлу.
|
||||
|
||||
Ожидается:
|
||||
- сообщение `mdview: cannot load file`;
|
||||
- корректный код причины (`open failed` и т.п.);
|
||||
- ожидание клавиши перед выходом.
|
||||
|
||||
### P03 — Пустой файл
|
||||
Вход:
|
||||
- пустой `.md`.
|
||||
|
||||
Ожидается:
|
||||
- сообщение `mdview: empty file`;
|
||||
- корректное завершение после нажатия клавиши.
|
||||
|
||||
### P04 — Обычный параграф и переносы
|
||||
Шаблон:
|
||||
```md
|
||||
Это длинный абзац для проверки переноса строк в обычном тексте.
|
||||
Вторая строка должна мягко склеиться с первой.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- строки склеиваются как единый параграф;
|
||||
- переносы происходят по ширине экрана без потери символов.
|
||||
|
||||
### P05 — Hard break в параграфе
|
||||
Шаблон:
|
||||
```md
|
||||
Первая строка с двумя пробелами в конце.
|
||||
Вторая строка после hard break.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- между строками сохраняется принудительный разрыв;
|
||||
- inline-стиль не ломается.
|
||||
|
||||
### P06 — Многострочный список с lazy continuation
|
||||
Шаблон:
|
||||
```md
|
||||
- Пункт списка, который продолжается
|
||||
на следующей строке с отступом.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- continuation-строка склеивается с пунктом через один пробел;
|
||||
- лишние ведущие отступы continuation не попадают в итоговый текст;
|
||||
- переносы не ломают маркер списка.
|
||||
|
||||
### P07 — Многострочная цитата
|
||||
Шаблон:
|
||||
```md
|
||||
> Первая строка цитаты
|
||||
> Вторая строка цитаты
|
||||
> Третья строка цитаты
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- для продолжений корректно повторяется quote-префикс;
|
||||
- сырой символ `>` из исходника не «просачивается» в середину текста.
|
||||
|
||||
### P08 — Пустая quoted-строка как разделитель
|
||||
Шаблон:
|
||||
```md
|
||||
> Первый параграф цитаты
|
||||
>
|
||||
> Второй параграф цитаты
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- пустая quoted-строка разделяет два параграфа;
|
||||
- рендер не объединяет их в один поток.
|
||||
|
||||
### P09 — Таблица в nowrap-режиме
|
||||
Шаблон:
|
||||
```md
|
||||
| col1 | col2 | col3 |
|
||||
|------|------|------|
|
||||
| очень длинное значение | очень длинное значение | очень длинное значение |
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- строка таблицы не переносится по словам;
|
||||
- работает горизонтальный сдвиг `Left/Right`;
|
||||
- индикаторы `<`/`>` показывают скрытый контент.
|
||||
|
||||
### P10 — Fenced code-block
|
||||
Шаблон:
|
||||
~~~md
|
||||
```text
|
||||
**это не жирный**
|
||||
_это не подчёркнутый_
|
||||
```
|
||||
~~~
|
||||
|
||||
Ожидается:
|
||||
- внутри code-блока inline-разметка не применяется;
|
||||
- текст отображается как литерал.
|
||||
|
||||
### P11 — Inline-стили на обычном тексте
|
||||
Шаблон:
|
||||
```md
|
||||
Текст с **жирным**, *курсивом*, _подчёркнутым_, ~~зачёркнутым~~ и `code`.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- все перечисленные стили рендерятся корректно;
|
||||
- литеральные случаи вроде `2 * 3` не превращаются в стиль.
|
||||
|
||||
### P12 — Статус-бар и границы прокрутки
|
||||
Вход:
|
||||
- документ на несколько экранов.
|
||||
|
||||
Ожидается:
|
||||
- корректные `L x-y / total` и `%`;
|
||||
- `Home/End` приводят к ожидаемым позициям;
|
||||
- при выходе за границы документа top-line корректно clamp-ится.
|
||||
|
||||
## Рекомендация по регрессии
|
||||
После любых изменений в логике индексации/рендера прогонять минимум:
|
||||
- `P04`, `P05`, `P06`, `P07`, `P09`, `P10`, `P11`, `P12`.
|
||||
@@ -1,17 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
void bank1_func(int x) __banked
|
||||
{
|
||||
(void)x;
|
||||
puts("BANK1: hello from a banked function (W1)!");
|
||||
puts("BANK1: window 1 phys page = ");
|
||||
print_hex(_io_page_w1); /* should be BANK1's phys page */
|
||||
putchar('\n');
|
||||
putchar('1');
|
||||
putchar('=');
|
||||
putchar('0' + (x / 10) % 10);
|
||||
putchar('0' + x % 10);
|
||||
putchar('\n');
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Build mdview.exe — Markdown viewer for Sprinter.
|
||||
#
|
||||
# small memory mode: code in W1, data/stack/heap in W2 (32 KB total).
|
||||
# W3 stays free for the file buffer (EMM-mapped).
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mdview
|
||||
MEMORY := small
|
||||
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Образ дискеты: только mdview.exe + README.MD (перекодированный
|
||||
# из UTF-8 в CP866 — рабочую кодировку Sprinter).
|
||||
#
|
||||
# README.MD хранится в репозитории в UTF-8; iconv -c конвертирует
|
||||
# его в CP866, отбрасывая символы без аналога в целевой кодировке.
|
||||
# Результат кладётся в .disk_tmp/README.MD, чтобы make_disk.py
|
||||
# использовал правильное имя файла на диске.
|
||||
# ------------------------------------------------------------------
|
||||
DISK_TMP := .disk_tmp
|
||||
README_DISK := $(DISK_TMP)/README.MD
|
||||
|
||||
$(DISK_TMP):
|
||||
mkdir -p $@
|
||||
|
||||
$(README_DISK): README.MD | $(DISK_TMP)
|
||||
iconv -c -f UTF-8 -t CP866 README.MD > $@
|
||||
|
||||
floppy: $(EXAMPLE).exe $(README_DISK)
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK)
|
||||
@echo
|
||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||
|
||||
clean:
|
||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP)
|
||||
|
||||
.PHONY: all clean floppy run
|
||||
@@ -0,0 +1,133 @@
|
||||
# MDView — Просмотрщик Markdown для Sprinter
|
||||
|
||||
**MDView** — программа для просмотра документов в формате *Markdown* на компьютере
|
||||
Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает
|
||||
основную RAM программы.
|
||||
|
||||
## Возможности
|
||||
|
||||
- Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая)
|
||||
- До **16 384** экранных строк в индексе
|
||||
- Автоматический перенос слов по ширине экрана (80 столбцов)
|
||||
- Горизонтальный сдвиг для широких строк (блоки кода, таблицы)
|
||||
- Статус-бар: имя файла, диапазон строк, процент прокрутки
|
||||
- Спиннер в строке состояния во время загрузки и индексации
|
||||
- Поддержка «мягкого» склеивания строк в абзацах и цитатах
|
||||
|
||||
## Запуск
|
||||
|
||||
```
|
||||
mdview [имя_файла.md]
|
||||
```
|
||||
|
||||
Если имя файла не задано, загружается `README.MD`.
|
||||
|
||||
## Управление
|
||||
|
||||
```
|
||||
Клавиша Действие
|
||||
───────────── ────────────────────────────────────────
|
||||
Up Down Прокрутка на одну строку вверх / вниз
|
||||
PgUp PgDn Прокрутка на страницу (30 строк)
|
||||
Home Начало документа
|
||||
End Конец документа
|
||||
Left Right Горизонтальный сдвиг (только nowrap-строки)
|
||||
F1 Окно справки
|
||||
F10 / Esc Выход из программы
|
||||
```
|
||||
|
||||
## Синтаксис Markdown
|
||||
|
||||
### Заголовки
|
||||
|
||||
Поддерживаются уровни H1–H4. Уровни H5 и H6 отображаются как H4.
|
||||
|
||||
# Заголовок первого уровня
|
||||
## Заголовок второго уровня
|
||||
### Заголовок третьего уровня
|
||||
#### Заголовок четвёртого уровня
|
||||
|
||||
### Текстовое форматирование
|
||||
|
||||
**Жирный текст** выделяется двойными звёздочками: `**текст**`
|
||||
|
||||
*Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_`
|
||||
|
||||
`Встроенный код` обозначается обратными кавычками
|
||||
|
||||
~~Зачёркнутый текст~~ — двойные тильды: `~~текст~~`
|
||||
|
||||
### Ненумерованный список
|
||||
|
||||
Маркеры `-`, `*` или `+`:
|
||||
|
||||
- Первый пункт списка
|
||||
- Второй пункт списка
|
||||
- Третий пункт с достаточно длинным текстом, который при необходимости
|
||||
будет перенесён на следующую строку с сохранением отступа
|
||||
|
||||
### Нумерованный список
|
||||
|
||||
1. Первый элемент
|
||||
2. Второй элемент
|
||||
3. Третий элемент
|
||||
|
||||
### Цитата
|
||||
|
||||
> Блок цитаты начинается с символа `>`. Несколько последовательных
|
||||
> строк одной цитаты склеиваются в единый абзац с автоматическим
|
||||
> переносом слов.
|
||||
|
||||
### Блок кода (verbatim)
|
||||
|
||||
Блок кода заключается в тройные обратные кавычки. Внутри блока
|
||||
текст отображается «как есть» без разбора Markdown:
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
int main(void) {
|
||||
puts("Hello, Sprinter!");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Горизонтальная линия
|
||||
|
||||
Три или более символов `---`, `***` или `___` на отдельной строке:
|
||||
|
||||
---
|
||||
|
||||
## Технические характеристики
|
||||
|
||||
- **Платформа:** Sprinter, процессор Z80 @ 21 МГц
|
||||
- **Кодировка:** CP866 (DOS Cyrillic)
|
||||
- **Максимальный размер файла:** 128 КБ
|
||||
- **Максимальное число строк в индексе:** 16 384
|
||||
- **Режим памяти:** small
|
||||
- Код программы, cтек, данные, куча — окнa W1-W2 (32 КБ, адреса 0x4000–0xBFFF).
|
||||
- Буфер файла — страницы EMM, отображаемые в W3 (0xC000–0xFFFF)
|
||||
|
||||
## TODO
|
||||
|
||||
1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно
|
||||
разрешить работать с большим кол-вом страниц памяти, пока оттестированно
|
||||
на работе с 8-мю страницами по 16Кб.
|
||||
|
||||
2. **Форматированные таблицы.** Разбирать строки вида `| ячейка | ячейка |`
|
||||
с автоматическим выравниванием столбцов и отрисовкой разделительных
|
||||
линий (строки `|---|---|`). На текущий момент таблицы отображаются
|
||||
как обычные nowrap-строки без выравнивания.
|
||||
|
||||
3. **Поддержка кодировок CP1251 и UTF-8.** Автоопределение кодировки
|
||||
по BOM, явное указание через аргумент командной строки (`--encoding cp1251`),
|
||||
возможность переключения кодировки во время просмотра (`F8`).
|
||||
Нужно прежде всего для документов на русском языке; CP866 кодировака уже поддерживается.
|
||||
|
||||
4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода
|
||||
символов через BIOS WRCHAR (пакетный вывод, DMA).
|
||||
|
||||
---
|
||||
|
||||
*MDView v0.2 · (c) 2026 Петров А.Г.*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
# Build open_env_test.exe — uses lib/sprinter.lib in TINY memory mode.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := openenv
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
+40
-15
@@ -22,23 +22,48 @@ BUILD := $(PROJ_ROOT)/lib/build
|
||||
|
||||
# All libc C modules.
|
||||
LIBC_C := \
|
||||
libc/io/atexit.c libc/io/conio.c libc/io/cprintf.c libc/io/dir.c \
|
||||
libc/io/videomode_raw.c \
|
||||
libc/io/_errno_set.c \
|
||||
libc/io/env.c libc/io/errno.c libc/io/fsdir.c \
|
||||
libc/io/lseek.c libc/io/mouse.c libc/io/open.c \
|
||||
libc/io/read.c libc/io/sleep.c \
|
||||
libc/io/time.c libc/io/posix_time.c libc/io/unlink.c \
|
||||
libc/sys/atexit.c \
|
||||
libc/conio/conio.c \
|
||||
libc/conio/cprintf.c \
|
||||
libc/conio/text_palette.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.c libc/mem/mem_alloc.c \
|
||||
libc/gfx/gfx_core.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/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/print_hex.c \
|
||||
libc/stdio/putchar.c libc/stdio/puts.c libc/stdio/file.c \
|
||||
libc/stdio/solid_helpers.c libc/io/solid_compat.c
|
||||
libc/stdio/getchar.c \
|
||||
libc/stdio/putchar.c \
|
||||
libc/stdio/puts.c \
|
||||
libc/file/file.c \
|
||||
libc/stdio/hex_print.c \
|
||||
libc/stdio/dec_print.c \
|
||||
libc/string/strlwr.c \
|
||||
libc/string/strupr.c
|
||||
|
||||
# Runtime modules to bundle (pulled by symbol references from libc-using code).
|
||||
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
|
||||
|
||||
@@ -58,6 +58,30 @@ char getche(void) __naked
|
||||
__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
|
||||
@@ -332,6 +356,11 @@ char cputs(const char *s) __naked
|
||||
or a, l
|
||||
ret z
|
||||
|
||||
;; IX is callee-saved under SDCC's z80 ABI. We use it as a
|
||||
;; function pointer below (ld ix, #__raw_putch_raw0/1) so save
|
||||
;; the caller's value up front and restore before every ret.
|
||||
push ix
|
||||
|
||||
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
|
||||
ld a, (_g_text_attr + 1)
|
||||
or a, a
|
||||
@@ -374,11 +403,13 @@ char cputs(const char *s) __naked
|
||||
_cputs_loop_end:
|
||||
|
||||
call __set_cursor
|
||||
pop ix ; restore caller's IX
|
||||
xor a, a ; return 0
|
||||
ret
|
||||
|
||||
_cputs_fast:
|
||||
call __cputs_pchars
|
||||
pop ix ; restore caller's IX
|
||||
xor a, a ; return 0
|
||||
ret
|
||||
__endasm;
|
||||
@@ -462,6 +493,37 @@ uint16_t wherexy(void) __naked
|
||||
__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)
|
||||
*
|
||||
Vendored
@@ -1,14 +1,40 @@
|
||||
/*
|
||||
* file.c — minimal unbuffered FILE * implementation on top of the
|
||||
* POSIX-style fd I/O (open/read/write/lseek/close).
|
||||
* file.c — *** PROVISIONAL *** minimal unbuffered FILE * implementation
|
||||
* on top of POSIX-style fd I/O
|
||||
* (open/read/write/lseek/close).
|
||||
*
|
||||
* No buffering: each fputc/fgetc maps to one read/write syscall. For
|
||||
* heavy-throughput code, prefer fread/fwrite with a sizable buffer or
|
||||
* the raw fd I/O directly.
|
||||
* ============================================================
|
||||
* TODO (v2): replace with a proper BUFFERED implementation.
|
||||
* See docs/TODO.md / Solid-C reference layout:
|
||||
*
|
||||
* stdin/stdout/stderr are STATIC sentinel FILEs with fd=-1 and the
|
||||
* _F_CONIN/_F_CONOUT flags set; fputc/fgetc detect them and call
|
||||
* putchar()/getchar() (which already do CR/LF mapping and ESTEX calls).
|
||||
* 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>
|
||||
@@ -19,17 +45,13 @@
|
||||
#include <errno.h>
|
||||
|
||||
/* ---- console pseudo-streams ----------------------------------------*/
|
||||
static FILE _stdin = { -1, _F_READ | _F_CONIN };
|
||||
static FILE _stdout = { -2, _F_WRITE | _F_CONOUT };
|
||||
static FILE _stderr = { -3, _F_WRITE | _F_CONOUT };
|
||||
static FILE _stdaux = { -4, _F_WRITE | _F_CONOUT };
|
||||
static FILE _stdprn = { -5, _F_WRITE | _F_CONOUT };
|
||||
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;
|
||||
FILE *const stdaux = &_stdaux;
|
||||
FILE *const stdprn = &_stdprn;
|
||||
|
||||
/* ---- fopen / fclose -------------------------------------------------*/
|
||||
|
||||
@@ -121,7 +143,8 @@ int fputc(int c, FILE *fp)
|
||||
{
|
||||
if (!fp) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
return putchar(c);
|
||||
putchar(c);
|
||||
return (int)c;
|
||||
}
|
||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
||||
uint8_t ch = (uint8_t)c;
|
||||
@@ -153,7 +176,7 @@ int fputs(const char *s, FILE *fp)
|
||||
if (!fp || !s) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
while (*s) {
|
||||
if (putchar((unsigned char)*s++) == EOF) return EOF;
|
||||
putchar((unsigned char)*s++);;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -217,7 +240,7 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp)
|
||||
const char *p = (const char *)ptr;
|
||||
size_t total = size * nmemb;
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
if (putchar((unsigned char)p[i]) == EOF) return i / size;
|
||||
putchar((unsigned char)p[i]);
|
||||
}
|
||||
return nmemb;
|
||||
}
|
||||
+4
-41
@@ -13,7 +13,7 @@
|
||||
* gfx_set_draw_page / get — updates _gfx_addr_base for the new page
|
||||
* gfx_set_bank / get — sets the W3 page byte (0x50..0x5F)
|
||||
* gfx_wait_vsync — EI; HALT until next frame interrupt
|
||||
* gfx_pal_load / gfx_pal_set — BIOS $A4 PIC_SET_PAL wrappers
|
||||
* (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c)
|
||||
*
|
||||
* Shared state (extern from this file):
|
||||
* _gfx_addr_base — 0xC000 for page 0, 0xC140 for page 1. Every
|
||||
@@ -136,43 +136,6 @@ void gfx_wait_vsync(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ---- Palette (BIOS $A4 PIC_SET_PAL) ----------------------------- */
|
||||
|
||||
static uint8_t pal_num_;
|
||||
static uint8_t pal_start_;
|
||||
static uint8_t pal_count_;
|
||||
static uint16_t pal_data_;
|
||||
|
||||
void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count,
|
||||
const uint8_t *data)
|
||||
{
|
||||
pal_num_ = pal_num;
|
||||
pal_start_ = start;
|
||||
pal_count_ = count;
|
||||
pal_data_ = (uint16_t)(uintptr_t)data;
|
||||
|
||||
__asm
|
||||
push ix
|
||||
ld a, (_pal_start_)
|
||||
ld e, a ; E = start
|
||||
ld a, (_pal_count_)
|
||||
ld d, a ; D = count (0 → 256)
|
||||
ld hl, (_pal_data_) ; HL = data
|
||||
ld b, #0xFF ; mask
|
||||
ld a, (_pal_num_) ; A = palette number
|
||||
ld c, #0xA4 ; BIOS PIC_SET_PAL
|
||||
rst #0x08
|
||||
pop ix
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void gfx_pal_set(uint8_t pal_num, uint8_t idx,
|
||||
uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
uint8_t entry[4];
|
||||
entry[0] = b;
|
||||
entry[1] = g;
|
||||
entry[2] = r;
|
||||
entry[3] = 0;
|
||||
gfx_pal_load(pal_num, idx, 1, entry);
|
||||
}
|
||||
/* Palette wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get /
|
||||
* gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also
|
||||
* libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */
|
||||
|
||||
+86
-4
@@ -38,6 +38,42 @@
|
||||
char kbhit (void);
|
||||
char getch (void);
|
||||
char getche(void);
|
||||
|
||||
/* Extended-key reader. Returns (scan << 8) | ascii. Plain ASCII keys
|
||||
* have ascii in the low byte; extended keys (arrows / F1..F12 /
|
||||
* PgUp/PgDn / Home / End / Ins / Del) carry ascii == 0 and the
|
||||
* KEY_* code in the high byte. */
|
||||
uint16_t getkey(void);
|
||||
|
||||
/* Scan codes returned in the high byte of getkey() when the low byte
|
||||
* (ASCII) is 0. Empirically verified in MAME — the ProgrammerManual.txt
|
||||
* "positional code" column is misleading; BIOS returns IBM-style codes
|
||||
* for the F-keys and a "5N + numpad-position" pattern for the cursor /
|
||||
* editing keys.
|
||||
*
|
||||
* Verified 2026-06-04 by reading raw getkey() output. */
|
||||
#define KEY_F1 0x3B
|
||||
#define KEY_F2 0x3C
|
||||
#define KEY_F3 0x3D
|
||||
#define KEY_F4 0x3E
|
||||
#define KEY_F5 0x3F
|
||||
#define KEY_F6 0x40
|
||||
#define KEY_F7 0x41
|
||||
#define KEY_F8 0x42
|
||||
#define KEY_F9 0x43
|
||||
#define KEY_F10 0x44
|
||||
#define KEY_F11 0x45 /* not verified */
|
||||
#define KEY_F12 0x46 /* not verified */
|
||||
#define KEY_END 0x51
|
||||
#define KEY_DOWN 0x52
|
||||
#define KEY_PGDN 0x53
|
||||
#define KEY_LEFT 0x54
|
||||
#define KEY_RIGHT 0x56
|
||||
#define KEY_HOME 0x57
|
||||
#define KEY_UP 0x58
|
||||
#define KEY_PGUP 0x59
|
||||
#define KEY_INS 0x50 /* numpad 0; not verified */
|
||||
#define KEY_DEL 0x55 /* numpad 5/.; not verified */
|
||||
char putch (char c);
|
||||
char cputs (const char *s);
|
||||
int cprintf(const char *fmt, ...);
|
||||
@@ -63,6 +99,8 @@ uint8_t wherex (void);
|
||||
uint8_t wherey (void);
|
||||
uint16_t wherexy(void); // high byte = Y, low byte = X coords.
|
||||
|
||||
void scroll(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
|
||||
|
||||
/* Direct character/attribute screen access (ESTEX $57 / $58).
|
||||
* wrchar — write char + attribute at (x, y); does NOT advance the cursor
|
||||
* and does NOT interpret control characters. Useful for
|
||||
@@ -143,12 +181,56 @@ uint8_t get_putch_raw_mode(void);
|
||||
* Colour order is standard CGA / Borland-conio.h. Constants 0..7 are
|
||||
* usable for both fg and bg; 8..15 are foreground-only. */
|
||||
enum {
|
||||
COLOR_BLACK = 0, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN,
|
||||
COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY, COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED, COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
|
||||
COLOR_BLACK = 0,
|
||||
COLOR_BLUE,
|
||||
COLOR_GREEN,
|
||||
COLOR_CYAN,
|
||||
COLOR_RED,
|
||||
COLOR_MAGENTA,
|
||||
COLOR_BROWN,
|
||||
COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY,
|
||||
COLOR_LIGHTBLUE,
|
||||
COLOR_LIGHTGREEN,
|
||||
COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED,
|
||||
COLOR_LIGHTMAGENTA,
|
||||
COLOR_YELLOW,
|
||||
COLOR_WHITE
|
||||
};
|
||||
#define COLOR_BLINK 0x80u
|
||||
#define COLOR(fg, bg) ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F)))
|
||||
|
||||
/* Text-mode palette. The 16 logical CGA colours seen by COLOR(fg, bg)
|
||||
* actually live in four 256-entry hardware palette planes indexed by the
|
||||
* full 8-bit attribute byte:
|
||||
*
|
||||
* TEXT_PAL_PAPER — background colour, non-blink phase
|
||||
* TEXT_PAL_INK — foreground colour, non-blink phase
|
||||
* TEXT_PAL_BLINK_PAPER — background colour during the blink half-cycle
|
||||
* TEXT_PAL_BLINK_INK — foreground colour during the blink half-cycle
|
||||
*
|
||||
* For non-blinking attributes (bit 7 = 0) all four planes display the
|
||||
* same colours, so writing to PAPER/INK is enough. For blinking attrs
|
||||
* (bit 7 = 1) the renderer alternates between the non-blink and blink
|
||||
* planes — that's how flash is implemented in hardware.
|
||||
*
|
||||
* These wrappers add 4 to the plane index and forward to the low-level
|
||||
* <palette.h> API (pal_load / pal_set_color / pal_get / pal_get_color).
|
||||
* Use text_pal_reset() to restore the system default CGA palette. */
|
||||
#define TEXT_PAL_PAPER 0
|
||||
#define TEXT_PAL_INK 1
|
||||
#define TEXT_PAL_BLINK_PAPER 2
|
||||
#define TEXT_PAL_BLINK_INK 3
|
||||
|
||||
void text_pal_load (uint8_t plane, uint8_t start, uint8_t count,
|
||||
const uint8_t *bgr0);
|
||||
void text_pal_set_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t r, uint8_t g, uint8_t b);
|
||||
void text_pal_get (uint8_t plane, uint8_t start, uint8_t count,
|
||||
uint8_t *bgr0);
|
||||
void text_pal_get_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
void text_pal_reset (void);
|
||||
|
||||
#endif
|
||||
|
||||
+15
-7
@@ -16,14 +16,22 @@
|
||||
#ifndef FCNTL_H
|
||||
#define FCNTL_H
|
||||
|
||||
#define O_RDONLY 0
|
||||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
#ifndef _STD_SEEK_
|
||||
#define _STD_SEEK_
|
||||
/* constants to be used as 3rd argument for "fseek" function */
|
||||
#define SEEK_SET 0
|
||||
#define SEEK_CUR 1
|
||||
#define SEEK_END 2
|
||||
#endif
|
||||
|
||||
#define O_CREAT 0x040
|
||||
#define O_EXCL 0x080
|
||||
#define O_TRUNC 0x200
|
||||
#define O_APPEND 0x400
|
||||
/* Definition "open flags" */
|
||||
#define O_WRONLY 0x01 /* 0 file write only */
|
||||
#define O_RDONLY 0x02 /* 1 file read only */
|
||||
#define O_RDWR 0x03 /* 1,0 file read/write */
|
||||
#define O_TRUNC 0x04 /* 2 open with truncation */
|
||||
#define O_CREAT 0x08 /* 3 create and open file */
|
||||
#define O_EXCL 0x10 /* 4 exclusive open */
|
||||
#define O_APPEND 0x20 /* 5 to end of file */
|
||||
|
||||
int open (const char *path, int flags);
|
||||
int creat(const char *path, int mode); /* mode arg ignored on Sprinter */
|
||||
|
||||
@@ -145,4 +145,15 @@ void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count,
|
||||
void gfx_pal_set (uint8_t pal_num, uint8_t idx,
|
||||
uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
/* Read a contiguous block of entries back from a graphics palette. */
|
||||
void gfx_pal_get (uint8_t pal_num, uint8_t start, uint8_t count,
|
||||
uint8_t *data);
|
||||
|
||||
/* Read one entry into R, G, B pointers (any may be NULL). */
|
||||
void gfx_pal_get_color(uint8_t pal_num, uint8_t idx,
|
||||
uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
|
||||
/* Restore the system default graphics palette (BIOS $A6, type=1). */
|
||||
void gfx_pal_reset(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -113,8 +113,7 @@ static inline void sprinter_page_w2(uint8_t page) { _io_page_w2 = page; }
|
||||
static inline void sprinter_page_w3(uint8_t page) { _io_page_w3 = page; }
|
||||
|
||||
/* ---- Sprinter-specific debug helpers ------------------------------ */
|
||||
/* Print one byte as two uppercase hex digits via putchar(). */
|
||||
void print_hex(uint8_t v);
|
||||
/* Use hex8() / hex16() / hex32() from <stdio.h> for hex debug output. */
|
||||
|
||||
#ifdef DEBUG_RT
|
||||
/*
|
||||
|
||||
+105
-15
@@ -3,18 +3,35 @@
|
||||
*
|
||||
* For data that doesn't fit in window 2's heap, allocate physical pages
|
||||
* directly through ESTEX EMM and access them via bank_read / bank_write
|
||||
* (which swap window 3 internally).
|
||||
* (which swap a CPU window internally).
|
||||
*
|
||||
* mem_alloc_pages(N) — reserve N contiguous 16 KB pages, return block id.
|
||||
* mem_free_block(id) — release a previously-allocated block.
|
||||
* mem_get_page(id, i) — translate (block, page-index) to physical page.
|
||||
* mem_info(&total, &free) — query EMM about total/free 16 KB pages.
|
||||
*
|
||||
* bank_load_byte / bank_store_byte — single-byte access via W3.
|
||||
* bank_read / bank_write — bulk copy between far page and a near buffer.
|
||||
* bank_load_byte / bank_store_byte — single-byte access through W3.
|
||||
* bank_read / bank_write — bulk copy through W3.
|
||||
* bank_*_w1 — same family but through W1 (for
|
||||
* memory modes where code lives in
|
||||
* W2 and W1 is free for data).
|
||||
*
|
||||
* The bank_* helpers live in HOME so they are always reachable; they save
|
||||
* the previous W3 page, swap to the target, do the work, restore W3.
|
||||
* Each helper saves the previous window-mapping, swaps to the target
|
||||
* physical page, does the access, restores the saved page.
|
||||
*
|
||||
* SAFETY by memory mode (-memory FLAG to sprinter-cc):
|
||||
*
|
||||
* `tiny` : both bank_* and bank_*_w1 are safe.
|
||||
* `small`/`huge`: only bank_* (W3) is safe; bank_*_w1 CRASHES
|
||||
* because code lives in W1.
|
||||
* `big` : only bank_* (W3) is safe; bank_*_w1 would clobber
|
||||
* the banked-code segment in W1.
|
||||
* `huge` (data path through W3 banked code): bank_* (W3) is unsafe;
|
||||
* avoid banking via W3 in huge programs.
|
||||
*
|
||||
* In short: `_w1` accessors are a `tiny`-mode optimisation, useful when
|
||||
* the program is small enough to fit in W2 and wants both W1 and W3 as
|
||||
* independent banked-data windows.
|
||||
*/
|
||||
|
||||
#ifndef SPRINTER_MEM_H
|
||||
@@ -22,16 +39,89 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ESTEX EMM allocator wrappers. */
|
||||
uint8_t mem_alloc_pages(uint8_t n); /* 0 = failure */
|
||||
void mem_free_block (uint8_t blk_id);
|
||||
uint8_t mem_get_page (uint8_t blk_id, uint8_t idx);
|
||||
void mem_info (uint16_t *total, uint16_t *free_pages);
|
||||
/* ===================================================================
|
||||
* ESTEX EMM allocator wrappers
|
||||
* =================================================================== */
|
||||
|
||||
/* Far-page accessors (always-mapped HOME, swap W3 internally). */
|
||||
uint8_t bank_load_byte (uint8_t phys_page, uint16_t off_in_window);
|
||||
void bank_store_byte(uint8_t phys_page, uint16_t off_in_window, uint8_t v);
|
||||
void bank_read (uint8_t phys_page, uint16_t off, void *dst, uint16_t n);
|
||||
void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n);
|
||||
/* Allocate `n` contiguous 16-KB physical pages from the EMM pool.
|
||||
* n : 1..255
|
||||
* ret : blk_id (1..255) on success; 0 on failure with errno set.
|
||||
* The id is opaque — pass it to mem_get_page() and mem_free_block(). */
|
||||
uint8_t mem_alloc_pages_estex(uint8_t n);
|
||||
uint8_t mem_alloc_pages_bios(uint8_t n);
|
||||
|
||||
/* Release a block previously returned by mem_alloc_pages().
|
||||
* On error errno is set (e.g. EINVAL for unknown id). Double-free is
|
||||
* NOT idempotent: the second call sets errno. */
|
||||
void mem_free_block_estex(uint8_t blk_id);
|
||||
void mem_free_block_bios(uint8_t blk_id);
|
||||
|
||||
/* Translate (block, page-index) into a physical page number suitable
|
||||
* for sprinter_page_w1/w2/w3() or the bank_*() helpers below.
|
||||
* blk_id: from mem_alloc_pages()
|
||||
* idx : 0..(n-1)
|
||||
* ret : physical page (1..255) on success; 0 on failure (errno set). */
|
||||
uint8_t mem_get_page_bios(uint8_t blk_id, uint8_t idx);
|
||||
|
||||
/* Query the EMM allocator state. Both pointers must be non-NULL.
|
||||
* Cannot fail (no error path). */
|
||||
void mem_info_estex(uint16_t *total, uint16_t *free_pages);
|
||||
void mem_info_bios(uint16_t *total, uint16_t *free_pages);
|
||||
|
||||
#define MEM_MANAGE_MODE_BIOS
|
||||
|
||||
#ifdef MEM_MANAGE_MODE_ESTEX
|
||||
|
||||
#define mem_alloc_pages mem_alloc_pages_estex
|
||||
#define mem_free_block mem_free_block_estex
|
||||
#define mem_info mem_info_estex
|
||||
|
||||
#define mem_get_page mem_get_page_bios
|
||||
|
||||
#elif defined MEM_MANAGE_MODE_BIOS
|
||||
|
||||
#define mem_alloc_pages mem_alloc_pages_bios
|
||||
#define mem_free_block mem_free_block_bios
|
||||
#define mem_info mem_info_bios
|
||||
|
||||
#define mem_get_page mem_get_page_bios
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* Far-page accessors via window 3 (base 0xC000, port 0xE2)
|
||||
*
|
||||
* Each call saves the current W3 mapping, sets W3 to `phys_page`, does
|
||||
* the access at (base + off_in_window), then restores the saved page.
|
||||
* Safe in `tiny`, `small`, `big` memory modes (W3 is free for data).
|
||||
* NOT safe in `huge` — banked code lives in W3 there.
|
||||
* =================================================================== */
|
||||
|
||||
/* Read one byte from phys_page at offset off_in_window (0..0x3FFF). */
|
||||
uint8_t bank_load_byte(uint8_t phys_page, uint16_t off_in_window);
|
||||
|
||||
/* Write byte `v` into phys_page at offset off_in_window. */
|
||||
void bank_store_byte(uint8_t phys_page, uint16_t off_in_window, uint8_t v);
|
||||
|
||||
/* Copy `n` bytes from phys_page[off..off+n-1] into the near buffer `dst`. */
|
||||
void bank_read(uint8_t phys_page, uint16_t off, void *dst, uint16_t n);
|
||||
|
||||
/* Copy `n` bytes from near buffer `src` into phys_page[off..off+n-1]. */
|
||||
void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n);
|
||||
|
||||
/* ===================================================================
|
||||
* Far-page accessors via window 1 (base 0x4000, port 0xA2)
|
||||
*
|
||||
* Same semantics as the W3 family but swap W1 instead. SAFE ONLY in
|
||||
* `--memory tiny` builds — in any other mode code lives in (or uses) W1
|
||||
* and swapping it mid-call will crash. See header notes above for the
|
||||
* full safety matrix.
|
||||
* =================================================================== */
|
||||
|
||||
uint8_t bank_load_byte_w1(uint8_t phys_page, uint16_t off_in_window);
|
||||
void bank_store_byte_w1(uint8_t phys_page, uint16_t off_in_window, uint8_t v);
|
||||
void bank_read_w1(uint8_t phys_page, uint16_t off, void *dst, uint16_t n);
|
||||
void bank_write_w1(uint8_t phys_page, uint16_t off, const void *src, uint16_t n);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ int vprintf(const char *, va_list);
|
||||
int vsprintf(char *, const char *, va_list);
|
||||
|
||||
/* puts / putchar / getchar — overridden by our libc to use ESTEX. */
|
||||
int puts (const char *);
|
||||
char puts (const char *);
|
||||
int putchar(int);
|
||||
int getchar(void);
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'A' && *p <= 'Z') *p += 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'a' && *p <= 'z') *p -= 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* div() comes from SDCC's z80.lib. */
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* bank_io_w1.c — far-page accessors that swap window 1 (port 0xA2,
|
||||
* base 0x4000). Mirror of bank_io.c but through W1.
|
||||
*
|
||||
* SAFE ONLY in `--memory tiny` builds. In tiny mode all code and data
|
||||
* fit in W2, so both W1 and W3 are free for arbitrary banked data.
|
||||
*
|
||||
* small / huge: code lives in W1 (HOME) → swapping W1 unmaps the
|
||||
* function mid-call and crashes.
|
||||
* big : banked code segment lives in W1 → swap clobbers it.
|
||||
*
|
||||
* Split into its own .rel so that programs using only the W3 variants
|
||||
* do NOT pull these functions in (and vice versa).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
uint8_t bank_load_byte_w1(uint8_t phys_page, uint16_t off_in_window)
|
||||
{
|
||||
uint8_t saved = _io_page_w1;
|
||||
sprinter_page_w1(phys_page);
|
||||
uint8_t v = *((volatile uint8_t *)(0x4000u + off_in_window));
|
||||
sprinter_page_w1(saved);
|
||||
return v;
|
||||
}
|
||||
|
||||
void bank_store_byte_w1(uint8_t phys_page, uint16_t off_in_window, uint8_t v)
|
||||
{
|
||||
uint8_t saved = _io_page_w1;
|
||||
sprinter_page_w1(phys_page);
|
||||
*((volatile uint8_t *)(0x4000u + off_in_window)) = v;
|
||||
sprinter_page_w1(saved);
|
||||
}
|
||||
|
||||
void bank_read_w1(uint8_t phys_page, uint16_t off, void *dst, uint16_t n)
|
||||
{
|
||||
uint8_t saved = _io_page_w1;
|
||||
sprinter_page_w1(phys_page);
|
||||
memcpy(dst, (const void *)(0x4000u + off), n);
|
||||
sprinter_page_w1(saved);
|
||||
}
|
||||
|
||||
void bank_write_w1(uint8_t phys_page, uint16_t off, const void *src, uint16_t n)
|
||||
{
|
||||
uint8_t saved = _io_page_w1;
|
||||
sprinter_page_w1(phys_page);
|
||||
memcpy((void *)(0x4000u + off), src, n);
|
||||
sprinter_page_w1(saved);
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
/*
|
||||
* bank_io — HOME-resident helpers to read/write a "far" page that lives
|
||||
* in some physical RAM page outside the currently-mapped W3 bank.
|
||||
* bank_io_w3.c — far-page accessors that swap window 3 (port 0xE2,
|
||||
* base 0xC000). Save the current W3 page, set the target, do the
|
||||
* byte/memcpy access, restore the saved page.
|
||||
*
|
||||
* - We always run from HOME (window 1, always mapped), so we are free
|
||||
* to swap W3 (port 0xE2) between the caller's bank and the data
|
||||
* page, then restore it before returning.
|
||||
* - The caller must not rely on W3 contents during the call — the swap
|
||||
* is transparent to instruction-fetch (we execute from W1), and only
|
||||
* this function touches W3.
|
||||
* - DI/EI is NOT applied around the swap; ISRs in HOME are unaffected,
|
||||
* and banked-call ISRs are not expected in our current design.
|
||||
* Safe in `tiny`, `small`, `big` memory modes (W3 is free for data).
|
||||
* NOT safe in `huge` — there banked code lives in W3.
|
||||
*
|
||||
* No DI/EI around the swap: ISRs that don't touch W3 are unaffected;
|
||||
* concurrent W3 use from an ISR is unsafe.
|
||||
*
|
||||
* The `_w1` family (bank_io_w1.c) is the mirror through W1 and is
|
||||
* `tiny`-only. See sprinter_mem.h for the full safety matrix.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM
|
||||
* wrappers for explicit 16 KB-page allocation.
|
||||
*
|
||||
* ESTEX $3C INFOMEM → HL=total pages, BC=free pages
|
||||
* ESTEX $3D GETMEM B=npages → A=block id, CF=err
|
||||
* ESTEX $3E FREEMEM A=block id → CF=err
|
||||
* BIOS $C4 EMM_GETPAGE A=blk, B=idx → A=physical page CF=err
|
||||
*
|
||||
* Pattern: every RST 10h / RST 8 is bracketed with push/pop IX because
|
||||
* ESTEX/BIOS clobber it and the C caller uses it as a frame pointer.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
uint8_t mem_alloc_pages(uint8_t n) __naked
|
||||
{
|
||||
(void)n;
|
||||
__asm
|
||||
;; SDCC single-uint8 arg → A on entry.
|
||||
push ix
|
||||
ld b, a
|
||||
ld c, #0x3D
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _alloc_fail
|
||||
ret
|
||||
_alloc_fail:
|
||||
call __errno_set
|
||||
xor a, a ; 0 = failure
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void mem_free_block(uint8_t blk_id) __naked
|
||||
{
|
||||
(void)blk_id;
|
||||
__asm
|
||||
;; SDCC single-uint8 arg → A on entry.
|
||||
push ix
|
||||
ld c, #0x3E
|
||||
rst #0x10
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint8_t mem_get_page(uint8_t blk_id, uint8_t idx) __naked
|
||||
{
|
||||
(void)blk_id; (void)idx;
|
||||
__asm
|
||||
;; 2-arg uint8/uint8: blk_id → A, idx → L.
|
||||
push ix
|
||||
ld b, l ; BIOS wants idx in B
|
||||
;; A still has blk_id
|
||||
ld c, #0xC4 ; BIOS EMM_GETPAGE
|
||||
rst #0x08
|
||||
pop ix
|
||||
;; A = physical page number. Return as uint8 → A.
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void mem_info(uint16_t *total, uint16_t *free_pages) __naked
|
||||
{
|
||||
(void)total; (void)free_pages;
|
||||
__asm
|
||||
;; HL = total, DE = free_pages on entry.
|
||||
;; ESTEX INFOMEM clobbers everything; stash both pointers on stack.
|
||||
push ix
|
||||
push hl ; [SP+0..1] = total ptr
|
||||
push de ; [SP+2..3] = free_pages ptr (wait wrong order)
|
||||
|
||||
;; Actually after two pushes: SP+0 = free_pages_ptr, SP+2 = total_ptr.
|
||||
;; That's the layout we'll use below.
|
||||
|
||||
ld c, #0x3C ; ESTEX INFOMEM → HL=total, BC=free
|
||||
rst #0x10
|
||||
;; HL = total value, BC = free value.
|
||||
|
||||
pop de ; DE = free_pages ptr
|
||||
ld a, c
|
||||
ld (de), a
|
||||
inc de
|
||||
ld a, b
|
||||
ld (de), a
|
||||
|
||||
pop de ; DE = total ptr
|
||||
ld a, l
|
||||
ld (de), a
|
||||
inc de
|
||||
ld a, h
|
||||
ld (de), a
|
||||
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* dec_print.c — compact decimal print primitives ported from solid-c
|
||||
* (third_party/solid-c/SRC/CLIB/STDLIB.ASM, modules dec8/dec16/dec32).
|
||||
*
|
||||
* void dec8 (uint8_t v) — no-leading-zero "0" .. "255"
|
||||
* void dec16(uint16_t v) — no-leading-zero "0" .. "65535"
|
||||
* void dec32(uint32_t v) — no-leading-zero "0" .. "4294967295"
|
||||
*
|
||||
* Algorithm: subtract-power-of-10 with running counter, then emit the
|
||||
* digit unless we are still on leading zeros. 32-bit values use a
|
||||
* BC/DE/HL pair plus the shadow set (exx) for the high half.
|
||||
*
|
||||
* Layout: dec32 is the master routine; dec8/dec16 are tiny wrappers
|
||||
* that prepare state and jump to internal entry points (__dec_entry3
|
||||
* for "last 3 digits", __dec_entry5 for "last 5") — same idea as
|
||||
* solid-c, sharing most of the per-digit code.
|
||||
*
|
||||
* ESTEX PUTCHAR ($5B) preserves IX (empirically verified) so no
|
||||
* push/pop ix around the RST.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Leading-zero suppression flag — 0 means "still skipping zeros",
|
||||
* non-zero means "first non-zero digit seen, print everything from
|
||||
* here on (including subsequent zeros)". */
|
||||
static uint8_t dec_flag = 0;
|
||||
|
||||
void dec8(uint8_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; A = v.
|
||||
ld l, a
|
||||
ld h, #0 ; HL = value
|
||||
xor a, a
|
||||
ld (_dec_flag), a ; reset suppress-leading-zero flag
|
||||
jp __dec_entry3
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void dec16(uint16_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; HL = v.
|
||||
exx
|
||||
ld hl, #0 ; HL alt = 0 (high 16 of composite)
|
||||
exx
|
||||
xor a, a
|
||||
ld (_dec_flag), a
|
||||
jp __dec_entry5
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void dec32(uint32_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; HL = high16, DE = low16 on entry (SDCC HLDE).
|
||||
;; Move high16 into HL alt (shadow set), low16 into HL.
|
||||
push hl
|
||||
exx
|
||||
pop hl ; HL alt = high16
|
||||
exx
|
||||
ex de, hl ; HL = low16
|
||||
xor a, a
|
||||
ld (_dec_flag), a
|
||||
|
||||
;; ---- 5 most-significant decades (1e9..1e5) ----
|
||||
ld de, #0xCA00
|
||||
exx
|
||||
ld de, #0x3B9A ; 0x3B9ACA00 = 1,000,000,000
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
ld de, #0xE100
|
||||
exx
|
||||
ld de, #0x05F5 ; 0x05F5E100 = 100,000,000
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
ld de, #0x9680
|
||||
exx
|
||||
ld de, #0x0098 ; 0x00989680 = 10,000,000
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
ld de, #0x4240
|
||||
exx
|
||||
ld de, #0x000F ; 0x000F4240 = 1,000,000
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
ld de, #0x86A0
|
||||
exx
|
||||
ld de, #0x0001 ; 0x000186A0 = 100,000
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
__dec_entry5:: ; entered from dec16
|
||||
ld de, #10000
|
||||
exx
|
||||
ld de, #0
|
||||
exx
|
||||
call _dec_get_d32
|
||||
|
||||
ld de, #1000
|
||||
call _dec_get_d16
|
||||
|
||||
__dec_entry3:: ; entered from dec8
|
||||
ld de, #100
|
||||
call _dec_get_d16
|
||||
ld de, #10
|
||||
call _dec_get_d16
|
||||
|
||||
;; Units digit — always emitted (so dec*(0) prints "0").
|
||||
ld a, l
|
||||
add a, #0x30
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
ret
|
||||
|
||||
;; ---- 32-bit: how many times DE+DE_alt fits in HL+HL_alt ----
|
||||
_dec_get_d32:
|
||||
ld a, #0x2F ; 0x2F = '0' minus 1 (pre-decrement)
|
||||
and a, a ; CF = 0
|
||||
_dec_get_d32_loop:
|
||||
inc a
|
||||
sbc hl, de ; low half
|
||||
exx
|
||||
sbc hl, de ; high half (with chained borrow)
|
||||
exx
|
||||
jp nc, _dec_get_d32_loop
|
||||
;; Overshot — restore the last good value.
|
||||
add hl, de
|
||||
exx
|
||||
adc hl, de
|
||||
exx
|
||||
jr _dec_emit_or_skip
|
||||
|
||||
;; ---- 16-bit: how many times DE fits in HL ----
|
||||
_dec_get_d16:
|
||||
ld a, #0x2F
|
||||
and a, a
|
||||
_dec_get_d16_loop:
|
||||
inc a
|
||||
sbc hl, de
|
||||
jp nc, _dec_get_d16_loop
|
||||
add hl, de
|
||||
;; Fall through to emit/skip.
|
||||
|
||||
_dec_emit_or_skip:
|
||||
;; A = digit char in 0x30..0x39. If non-'0', latch the flag.
|
||||
;; Print only if flag is non-zero.
|
||||
ld b, a ; save digit across the flag test
|
||||
cp a, #0x30
|
||||
jr z, _dec_check_flag
|
||||
ld (_dec_flag), a ; non-zero digit seen
|
||||
_dec_check_flag:
|
||||
ld a, (_dec_flag)
|
||||
or a, a
|
||||
ld a, b ; restore digit (ld does not touch flags)
|
||||
ret z ; leading zero — skip print
|
||||
|
||||
;; ESTEX PUTCHAR ($5B) preserves the main register set (BC, DE,
|
||||
;; HL, IX) but CLOBBERS the shadow set (BC alt, DE alt, HL alt).
|
||||
;; The 32-bit subtract-power-of-10 loop in _dec_get_d32 keeps
|
||||
;; the high 16 bits of the running remainder in HL alt, so we
|
||||
;; save/restore HL alt around the RST. Main HL (= low 16 of
|
||||
;; remainder) survives the call untouched, no save needed.
|
||||
;; See memory/estex_putchar_abi.md.
|
||||
exx
|
||||
push hl
|
||||
exx
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
exx
|
||||
pop hl
|
||||
exx
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -17,13 +17,7 @@ int getchar(void) __naked
|
||||
ld c, #0x30 ; ESTEX WAITKEY
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld a, e ; E = ASCII (already the low byte of our return DE)
|
||||
or a, a
|
||||
jr Z, no_ascii
|
||||
ld d, #0
|
||||
ret
|
||||
no_ascii:
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* hex_print.c — compact hex print primitives ported from solid-c
|
||||
* (third_party/solid-c/SRC/CLIB/STDLIB.ASM, modules hex8/hex16/hex32).
|
||||
*
|
||||
* void hex8 (uint8_t v) — always-2-digit "00" .. "FF"
|
||||
* void hex16(uint16_t v) — always-4-digit "0000" .. "FFFF"
|
||||
* void hex32(uint32_t v) — always-8-digit "00000000" .. "FFFFFFFF"
|
||||
*
|
||||
* Each nibble is emitted via the classic Z80 `cp 10 / sbc 0x69 / daa`
|
||||
* trick — 5 bytes per nibble. hex8 self-calls for the high nibble
|
||||
* then falls through for the low nibble. hex16/hex32 split into two
|
||||
* hex8/hex16 calls.
|
||||
*
|
||||
* ESTEX PUTCHAR ($5B) preserves IX (empirically verified) so we skip
|
||||
* the usual push/pop ix around the RST.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void hex8(uint8_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; A = v on entry.
|
||||
push af
|
||||
rra
|
||||
rra
|
||||
rra
|
||||
rra
|
||||
call _hex8_digit
|
||||
pop af
|
||||
_hex8_digit:
|
||||
and a, #0x0F
|
||||
cp a, #10
|
||||
sbc a, #0x69
|
||||
daa
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void hex16(uint16_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; HL = v on entry.
|
||||
ld a, h
|
||||
push hl
|
||||
call _hex8
|
||||
pop hl
|
||||
ld a, l
|
||||
jp _hex8 ; tail-call
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void hex32(uint32_t v) __naked
|
||||
{
|
||||
(void)v;
|
||||
__asm
|
||||
;; HL = high16, DE = low16 on entry (SDCC HLDE).
|
||||
push de
|
||||
call _hex16
|
||||
pop hl
|
||||
jp _hex16 ; tail-call
|
||||
__endasm;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* print_hex — print a single byte as two uppercase hex digits.
|
||||
*
|
||||
* No printf yet; this is what bare-metal debug looks like in stage 3.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
void print_hex(uint8_t v)
|
||||
{
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
putchar(digits[(v >> 4) & 0x0F]);
|
||||
putchar(digits[v & 0x0F]);
|
||||
}
|
||||
+6
-14
@@ -19,23 +19,15 @@ int putchar(int c) __naked
|
||||
{
|
||||
(void)c;
|
||||
__asm
|
||||
ld a, l ; SDCC __sdcccall(1) int → HL
|
||||
push ix
|
||||
ld a, l
|
||||
cp #0x0A
|
||||
jr nz, _pc_emit
|
||||
ld a, #0x0D ; CR before LF
|
||||
push af
|
||||
jr nz, cputc
|
||||
call cputc
|
||||
ld a, #0x0D
|
||||
cputc:
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
ld a, #0x0A
|
||||
_pc_emit:
|
||||
push af
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
pop ix
|
||||
ld e, a
|
||||
ld e, l
|
||||
ld d, #0
|
||||
ret
|
||||
__endasm;
|
||||
|
||||
+21
-34
@@ -14,17 +14,11 @@
|
||||
* - Avoid trailing PUTCHAR after PCHARS — empirically that sometimes
|
||||
* drops the next char. Embed the line ending inside the PCHARS
|
||||
* buffer instead.
|
||||
* - Strings longer than the buffer fall back to per-char putchar so
|
||||
* we never silently truncate.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PUTS_BUF_SIZE 256 /* body bytes before CR expansion */
|
||||
|
||||
static char puts_buf[PUTS_BUF_SIZE + 3]; /* +3 for trailing CR LF NUL */
|
||||
|
||||
static void pchars(const char *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
@@ -37,33 +31,26 @@ static void pchars(const char *s) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
int puts(const char *s)
|
||||
char puts(const char *s) __naked
|
||||
{
|
||||
uint16_t n = 0;
|
||||
uint16_t i = 0;
|
||||
|
||||
while (s[i] && n < PUTS_BUF_SIZE - 1) {
|
||||
char c = s[i++];
|
||||
if (c == '\n') {
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
} else {
|
||||
puts_buf[n++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (s[i]) {
|
||||
/* Overflow — char-by-char fallback so we never truncate. */
|
||||
for (uint16_t k = 0; s[k]; k++)
|
||||
putchar((unsigned char)s[k]);
|
||||
putchar('\n');
|
||||
return 0;
|
||||
}
|
||||
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
puts_buf[n] = 0;
|
||||
|
||||
pchars(puts_buf);
|
||||
return 0;
|
||||
(void)s;
|
||||
__asm
|
||||
puts_:
|
||||
ld a, (hl)
|
||||
or a
|
||||
jr z, fin_
|
||||
push hl
|
||||
ld l, a
|
||||
ld h, #0
|
||||
call _putchar
|
||||
pop hl
|
||||
inc hl
|
||||
jp puts_
|
||||
;
|
||||
fin_:
|
||||
ld l, #0x0A
|
||||
ld h, #0
|
||||
call _putchar
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* solid_helpers.c — small Solid-C compatibility helpers.
|
||||
*
|
||||
* Each function maps to the standard printf/sprintf machinery already
|
||||
* available from SDCC's z80.lib + our overrides. No new syscalls.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- gets — dangerous but Solid-C provides it ---------------------- */
|
||||
char *gets(char *buf)
|
||||
{
|
||||
int i = 0;
|
||||
int c;
|
||||
for (;;) {
|
||||
c = getchar();
|
||||
if (c == EOF) {
|
||||
if (i == 0) return 0;
|
||||
break;
|
||||
}
|
||||
if (c == '\n' || c == '\r') break;
|
||||
buf[i++] = (char)c;
|
||||
}
|
||||
buf[i] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ---- decimal output: use printf %u ---------------------------------- */
|
||||
|
||||
void dec8(uint8_t v)
|
||||
{
|
||||
printf("%u", (unsigned)v);
|
||||
}
|
||||
|
||||
void dec16(uint16_t v)
|
||||
{
|
||||
printf("%u", (unsigned)v);
|
||||
}
|
||||
|
||||
void dec32(uint32_t v)
|
||||
{
|
||||
printf("%lu", (unsigned long)v);
|
||||
}
|
||||
|
||||
/* ---- hex output: zero-padded ---------------------------------------- */
|
||||
|
||||
void hex8(uint8_t v)
|
||||
{
|
||||
printf("%02X", (unsigned)v);
|
||||
}
|
||||
|
||||
void hex16(uint16_t v)
|
||||
{
|
||||
printf("%04X", (unsigned)v);
|
||||
}
|
||||
|
||||
void hex32(uint32_t v)
|
||||
{
|
||||
printf("%08lX", (unsigned long)v);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'A' && *p <= 'Z') || (*p >= 0x80 && *p <= 0x8F)) *p += 'a' - 'A';
|
||||
else if ((*p >= 0x90 && *p <= 0x9F)) *p += 0x50;
|
||||
else if ((*p == 0xF0)) *p = 0xF1;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'a' && *p <= 'z') || (*p >= 0xA0 && *p <= 0xAF)) *p -= 'a' - 'A';
|
||||
else if ((*p >= 0xE0 && *p <= 0xEF)) *p -= 0x50;
|
||||
else if ((*p == 0xF1)) *p = 0xF0;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := argv
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := assrtest
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := attrprob
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -8,4 +8,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := banked
|
||||
MEMORY := huge
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --bank 2=bank2.c
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -7,7 +7,7 @@ void bank1_func(int x) __banked
|
||||
(void)x;
|
||||
puts("BANK1: hello from a banked function!");
|
||||
puts("BANK1: window 3 phys page = ");
|
||||
print_hex(_io_page_w3); /* should be BANK1's phys page */
|
||||
hex8(_io_page_w3); /* should be BANK1's phys page */
|
||||
putchar('\n');
|
||||
putchar('1');
|
||||
putchar('=');
|
||||
@@ -7,7 +7,7 @@ void bank2_func(int x) __banked
|
||||
(void)x;
|
||||
puts("BANK2: hello from the second bank!");
|
||||
puts("BANK2: window 3 phys page = ");
|
||||
print_hex(_io_page_w3); /* should be BANK2's phys page */
|
||||
hex8(_io_page_w3); /* should be BANK2's phys page */
|
||||
putchar('\n');
|
||||
putchar('2');
|
||||
putchar('=');
|
||||
@@ -13,7 +13,7 @@ extern uint8_t bank_pages[]; /* filled by crt0_banked.s */
|
||||
static void show_w3(const char *who)
|
||||
{
|
||||
puts(who);
|
||||
print_hex(_io_page_w3); /* current physical page mapped in window 3 */
|
||||
hex8(_io_page_w3); /* current physical page mapped in window 3 */
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ int main(void)
|
||||
show_w3("HOME: window 3 phys page = ");
|
||||
|
||||
puts("HOME: bank_pages[1] (BANK1 phys) = ");
|
||||
print_hex(bank_pages[1]);
|
||||
hex8(bank_pages[1]);
|
||||
putchar('\n');
|
||||
puts("HOME: bank_pages[2] (BANK2 phys) = ");
|
||||
print_hex(bank_pages[2]);
|
||||
hex8(bank_pages[2]);
|
||||
putchar('\n');
|
||||
|
||||
puts("HOME: calling bank1_func(42)...");
|
||||
@@ -9,4 +9,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := bankedbg
|
||||
MEMORY := big
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --bank 2=bank2.c
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
void bank1_func(int x) __banked
|
||||
{
|
||||
(void)x;
|
||||
puts("BANK1: hello from a banked function (W1)!");
|
||||
puts("BANK1: window 1 phys page = ");
|
||||
hex8(_io_page_w1); /* should be BANK1's phys page */
|
||||
putchar('\n');
|
||||
putchar('1');
|
||||
putchar('=');
|
||||
putchar('0' + (x / 10) % 10);
|
||||
putchar('0' + x % 10);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
void bank1_func2(int x) __banked
|
||||
{
|
||||
(void)x;
|
||||
puts("BANK1-2: hello from a banked function (W1)!");
|
||||
puts("BANK1-2: window 1 phys page = ");
|
||||
hex8(_io_page_w1); /* should be BANK1's phys page */
|
||||
putchar('\n');
|
||||
putchar('1');
|
||||
putchar('=');
|
||||
putchar('0' + (x / 10) % 10);
|
||||
putchar('0' + x % 10);
|
||||
putchar('\n');
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
void bank1_func2(int x) __banked;
|
||||
|
||||
void bank2_func(int x) __banked
|
||||
{
|
||||
(void)x;
|
||||
puts("BANK2: hello from the second bank (W1)!");
|
||||
puts("BANK2: window 1 phys page = ");
|
||||
print_hex(_io_page_w1); /* should be BANK2's phys page */
|
||||
hex8(_io_page_w1); /* should be BANK2's phys page */
|
||||
putchar('\n');
|
||||
putchar('2');
|
||||
putchar('=');
|
||||
@@ -15,4 +17,6 @@ void bank2_func(int x) __banked
|
||||
putchar('0' + (x / 10) % 10);
|
||||
putchar('0' + x % 10);
|
||||
putchar('\n');
|
||||
|
||||
bank1_func2(10);
|
||||
}
|
||||
@@ -24,7 +24,7 @@ extern uint8_t bank_pages[]; /* filled by crt0_banked.s */
|
||||
static void show_w1(const char *who)
|
||||
{
|
||||
puts(who);
|
||||
print_hex(_io_page_w1); /* current physical page mapped in window 1 */
|
||||
hex8(_io_page_w1); /* current physical page mapped in window 1 */
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ int main(void)
|
||||
show_w1("HOME: window 1 phys page = ");
|
||||
|
||||
puts("HOME: bank_pages[1] (BANK1 phys) = ");
|
||||
print_hex(bank_pages[1]);
|
||||
hex8(bank_pages[1]);
|
||||
putchar('\n');
|
||||
puts("HOME: bank_pages[2] (BANK2 phys) = ");
|
||||
print_hex(bank_pages[2]);
|
||||
hex8(bank_pages[2]);
|
||||
putchar('\n');
|
||||
|
||||
puts("HOME: calling bank1_func(42)...");
|
||||
@@ -9,4 +9,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := banklocl
|
||||
MEMORY := huge
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --mkexe -p --mkexe 0
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -3,4 +3,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := cat
|
||||
EXTRA_DATA := test.txt
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := conio
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := conio2
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := dec_test
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <stdio.h>
|
||||
extern void dec8 (unsigned char);
|
||||
extern void dec16(unsigned int);
|
||||
extern void dec32(unsigned long);
|
||||
extern void hex8 (unsigned char);
|
||||
extern void hex16(unsigned int);
|
||||
extern void hex32(unsigned long);
|
||||
|
||||
int main(void) {
|
||||
puts("hex8:");
|
||||
hex8(0x00); puts("");
|
||||
hex8(0x5A); puts("");
|
||||
hex8(0xFF); puts("");
|
||||
puts("hex16:");
|
||||
hex16(0x0000); puts("");
|
||||
hex16(0xCAFE); puts("");
|
||||
hex16(0xFFFF); puts("");
|
||||
puts("hex32:");
|
||||
hex32(0x00000000UL); puts("");
|
||||
hex32(0xDEADBEEFUL); puts("");
|
||||
hex32(0xFFFFFFFFUL); puts("");
|
||||
puts("dec8:");
|
||||
dec8(0); puts("");
|
||||
dec8(42); puts("");
|
||||
dec8(255); puts("");
|
||||
puts("dec16:");
|
||||
dec16(0); puts("");
|
||||
dec16(1234); puts("");
|
||||
dec16(65535); puts("");
|
||||
puts("dec32:");
|
||||
dec32(0UL); puts("");
|
||||
dec32(98765UL); puts("");
|
||||
dec32(123456UL); puts("");
|
||||
dec32(123456789UL); puts("");
|
||||
dec32(4294967295UL); puts("");
|
||||
puts("done — any key");
|
||||
(void)getchar();
|
||||
return 0;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := errno
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := filetest
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,5 @@
|
||||
# Build cat.exe — uses lib/sprinter.lib in TINY memory mode.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gets
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char buff [256];
|
||||
|
||||
puts("--- gets test ---");
|
||||
|
||||
gets(buff);
|
||||
|
||||
puts("");
|
||||
puts("loaded string:");
|
||||
puts(buff);
|
||||
puts("done");
|
||||
(void)getchar();
|
||||
return 0;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_d16
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_dbuf
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_demo
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_mous
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_text
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := hello
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -18,7 +18,7 @@ int main(void)
|
||||
{
|
||||
errno = -1;
|
||||
int16_t a0 = 0, a1 = 0;
|
||||
a0 = get_text_attr();
|
||||
a0 = get_text_attr();
|
||||
|
||||
uint8_t vMode = get_videotextmode();
|
||||
set_videotextmode(TEXT_MODE_80x32);
|
||||
@@ -52,7 +52,7 @@ int main(void)
|
||||
|
||||
/* Back to a normal attribute so the goodbye reads cleanly. */
|
||||
textattr(COLOR(COLOR_LIGHTGRAY, COLOR_BLACK));
|
||||
printf("a0=%d a1=%d now=%d errno=%d\n",
|
||||
cprintf("a0=%d a1=%d now=%d errno=%d\n\r",
|
||||
a0, a1, get_text_attr(), errno);
|
||||
#ifdef DEBUG_RT
|
||||
printf("w2_self_allocated = %u\n", w2_self_allocated);
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := ls
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := malloc
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mem_test
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -16,6 +16,8 @@
|
||||
* 5. Free the block, show the free-page count again.
|
||||
*/
|
||||
|
||||
uint32_t buff[256];
|
||||
|
||||
static void show_mem(const char *label)
|
||||
{
|
||||
uint16_t total, free_pages;
|
||||
@@ -28,6 +30,13 @@ int main(void)
|
||||
{
|
||||
puts("Sprinter page allocator demo");
|
||||
puts("");
|
||||
|
||||
memset(buff, 0, sizeof(buff));
|
||||
// printf("mem_io_ports_test() = 0x%02X\n", mem_io_ports_test());
|
||||
printf("buff size (sizeof) = %u\n", sizeof(buff));
|
||||
printf("buff size one element (sizeof) = %u\n", sizeof(buff[0]));
|
||||
printf("buff first element = %u\n", buff[0]);
|
||||
printf("buff last element = %u\n", buff[(sizeof(buff) / sizeof(buff[0])) - 1]);
|
||||
|
||||
show_mem("before:");
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mouse
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <conio.h>
|
||||
#include <mouse.h>
|
||||
|
||||
mouse_state_t st;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
textattr(COLOR(COLOR_LIGHTGRAY, COLOR_BLACK));
|
||||
@@ -29,7 +31,6 @@ int main(void)
|
||||
mouse_bounds_y(0, 255);
|
||||
mouse_show();
|
||||
|
||||
mouse_state_t st;
|
||||
int last_x = -1, last_y = -1;
|
||||
uint8_t last_btn = 0xFF;
|
||||
/* Sensitivity is a "raw steps per cursor pixel" divider: smaller =
|
||||
@@ -41,11 +42,13 @@ int main(void)
|
||||
mouse_set_sensitivity(sens_x, sens_y);
|
||||
int sens_dirty = 1;
|
||||
|
||||
st.x = st.y = 0; st.buttons = 0;
|
||||
|
||||
while (1) {
|
||||
mouse_read(&st);
|
||||
if (st.x != last_x || st.y != last_y || st.buttons != last_btn) {
|
||||
gotoxy(0, 6);
|
||||
cprintf("x=%4u y=%4u text(%2u,%2u) buttons=0x%02X L%c R%c ",
|
||||
printf("x=%4u y=%4u text(%2u,%2u) buttons=0x%02X L%c R%c ",
|
||||
st.x, st.y,
|
||||
st.x / 8, st.y / 8,
|
||||
st.buttons,
|
||||
@@ -57,19 +60,19 @@ int main(void)
|
||||
}
|
||||
if (sens_dirty) {
|
||||
gotoxy(0, 8);
|
||||
cprintf("sensitivity horz=%3u vert=%3u ", sens_x, sens_y);
|
||||
printf("sensitivity horz=%3u vert=%3u ", sens_x, sens_y);
|
||||
sens_dirty = 0;
|
||||
}
|
||||
|
||||
if (!kbhit()) continue;
|
||||
int k = getch();
|
||||
if (k == 27) break; /* ESC */
|
||||
if (k == '1' && sens_x > 1) { sens_x -= 1; sens_dirty = 1; }
|
||||
if (k == '2' && sens_x < 254) { sens_x += 1; sens_dirty = 1; }
|
||||
if (k == '3' && sens_y > 1) { sens_y -= 1; sens_dirty = 1; }
|
||||
if (k == '4' && sens_y < 254) { sens_y += 1; sens_dirty = 1; }
|
||||
if (sens_dirty)
|
||||
mouse_set_sensitivity(sens_x, sens_y);
|
||||
if (!kbhit()) continue;
|
||||
int k = getch();
|
||||
if (k == 27) break; /* ESC */
|
||||
if (k == '1' && sens_x > 1) { sens_x -= 1; sens_dirty = 1; }
|
||||
if (k == '2' && sens_x < 254) { sens_x += 1; sens_dirty = 1; }
|
||||
if (k == '3' && sens_y > 1) { sens_y -= 1; sens_dirty = 1; }
|
||||
if (k == '4' && sens_y < 254) { sens_y += 1; sens_dirty = 1; }
|
||||
if (sens_dirty)
|
||||
mouse_set_sensitivity(sens_x, sens_y);
|
||||
}
|
||||
|
||||
mouse_hide();
|
||||
@@ -0,0 +1,7 @@
|
||||
# Build open_env_test.exe — uses lib/sprinter.lib in TINY memory mode.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := openenv
|
||||
MEMORY := big
|
||||
EXTRA_FLAGS :=
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -11,6 +11,9 @@
|
||||
* Touches the floppy: creates TMP1.TXT and TMP2.TXT, writes a few bytes,
|
||||
* verifies O_CREAT / O_EXCL / O_TRUNC / O_APPEND behaviour, then deletes.
|
||||
*/
|
||||
|
||||
extern uint8_t estex_file_handle;
|
||||
|
||||
|
||||
static void show_errno(const char *label)
|
||||
{
|
||||
@@ -22,6 +25,8 @@ int main(void)
|
||||
puts("Sprinter open() + env API test");
|
||||
puts("");
|
||||
|
||||
printf("estex_file_handle: fd=%u\n", estex_file_handle);
|
||||
|
||||
/* --- open() state machine ------------------------------------- */
|
||||
|
||||
/* 1. O_CREAT|O_EXCL: must succeed first time, must fail on retry. */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user