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
+12
View File
@@ -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 байт), поток продолжает работать. */
+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+).
*
* FILE берётся из статической таблицы _file_slots (свободный слот:
* flags == 0), без malloc — сам буфер выделится лениво при первой
* операции. Первый успешный fopen вешает _fclosall на atexit, чтобы
* exit() сбрасывал несброшенную запись всех потоков.
* FILE берётся из статической таблицы (_file_slot_take, он же взводит
* atexit(_fclosall)); сам буфер выделится лениво при первой операции.
* NULL при: плохом режиме (EINVAL), занятой таблице (EMFILE), ошибке
* open (errno от него).
*/
#include <stdlib.h>
#include <sprinter_exit.h> /* atexit */
#include <fcntl.h>
#include <unistd.h>
#include <errno.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)
{
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;
}
+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. */
#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);
+5
View File
@@ -21,6 +21,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <dir.h>
#include <dos.h> /* getdate/gettime, getdisk, absread */
#include <sprinter_exit.h> /* _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
+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
+22 -3
View File
@@ -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);
+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). */
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. */
+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);
}