libc: Solid-C совместимость (П3) + rename/isatty (П4); scanf-семейство

- <dos.h>: 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/...) как алиасы
- <sprinter_solid.h> — зонтичный заголовок для портирования;
  ltell/_setargv в sprinter_compat.h; div/ldiv — из SDCC (проверено)
- tests/solidt — smoke всех П3/П4 API, зелёный в MAME (вкл. absread
  boot-сектора с сигнатурой 55AA)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 16:35:58 +03:00
parent 057dd615ba
commit 60373930fb
35 changed files with 1017 additions and 88 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
# Small libc-feature tests (one program per .c-language feature or libc API). # Small libc-feature tests (one program per .c-language feature or libc API).
TESTS := hello banked bankedbg strtest cat seek malloc mem_test argv errno \ TESTS := hello banked bankedbg strtest cat seek malloc mem_test argv errno \
rt_test openenv ls conio attrprob timedir mouse banklocl stdlib \ 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 gfx_demo gfx_d16 gfx_text gfx_mous
# Larger end-user applications under examples/. # Larger end-user applications under examples/.
+11 -18
View File
@@ -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 (замер скорости)** - [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) - [ ] 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_*, Всё сделано (детали в docs/solid_c_compatibility.md): getdisk/setdisk,
getc/putc, min/max, dec*/hex*, strlwr/strupr, cgets есть. Осталось: getdate/gettime/setdate/settime + <dos.h>, ltell/_setargv, errno-алиасы,
<sprinter_solid.h>, div из SDCC (проверено), absread/abswrite (BIOS
$55/$56 — номера найдены в solid-c DOS.ASM), **scanf/fscanf/sscanf**
(своё C-ядро _scanf_core, 22 хост-теста), fdopen/freopen/fclosall/
fgetpos/fsetpos (хвост П2). bdos/brk/ioctl — отказ решением.
Тест tests/solidt ждёт MAME-прогона.
- [ ] getdisk()/setdisk() — ESTEX $02 CURDISK / $01 CHDISK ## П4. Недостающие POSIX-мелочи — ЗАКРЫТ 2026-07-06
- [ ] getdate/gettime/setdate/settime — обёртки над getdatetime/setdatetime
- [ ] seek/tell/ltell, setmem/movmem, isascii, abort(), div(), remove→unlink, _ffirst
- [ ] errno-константы Solid-C (EZERO/EINVFNC/…) как алиасы в errno.h
- [ ] `<sprinter_solid.h>` — зонтичный compat-заголовок
- [ ] обновить статусы/history в solid_c_compatibility.md (Phase 1 done)
Research (Phase 3): absread/abswrite (найти ESTEX READ_SECT/WRITE_SECT), - [x] rename() — ESTEX RENAME $10 (HL=старое, DE=новое), libc/io/rename.c
scanf-семейство (**в SDCC 4.5 z80 его НЕТ** — писать самим или задокументировать - [x] isatty(fd) — fd < 2 (манипуляторы DSS с 2; консольные псевдо-fd 0/-1/-2)
отказ), brk/sbrk vs SDCC heap, isatty, ioctl (скорее скип).
## П4. Недостающие POSIX-мелочи вне Solid-C списка
- [ ] **rename()** — в gap-анализе отсутствует, ESTEX почти наверняка имеет
RENAME (~$0F, рядом с DELETE $0E) — проверить по докам ESTEX и добавить
- [ ] isatty(fd) — тривиально (fd<0/флаги)
## П5. Заголовки и гигиена сборки ## П5. Заголовки и гигиена сборки
+25
View File
@@ -130,6 +130,31 @@ These will be in `docs/solid_c_diff.md`:
To make porting easier, add a single `<sprinter_solid.h>` that includes all the standard headers (`stdio.h`, `string.h`, `conio.h`, etc.) — Solid-C programs can `#include <sprinter_solid.h>` and have most functions available. To make porting easier, add a single `<sprinter_solid.h>` that includes all the standard headers (`stdio.h`, `string.h`, `conio.h`, etc.) — Solid-C programs can `#include <sprinter_solid.h>` and have most functions available.
## Status 2026-07-06 — Phase 1/2/3 ЗАКРЫТЫ
Всё из категорий A/B/C реализовано или закрыто решением:
- **A (алиасы)**: все на месте в sprinter_compat.h (+ ltell, _setargv
добавлены 2026-07-06); div/ldiv — из SDCC z80.lib (проверено).
- **B**: getdisk/setdisk (ESTEX $02/$01, libc/io), getdate/gettime/
setdate/settime (обёртки над getdatetime, структуры Turbo-C в
<dos.h>), остальное было готово ранее.
- **C**: fdopen/freopen/fclosall/fgetpos/fsetpos — реализованы поверх
таблицы FILE v2 (libc/file); ungetc — есть (FILE v2);
**absread/abswrite — BIOS $55/$56** (rst 8, A=диск, HL:IX=сектор,
DE=буфер, B=счётчик; найдено в solid-c DOS.ASM) — реализованы в
libc/io; **scanf/fscanf/sscanf — реализованы** (своё C-ядро
_scanf_core с семантикой Solid-C: %d %u %x %o %c %s, l, ширина, %*;
в SDCC z80 scanf нет); isatty — fd < 2 (см. memory/dss_fd_limit).
bdos/bdosh/intdos — НЕ экспонируем (решение: типизированные
обёртки); brk/sbrk — НЕ нужны (heap SDCC); ioctl — скип.
- **errno**: Solid-C имена (EZERO/EINVFNC/ENOFILE/…) — алиасы в errno.h.
- **Зонтичный заголовок**: <sprinter_solid.h>.
- Тест: tests/solidt (MAME).
## History ## History
- 2026-07-06 — Phase 1/2/3 закрыты: dos.h (даты/диски/сектора),
scanf-семейство, fdopen/freopen/fclosall/fgetpos/fsetpos,
rename/isatty (П4), errno-алиасы, sprinter_solid.h, тест solidt
- 2026-06-01 — initial gap analysis vs Solid-C v2004 - 2026-06-01 — initial gap analysis vs Solid-C v2004
+12
View File
@@ -19,6 +19,18 @@
/* Таблица потоков (_file_slots.c); свободный слот: flags == 0. */ /* Таблица потоков (_file_slots.c); свободный слот: flags == 0. */
extern FILE _file_slots[OPEN_MAX]; 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). /* Обеспечить буфер: вернуть fp->buf, при необходимости malloc(BUFSIZ).
* NULL = malloc не дал — вызывающий уходит на небуферизованный путь * NULL = malloc не дал — вызывающий уходит на небуферизованный путь
* (прямой syscall на 1 байт), поток продолжает работать. */ * (прямой syscall на 1 байт), поток продолжает работать. */
+43
View File
@@ -0,0 +1,43 @@
/*
* _file_mode_flags — парсер строки режима fopen/fdopen/freopen.
* Поддержаны r, w, a; '+' в любом месте хвоста; 'b'/'t' игнорируются —
* весь I/O на Sprinter бинарный.
*/
#include <fcntl.h>
#include <errno.h>
#include "_file.h"
int _file_mode_flags(const char *mode, uint8_t *ff)
{
if (!mode || !*mode) {
errno = EINVAL;
return -1;
}
int oflags = 0;
uint8_t f = 0;
char base = *mode;
int plus = 0;
for (const char *p = mode + 1; *p; p++) {
if (*p == '+') plus = 1;
}
switch (base) {
case 'r':
oflags = plus ? O_RDWR : O_RDONLY;
f = _F_READ | (plus ? _F_WRITE : 0);
break;
case 'w':
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
f = _F_WRITE | (plus ? _F_READ : 0);
break;
case 'a':
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND;
f = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0);
break;
default:
errno = EINVAL;
return -1;
}
*ff = f;
return oflags;
}
+30
View File
@@ -0,0 +1,30 @@
/*
* _file_slot_take — найти свободный слот таблицы потоков и при первом
* обращении взвести atexit(_fclosall), чтобы exit() сбрасывал
* несброшенную запись всех потоков (требование стандарта).
* Слот НЕ помечается занятым — вызывающий (fopen/fdopen) заполняет
* поля и последним выставляет flags.
*/
#include <sprinter_exit.h> /* atexit */
#include <errno.h>
#include "_file.h"
FILE *_file_slot_take(void)
{
static uint8_t atexit_armed;
FILE *fp = _file_slots;
for (uint8_t i = 0; ; i++, fp++) {
if (i >= OPEN_MAX) {
errno = EMFILE;
return 0;
}
if (fp->flags == 0) break;
}
if (!atexit_armed) {
atexit_armed = 1;
atexit(_fclosall);
}
return fp;
}
+11
View File
@@ -0,0 +1,11 @@
/*
* fclosall — закрыть все открытые потоки таблицы (имя Solid-C);
* публичная обёртка над _fclosall, который и так навешен на atexit.
*/
#include "_file.h"
void fclosall(void)
{
_fclosall();
}
+30
View File
@@ -0,0 +1,30 @@
/*
* fdopen — завернуть уже открытый низкоуровневый fd в поток FILE*
* (Solid-C категория C). Режим должен соответствовать тому, с которым
* открывался fd — библиотека проверить это не может. После fdopen
* файл закрывается через fclose (не close!): fclose сбросит буфер и
* закроет сам fd. Счётчик _fd_guard не трогаем — fd уже посчитан
* своим open().
*/
#include <errno.h>
#include "_file.h"
FILE *fdopen(int fd, const char *mode)
{
if (fd < 0) { errno = EBADF; return 0; }
uint8_t ff;
if (_file_mode_flags(mode, &ff) < 0) return 0;
FILE *fp = _file_slot_take();
if (!fp) return 0;
fp->fd = fd;
fp->buf = 0;
fp->curp = 0;
fp->level = 0;
fp->hold = EOF;
fp->flags = ff;
return fp;
}
+15
View File
@@ -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;
}
+9 -66
View File
@@ -1,89 +1,32 @@
/* /*
* fopen — открыть файл как буферизованный поток FILE* (вариант B+). * fopen — открыть файл как буферизованный поток FILE* (вариант B+).
* *
* FILE берётся из статической таблицы _file_slots (свободный слот: * FILE берётся из статической таблицы (_file_slot_take, он же взводит
* flags == 0), без malloc — сам буфер выделится лениво при первой * atexit(_fclosall)); сам буфер выделится лениво при первой операции.
* операции. Первый успешный fopen вешает _fclosall на atexit, чтобы
* exit() сбрасывал несброшенную запись всех потоков.
* NULL при: плохом режиме (EINVAL), занятой таблице (EMFILE), ошибке * NULL при: плохом режиме (EINVAL), занятой таблице (EMFILE), ошибке
* open (errno от него). * open (errno от него).
*/ */
#include <stdlib.h>
#include <sprinter_exit.h> /* atexit */
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "_file.h" #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) FILE *fopen(const char *path, const char *mode)
{ {
static uint8_t atexit_armed;
uint8_t ff; uint8_t ff;
int oflags = mode_to_flags(mode, &ff); int oflags = _file_mode_flags(mode, &ff);
if (oflags < 0) return NULL; if (oflags < 0) return 0;
/* Свободный слот таблицы — ДО open, чтобы не открывать зря. */ FILE *fp = _file_slot_take(); /* слот — ДО open, чтобы не открывать зря */
FILE *fp = _file_slots; if (!fp) return 0;
for (uint8_t i = 0; ; i++, fp++) {
if (i >= OPEN_MAX) {
errno = EMFILE;
return NULL;
}
if (fp->flags == 0) break;
}
int fd = open(path, oflags); int fd = open(path, oflags);
if (fd < 0) return NULL; if (fd < 0) return 0;
fp->fd = fd; fp->fd = fd;
fp->buf = NULL; fp->buf = 0;
fp->curp = NULL; fp->curp = 0;
fp->level = 0; fp->level = 0;
fp->hold = EOF; fp->hold = EOF;
fp->flags = ff; /* последним — занимает слот */ fp->flags = ff; /* последним — занимает слот */
if (!atexit_armed) {
atexit_armed = 1;
atexit(_fclosall); /* exit() сбросит все потоки */
}
return fp; return fp;
} }
+42
View File
@@ -0,0 +1,42 @@
/*
* freopen — переоткрыть поток на другой файл, переиспользуя тот же
* FILE* (и его буфер, если уже выделен).
*
* Для файлового потока: сброс буфера, close старого fd, open нового.
* Для консольного псевдопотока (stdout/stderr/stdin): превращает его
* в файловый — работает ТОЛЬКО для операций через FILE* (fputs(...,
* stdout) и т.п.); printf/puts/putchar идут через ESTEX напрямую и
* НЕ перенаправляются. NULL при ошибке (поток при этом закрыт —
* по стандарту).
*/
#include <fcntl.h>
#include <unistd.h>
#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;
}
+27
View File
@@ -0,0 +1,27 @@
/*
* fscanf — форматированный ввод из потока; ядро — _scanf_core поверх
* fgetc/ungetc (putback гарантирован полем hold).
*/
#include <stdarg.h>
#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;
}
+12
View File
@@ -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);
}
+28
View File
@@ -0,0 +1,28 @@
/*
* scanf — форматированный ввод с консоли (stdin: ESTEX WAITKEY через
* getchar без эха; для строчного ввода с редактированием удобнее
* gets/cgets + sscanf).
*/
#include <stdarg.h>
#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;
}
+49
View File
@@ -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 <stdint.h>
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
+21
View File
@@ -55,6 +55,27 @@ extern int errno;
* Folded onto the closest existing code so error strings stay sane. */ * Folded onto the closest existing code so error strings stay sane. */
#define EINVAL EUNKOP /* "Invalid argument" → "Unknown operation" */ #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. */ /* C99 perror / strerror surface. */
const char *strerror(int err); const char *strerror(int err);
void perror (const char *prefix); void perror (const char *prefix);
+5
View File
@@ -21,6 +21,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <dir.h> #include <dir.h>
#include <dos.h> /* getdate/gettime, getdisk, absread */
#include <sprinter_exit.h> /* _exit, atexit */ #include <sprinter_exit.h> /* _exit, atexit */
/* ---- Solid-C types ----------------------------------------------- */ /* ---- Solid-C types ----------------------------------------------- */
@@ -98,8 +99,12 @@ char *strupr(char *s);
/* ---- io.h aliases (Solid-C fd shortcuts) ------------------------- */ /* ---- io.h aliases (Solid-C fd shortcuts) ------------------------- */
#define seek(fd, off) lseek((fd), (long)(off), SEEK_SET) #define seek(fd, off) lseek((fd), (long)(off), SEEK_SET)
#define tell(fd) ((uint16_t)lseek((fd), 0, SEEK_CUR)) #define tell(fd) ((uint16_t)lseek((fd), 0, SEEK_CUR))
#define ltell(fd) lseek((fd), 0L, SEEK_CUR)
#define remove(name) unlink(name) #define remove(name) unlink(name)
/* crt0 разбирает argv сам — заглушка для портируемого кода. */
#define _setargv() 0
/* ---- dir.h alias ------------------------------------------------- */ /* ---- dir.h alias ------------------------------------------------- */
#define _ffirst ffirst #define _ffirst ffirst
+22
View File
@@ -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 <sprinter_compat.h> /* включает stdio/stdlib/string/conio/
ctype/errno/fcntl/unistd/dir/dos/
mouse + типы и алиасы Solid-C */
#include <time.h>
#include <sprinter.h>
#endif
+21 -2
View File
@@ -83,9 +83,15 @@ extern FILE *const stderr;
#define SEEK_END 2 #define SEEK_END 2
#endif #endif
/* Тип позиции файла для fgetpos/fsetpos. */
typedef long fpos_t;
FILE *fopen (const char *path, const char *mode); FILE *fopen (const char *path, const char *mode);
int fclose(FILE *fp); FILE *fdopen (int fd, const char *mode);
int fflush(FILE *fp); 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 fputc (int c, FILE *fp);
int fgetc (FILE *fp); int fgetc (FILE *fp);
@@ -96,12 +102,25 @@ int ungetc(int c, FILE *fp);
int fprintf (FILE *fp, const char *fmt, ...); int fprintf (FILE *fp, const char *fmt, ...);
int vfprintf(FILE *fp, const char *fmt, va_list ap); 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 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); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
int fseek (FILE *fp, long off, int whence); int fseek (FILE *fp, long off, int whence);
long ftell (FILE *fp); long ftell (FILE *fp);
void rewind(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 feof (FILE *fp);
int ferror(FILE *fp); int ferror(FILE *fp);
+4
View File
@@ -28,6 +28,10 @@ long lseek(int fd, long offset, int whence);
/* Block the calling task for `seconds` seconds (50 Hz IRQ-based timer). */ /* Block the calling task for `seconds` seconds (50 Hz IRQ-based timer). */
void sleep(unsigned int seconds); 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 /* 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. * with errno set on failure; getcwd returns the buffer on success or NULL.
* size is ignored — ESTEX always wants a 256-byte buffer. */ * size is ignored — ESTEX always wants a 256-byte buffer. */
+46
View File
@@ -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 <dos.h>
#include <errno.h>
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;
}
+45
View File
@@ -0,0 +1,45 @@
/*
* abswrite — абсолютная запись секторов логического диска через
* BIOS $56 (rst 8); регистры и нумерация — как у absread ($55).
* При ошибке errno=EWRITE (как у Solid-C), возврат -1.
* ОСТОРОЖНО: пишет мимо файловой системы — можно разрушить FAT.
*/
#include <dos.h>
#include <errno.h>
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;
}
+18
View File
@@ -0,0 +1,18 @@
/*
* getdisk — номер текущего диска (0=A, 1=B, ...) через ESTEX
* CURDISK ($02). Ошибок не возвращает.
*/
#include <dos.h>
uint8_t getdisk(void) __naked
{
__asm
push ix
ld c, #0x02 ; ESTEX CURDISK
rst #0x10
pop ix
;; uint8_t возвращается в A номер диска уже там.
ret
__endasm;
}
+16
View File
@@ -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 <unistd.h>
int isatty(int fd)
{
return fd <= 0;
}
+26
View File
@@ -0,0 +1,26 @@
/*
* rename — переименовать файл через ESTEX RENAME ($10).
* HL = старое имя (ASCIIZ), DE = новое имя; CF=err с кодом в A.
* Возвращает 0 или -1 + errno.
*/
#include <stdio.h>
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;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* setdisk — сменить текущий диск (0=A, 1=B, ...) через ESTEX
* CHDISK ($01). Возвращает число дисков в системе (из A) или
* -1 + errno при ошибке (EINVDRV, если диска нет).
*/
#include <dos.h>
#include <errno.h>
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;
}
+22
View File
@@ -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 <stdarg.h>
/* Следующий символ источника или 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
+176
View File
@@ -0,0 +1,176 @@
/*
* _scanf_core — ядро scanf-семейства (см. _scanf.h).
*
* Поддержано: %d (знак ±), %u, %x, %o, %c (ширина = число символов,
* по умолчанию 1, пробелы НЕ скипаются), %s (скип пробелов, до
* пробела/ширины, NUL добавляется), модификатор l (long), ширина,
* '*' — подавление присваивания, %% — литерал. Пробельный символ в
* формате съедает любую цепочку пробелов ввода; прочие символы
* формата должны совпасть с вводом (иначе стоп).
*
* Возврат: число присвоенных полей; EOF, если ввод кончился до
* первого присваивания/совпадения (ANSI-семантика).
*/
#include <ctype.h>
#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;
}
+32
View File
@@ -0,0 +1,32 @@
/*
* sscanf — форматированный разбор строки; ядро — _scanf_core,
* источник — движущийся указатель по ASCIIZ-строке.
*/
#include <stdarg.h>
#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;
}
+16
View File
@@ -0,0 +1,16 @@
/*
* getdate — текущая дата в Turbo-C структуру struct date
* (обёртка над getdatetime / ESTEX SYSTIME).
*/
#include <dos.h>
#include <time.h>
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;
}
+18
View File
@@ -0,0 +1,18 @@
/*
* gettime — текущее время в Turbo-C структуру struct time
* (обёртка над getdatetime / ESTEX SYSTIME). ti_hund всегда 0 —
* RTC Sprinter не отдаёт сотые.
*/
#include <dos.h>
#include <time.h>
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;
}
+18
View File
@@ -0,0 +1,18 @@
/*
* setdate — установить системную дату, не трогая время:
* read-modify-write полного datetime_t через getdatetime/setdatetime.
* Возврат 0 или -1 + errno (от setdatetime).
*/
#include <dos.h>
#include <time.h>
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);
}
+18
View File
@@ -0,0 +1,18 @@
/*
* settime — установить системное время, не трогая дату:
* read-modify-write полного datetime_t через getdatetime/setdatetime.
* ti_hund игнорируется. Возврат 0 или -1 + errno (от setdatetime).
*/
#include <dos.h>
#include <time.h>
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);
}
+5
View File
@@ -0,0 +1,5 @@
# Build solidt.exe — smoke-тест Solid-C совместимости (П3/П4).
PROJ_ROOT := $(abspath $(CURDIR)/../..)
EXAMPLE := solidt
include $(PROJ_ROOT)/app.mk
+114
View File
@@ -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 <sprinter_solid.h>
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;
}