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:
@@ -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
@@ -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. Заголовки и гигиена сборки
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 байт), поток продолжает работать. */
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* _file_mode_flags — парсер строки режима fopen/fdopen/freopen.
|
||||||
|
* Поддержаны r, w, a; '+' в любом месте хвоста; 'b'/'t' игнорируются —
|
||||||
|
* весь I/O на Sprinter бинарный.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
int _file_mode_flags(const char *mode, uint8_t *ff)
|
||||||
|
{
|
||||||
|
if (!mode || !*mode) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int oflags = 0;
|
||||||
|
uint8_t f = 0;
|
||||||
|
char base = *mode;
|
||||||
|
int plus = 0;
|
||||||
|
for (const char *p = mode + 1; *p; p++) {
|
||||||
|
if (*p == '+') plus = 1;
|
||||||
|
}
|
||||||
|
switch (base) {
|
||||||
|
case 'r':
|
||||||
|
oflags = plus ? O_RDWR : O_RDONLY;
|
||||||
|
f = _F_READ | (plus ? _F_WRITE : 0);
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
|
||||||
|
f = _F_WRITE | (plus ? _F_READ : 0);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND;
|
||||||
|
f = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*ff = f;
|
||||||
|
return oflags;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* _file_slot_take — найти свободный слот таблицы потоков и при первом
|
||||||
|
* обращении взвести atexit(_fclosall), чтобы exit() сбрасывал
|
||||||
|
* несброшенную запись всех потоков (требование стандарта).
|
||||||
|
* Слот НЕ помечается занятым — вызывающий (fopen/fdopen) заполняет
|
||||||
|
* поля и последним выставляет flags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sprinter_exit.h> /* atexit */
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE *_file_slot_take(void)
|
||||||
|
{
|
||||||
|
static uint8_t atexit_armed;
|
||||||
|
|
||||||
|
FILE *fp = _file_slots;
|
||||||
|
for (uint8_t i = 0; ; i++, fp++) {
|
||||||
|
if (i >= OPEN_MAX) {
|
||||||
|
errno = EMFILE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fp->flags == 0) break;
|
||||||
|
}
|
||||||
|
if (!atexit_armed) {
|
||||||
|
atexit_armed = 1;
|
||||||
|
atexit(_fclosall);
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* fclosall — закрыть все открытые потоки таблицы (имя Solid-C);
|
||||||
|
* публичная обёртка над _fclosall, который и так навешен на atexit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
void fclosall(void)
|
||||||
|
{
|
||||||
|
_fclosall();
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* fdopen — завернуть уже открытый низкоуровневый fd в поток FILE*
|
||||||
|
* (Solid-C категория C). Режим должен соответствовать тому, с которым
|
||||||
|
* открывался fd — библиотека проверить это не может. После fdopen
|
||||||
|
* файл закрывается через fclose (не close!): fclose сбросит буфер и
|
||||||
|
* закроет сам fd. Счётчик _fd_guard не трогаем — fd уже посчитан
|
||||||
|
* своим open().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include "_file.h"
|
||||||
|
|
||||||
|
FILE *fdopen(int fd, const char *mode)
|
||||||
|
{
|
||||||
|
if (fd < 0) { errno = EBADF; return 0; }
|
||||||
|
|
||||||
|
uint8_t ff;
|
||||||
|
if (_file_mode_flags(mode, &ff) < 0) return 0;
|
||||||
|
|
||||||
|
FILE *fp = _file_slot_take();
|
||||||
|
if (!fp) return 0;
|
||||||
|
|
||||||
|
fp->fd = fd;
|
||||||
|
fp->buf = 0;
|
||||||
|
fp->curp = 0;
|
||||||
|
fp->level = 0;
|
||||||
|
fp->hold = EOF;
|
||||||
|
fp->flags = ff;
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+20
-1
@@ -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);
|
||||||
|
FILE *fdopen (int fd, const char *mode);
|
||||||
|
FILE *freopen(const char *path, const char *mode, FILE *fp);
|
||||||
int fclose (FILE *fp);
|
int fclose (FILE *fp);
|
||||||
int fflush(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);
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Build solidt.exe — smoke-тест Solid-C совместимости (П3/П4).
|
||||||
|
|
||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := solidt
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user