From 60373930fb7dfdca62b5fc37fce3c6d0e6a36257 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 6 Jul 2026 16:35:58 +0300 Subject: [PATCH] =?UTF-8?q?libc:=20Solid-C=20=D1=81=D0=BE=D0=B2=D0=BC?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20(?= =?UTF-8?q?=D0=9F3)=20+=20rename/isatty=20(=D0=9F4);=20scanf-=D1=81=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - : getdate/gettime/setdate/settime (структуры Turbo-C, обёртки над getdatetime), getdisk/setdisk (ESTEX $02/$01), absread/abswrite (BIOS $55/$56, rst 8 — номера найдены в solid-c DOS.ASM; сектор 0 = boot логического диска) - scanf/fscanf/sscanf: своё C-ядро _scanf_core (%d %u %x %o %c %s, модификатор l, ширина, подавление '*', %%); в SDCC z80 scanf нет, asm solid-c не портируем из-за чужого ABI; 22 хост-теста ядра - хвост П2: fdopen/freopen/fclosall/fgetpos/fsetpos поверх таблицы FILE; парсер режима и выдача слота вынесены в _file_mode/_file_slot - rename() — ESTEX RENAME $10; isatty(fd) = fd <= 0 (tty только псевдо-fd 0/-1/-2: из CLI DSS манипуляторы идут с 1 — verified, fd 1 не резерв, под Flex Navigator его держит навигатор) - errno.h: Solid-C имена ошибок (EZERO/EINVFNC/ENOFILE/...) как алиасы - — зонтичный заголовок для портирования; ltell/_setargv в sprinter_compat.h; div/ldiv — из SDCC (проверено) - tests/solidt — smoke всех П3/П4 API, зелёный в MAME (вкл. absread boot-сектора с сигнатурой 55AA) Co-Authored-By: Claude Fable 5 --- Makefile | 2 +- docs/libc-roadmap.md | 29 +++--- docs/solid_c_compatibility.md | 25 +++++ libc/file/_file.h | 12 +++ libc/file/_file_mode.c | 43 ++++++++ libc/file/_file_slot.c | 30 ++++++ libc/file/fclosall.c | 11 +++ libc/file/fdopen.c | 30 ++++++ libc/file/fgetpos.c | 15 +++ libc/file/fopen.c | 75 ++------------ libc/file/freopen.c | 42 ++++++++ libc/file/fscanf.c | 27 +++++ libc/file/fsetpos.c | 12 +++ libc/file/scanf.c | 28 ++++++ libc/include/dos.h | 49 +++++++++ libc/include/errno.h | 21 ++++ libc/include/sprinter_compat.h | 5 + libc/include/sprinter_solid.h | 22 +++++ libc/include/stdio.h | 25 ++++- libc/include/unistd.h | 4 + libc/io/absread.c | 46 +++++++++ libc/io/abswrite.c | 45 +++++++++ libc/io/getdisk.c | 18 ++++ libc/io/isatty.c | 16 +++ libc/io/rename.c | 26 +++++ libc/io/setdisk.c | 28 ++++++ libc/stdio/_scanf.h | 22 +++++ libc/stdio/_scanf_core.c | 176 +++++++++++++++++++++++++++++++++ libc/stdio/sscanf.c | 32 ++++++ libc/time/getdate.c | 16 +++ libc/time/gettime.c | 18 ++++ libc/time/setdate.c | 18 ++++ libc/time/settime.c | 18 ++++ tests/solidt/Makefile | 5 + tests/solidt/solidt.c | 114 +++++++++++++++++++++ 35 files changed, 1017 insertions(+), 88 deletions(-) create mode 100644 libc/file/_file_mode.c create mode 100644 libc/file/_file_slot.c create mode 100644 libc/file/fclosall.c create mode 100644 libc/file/fdopen.c create mode 100644 libc/file/fgetpos.c create mode 100644 libc/file/freopen.c create mode 100644 libc/file/fscanf.c create mode 100644 libc/file/fsetpos.c create mode 100644 libc/file/scanf.c create mode 100644 libc/include/dos.h create mode 100644 libc/include/sprinter_solid.h create mode 100644 libc/io/absread.c create mode 100644 libc/io/abswrite.c create mode 100644 libc/io/getdisk.c create mode 100644 libc/io/isatty.c create mode 100644 libc/io/rename.c create mode 100644 libc/io/setdisk.c create mode 100644 libc/stdio/_scanf.h create mode 100644 libc/stdio/_scanf_core.c create mode 100644 libc/stdio/sscanf.c create mode 100644 libc/time/getdate.c create mode 100644 libc/time/gettime.c create mode 100644 libc/time/setdate.c create mode 100644 libc/time/settime.c create mode 100644 tests/solidt/Makefile create mode 100644 tests/solidt/solidt.c diff --git a/Makefile b/Makefile index 2804e3b..a3888d1 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ # 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 fdmax fbench \ + assrtest ptime stattest filetest fdmax fbench solidt \ gfx_demo gfx_d16 gfx_text gfx_mous # Larger end-user applications under examples/. diff --git a/docs/libc-roadmap.md b/docs/libc-roadmap.md index e6f3291..8dc1243 100644 --- a/docs/libc-roadmap.md +++ b/docs/libc-roadmap.md @@ -53,27 +53,20 @@ internal-модули, комментарии на русском, без `= 0`. - [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 совместимость — остатки (docs/solid_c_compatibility.md) +## П3. Solid-C совместимость — ЗАКРЫТ 2026-07-06 -Phase 1 (алиасы) фактически закрыт — home/inp/outp/enable/disable, ms_*, -getc/putc, min/max, dec*/hex*, strlwr/strupr, cgets есть. Осталось: +Всё сделано (детали в docs/solid_c_compatibility.md): getdisk/setdisk, +getdate/gettime/setdate/settime + , ltell/_setargv, errno-алиасы, +, 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-прогона. -- [ ] getdisk()/setdisk() — ESTEX $02 CURDISK / $01 CHDISK -- [ ] getdate/gettime/setdate/settime — обёртки над getdatetime/setdatetime -- [ ] seek/tell/ltell, setmem/movmem, isascii, abort(), div(), remove→unlink, _ffirst -- [ ] errno-константы Solid-C (EZERO/EINVFNC/…) как алиасы в errno.h -- [ ] `` — зонтичный compat-заголовок -- [ ] обновить статусы/history в solid_c_compatibility.md (Phase 1 done) +## П4. Недостающие POSIX-мелочи — ЗАКРЫТ 2026-07-06 -Research (Phase 3): absread/abswrite (найти ESTEX READ_SECT/WRITE_SECT), -scanf-семейство (**в SDCC 4.5 z80 его НЕТ** — писать самим или задокументировать -отказ), brk/sbrk vs SDCC heap, isatty, ioctl (скорее скип). - -## П4. Недостающие POSIX-мелочи вне Solid-C списка - -- [ ] **rename()** — в gap-анализе отсутствует, ESTEX почти наверняка имеет - RENAME (~$0F, рядом с DELETE $0E) — проверить по докам ESTEX и добавить -- [ ] isatty(fd) — тривиально (fd<0/флаги) +- [x] rename() — ESTEX RENAME $10 (HL=старое, DE=новое), libc/io/rename.c +- [x] isatty(fd) — fd < 2 (манипуляторы DSS с 2; консольные псевдо-fd 0/-1/-2) ## П5. Заголовки и гигиена сборки diff --git a/docs/solid_c_compatibility.md b/docs/solid_c_compatibility.md index 86b8e65..6130e2c 100644 --- a/docs/solid_c_compatibility.md +++ b/docs/solid_c_compatibility.md @@ -130,6 +130,31 @@ These will be in `docs/solid_c_diff.md`: To make porting easier, add a single `` that includes all the standard headers (`stdio.h`, `string.h`, `conio.h`, etc.) — Solid-C programs can `#include ` 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 в + ), остальное было готово ранее. +- **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. +- **Зонтичный заголовок**: . +- Тест: tests/solidt (MAME). + ## History +- 2026-07-06 — Phase 1/2/3 закрыты: dos.h (даты/диски/сектора), + scanf-семейство, fdopen/freopen/fclosall/fgetpos/fsetpos, + rename/isatty (П4), errno-алиасы, sprinter_solid.h, тест solidt - 2026-06-01 — initial gap analysis vs Solid-C v2004 diff --git a/libc/file/_file.h b/libc/file/_file.h index d859b3f..934f284 100644 --- a/libc/file/_file.h +++ b/libc/file/_file.h @@ -19,6 +19,18 @@ /* Таблица потоков (_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 байт), поток продолжает работать. */ diff --git a/libc/file/_file_mode.c b/libc/file/_file_mode.c new file mode 100644 index 0000000..99fc2c6 --- /dev/null +++ b/libc/file/_file_mode.c @@ -0,0 +1,43 @@ +/* + * _file_mode_flags — парсер строки режима fopen/fdopen/freopen. + * Поддержаны r, w, a; '+' в любом месте хвоста; 'b'/'t' игнорируются — + * весь I/O на Sprinter бинарный. + */ + +#include +#include +#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; +} diff --git a/libc/file/_file_slot.c b/libc/file/_file_slot.c new file mode 100644 index 0000000..99ea04b --- /dev/null +++ b/libc/file/_file_slot.c @@ -0,0 +1,30 @@ +/* + * _file_slot_take — найти свободный слот таблицы потоков и при первом + * обращении взвести atexit(_fclosall), чтобы exit() сбрасывал + * несброшенную запись всех потоков (требование стандарта). + * Слот НЕ помечается занятым — вызывающий (fopen/fdopen) заполняет + * поля и последним выставляет flags. + */ + +#include /* atexit */ +#include +#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; +} diff --git a/libc/file/fclosall.c b/libc/file/fclosall.c new file mode 100644 index 0000000..d23c1e8 --- /dev/null +++ b/libc/file/fclosall.c @@ -0,0 +1,11 @@ +/* + * fclosall — закрыть все открытые потоки таблицы (имя Solid-C); + * публичная обёртка над _fclosall, который и так навешен на atexit. + */ + +#include "_file.h" + +void fclosall(void) +{ + _fclosall(); +} diff --git a/libc/file/fdopen.c b/libc/file/fdopen.c new file mode 100644 index 0000000..a02acb8 --- /dev/null +++ b/libc/file/fdopen.c @@ -0,0 +1,30 @@ +/* + * fdopen — завернуть уже открытый низкоуровневый fd в поток FILE* + * (Solid-C категория C). Режим должен соответствовать тому, с которым + * открывался fd — библиотека проверить это не может. После fdopen + * файл закрывается через fclose (не close!): fclose сбросит буфер и + * закроет сам fd. Счётчик _fd_guard не трогаем — fd уже посчитан + * своим open(). + */ + +#include +#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; +} diff --git a/libc/file/fgetpos.c b/libc/file/fgetpos.c new file mode 100644 index 0000000..8b84e49 --- /dev/null +++ b/libc/file/fgetpos.c @@ -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; +} diff --git a/libc/file/fopen.c b/libc/file/fopen.c index c4291f5..cea6ceb 100644 --- a/libc/file/fopen.c +++ b/libc/file/fopen.c @@ -1,89 +1,32 @@ /* * fopen — открыть файл как буферизованный поток FILE* (вариант B+). * - * FILE берётся из статической таблицы _file_slots (свободный слот: - * flags == 0), без malloc — сам буфер выделится лениво при первой - * операции. Первый успешный fopen вешает _fclosall на atexit, чтобы - * exit() сбрасывал несброшенную запись всех потоков. + * FILE берётся из статической таблицы (_file_slot_take, он же взводит + * atexit(_fclosall)); сам буфер выделится лениво при первой операции. * NULL при: плохом режиме (EINVAL), занятой таблице (EMFILE), ошибке * open (errno от него). */ -#include -#include /* atexit */ #include -#include -#include #include "_file.h" -/* Транслировать строку режима fopen() в подмножество флагов open(), - * которое понимает libc/io/open.c. Поддержаны r, w, a, "+" в любом - * месте хвоста; "b"/"t" игнорируются — весь I/O бинарный. */ -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; - } - 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) { - static uint8_t atexit_armed; - uint8_t ff; - int oflags = mode_to_flags(mode, &ff); - if (oflags < 0) return NULL; + int oflags = _file_mode_flags(mode, &ff); + if (oflags < 0) return 0; - /* Свободный слот таблицы — ДО open, чтобы не открывать зря. */ - FILE *fp = _file_slots; - for (uint8_t i = 0; ; i++, fp++) { - if (i >= OPEN_MAX) { - errno = EMFILE; - return NULL; - } - if (fp->flags == 0) break; - } + FILE *fp = _file_slot_take(); /* слот — ДО open, чтобы не открывать зря */ + if (!fp) return 0; int fd = open(path, oflags); - if (fd < 0) return NULL; + if (fd < 0) return 0; fp->fd = fd; - fp->buf = NULL; - fp->curp = NULL; + fp->buf = 0; + fp->curp = 0; fp->level = 0; fp->hold = EOF; fp->flags = ff; /* последним — занимает слот */ - - if (!atexit_armed) { - atexit_armed = 1; - atexit(_fclosall); /* exit() сбросит все потоки */ - } return fp; } diff --git a/libc/file/freopen.c b/libc/file/freopen.c new file mode 100644 index 0000000..913874a --- /dev/null +++ b/libc/file/freopen.c @@ -0,0 +1,42 @@ +/* + * freopen — переоткрыть поток на другой файл, переиспользуя тот же + * FILE* (и его буфер, если уже выделен). + * + * Для файлового потока: сброс буфера, close старого fd, open нового. + * Для консольного псевдопотока (stdout/stderr/stdin): превращает его + * в файловый — работает ТОЛЬКО для операций через FILE* (fputs(..., + * stdout) и т.п.); printf/puts/putchar идут через ESTEX напрямую и + * НЕ перенаправляются. NULL при ошибке (поток при этом закрыт — + * по стандарту). + */ + +#include +#include +#include "_file.h" + +FILE *freopen(const char *path, const char *mode, FILE *fp) +{ + if (!fp) return 0; + + uint8_t ff; + int oflags = _file_mode_flags(mode, &ff); + + /* Закрыть текущее содержимое потока (буфер сохраняем). */ + if (!(fp->flags & (_F_CONIN | _F_CONOUT))) { + _file_sync(fp); + close(fp->fd); + } + fp->flags = 0; /* пока свободен/закрыт */ + fp->level = 0; + fp->curp = fp->buf; + fp->hold = EOF; + + if (oflags < 0) return 0; /* плохой режим — поток закрыт */ + + int fd = open(path, oflags); + if (fd < 0) return 0; + + fp->fd = fd; + fp->flags = ff; + return fp; +} diff --git a/libc/file/fscanf.c b/libc/file/fscanf.c new file mode 100644 index 0000000..cd54321 --- /dev/null +++ b/libc/file/fscanf.c @@ -0,0 +1,27 @@ +/* + * fscanf — форматированный ввод из потока; ядро — _scanf_core поверх + * fgetc/ungetc (putback гарантирован полем hold). + */ + +#include +#include "_file.h" +#include "../stdio/_scanf.h" + +static int fs_get(void *ctx) +{ + return fgetc((FILE *)ctx); +} + +static void fs_unget(void *ctx, int c) +{ + ungetc(c, (FILE *)ctx); +} + +int fscanf(FILE *fp, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = _scanf_core(fs_get, fs_unget, fp, fmt, ap); + va_end(ap); + return r; +} diff --git a/libc/file/fsetpos.c b/libc/file/fsetpos.c new file mode 100644 index 0000000..7397262 --- /dev/null +++ b/libc/file/fsetpos.c @@ -0,0 +1,12 @@ +/* + * fsetpos — восстановить позицию потока из *pos (парный fgetpos); + * fseek(SEEK_SET) — сбрасывает буфер и ungetc. 0 или -1 + errno. + */ + +#include "_file.h" + +int fsetpos(FILE *fp, const fpos_t *pos) +{ + if (!pos) return -1; + return fseek(fp, *pos, SEEK_SET); +} diff --git a/libc/file/scanf.c b/libc/file/scanf.c new file mode 100644 index 0000000..743c4cd --- /dev/null +++ b/libc/file/scanf.c @@ -0,0 +1,28 @@ +/* + * scanf — форматированный ввод с консоли (stdin: ESTEX WAITKEY через + * getchar без эха; для строчного ввода с редактированием удобнее + * gets/cgets + sscanf). + */ + +#include +#include "_file.h" +#include "../stdio/_scanf.h" + +static int sc_get(void *ctx) +{ + return fgetc((FILE *)ctx); +} + +static void sc_unget(void *ctx, int c) +{ + ungetc(c, (FILE *)ctx); +} + +int scanf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = _scanf_core(sc_get, sc_unget, stdin, fmt, ap); + va_end(ap); + return r; +} diff --git a/libc/include/dos.h b/libc/include/dos.h new file mode 100644 index 0000000..07a1bc7 --- /dev/null +++ b/libc/include/dos.h @@ -0,0 +1,49 @@ +/* + * dos.h — DOS-подобный слой Solid-C поверх ESTEX/BIOS: диски, + * дата/время в Turbo-C-структурах, абсолютное чтение/запись секторов. + * + * Структуры совместимы с Solid-C DOS.H (и Turbo C): + * struct time { ti_min, ti_hour, ti_hund, ti_sec } + * struct date { da_year, da_day, da_mon (1 = январь) } + * ti_hund (сотые секунды) не поддержан RTC Sprinter — всегда 0. + */ + +#ifndef DOS_H +#define DOS_H + +#include + +struct time { + unsigned char ti_min; /* минуты */ + unsigned char ti_hour; /* часы */ + unsigned char ti_hund; /* сотые доли секунды (не используются) */ + unsigned char ti_sec; /* секунды */ +}; + +struct date { + int da_year; /* год (полный, напр. 2026) */ + char da_day; /* день месяца */ + char da_mon; /* месяц (1 = январь) */ +}; + +/* Чтение/установка даты и времени (обёртки над getdatetime/ + * setdatetime — set* делает read-modify-write полного datetime_t). */ +void getdate(struct date *d); +int setdate(const struct date *d); +void gettime(struct time *t); +int settime(const struct time *t); + +/* Текущий диск: 0=A, 1=B, ... setdisk возвращает число дисков или + * -1 + errno. */ +uint8_t getdisk(void); +int setdisk(uint8_t drive); + +/* Абсолютное чтение/запись секторов ЛОГИЧЕСКОГО диска (BIOS $55/$56, + * rst 8; сектор 0 = boot-сектор, с 1 — первая FAT и т.д.). + * Возврат 0 или -1 + errno (EREAD/EWRITE, как у Solid-C). + * Буфер должен лежать в #4000-#BFFF (требование BIOS). */ +int absread (uint8_t disk, uint16_t nsect, uint8_t count, void *buffer); +int abswrite(uint8_t disk, uint16_t nsect, uint8_t count, + const void *buffer); + +#endif diff --git a/libc/include/errno.h b/libc/include/errno.h index 1b5aa56..06441ef 100644 --- a/libc/include/errno.h +++ b/libc/include/errno.h @@ -55,6 +55,27 @@ extern int errno; * Folded onto the closest existing code so error strings stay sane. */ #define EINVAL EUNKOP /* "Invalid argument" → "Unknown operation" */ +/* ---- Solid-C aliases (ERRNO.H из Solid-C v2004) ------------------- + * Те же числовые коды DSS, только имена другие — портируемые + * программы работают без правок. */ +#define EZERO EOK +#define EINVFNC EINVFN +#define EINVDRV ENODRV +#define ENOFILE ENOENT +#define EINVHND EBADF +#define EROFILE EROFS +#define EROOT EROOTFULL +#define ENOSPACE ENOSPC +#define ENOEMPTY ENOTEMPTY +#define ECURDIR EBUSY +#define EINVMED EMEDIA +#define EOPER EUNKOP +#define EEXISDIR EISDIR +#define EINVFNAM EINAME +#define ENSUPEXE ENOEXEC +#define ENORDY ENOTREADY +#define EWRTPRT EWRPROT + /* C99 perror / strerror surface. */ const char *strerror(int err); void perror (const char *prefix); diff --git a/libc/include/sprinter_compat.h b/libc/include/sprinter_compat.h index 48f79bd..e0c88af 100644 --- a/libc/include/sprinter_compat.h +++ b/libc/include/sprinter_compat.h @@ -21,6 +21,7 @@ #include #include #include +#include /* getdate/gettime, getdisk, absread */ #include /* _exit, atexit */ /* ---- Solid-C types ----------------------------------------------- */ @@ -98,8 +99,12 @@ char *strupr(char *s); /* ---- io.h aliases (Solid-C fd shortcuts) ------------------------- */ #define seek(fd, off) lseek((fd), (long)(off), SEEK_SET) #define tell(fd) ((uint16_t)lseek((fd), 0, SEEK_CUR)) +#define ltell(fd) lseek((fd), 0L, SEEK_CUR) #define remove(name) unlink(name) +/* crt0 разбирает argv сам — заглушка для портируемого кода. */ +#define _setargv() 0 + /* ---- dir.h alias ------------------------------------------------- */ #define _ffirst ffirst diff --git a/libc/include/sprinter_solid.h b/libc/include/sprinter_solid.h new file mode 100644 index 0000000..3885986 --- /dev/null +++ b/libc/include/sprinter_solid.h @@ -0,0 +1,22 @@ +/* + * sprinter_solid.h — зонтичный заголовок для портирования программ + * Solid-C: подключает все стандартные заголовки + compat-алиасы. + * Программа Solid-C может заменить свои include на один этот. + * + * Известные отличия от Solid-C (см. docs/solid_c_compatibility.md): + * - bdos/bdosh/intdos (сырой интерфейс ESTEX) не экспонируются — + * использовать типизированные обёртки libc; + * - lseek/ltell возвращают long, а не struct fpoint*; + * - FILE* буферизован (Solid-C тоже, семантика совместима). + */ + +#ifndef SPRINTER_SOLID_H +#define SPRINTER_SOLID_H + +#include /* включает stdio/stdlib/string/conio/ + ctype/errno/fcntl/unistd/dir/dos/ + mouse + типы и алиасы Solid-C */ +#include +#include + +#endif diff --git a/libc/include/stdio.h b/libc/include/stdio.h index d7f6592..3be1c69 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -83,9 +83,15 @@ extern FILE *const stderr; #define SEEK_END 2 #endif -FILE *fopen (const char *path, const char *mode); -int fclose(FILE *fp); -int fflush(FILE *fp); +/* Тип позиции файла для fgetpos/fsetpos. */ +typedef long fpos_t; + +FILE *fopen (const char *path, const char *mode); +FILE *fdopen (int fd, const char *mode); +FILE *freopen(const char *path, const char *mode, FILE *fp); +int fclose (FILE *fp); +void fclosall(void); +int fflush (FILE *fp); /* fflush(NULL) — все потоки */ int fputc (int c, FILE *fp); int fgetc (FILE *fp); @@ -96,12 +102,25 @@ int ungetc(int c, FILE *fp); int fprintf (FILE *fp, const char *fmt, ...); int vfprintf(FILE *fp, const char *fmt, va_list ap); +/* scanf-семейство (семантика Solid-C, реализация своя — в SDCC z80 + * его нет). Конверсии: %d %u %x %o %c %s (+ модификатор l, ширина, + * подавление '*', %%). Возврат: число присвоенных полей, EOF если + * ввод кончился до первого совпадения. */ +int scanf (const char *fmt, ...); +int fscanf(FILE *fp, const char *fmt, ...); +int sscanf(const char *s, const char *fmt, ...); + +/* Переименование файла (ESTEX RENAME $10). */ +int rename(const char *oldpath, const char *newpath); + size_t fread (void *ptr, size_t size, size_t nmemb, FILE *fp); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp); int fseek (FILE *fp, long off, int whence); long ftell (FILE *fp); void rewind(FILE *fp); +int fgetpos(FILE *fp, fpos_t *pos); +int fsetpos(FILE *fp, const fpos_t *pos); int feof (FILE *fp); int ferror(FILE *fp); diff --git a/libc/include/unistd.h b/libc/include/unistd.h index 2424bda..1677390 100644 --- a/libc/include/unistd.h +++ b/libc/include/unistd.h @@ -28,6 +28,10 @@ long lseek(int fd, long offset, int whence); /* Block the calling task for `seconds` seconds (50 Hz IRQ-based timer). */ void sleep(unsigned int seconds); +/* 1, если fd — консоль (наши псевдо-fd 0/-1/-2), 0 — файл (любой + * положительный манипулятор DSS; из командной строки они идут с 1). */ +int isatty(int fd); + /* Directory operations (ESTEX $1B-$1E). All return 0 on success and -1 * with errno set on failure; getcwd returns the buffer on success or NULL. * size is ignored — ESTEX always wants a 256-byte buffer. */ diff --git a/libc/io/absread.c b/libc/io/absread.c new file mode 100644 index 0000000..895d981 --- /dev/null +++ b/libc/io/absread.c @@ -0,0 +1,46 @@ +/* + * absread — абсолютное чтение секторов логического диска через + * BIOS $55 (rst 8): A=диск, HL:IX=32-битный номер сектора (HL=старшие, + * у нас всегда 0), DE=буфер, B=число секторов. Нумерация с 0 от + * начала ЛОГИЧЕСКОГО диска (0 = boot-сектор, с 1 — первая FAT). + * При ошибке errno=EREAD (как у Solid-C), возврат -1. + * Буфер — в #4000-#BFFF (требование BIOS). + */ + +#include +#include + +static uint8_t abs_disk; +static uint8_t abs_cnt; +static uint16_t abs_sect; +static uint16_t abs_buf; +static int8_t abs_rc; + +int absread(uint8_t disk, uint16_t nsect, uint8_t count, void *buffer) +{ + abs_disk = disk; + abs_sect = nsect; + abs_cnt = count; + abs_buf = (uint16_t)buffer; + + __asm + push ix + ld ix, (_abs_sect) ; IX = младшие 16 бит номера сектора + ld hl, #0 ; HL = старшие 16 бит (всегда 0) + ld de, (_abs_buf) ; DE = буфер + ld a, (_abs_cnt) + ld b, a ; B = число секторов + ld a, (_abs_disk) ; A = диск + ld c, #0x55 ; BIOS READ (сектора) + rst #0x08 + pop ix + ld a, #0 + jr nc, _absr_done + ld a, #25 ; EREAD (код Solid-C) + call __errno_set + ld a, #0xFF + _absr_done: + ld (_abs_rc), a + __endasm; + return abs_rc; +} diff --git a/libc/io/abswrite.c b/libc/io/abswrite.c new file mode 100644 index 0000000..f40aaf3 --- /dev/null +++ b/libc/io/abswrite.c @@ -0,0 +1,45 @@ +/* + * abswrite — абсолютная запись секторов логического диска через + * BIOS $56 (rst 8); регистры и нумерация — как у absread ($55). + * При ошибке errno=EWRITE (как у Solid-C), возврат -1. + * ОСТОРОЖНО: пишет мимо файловой системы — можно разрушить FAT. + */ + +#include +#include + +static uint8_t absw_disk; +static uint8_t absw_cnt; +static uint16_t absw_sect; +static uint16_t absw_buf; +static int8_t absw_rc; + +int abswrite(uint8_t disk, uint16_t nsect, uint8_t count, + const void *buffer) +{ + absw_disk = disk; + absw_sect = nsect; + absw_cnt = count; + absw_buf = (uint16_t)buffer; + + __asm + push ix + ld ix, (_absw_sect) ; IX = младшие 16 бит номера сектора + ld hl, #0 ; HL = старшие 16 бит (всегда 0) + ld de, (_absw_buf) ; DE = буфер + ld a, (_absw_cnt) + ld b, a ; B = число секторов + ld a, (_absw_disk) ; A = диск + ld c, #0x56 ; BIOS WRITE (сектора) + rst #0x08 + pop ix + ld a, #0 + jr nc, _absw_done + ld a, #26 ; EWRITE (код Solid-C) + call __errno_set + ld a, #0xFF + _absw_done: + ld (_absw_rc), a + __endasm; + return absw_rc; +} diff --git a/libc/io/getdisk.c b/libc/io/getdisk.c new file mode 100644 index 0000000..03bcd22 --- /dev/null +++ b/libc/io/getdisk.c @@ -0,0 +1,18 @@ +/* + * getdisk — номер текущего диска (0=A, 1=B, ...) через ESTEX + * CURDISK ($02). Ошибок не возвращает. + */ + +#include + +uint8_t getdisk(void) __naked +{ + __asm + push ix + ld c, #0x02 ; ESTEX CURDISK + rst #0x10 + pop ix + ;; uint8_t возвращается в A — номер диска уже там. + ret + __endasm; +} diff --git a/libc/io/isatty.c b/libc/io/isatty.c new file mode 100644 index 0000000..b38b7de --- /dev/null +++ b/libc/io/isatty.c @@ -0,0 +1,16 @@ +/* + * isatty — 1, если fd указывает на консоль, 0 — на файл. + * + * Консольные псевдо-fd наших stdin/stdout/stderr: 0, -1, -2 + * (см. libc/file/std_streams.c). Все положительные fd — файловые + * манипуляторы DSS: из командной строки DSS нумерация начинается + * с 1, под Flex Navigator — с 2 (fd 1 держит сам навигатор), т.е. + * fd == 1 может быть обычным файлом. + */ + +#include + +int isatty(int fd) +{ + return fd <= 0; +} diff --git a/libc/io/rename.c b/libc/io/rename.c new file mode 100644 index 0000000..0622d86 --- /dev/null +++ b/libc/io/rename.c @@ -0,0 +1,26 @@ +/* + * rename — переименовать файл через ESTEX RENAME ($10). + * HL = старое имя (ASCIIZ), DE = новое имя; CF=err с кодом в A. + * Возвращает 0 или -1 + errno. + */ + +#include + +int rename(const char *oldpath, const char *newpath) __naked +{ + (void)oldpath; (void)newpath; + __asm + ;; __sdcccall(1): oldpath в HL, newpath в DE — как ждёт ESTEX. + push ix + ld c, #0x10 ; ESTEX RENAME + rst #0x10 + pop ix + jr c, _rename_err + ld de, #0 + ret + _rename_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/io/setdisk.c b/libc/io/setdisk.c new file mode 100644 index 0000000..0cb6714 --- /dev/null +++ b/libc/io/setdisk.c @@ -0,0 +1,28 @@ +/* + * setdisk — сменить текущий диск (0=A, 1=B, ...) через ESTEX + * CHDISK ($01). Возвращает число дисков в системе (из A) или + * -1 + errno при ошибке (EINVDRV, если диска нет). + */ + +#include +#include + +int setdisk(uint8_t drive) __naked +{ + (void)drive; + __asm + ;; __sdcccall(1): uint8_t-аргумент уже в A. + push ix + ld c, #0x01 ; ESTEX CHDISK + rst #0x10 + pop ix + jr c, _setdisk_err + ld e, a ; A = число дисков + ld d, #0 + ret + _setdisk_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/stdio/_scanf.h b/libc/stdio/_scanf.h new file mode 100644 index 0000000..30fdea5 --- /dev/null +++ b/libc/stdio/_scanf.h @@ -0,0 +1,22 @@ +/* + * _scanf.h — внутреннее ядро scanf-семейства (НЕ публичный заголовок). + * + * Одно C-ядро на scanf/fscanf/sscanf; источник символов абстрагирован + * парой колбэков (ядру нужен putback максимум на 1 символ). + * Семантика — по Solid-C/ANSI: %d %u %x %o %c %s, модификатор l, + * ширина, подавление '*', %%, пробелы в формате = скип пробелов ввода. + */ +#ifndef _SCANF_INTERNAL_H +#define _SCANF_INTERNAL_H + +#include + +/* Следующий символ источника или EOF. */ +typedef int (*_sc_get)(void *ctx); +/* Вернуть символ (гарантированно не EOF; максимум 1 подряд). */ +typedef void (*_sc_unget)(void *ctx, int c); + +int _scanf_core(_sc_get get, _sc_unget unget, void *ctx, + const char *fmt, va_list ap); + +#endif diff --git a/libc/stdio/_scanf_core.c b/libc/stdio/_scanf_core.c new file mode 100644 index 0000000..0ac1873 --- /dev/null +++ b/libc/stdio/_scanf_core.c @@ -0,0 +1,176 @@ +/* + * _scanf_core — ядро scanf-семейства (см. _scanf.h). + * + * Поддержано: %d (знак ±), %u, %x, %o, %c (ширина = число символов, + * по умолчанию 1, пробелы НЕ скипаются), %s (скип пробелов, до + * пробела/ширины, NUL добавляется), модификатор l (long), ширина, + * '*' — подавление присваивания, %% — литерал. Пробельный символ в + * формате съедает любую цепочку пробелов ввода; прочие символы + * формата должны совпасть с вводом (иначе стоп). + * + * Возврат: число присвоенных полей; EOF, если ввод кончился до + * первого присваивания/совпадения (ANSI-семантика). + */ + +#include +#include "_scanf.h" + +/* Цифра c в базе base или -1. */ +static signed char digit_val(int c, unsigned char base) +{ + unsigned char v; + if (c >= '0' && c <= '9') v = (unsigned char)(c - '0'); + else if (c >= 'a' && c <= 'f') v = (unsigned char)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') v = (unsigned char)(c - 'A' + 10); + else return -1; + return v < base ? (signed char)v : -1; +} + +/* Пропустить пробелы ввода; вернуть первый непробельный символ + * (или EOF), НЕ возвращая его в источник. */ +static int skip_ws(_sc_get get, void *ctx) +{ + int c; + do { c = get(ctx); } while (c != -1 && isspace(c)); + return c; +} + +int _scanf_core(_sc_get get, _sc_unget unget, void *ctx, + const char *fmt, va_list ap) +{ + int assigned = 0; + int touched = 0; /* было ли хоть одно чтение ввода */ + int c; + + for (; *fmt; fmt++) { + if (isspace((unsigned char)*fmt)) { + c = skip_ws(get, ctx); + if (c != -1) unget(ctx, c); + continue; + } + if (*fmt != '%') { + /* Литерал формата должен совпасть с вводом. */ + c = get(ctx); + touched = 1; + if (c != (unsigned char)*fmt) { + if (c != -1) { unget(ctx, c); goto out; } + goto out_eof; + } + continue; + } + + /* ---- %-конверсия ---- */ + fmt++; + if (*fmt == '%') { + c = get(ctx); + touched = 1; + if (c != '%') { + if (c != -1) { unget(ctx, c); goto out; } + goto out_eof; + } + continue; + } + + unsigned char suppress = 0; + if (*fmt == '*') { suppress = 1; fmt++; } + + unsigned width = 0; + while (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + (unsigned)(*fmt - '0'); + fmt++; + } + + unsigned char lng = 0; + if (*fmt == 'l') { lng = 1; fmt++; } + + switch (*fmt) { + + case 'c': { + unsigned w = width ? width : 1; + char *dst = suppress ? 0 : va_arg(ap, char *); + while (w--) { + c = get(ctx); + touched = 1; + if (c == -1) { + if (!assigned) goto out_eof; + goto out; + } + if (dst) *dst++ = (char)c; + } + if (!suppress) assigned++; + break; + } + + case 's': { + char *dst = suppress ? 0 : va_arg(ap, char *); + unsigned w = width ? width : 0xFFFF; + c = skip_ws(get, ctx); + touched = 1; + if (c == -1) { + if (!assigned) goto out_eof; + goto out; + } + unsigned n = 0; + while (c != -1 && !isspace(c) && n < w) { + if (dst) dst[n] = (char)c; + n++; + c = get(ctx); + } + if (c != -1) unget(ctx, c); + if (n == 0) goto out; + if (dst) { dst[n] = '\0'; assigned++; } + break; + } + + case 'd': case 'u': case 'x': case 'o': { + unsigned char base = 10; + if (*fmt == 'x') base = 16; + else if (*fmt == 'o') base = 8; + + unsigned w = width ? width : 0xFFFF; + unsigned char neg = 0; + unsigned long val = 0; + unsigned ndig = 0; + + c = skip_ws(get, ctx); + touched = 1; + if (c == -1) { + if (!assigned) goto out_eof; + goto out; + } + if (*fmt == 'd' && (c == '-' || c == '+') && w) { + neg = (c == '-'); + w--; + c = get(ctx); + } + while (c != -1 && w) { + signed char d = digit_val(c, base); + if (d < 0) break; + val = val * base + (unsigned char)d; + ndig++; + w--; + c = get(ctx); + } + if (c != -1) unget(ctx, c); + if (ndig == 0) goto out; /* цифр не было — стоп */ + if (neg) val = (unsigned long)(-(long)val); + if (!suppress) { + if (lng) *va_arg(ap, long *) = (long)val; + else *va_arg(ap, int *) = (int)val; + assigned++; + } + break; + } + + default: + /* Незнакомая конверсия — прекратить разбор. */ + goto out; + } + } + +out: + return assigned; +out_eof: + /* Ввод кончился до первого присвоенного поля. */ + return (assigned == 0 && touched) ? -1 : assigned; +} diff --git a/libc/stdio/sscanf.c b/libc/stdio/sscanf.c new file mode 100644 index 0000000..138e760 --- /dev/null +++ b/libc/stdio/sscanf.c @@ -0,0 +1,32 @@ +/* + * sscanf — форматированный разбор строки; ядро — _scanf_core, + * источник — движущийся указатель по ASCIIZ-строке. + */ + +#include +#include "_scanf.h" + +static const char *ss_p; + +static int ss_get(void *ctx) +{ + (void)ctx; + if (!*ss_p) return -1; + return (unsigned char)*ss_p++; +} + +static void ss_unget(void *ctx, int c) +{ + (void)ctx; (void)c; + ss_p--; +} + +int sscanf(const char *s, const char *fmt, ...) +{ + ss_p = s; + va_list ap; + va_start(ap, fmt); + int r = _scanf_core(ss_get, ss_unget, 0, fmt, ap); + va_end(ap); + return r; +} diff --git a/libc/time/getdate.c b/libc/time/getdate.c new file mode 100644 index 0000000..55787c4 --- /dev/null +++ b/libc/time/getdate.c @@ -0,0 +1,16 @@ +/* + * getdate — текущая дата в Turbo-C структуру struct date + * (обёртка над getdatetime / ESTEX SYSTIME). + */ + +#include +#include + +void getdate(struct date *d) +{ + datetime_t dt; + getdatetime(&dt); + d->da_year = (int)dt.year; + d->da_day = (char)dt.day; + d->da_mon = (char)dt.month; +} diff --git a/libc/time/gettime.c b/libc/time/gettime.c new file mode 100644 index 0000000..31cd66d --- /dev/null +++ b/libc/time/gettime.c @@ -0,0 +1,18 @@ +/* + * gettime — текущее время в Turbo-C структуру struct time + * (обёртка над getdatetime / ESTEX SYSTIME). ti_hund всегда 0 — + * RTC Sprinter не отдаёт сотые. + */ + +#include +#include + +void gettime(struct time *t) +{ + datetime_t dt; + getdatetime(&dt); + t->ti_min = dt.minute; + t->ti_hour = dt.hour; + t->ti_hund = 0; + t->ti_sec = dt.second; +} diff --git a/libc/time/setdate.c b/libc/time/setdate.c new file mode 100644 index 0000000..5ea3578 --- /dev/null +++ b/libc/time/setdate.c @@ -0,0 +1,18 @@ +/* + * setdate — установить системную дату, не трогая время: + * read-modify-write полного datetime_t через getdatetime/setdatetime. + * Возврат 0 или -1 + errno (от setdatetime). + */ + +#include +#include + +int setdate(const struct date *d) +{ + datetime_t dt; + getdatetime(&dt); + dt.year = (uint16_t)d->da_year; + dt.day = (uint8_t)d->da_day; + dt.month = (uint8_t)d->da_mon; + return setdatetime(&dt); +} diff --git a/libc/time/settime.c b/libc/time/settime.c new file mode 100644 index 0000000..3cc4959 --- /dev/null +++ b/libc/time/settime.c @@ -0,0 +1,18 @@ +/* + * settime — установить системное время, не трогая дату: + * read-modify-write полного datetime_t через getdatetime/setdatetime. + * ti_hund игнорируется. Возврат 0 или -1 + errno (от setdatetime). + */ + +#include +#include + +int settime(const struct time *t) +{ + datetime_t dt; + getdatetime(&dt); + dt.minute = t->ti_min; + dt.hour = t->ti_hour; + dt.second = t->ti_sec; + return setdatetime(&dt); +} diff --git a/tests/solidt/Makefile b/tests/solidt/Makefile new file mode 100644 index 0000000..f77c6e9 --- /dev/null +++ b/tests/solidt/Makefile @@ -0,0 +1,5 @@ +# Build solidt.exe — smoke-тест Solid-C совместимости (П3/П4). + +PROJ_ROOT := $(abspath $(CURDIR)/../..) +EXAMPLE := solidt +include $(PROJ_ROOT)/app.mk diff --git a/tests/solidt/solidt.c b/tests/solidt/solidt.c new file mode 100644 index 0000000..2ac7339 --- /dev/null +++ b/tests/solidt/solidt.c @@ -0,0 +1,114 @@ +/* + * solidt — smoke-тест П3/П4: scanf-семейство, rename, isatty, + * getdisk/setdisk, getdate/gettime, fdopen/freopen/fgetpos/fsetpos, + * fclosall, absread, div, errno-алиасы Solid-C. + */ + +#include + +static char buf[64]; +static uint8_t sect[512]; + +int main(void) +{ + int fails = 0; + + /* ---- sscanf ---- */ + { + int a = 0, b = 0; + unsigned x = 0; + int n = sscanf("a=5,b=-17,x=BEEF", "a=%d,b=%d,x=%x", &a, &b, &x); + if (n == 3 && a == 5 && b == -17 && x == 0xBEEF) + puts(" sscanf: OK"); + else { printf(" sscanf: FAIL n=%d a=%d b=%d x=%x\n", n, a, b, x); fails++; } + } + + /* ---- div (из SDCC z80.lib) ---- */ + { + div_t d = div(-7, 2); + if (d.quot == -3 && d.rem == -1) puts(" div: OK"); + else { printf(" div: FAIL %d %d\n", d.quot, d.rem); fails++; } + } + + /* ---- isatty: tty — только псевдо-fd 0/-1/-2 ---- */ + if (isatty(0) && isatty(-1) && isatty(-2) && !isatty(1) && !isatty(2)) + puts(" isatty: OK"); + else { puts(" isatty: FAIL"); fails++; } + + /* ---- getdisk / setdisk ---- */ + { + uint8_t d = getdisk(); + int ndisks = setdisk(d); /* смена на текущий — безопасно */ + printf(" getdisk: %c:, setdisk -> %d disks\n", 'A' + d, ndisks); + if (ndisks < 1) fails++; + } + + /* ---- getdate / gettime ---- */ + { + struct date d; + struct time t; + getdate(&d); + gettime(&t); + printf(" date: %04d-%02d-%02d time: %02d:%02d:%02d\n", + d.da_year, d.da_mon, d.da_day, t.ti_hour, t.ti_min, t.ti_sec); + if (d.da_year < 2000 || d.da_mon < 1 || d.da_mon > 12) fails++; + } + + /* ---- файл: fprintf → rename → fdopen+fscanf → freopen ---- */ + { + FILE *fp = fopen("SOLIDT.TMP", "w"); + if (!fp) { puts(" fopen: FAIL"); return 1; } + fprintf(fp, "num %d hex %x str token\n", 123, 0xAB); + fclose(fp); + + unlink("SOLIDT2.TMP"); /* rename поверх не пойдёт */ + if (rename("SOLIDT.TMP", "SOLIDT2.TMP") == 0) puts(" rename: OK"); + else { printf(" rename: FAIL errno=%d\n", errno); fails++; } + + /* fdopen поверх сырого fd */ + int fd = open("SOLIDT2.TMP", O_RDONLY); + FILE *f2 = fdopen(fd, "r"); + int num = 0; unsigned hx = 0; + int n = f2 ? fscanf(f2, "num %d hex %x str %s", &num, &hx, buf) : -1; + if (n == 3 && num == 123 && hx == 0xAB && !strcmp(buf, "token")) + puts(" fdopen+fscanf: OK"); + else { printf(" fdopen+fscanf: FAIL n=%d\n", n); fails++; } + + /* fgetpos/fsetpos: перечитать последний токен */ + fpos_t pos; + rewind(f2); + fscanf(f2, "num %d hex %x str", &num, &hx); + fgetpos(f2, &pos); + fscanf(f2, "%s", buf); + fsetpos(f2, &pos); + buf[0] = 0; + fscanf(f2, "%s", buf); + if (!strcmp(buf, "token")) puts(" fgetpos/fsetpos: OK"); + else { printf(" fgetpos/fsetpos: FAIL '%s'\n", buf); fails++; } + + /* freopen: тот же FILE на другой файл */ + FILE *f3 = freopen("SOLIDT2.TMP", "r", f2); + if (f3 && fgetc(f3) == 'n') puts(" freopen: OK"); + else { puts(" freopen: FAIL"); fails++; } + fclose(f3); + unlink("SOLIDT2.TMP"); + } + + /* ---- absread: boot-сектор текущего диска ---- */ + { + if (absread(getdisk(), 0, 1, sect) == 0) { + printf(" absread: OK (boot: %02x %02x .. %02x %02x)\n", + sect[0], sect[1], sect[510], sect[511]); + } else { printf(" absread: FAIL errno=%d\n", errno); fails++; } + } + + /* ---- errno-алиасы Solid-C (компилируемость + значения) ---- */ + if (ENOFILE == 3 && EINVHND == 5 && ENOSPACE == 10) puts(" errno aliases: OK"); + else { puts(" errno aliases: FAIL"); fails++; } + + fclosall(); /* публичное имя Solid-C */ + + if (fails) printf("solidt: %d FAIL(s)\n", fails); + else puts("solidt done, all OK."); + return fails; +}